PageSourceSearch

https://html5test.co/scripts/highcharts/highcharts.src.js

js html5test.co collected 2026-10-02 07:37:57 UTC 344,371 bytes, 13,543 lines download raw bytes

1// ==ClosureCompiler==
2// @compilation_level SIMPLE_OPTIMIZATIONS
3
4/**
5 * @license Highcharts JS v2.2.1 (2012-03-15)
6 *
7 * (c) 2009-2011 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 */
14
15(function () {
16// encapsulated variables
17var 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	isIE = /msie/i.test(userAgent) && !win.opera,
36	docMode8 = doc.documentMode === 8,
37	isWebKit = /AppleWebKit/.test(userAgent),
38	isFirefox = /Firefox/.test(userAgent),
39	SVG_NS = 'http://www.w3.org/2000/svg',
40	hasSVG = !!doc.createElementNS && !!doc.createElementNS(SVG_NS, 'svg').createSVGRect,
41	hasBidiBug = isFirefox && parseInt(userAgent.split('Firefox/')[1], 10) < 4, // issue #38
42	useCanVG = !hasSVG && !isIE && !!doc.createElement('canvas').getContext,
43	Renderer,
44	hasTouch = doc.documentElement.ontouchstart !== UNDEFINED,
45	symbolSizes = {},
46	idCounter = 0,
47	garbageBin,
48	defaultOptions,
49	dateFormat, // function
50	globalAnimation,
51	pathAnim,
52	timeUnits,
53
54	// some constants for frequently used strings
55	DIV = 'div',
56	ABSOLUTE = 'absolute',
57	RELATIVE = 'relative',
58	HIDDEN = 'hidden',
59	PREFIX = 'highcharts-',
60	VISIBLE = 'visible',
61	PX = 'px',
62	NONE = 'none',
63	M = 'M',
64	L = 'L',
65	/*
66	 * Empirical lowest possible opacities for TRACKER_FILL
67	 * IE6: 0.002
68	 * IE7: 0.002
69	 * IE8: 0.002
70	 * IE9: 0.00000000001 (unlimited)
71	 * FF: 0.00000000001 (unlimited)
72	 * Chrome: 0.000001
73	 * Safari: 0.000001
74	 * Opera: 0.00000000001 (unlimited)
75	 */
76	TRACKER_FILL = 'rgba(192,192,192,' + (hasSVG ? 0.000001 : 0.002) + ')', // invisible but clickable
77	//TRACKER_FILL = 'rgba(192,192,192,0.5)',
78	NORMAL_STATE = '',
79	HOVER_STATE = 'hover',
80	SELECT_STATE = 'select',
81	MILLISECOND = 'millisecond',
82	SECOND = 'second',
83	MINUTE = 'minute',
84	HOUR = 'hour',
85	DAY = 'day',
86	WEEK = 'week',
87	MONTH = 'month',
88	YEAR = 'year',
89
90	// constants for attributes
91	FILL = 'fill',
92	LINEAR_GRADIENT = 'linearGradient',
93	STOPS = 'stops',
94	STROKE = 'stroke',
95	STROKE_WIDTH = 'stroke-width',
96
97	// time methods, changed based on whether or not UTC is used
98	makeTime,
99	getMinutes,
100	getHours,
101	getDay,
102	getDate,
103	getMonth,
104	getFullYear,
105	setMinutes,
106	setHours,
107	setDate,
108	setMonth,
109	setFullYear,
110
111	// check for a custom HighchartsAdapter defined prior to this file
112	globalAdapter = win.HighchartsAdapter,
113	adapter = globalAdapter || {},
114
115	// Utility functions. If the HighchartsAdapter is not defined, adapter is an empty object
116	// and all the utility functions will be null. In that case they are populated by the
117	// default adapters below.
118	getScript = adapter.getScript,
119	each = adapter.each,
120	grep = adapter.grep,
121	offset = adapter.offset,
122	map = adapter.map,
123	merge = adapter.merge,
124	addEvent = adapter.addEvent,
125	removeEvent = adapter.removeEvent,
126	fireEvent = adapter.fireEvent,
127	animate = adapter.animate,
128	stop = adapter.stop,
129
130	// lookup over the types and the associated classes
131	seriesTypes = {};
132
133// The Highcharts namespace
134win.Highcharts = {};
135
136/**
137 * Extend an object with the members of another
138 * @param {Object} a The object to be extended
139 * @param {Object} b The object to add to the first one
140 */
141function extend(a, b) {
142	var n;
143	if (!a) {
144		a = {};
145	}
146	for (n in b) {
147		a[n] = b[n];
148	}
149	return a;
150}
151
152/**
153 * Take an array and turn into a hash with even number arguments as keys and odd numbers as
154 * values. Allows creating constants for commonly used style properties, attributes etc.
155 * Avoid it in performance critical situations like looping
156 */
157function hash() {
158	var i = 0,
159		args = arguments,
160		length = args.length,
161		obj = {};
162	for (; i < length; i++) {
163		obj[args[i++]] = args[i];
164	}
165	return obj;
166}
167
168/**
169 * Shortcut for parseInt
170 * @param {Object} s
171 * @param {Number} mag Magnitude
172 */
173function pInt(s, mag) {
174	return parseInt(s, mag || 10);
175}
176
177/**
178 * Check for string
179 * @param {Object} s
180 */
181function isString(s) {
182	return typeof s === 'string';
183}
184
185/**
186 * Check for object
187 * @param {Object} obj
188 */
189function isObject(obj) {
190	return typeof obj === 'object';
191}
192
193/**
194 * Check for array
195 * @param {Object} obj
196 */
197function isArray(obj) {
198	return Object.prototype.toString.call(obj) === '[object Array]';
199}
200
201/**
202 * Check for number
203 * @param {Object} n
204 */
205function isNumber(n) {
206	return typeof n === 'number';
207}
208
209function log2lin(num) {
210	return math.log(num) / math.LN10;
211}
212function lin2log(num) {
213	return math.pow(10, num);
214}
215
216/**
217 * Remove last occurence of an item from an array
218 * @param {Array} arr
219 * @param {Mixed} item
220 */
221function erase(arr, item) {
222	var i = arr.length;
223	while (i--) {
224		if (arr[i] === item) {
225			arr.splice(i, 1);
226			break;
227		}
228	}
229	//return arr;
230}
231
232/**
233 * Returns true if the object is not null or undefined. Like MooTools' $.defined.
234 * @param {Object} obj
235 */
236function defined(obj) {
237	return obj !== UNDEFINED && obj !== null;
238}
239
240/**
241 * Set or get an attribute or an object of attributes. Can't use jQuery attr because
242 * it attempts to set expando properties on the SVG element, which is not allowed.
243 *
244 * @param {Object} elem The DOM element to receive the attribute(s)
245 * @param {String|Object} prop The property or an abject of key-value pairs
246 * @param {String} value The value if a single property is set
247 */
248function attr(elem, prop, value) {
249	var key,
250		setAttribute = 'setAttribute',
251		ret;
252
253	// if the prop is a string
254	if (isString(prop)) {
255		// set the value
256		if (defined(value)) {
257
258			elem[setAttribute](prop, value);
259
260		// get the value
261		} else if (elem && elem.getAttribute) { // elem not defined when printing pie demo...
262			ret = elem.getAttribute(prop);
263		}
264
265	// else if prop is defined, it is a hash of key/value pairs
266	} else if (defined(prop) && isObject(prop)) {
267		for (key in prop) {
268			elem[setAttribute](key, prop[key]);
269		}
270	}
271	return ret;
272}
273/**
274 * Check if an element is an array, and if not, make it into an array. Like
275 * MooTools' $.splat.
276 */
277function splat(obj) {
278	return isArray(obj) ? obj : [obj];
279}
280
281
282/**
283 * Return the first value that is defined. Like MooTools' $.pick.
284 */
285function pick() {
286	var args = arguments,
287		i,
288		arg,
289		length = args.length;
290	for (i = 0; i < length; i++) {
291		arg = args[i];
292		if (typeof arg !== 'undefined' && arg !== null) {
293			return arg;
294		}
295	}
296}
297
298/**
299 * Set CSS on a given element
300 * @param {Object} el
301 * @param {Object} styles Style object with camel case property names
302 */
303function css(el, styles) {
304	if (isIE) {
305		if (styles && styles.opacity !== UNDEFINED) {
306			styles.filter = 'alpha(opacity=' + (styles.opacity * 100) + ')';
307		}
308	}
309	extend(el.style, styles);
310}
311
312/**
313 * Utility function to create element with attributes and styles
314 * @param {Object} tag
315 * @param {Object} attribs
316 * @param {Object} styles
317 * @param {Object} parent
318 * @param {Object} nopad
319 */
320function createElement(tag, attribs, styles, parent, nopad) {
321	var el = doc.createElement(tag);
322	if (attribs) {
323		extend(el, attribs);
324	}
325	if (nopad) {
326		css(el, {padding: 0, border: NONE, margin: 0});
327	}
328	if (styles) {
329		css(el, styles);
330	}
331	if (parent) {
332		parent.appendChild(el);
333	}
334	return el;
335}
336
337/**
338 * Extend a prototyped class by new members
339 * @param {Object} parent
340 * @param {Object} members
341 */
342function extendClass(parent, members) {
343	var object = function () {};
344	object.prototype = new parent();
345	extend(object.prototype, members);
346	return object;
347}
348
349/**
350 * Format a number and return a string based on input settings
351 * @param {Number} number The input number to format
352 * @param {Number} decimals The amount of decimals
353 * @param {String} decPoint The decimal point, defaults to the one given in the lang options
354 * @param {String} thousandsSep The thousands separator, defaults to the one given in the lang options
355 */
356function numberFormat(number, decimals, decPoint, thousandsSep) {
357	var lang = defaultOptions.lang,
358		// http://kevin.vanzonneveld.net/techblog/article/javascript_equivalent_for_phps_number_format/
359		n = number,
360		c = isNaN(decimals = mathAbs(decimals)) ? 2 : decimals,
361		d = decPoint === undefined ? lang.decimalPoint : decPoint,
362		t = thousandsSep === undefined ? lang.thousandsSep : thousandsSep,
363		s = n < 0 ? "-" : "",
364		i = String(pInt(n = mathAbs(+n || 0).toFixed(c))),
365		j = i.length > 3 ? i.length % 3 : 0;
366
367	return s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) +
368		(c ? d + mathAbs(n - i).toFixed(c).slice(2) : "");
369}
370
371/**
372 * Pad a string to a given length by adding 0 to the beginning
373 * @param {Number} number
374 * @param {Number} length
375 */
376function pad(number, length) {
377	// Create an array of the remaining length +1 and join it with 0's
378	return new Array((length || 2) + 1 - String(number).length).join(0) + number;
379}
380
381/**
382 * Based on http://www.php.net/manual/en/function.strftime.php
383 * @param {String} format
384 * @param {Number} timestamp
385 * @param {Boolean} capitalize
386 */
387dateFormat = function (format, timestamp, capitalize) {
388	if (!defined(timestamp) || isNaN(timestamp)) {
389		return 'Invalid date';
390	}
391	format = pick(format, '%Y-%m-%d %H:%M:%S');
392
393	var date = new Date(timestamp),
394		key, // used in for constuct below
395		// get the basic time values
396		hours = date[getHours](),
397		day = date[getDay](),
398		dayOfMonth = date[getDate](),
399		month = date[getMonth](),
400		fullYear = date[getFullYear](),
401		lang = defaultOptions.lang,
402		langWeekdays = lang.weekdays,
403		/* // uncomment this and the 'W' format key below to enable week numbers
404		weekNumber = function () {
405			var clone = new Date(date.valueOf()),
406				day = clone[getDay]() == 0 ? 7 : clone[getDay](),
407				dayNumber;
408			clone.setDate(clone[getDate]() + 4 - day);
409			dayNumber = mathFloor((clone.getTime() - new Date(clone[getFullYear](), 0, 1, -6)) / 86400000);
410			return 1 + mathFloor(dayNumber / 7);
411		},
412		*/
413
414		// list all format keys
415		replacements = {
416
417			// Day
418			'a': langWeekdays[day].substr(0, 3), // Short weekday, like 'Mon'
419			'A': langWeekdays[day], // Long weekday, like 'Monday'
420			'd': pad(dayOfMonth), // Two digit day of the month, 01 to 31
421			'e': dayOfMonth, // Day of the month, 1 through 31
422
423			// Week (none implemented)
424			//'W': weekNumber(),
425
426			// Month
427			'b': lang.shortMonths[month], // Short month, like 'Jan'
428			'B': lang.months[month], // Long month, like 'January'
429			'm': pad(month + 1), // Two digit month number, 01 through 12
430
431			// Year
432			'y': fullYear.toString().substr(2, 2), // Two digits year, like 09 for 2009
433			'Y': fullYear, // Four digits year, like 2009
434
435			// Time
436			'H': pad(hours), // Two digits hours in 24h format, 00 through 23
437			'I': pad((hours % 12) || 12), // Two digits hours in 12h format, 00 through 11
438			'l': (hours % 12) || 12, // Hours in 12h format, 1 through 12
439			'M': pad(date[getMinutes]()), // Two digits minutes, 00 through 59
440			'p': hours < 12 ? 'AM' : 'PM', // Upper case AM or PM
441			'P': hours < 12 ? 'am' : 'pm', // Lower case AM or PM
442			'S': pad(date.getSeconds()), // Two digits seconds, 00 through  59
443			'L': pad(mathRound(timestamp % 1000), 3) // Milliseconds (naming from Ruby)
444		};
445
446
447	// do the replaces
448	for (key in replacements) {
449		format = format.replace('%' + key, replacements[key]);
450	}
451
452	// Optionally capitalize the string and return
453	return capitalize ? format.substr(0, 1).toUpperCase() + format.substr(1) : format;
454};
455
456/**
457 * Take an interval and normalize it to multiples of 1, 2, 2.5 and 5
458 * @param {Number} interval
459 * @param {Array} multiples
460 * @param {Number} magnitude
461 * @param {Object} options
462 */
463function normalizeTickInterval(interval, multiples, magnitude, options) {
464	var normalized, i;
465
466	// round to a tenfold of 1, 2, 2.5 or 5
467	magnitude = pick(magnitude, 1);
468	normalized = interval / magnitude;
469
470	// multiples for a linear scale
471	if (!multiples) {
472		multiples = [1, 2, 2.5, 5, 10];
473
474		// the allowDecimals option
475		if (options && options.allowDecimals === false) {
476			if (magnitude === 1) {
477				multiples = [1, 2, 5, 10];
478			} else if (magnitude <= 0.1) {
479				multiples = [1 / magnitude];
480			}
481		}
482	}
483
484	// normalize the interval to the nearest multiple
485	for (i = 0; i < multiples.length; i++) {
486		interval = multiples[i];
487		if (normalized <= (multiples[i] + (multiples[i + 1] || multiples[i])) / 2) {
488			break;
489		}
490	}
491
492	// multiply back to the correct magnitude
493	interval *= magnitude;
494
495	return interval;
496}
497
498/**
499 * Get a normalized tick interval for dates. Returns a configuration object with
500 * unit range (interval), count and name. Used to prepare data for getTimeTicks. 
501 * Previously this logic was part of getTimeTicks, but as getTimeTicks now runs
502 * of segments in stock charts, the normalizing logic was extracted in order to 
503 * prevent it for running over again for each segment having the same interval. 
504 * #662, #697.
505 */
506function normalizeTimeTickInterval(tickInterval, unitsOption) {
507	var units = unitsOption || [[
508				MILLISECOND, // unit name
509				[1, 2, 5, 10, 20, 25, 50, 100, 200, 500] // allowed multiples
510			], [
511				SECOND,
512				[1, 2, 5, 10, 15, 30]
513			], [
514				MINUTE,
515				[1, 2, 5, 10, 15, 30]
516			], [
517				HOUR,
518				[1, 2, 3, 4, 6, 8, 12]
519			], [
520				DAY,
521				[1, 2]
522			], [
523				WEEK,
524				[1, 2]
525			], [
526				MONTH,
527				[1, 2, 3, 4, 6]
528			], [
529				YEAR,
530				null
531			]],
532		unit = units[units.length - 1], // default unit is years
533		interval = timeUnits[unit[0]],
534		multiples = unit[1],
535		count,
536		i;
537		
538	// loop through the units to find the one that best fits the tickInterval
539	for (i = 0; i < units.length; i++) {
540		unit = units[i];
541		interval = timeUnits[unit[0]];
542		multiples = unit[1];
543
544
545		if (units[i + 1]) {
546			// lessThan is in the middle between the highest multiple and the next unit.
547			var lessThan = (interval * multiples[multiples.length - 1] +
548						timeUnits[units[i + 1][0]]) / 2;
549
550			// break and keep the current unit
551			if (tickInterval <= lessThan) {
552				break;
553			}
554		}
555	}
556
557	// prevent 2.5 years intervals, though 25, 250 etc. are allowed
558	if (interval === timeUnits[YEAR] && tickInterval < 5 * interval) {
559		multiples = [1, 2, 5];
560	}
561	
562	// prevent 2.5 years intervals, though 25, 250 etc. are allowed
563	if (interval === timeUnits[YEAR] && tickInterval < 5 * interval) {
564		multiples = [1, 2, 5];
565	}
566
567	// get the count
568	count = normalizeTickInterval(tickInterval / interval, multiples);
569	
570	return {
571		unitRange: interval,
572		count: count,
573		unitName: unit[0]
574	};
575}
576
577/**
578 * Set the tick positions to a time unit that makes sense, for example
579 * on the first of each month or on every Monday. Return an array
580 * with the time positions. Used in datetime axes as well as for grouping
581 * data on a datetime axis.
582 *
583 * @param {Object} normalizedInterval The interval in axis values (ms) and the count
584 * @param {Number} min The minimum in axis values
585 * @param {Number} max The maximum in axis values
586 * @param {Number} startOfWeek
587 */
588function getTimeTicks(normalizedInterval, min, max, startOfWeek) {
589	var tickPositions = [],
590		i,
591		higherRanks = {},
592		useUTC = defaultOptions.global.useUTC,
593		minYear, // used in months and years as a basis for Date.UTC()
594		minDate = new Date(min),
595		interval = normalizedInterval.unitRange,
596		count = normalizedInterval.count;
597
598	
599
600	if (interval >= timeUnits[SECOND]) { // second
601		minDate.setMilliseconds(0);
602		minDate.setSeconds(interval >= timeUnits[MINUTE] ? 0 :
603			count * mathFloor(minDate.getSeconds() / count));
604	}
605
606	if (interval >= timeUnits[MINUTE]) { // minute
607		minDate[setMinutes](interval >= timeUnits[HOUR] ? 0 :
608			count * mathFloor(minDate[getMinutes]() / count));
609	}
610
611	if (interval >= timeUnits[HOUR]) { // hour
612		minDate[setHours](interval >= timeUnits[DAY] ? 0 :
613			count * mathFloor(minDate[getHours]() / count));
614	}
615
616	if (interval >= timeUnits[DAY]) { // day
617		minDate[setDate](interval >= timeUnits[MONTH] ? 1 :
618			count * mathFloor(minDate[getDate]() / count));
619	}
620
621	if (interval >= timeUnits[MONTH]) { // month
622		minDate[setMonth](interval >= timeUnits[YEAR] ? 0 :
623			count * mathFloor(minDate[getMonth]() / count));
624		minYear = minDate[getFullYear]();
625	}
626
627	if (interval >= timeUnits[YEAR]) { // year
628		minYear -= minYear % count;
629		minDate[setFullYear](minYear);
630	}
631
632	// week is a special case that runs outside the hierarchy
633	if (interval === timeUnits[WEEK]) {
634		// get start of current week, independent of count
635		minDate[setDate](minDate[getDate]() - minDate[getDay]() +
636			pick(startOfWeek, 1));
637	}
638
639
640	// get tick positions
641	i = 1;
642	minYear = minDate[getFullYear]();
643	var time = minDate.getTime(),
644		minMonth = minDate[getMonth](),
645		minDateDate = minDate[getDate]();
646
647	// iterate and add tick positions at appropriate values
648	while (time < max) {
649		tickPositions.push(time);
650
651		// if the interval is years, use Date.UTC to increase years
652		if (interval === timeUnits[YEAR]) {
653			time = makeTime(minYear + i * count, 0);
654
655		// if the interval is months, use Date.UTC to increase months
656		} else if (interval === timeUnits[MONTH]) {
657			time = makeTime(minYear, minMonth + i * count);
658
659		// if we're using global time, the interval is not fixed as it jumps
660		// one hour at the DST crossover
661		} else if (!useUTC && (interval === timeUnits[DAY] || interval === timeUnits[WEEK])) {
662			time = makeTime(minYear, minMonth, minDateDate +
663				i * count * (interval === timeUnits[DAY] ? 1 : 7));
664
665		// else, the interval is fixed and we use simple addition
666		} else {
667			time += interval * count;
668			
669			// mark new days if the time is dividable by day
670			if (interval <= timeUnits[HOUR] && time % timeUnits[DAY] === 0) {
671				higherRanks[time] = DAY;
672			}
673		}
674
675		i++;
676	}
677	
678	// push the last time
679	tickPositions.push(time);
680
681	// record information on the chosen unit - for dynamic label formatter
682	tickPositions.info = extend(normalizedInterval, {
683		higherRanks: higherRanks,
684		totalRange: interval * count
685	});
686
687	return tickPositions;
688}
689
690/**
691 * Helper class that contains variuos counters that are local to the chart.
692 */
693function ChartCounters() {
694	this.color = 0;
695	this.symbol = 0;
696}
697
698ChartCounters.prototype =  {
699	/**
700	 * Wraps the color counter if it reaches the specified length.
701	 */
702	wrapColor: function (length) {
703		if (this.color >= length) {
704			this.color = 0;
705		}
706	},
707
708	/**
709	 * Wraps the symbol counter if it reaches the specified length.
710	 */
711	wrapSymbol: function (length) {
712		if (this.symbol >= length) {
713			this.symbol = 0;
714		}
715	}
716};
717
718/**
719 * Utility method extracted from Tooltip code that places a tooltip in a chart without spilling over
720 * and not covering the point it self.
721 */
722function placeBox(boxWidth, boxHeight, outerLeft, outerTop, outerWidth, outerHeight, point, distance, preferRight) {
723	
724	// keep the box within the chart area
725	var pointX = point.x,
726		pointY = point.y,
727		x = pointX + outerLeft + (preferRight ? distance : -boxWidth - distance),
728		y = pointY - boxHeight + outerTop + 15, // 15 means the point is 15 pixels up from the bottom of the tooltip
729		alignedRight;
730
731	// it is too far to the left, adjust it
732	if (x < 7) {
733		x = outerLeft + pointX + distance;
734	}
735
736	// Test to see if the tooltip is too far to the right,
737	// if it is, move it back to be inside and then up to not cover the point.
738	if ((x + boxWidth) > (outerLeft + outerWidth)) {
739		x -= (x + boxWidth) - (outerLeft + outerWidth);
740		y = pointY - boxHeight + outerTop - distance;
741		alignedRight = true;
742	}
743
744	// if it is now above the plot area, align it to the top of the plot area
745	if (y < outerTop + 5) {
746		y = outerTop + 5;
747
748		// If the tooltip is still covering the point, move it below instead
749		if (alignedRight && pointY >= y && pointY <= (y + boxHeight)) {
750			y = pointY + outerTop + distance; // below
751		}
752	} else if (y + boxHeight > outerTop + outerHeight) {
753		y = outerTop + outerHeight - boxHeight - distance; // below
754	}
755
756	return {x: x, y: y};
757}
758
759/**
760 * Utility method that sorts an object array and keeping the order of equal items.
761 * ECMA script standard does not specify the behaviour when items are equal.
762 */
763function stableSort(arr, sortFunction) {
764	var length = arr.length,
765		sortValue,
766		i;
767
768	// Add index to each item
769	for (i = 0; i < length; i++) {
770		arr[i].ss_i = i; // stable sort index
771	}
772
773	arr.sort(function (a, b) {
774		sortValue = sortFunction(a, b);
775		return sortValue === 0 ? a.ss_i - b.ss_i : sortValue;
776	});
777
778	// Remove index from items
779	for (i = 0; i < length; i++) {
780		delete arr[i].ss_i; // stable sort index
781	}
782}
783
784/**
785 * Non-recursive method to find the lowest member of an array. Math.min raise
785s a maximum
786 * call stack size exceeded error in Chrome when trying to apply more than 150.000 points. This
787 * method is slightly slower, but safe.
788 */
789function arrayMin(data) {
790	var i = data.length,
791		min = data[0];
792
793	while (i--) {
794		if (data[i] < min) {
795			min = data[i];
796		}
797	}
798	return min;
799}
800
801/**
802 * Non-recursive method to find the lowest member of an array. Math.min raises a maximum
803 * call stack size exceeded error in Chrome when trying to apply more than 150.000 points. This
804 * method is slightly slower, but safe.
805 */
806function arrayMax(data) {
807	var i = data.length,
808		max = data[0];
809
810	while (i--) {
811		if (data[i] > max) {
812			max = data[i];
813		}
814	}
815	return max;
816}
817
818/**
819 * Utility method that destroys any SVGElement or VMLElement that are properties on the given object.
820 * It loops all properties and invokes destroy if there is a destroy method. The property is
821 * then delete'ed.
822 */
823function destroyObjectProperties(obj) {
824	var n;
825	for (n in obj) {
826		// If the object is non-null and destroy is defined
827		if (obj[n] && obj[n].destroy) {
828			// Invoke the destroy
829			obj[n].destroy();
830		}
831
832		// Delete the property from the object.
833		delete obj[n];
834	}
835}
836
837
838/**
839 * Discard an element by moving it to the bin and delete
840 * @param {Object} The HTML node to discard
841 */
842function discardElement(element) {
843	// create a garbage bin element, not part of the DOM
844	if (!garbageBin) {
845		garbageBin = createElement(DIV);
846	}
847
848	// move the node and empty bin
849	if (element) {
850		garbageBin.appendChild(element);
851	}
852	garbageBin.innerHTML = '';
853}
854
855/**
856 * Provide error messages for debugging, with links to online explanation 
857 */
858function error(code, stop) {
859	var msg = 'Highcharts error #' + code + ': www.highcharts.com/errors/' + code;
860	if (stop) {
861		throw msg;
862	} else if (win.console) {
863		console.log(msg);
864	}
865}
866
867/**
868 * Fix JS round off float errors
869 * @param {Number} num
870 */
871function correctFloat(num) {
872	return parseFloat(
873		num.toPrecision(14)
874	);
875}
876
877/**
878 * The time unit lookup
879 */
880/*jslint white: true*/
881timeUnits = hash(
882	MILLISECOND, 1,
883	SECOND, 1000,
884	MINUTE, 60000,
885	HOUR, 3600000,
886	DAY, 24 * 3600000,
887	WEEK, 7 * 24 * 3600000,
888	MONTH, 30 * 24 * 3600000,
889	YEAR, 31556952000
890);
891/*jslint white: false*/
892/**
893 * Path interpolation algorithm used across adapters
894 */
895pathAnim = {
896	/**
897	 * Prepare start and end values so that the path can be animated one to one
898	 */
899	init: function (elem, fromD, toD) {
900		fromD = fromD || '';
901		var shift = elem.shift,
902			bezier = fromD.indexOf('C') > -1,
903			numParams = bezier ? 7 : 3,
904			endLength,
905			slice,
906			i,
907			start = fromD.split(' '),
908			end = [].concat(toD), // copy
909			startBaseLine,
910			endBaseLine,
911			sixify = function (arr) { // in splines make move points have six parameters like bezier curves
912				i = arr.length;
913				while (i--) {
914					if (arr[i] === M) {
915						arr.splice(i + 1, 0, arr[i + 1], arr[i + 2], arr[i + 1], arr[i + 2]);
916					}
917				}
918			};
919
920		if (bezier) {
921			sixify(start);
922			sixify(end);
923		}
924
925		// pull out the base lines before padding
926		if (elem.isArea) {
927			startBaseLine = start.splice(start.length - 6, 6);
928			endBaseLine = end.splice(end.length - 6, 6);
929		}
930
931		// if shifting points, prepend a dummy point to the end path
932		if (shift === 1) {
933
934			end = [].concat(end).splice(0, numParams).concat(end);
935		}
936		elem.shift = 0; // reset for following animations
937
938		// copy and append last point until the length matches the end length
939		if (start.length) {
940			endLength = end.length;
941			while (start.length < endLength) {
942
943				//bezier && sixify(start);
944				slice = [].concat(start).splice(start.length - numParams, numParams);
945				if (bezier) { // disable first control point
946					slice[numParams - 6] = slice[numParams - 2];
947					slice[numParams - 5] = slice[numParams - 1];
948				}
949				start = start.concat(slice);
950			}
951		}
952
953		if (startBaseLine) { // append the base lines for areas
954			start = start.concat(startBaseLine);
955			end = end.concat(endBaseLine);
956		}
957		return [start, end];
958	},
959
960	/**
961	 * Interpolate each value of the path and return the array
962	 */
963	step: function (start, end, pos, complete) {
964		var ret = [],
965			i = start.length,
966			startVal;
967
968		if (pos === 1) { // land on the final path without adjustment points appended in the ends
969			ret = complete;
970
971		} else if (i === end.length && pos < 1) {
972			while (i--) {
973				startVal = parseFloat(start[i]);
974				ret[i] =
975					isNaN(startVal) ? // a letter instruction like M or L
976						start[i] :
977						pos * (parseFloat(end[i] - startVal)) + startVal;
978
979			}
980		} else { // if animation is finished or length not matching, land on right value
981			ret = end;
982		}
983		return ret;
984	}
985};
986
987
988/**
989 * Set the global animation to either a given value, or fall back to the
990 * given chart's animation option
991 * @param {Object} animation
992 * @param {Object} chart
993 */
994function setAnimation(animation, chart) {
995	globalAnimation = pick(animation, chart.animation);
996}
997
998/*
999 * Define the adapter for frameworks. If an external adapter is not defined,
1000 * Highcharts reverts to the built-in jQuery adapter.
1001 */
1002if (globalAdapter && globalAdapter.init) {
1003	// Initialize the adapter with the pathAnim object that takes care
1004	// of path animations.
1005	globalAdapter.init(pathAnim);
1006}
1007if (!globalAdapter && win.jQuery) {
1008	var jQ = jQuery;
1009
1010	/**
1011	 * Downloads a script and executes a callback when done.
1012	 * @param {String} scriptLocation
1013	 * @param {Function} callback
1014	 */
1015	getScript = jQ.getScript;
1016
1017	/**
1018	 * Utility for iterating over an array. Parameters are reversed compared to jQuery.
1019	 * @param {Array} arr
1020	 * @param {Function} fn
1021	 */
1022	each = function (arr, fn) {
1023		var i = 0,
1024			len = arr.length;
1025		for (; i < len; i++) {
1026			if (fn.call(arr[i], arr[i], i, arr) === false) {
1027				return i;
1028			}
1029		}
1030	};
1031
1032	/**
1033	 * Filter an array
1034	 */
1035	grep = jQ.grep;
1036
1037	/**
1038	 * Map an array
1039	 * @param {Array} arr
1040	 * @param {Function} fn
1041	 */
1042	map = function (arr, fn) {
1043		//return jQuery.map(arr, fn);
1044		var results = [],
1045			i = 0,
1046			len = arr.length;
1047		for (; i < len; i++) {
1048			results[i] = fn.call(arr[i], arr[i], i, arr);
1049		}
1050		return results;
1051
1052	};
1053
1054	/**
1055	 * Deep merge two objects and return a third object
1056	 */
1057	merge = function () {
1058		var args = arguments;
1059		return jQ.extend(true, null, args[0], args[1], args[2], args[3]);
1060	};
1061
1062	/**
1063	 * Get the position of an element relative to the top left of the page
1064	 */
1065	offset = function (el) {
1066		return jQ(el).offset();
1067	};
1068
1069	/**
1070	 * Add an event listener
1071	 * @param {Object} el A HTML element or custom object
1072	 * @param {String} event The event type
1073	 * @param {Function} fn The event handler
1074	 */
1075	addEvent = function (el, event, fn) {
1076		jQ(el).bind(event, fn);
1077	};
1078
1079	/**
1080	 * Remove event added with addEvent
1081	 * @param {Object} el The object
1082	 * @param {String} eventType The event type. Leave blank to remove all events.
1083	 * @param {Function} handler The function to remove
1084	 */
1085	removeEvent = function (el, eventType, handler) {
1086		// workaround for jQuery issue with unbinding custom events:
1087		// http://forum.jquery.com/topic/javascript-error-when-unbinding-a-custom-event-using-jquery-1-4-2
1088		var func = doc.removeEventListener ? 'removeEventListener' : 'detachEvent';
1089		if (doc[func] && !el[func]) {
1090			el[func] = function () {};
1091		}
1092
1093		jQ(el).unbind(eventType, handler);
1094	};
1095
1096	/**
1097	 * Fire an event on a custom object
1098	 * @param {Object} el
1099	 * @param {String} type
1100	 * @param {Object} eventArguments
1101	 * @param {Function} defaultFunction
1102	 */
1103	fireEvent = function (el, type, eventArguments, defaultFunction) {
1104		var event = jQ.Event(type),
1105			detachedType = 'detached' + type,
1106			defaultPrevented;
1107
1108		extend(event, eventArguments);
1109
1110		// Prevent jQuery from triggering the object method that is named the
1111		// same as the event. For example, if the event is 'select', jQuery
1112		// attempts calling el.select and it goes into a loop.
1113		if (el[type]) {
1114			el[detachedType] = el[type];
1115			el[type] = null;
1116		}
1117
1118		// Wrap preventDefault and stopPropagation in try/catch blocks in
1119		// order to prevent JS errors when cancelling events on non-DOM
1120		// objects. #615.
1121		each(['preventDefault', 'stopPropagation'], function (fn) {
1122			var base = event[fn];
1123			event[fn] = function () {
1124				try {
1125					base.call(event);
1126				} catch (e) {
1127					if (fn === 'preventDefault') {
1128						defaultPrevented = true;
1129					}
1130				}
1131			};
1132		});
1133
1134		// trigger it
1135		jQ(el).trigger(event);
1136
1137		// attach the method
1138		if (el[detachedType]) {
1139			el[type] = el[detachedType];
1140			el[detachedType] = null;
1141		}
1142
1143		if (defaultFunction && !event.isDefaultPrevented() && !defaultPrevented) {
1144			defaultFunction(event);
1145		}
1146	};
1147
1148	/**
1149	 * Animate a HTML element or SVG element wrapper
1150	 * @param {Object} el
1151	 * @param {Object} params
1152	 * @param {Object} options jQuery-like animation options: duration, easing, callback
1153	 */
1154	animate = function (el, params, options) {
1155		var $el = jQ(el);
1156		if (params.d) {
1157			el.toD = params.d; // keep the array form for paths, used in jQ.fx.step.d
1158			params.d = 1; // because in jQuery, animating to an array has a different meaning
1159		}
1160
1161		$el.stop();
1162		$el.animate(params, options);
1163
1164	};
1165	/**
1166	 * Stop running animation
1167	 */
1168	stop = function (el) {
1169		jQ(el).stop();
1170	};
1171
1172
1173	//=== Extend jQuery on init
1174
1175	/*jslint unparam: true*//* allow unused param x in this function */
1176	jQ.extend(jQ.easing, {
1177		easeOutQuad: function (x, t, b, c, d) {
1178			return -c * (t /= d) * (t - 2) + b;
1179		}
1180	});
1181	/*jslint unparam: false*/
1182
1183	// extend the animate function to allow SVG animations
1184	var jFx = jQuery.fx,
1185		jStep = jFx.step;
1186
1187	// extend some methods to check for elem.attr, which means it is a Highcharts SVG object
1188	each(['cur', '_default', 'width', 'height'], function (fn, i) {
1189		var obj = i ? jStep : jFx.prototype, // 'cur', the getter' relates to jFx.prototype
1190			base = obj[fn],
1191			elem;
1192
1193		if (base) { // step.width and step.height don't exist in jQuery < 1.7
1194
1195			// create the extended function replacement
1196			obj[fn] = function (fx) {
1197
1198				// jFx.prototype.cur does not use fx argument
1199				fx = i ? fx : this;
1200
1201				// shortcut
1202				elem = fx.elem;
1203
1204				// jFX.prototype.cur returns the current value. The other ones are setters
1205				// and returning a value has no effect.
1206				return elem.attr ? // is SVG element wrapper
1207					elem.attr(fx.prop, fx.now) : // apply the SVG wrapper's method
1208					base.apply(this, arguments); // use jQuery's built-in method
1209			};
1210		}
1211	});
1212
1213	// animate paths
1214	jStep.d = function (fx) {
1215		var elem = fx.elem;
1216
1217
1218		// Normally start and end should be set in state == 0, but sometimes,
1219		// for reasons unknown, this doesn't happen. Perhaps state == 0 is skipped
1220		// in these cases
1221		if (!fx.started) {
1222			var ends = pathAnim.init(elem, elem.d, elem.toD);
1223			fx.start = ends[0];
1224			fx.end = ends[1];
1225			fx.started = true;
1226		}
1227
1228
1229		// interpolate each value of the path
1230		elem.attr('d', pathAnim.step(fx.start, fx.end, fx.pos, elem.toD));
1231
1232	};
1233}
1234
1235/* ****************************************************************************
1236 * Handle the options                                                         *
1237 *****************************************************************************/
1238var
1239
1240defaultLabelOptions = {
1241	enabled: true,
1242	// rotation: 0,
1243	align: 'center',
1244	x: 0,
1245	y: 15,
1246	/*formatter: function () {
1247		return this.value;
1248	},*/
1249	style: {
1250		color: '#666',
1251		fontSize: '11px',
1252		lineHeight: '14px'
1253	}
1254};
1255
1256defaultOptions = {
1257	colors: ['#4572A7', '#AA4643', '#89A54E', '#80699B', '#3D96AE',
1258		'#DB843D', '#92A8CD', '#A47D7C', '#B5CA92'],
1259	symbols: ['circle', 'diamond', 'square', 'triangle', 'triangle-down'],
1260	lang: {
1261		loading: 'Loading...',
1262		months: ['January', 'February', 'March', 'April', 'May', 'June', 'July',
1263				'August', 'September', 'October', 'November', 'December'],
1264		shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
1265		weekdays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
1266		decimalPoint: '.',
1267		resetZoom: 'Reset zoom',
1268		resetZoomTitle: 'Reset zoom level 1:1',
1269		thousandsSep: ','
1270	},
1271	global: {
1272		useUTC: true,
1273		canvasToolsURL: 'http://code.highcharts.com/2.2.1/modules/canvas-tools.js'
1274	},
1275	chart: {
1276		//animation: true,
1277		//alignTicks: false,
1278		//reflow: true,
1279		//className: null,
1280		//events: { load, selection },
1281		//margin: [null],
1282		//marginTop: null,
1283		//marginRight: null,
1284		//marginBottom: null,
1285		//marginLeft: null,
1286		borderColor: '#4572A7',
1287		//borderWidth: 0,
1288		borderRadius: 5,
1289		defaultSeriesType: 'line',
1290		ignoreHiddenSeries: true,
1291		//inverted: false,
1292		//shadow: false,
1293		spacingTop: 10,
1294		spacingRight: 10,
1295		spacingBottom: 15,
1296		spacingLeft: 10,
1297		style: {
1298			fontFamily: '"Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, Helvetica, sans-serif', // default font
1299			fontSize: '12px'
1300		},
1301		backgroundColor: '#FFFFFF',
1302		//plotBackgroundColor: null,
1303		plotBorderColor: '#C0C0C0',
1304		//plotBorderWidth: 0,
1305		//plotShadow: false,
1306		//zoomType: ''
1307		resetZoomButton: {
1308			theme: {
1309				zIndex: 20
1310			},
1311			position: {
1312				align: 'right',
1313				x: -10,
1314				//verticalAlign: 'top',
1315				y: 10
1316			}
1317			// relativeTo: 'plot'
1318		}
1319	},
1320	title: {
1321		text: 'Chart title',
1322		align: 'center',
1323		// floating: false,
1324		// margin: 15,
1325		// x: 0,
1326		// verticalAlign: 'top',
1327		y: 15,
1328		style: {
1329			color: '#3E576F',
1330			fontSize: '16px'
1331		}
1332
1333	},
1334	subtitle: {
1335		text: '',
1336		align: 'center',
1337		// floating: false
1338		// x: 0,
1339		// verticalAlign: 'top',
1340		y: 30,
1341		style: {
1342			color: '#6D869F'
1343		}
1344	},
1345
1346	plotOptions: {
1347		line: { // base series options
1348			allowPointSelect: false,
1349			showCheckbox: false,
1350			animation: {
1351				duration: 1000
1352			},
1353			//connectNulls: false,
1354			//cursor: 'default',
1355			//clip: true,
1356			//dashStyle: null,
1357			//enableMouseTracking: true,
1358			events: {},
1359			//legendIndex: 0,
1360			lineWidth: 2,
1361			shadow: true,
1362			// stacking: null,
1363			marker: {
1364				enabled: true,
1365				//symbol: null,
1366				lineWidth: 0,
1367				radius: 4,
1368				lineColor: '#FFFFFF',
1369				//fillColor: null,
1370				states: { // states for a single point
1371					hover: {
1372						//radius: base + 2
1373					},
1374					select: {
1375						fillColor: '#FFFFFF',
1376						lineColor: '#000000',
1377						lineWidth: 2
1378					}
1379				}
1380			},
1381			point: {
1382				events: {}
1383			},
1384			dataLabels: merge(defaultLabelOptions, {
1385				enabled: false,
1386				y: -6,
1387				formatter: function () {
1388					return this.y;
1389				}
1390				// backgroundColor: undefined,
1391				// borderColor: undefined,
1392				// borderRadius: undefined,
1393				// borderWidth: undefined,
1394				// padding: 3,
1395				// shadow: false
1396			}),
1397			cropThreshold: 300, // draw points outside the plot area when the number of points is less than this
1398			pointRange: 0,
1399			//pointStart: 0,
1400			//pointInterval: 1,
1401			showInLegend: true,
1402			states: { // states for the entire series
1403				hover: {
1404					//enabled: false,
1405					//lineWidth: base + 1,
1406					marker: {
1407						// lineWidth: base + 1,
1408						// radius: base + 1
1409					}
1410				},
1411				select: {
1412					marker: {}
1413				}
1414			},
1415			stickyTracking: true
1416			//tooltip: {
1417				//pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.y}</b>'
1418				//valueDecimals: null,
1419				//xDateFormat: '%A, %b %e, %Y',
1420				//valuePrefix: '',
1421				//ySuffix: ''				
1422			//}
1423			// turboThreshold: 1000
1424			// zIndex: null
1425		}
1426	},
1427	labels: {
1428		//items: [],
1429		style: {
1430			//font: defaultFont,
1431			position: ABSOLUTE,
1432			color: '#3E576F'
1433		}
1434	},
1435	legend: {
1436		enabled: true,
1437		align: 'center',
1438		//floating: false,
1439		layout: 'horizontal',
1440		labelFormatter: function () {
1441			return this.name;
1442		},
1443		borderWidth: 1,
1444		borderColor: '#909090',
1445		borderRadius: 5,
1446		// margin: 10,
1447		// reversed: false,
1448		shadow: false,
1449		// backgroundColor: null,
1450		style: {
1451			padding: '5px'
1452		},
1453		itemStyle: {
1454			cursor: 'pointer',
1455			color: '#3E576F'
1456		},
1457		itemHoverStyle: {
1458			//cursor: 'pointer', removed as of #601
1459			color: '#000000'
1460		},
1461		itemHiddenStyle: {
1462			color: '#C0C0C0'
1463		},
1464		itemCheckboxStyle: {
1465			position: ABSOLUTE,
1466			width: '13px', // for IE precision
1467			height: '13px'
1468		},
1469		// itemWidth: undefined,
1470		symbolWidth: 16,
1471		symbolPadding: 5,
1472		verticalAlign: 'bottom',
1473		// width: undefined,
1474		x: 0,
1475		y: 0
1476	},
1477
1478	loading: {
1479		// hideDuration: 100,
1480		labelStyle: {
1481			fontWeight: 'bold',
1482			position: RELATIVE,
1483			top: '1em'
1484		},
1485		// showDuration: 0,
1486		style: {
1487			position: ABSOLUTE,
1488			backgroundColor: 'white',
1489			opacity: 0.5,
1490			textAlign: 'center'
1491		}
1492	},
1493
1494	tooltip: {
1495		enabled: true,
1496		//crosshairs: null,
1497		backgroundColor: 'rgba(255, 255, 255, .85)',
1498		borderWidth: 2,
1499		borderRadius: 5,
1500		//formatter: defaultFormatter,
1501		headerFormat: '<span style="font-size: 10px">{point.key}</span><br/>',
1502		pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.y}</b><br/>',
1503		shadow: true,
1504		shared: useCanVG,
1505		snap: hasTouch ? 25 : 10,
1506		style: {
1507			color: '#333333',
1508			fontSize: '12px',
1509			padding: '5px',
1510			whiteSpace: 'nowrap'
1511		}
1512		//xDateFormat: '%A, %b %e, %Y',
1513		//valueDecimals: null,
1514		//valuePrefix: '',
1515		//valueSuffix: ''
1516	},
1517
1518	credits: {
1519		enabled: true,
1520		text: 'Highcharts.com',
1521		href: 'http://www.highcharts.com',
1522		position: {
1523			align: 'right',
1524			x: -10,
1525			verticalAlign: 'bottom',
1526			y: -5
1527		},
1528		style: {
1529			cursor: 'pointer',
1530			color: '#909090',
1531			fontSize: '10px'
1532		}
1533	}
1534};
1535
1536// Axis defaults
1537/*jslint white: true*/
1538var defaultXAxisOptions = {
1539	// allowDecimals: null,
1540	// alternateGridColor: null,
1541	// categories: [],
1542	dateTimeLabelFormats: hash(
1543		MILLISECOND, '%H:%M:%S.%L',
1544		SECOND, '%H:%M:%S',
1545		MINUTE, '%H:%M',
1546		HOUR, '%H:%M',
1547		DAY, '%e. %b',
1548		WEEK, '%e. %b',
1549		MONTH, '%b \'%y',
1550		YEAR, '%Y'
1551	),
1552	endOnTick: false,
1553	gridLineColor: '#C0C0C0',
1554	// gridLineDashStyle: 'solid',
1555	// gridLineWidth: 0,
1556	// reversed: false,
1557
1558	labels: defaultLabelOptions,
1559		// { step: null },
1560	lineColor: '#C0D0E0',
1561	lineWidth: 1,
1562	//linkedTo: null,
1563	max: null,
1564	min: null,
1565	minPadding: 0.01,
1566	maxPadding: 0.01,
1567	//minRange: null,
1568	minorGridLineColor: '#E0E0E0',
1569	// minorGridLineDashStyle: null,
1570	minorGridLineWidth: 1,
1571	minorTickColor: '#A0A0A0',
1572	//minorTickInterval: null,
1573	minorTickLength: 2,
1574	minorTickPosition: 'outside', // inside or outside
1575	//minorTickWidth: 0,
1576	//opposite: false,
1577	//offset: 0,
1578	//plotBands: [{
1579	//	events: {},
1580	//	zIndex: 1,
1581	//	labels: { align, x, verticalAlign, y, style, rotation, textAlign }
1582	//}],
1583	//plotLines: [{
1584	//	events: {}
1585	//  dashStyle: {}
1586	//	zIndex:
1587	//	labels: { align, x, verticalAlign, y, style, rotation, textAlign }
1588	//}],
1589	//reversed: false,
1590	// showFirstLabel: true,
1591	// showLastLabel: true,
1592	startOfWeek: 1,
1593	startOnTick: false,
1594	tickColor: '#C0D0E0',
1595	//tickInterval: null,
1596	tickLength: 5,
1597	tickmarkPlacement: 'between', // on or between
1598	tickPixelInterval: 100,
1599	tickPosition: 'outside',
1600	tickWidth: 1,
1601	title: {
1602		//text: null,
1603		align: 'middle', // low, middle or high
1604		//margin: 0 for horizontal, 10 for vertical axes,
1605		//rotation: 0,
1606		//side: 'outside',
1607		style: {
1608			color: '#6D869F',
1609			//font: defaultFont.replace('normal', 'bold')
1610			fontWeight: 'bold'
1611		}
1612		//x: 0,
1613		//y: 0
1614	},
1615	type: 'linear' // linear, logarithmic or datetime
1616},
1617
1618defaultYAxisOptions = merge(defaultXAxisOptions, {
1619	endOnTick: true,
1620	gridLineWidth: 1,
1621	tickPixelInterval: 72,
1622	showLastLabel: true,
1623	labels: {
1624		align: 'right',
1625		x: -8,
1626		y: 3
1627	},
1628	lineWidth: 0,
1629	maxPadding: 0.05,
1630	minPadding: 0.05,
1631	startOnTick: true,
1632	tickWidth: 0,
1633	title: {
1634		rotation: 270,
1635		text: 'Y-values'
1636	},
1637	stackLabels: {
1638		enabled: false,
1639		//align: dynamic,
1640		//y: dynamic,
1641		//x: dynamic,
1642		//verticalAlign: dynamic,
1643		//textAlign: dynamic,
1644		//rotation: 0,
1645		formatter: function () {
1646			return this.total;
1647		},
1648		style: defaultLabelOptions.style
1649	}
1650}),
1651
1652defaultLeftAxisOptions = {
1653	labels: {
1654		align: 'right',
1655		x: -8,
1656		y: null
1657	},
1658	title: {
1659		rotation: 270
1660	}
1661},
1662defaultRightAxisOptions = {
1663	labels: {
1664		align: 'left',
1665		x: 8,
1666		y: null
1667	},
1668	title: {
1669		rotation: 90
1670	}
1671},
1672defaultBottomAxisOptions = { // horizontal axis
1673	labels: {
1674		align: 'center',
1675		x: 0,
1676		y: 14,
1677		overflow: 'justify' // docs
1678		// staggerLines: null
1679	},
1680	title: {
1681		rotation: 0
1682	}
1683}
1683,
1684defaultTopAxisOptions = merge(defaultBottomAxisOptions, {
1685	labels: {
1686		y: -5,
1687		overflow: 'justify'
1688		// staggerLines: null
1689	}
1690});
1691/*jslint white: false*/
1692
1693
1694
1695// Series defaults
1696var defaultPlotOptions = defaultOptions.plotOptions,
1697	defaultSeriesOptions = defaultPlotOptions.line;
1698//defaultPlotOptions.line = merge(defaultSeriesOptions);
1699defaultPlotOptions.spline = merge(defaultSeriesOptions);
1700defaultPlotOptions.scatter = merge(defaultSeriesOptions, {
1701	lineWidth: 0,
1702	states: {
1703		hover: {
1704			lineWidth: 0
1705		}
1706	},
1707	tooltip: {
1708		headerFormat: '<span style="font-size: 10px; color:{series.color}">{series.name}</span><br/>',
1709		pointFormat: 'x: <b>{point.x}</b><br/>y: <b>{point.y}</b><br/>'
1710	}
1711});
1712defaultPlotOptions.area = merge(defaultSeriesOptions, {
1713	threshold: 0
1714	// lineColor: null, // overrides color, but lets fillColor be unaltered
1715	// fillOpacity: 0.75,
1716	// fillColor: null
1717
1718});
1719defaultPlotOptions.areaspline = merge(defaultPlotOptions.area);
1720defaultPlotOptions.column = merge(defaultSeriesOptions, {
1721	borderColor: '#FFFFFF',
1722	borderWidth: 1,
1723	borderRadius: 0,
1724	//colorByPoint: undefined,
1725	groupPadding: 0.2,
1726	marker: null, // point options are specified in the base options
1727	pointPadding: 0.1,
1728	//pointWidth: null,
1729	minPointLength: 0,
1730	cropThreshold: 50, // when there are more points, they will not animate out of the chart on xAxis.setExtremes
1731	pointRange: null, // null means auto, meaning 1 in a categorized axis and least distance between points if not categories
1732	states: {
1733		hover: {
1734			brightness: 0.1,
1735			shadow: false
1736		},
1737		select: {
1738			color: '#C0C0C0',
1739			borderColor: '#000000',
1740			shadow: false
1741		}
1742	},
1743	dataLabels: {
1744		y: null,
1745		verticalAlign: null
1746	},
1747	threshold: 0
1748});
1749defaultPlotOptions.bar = merge(defaultPlotOptions.column, {
1750	dataLabels: {
1751		align: 'left',
1752		x: 5,
1753		y: null,
1754		verticalAlign: 'middle'
1755	}
1756});
1757defaultPlotOptions.pie = merge(defaultSeriesOptions, {
1758	//dragType: '', // n/a
1759	borderColor: '#FFFFFF',
1760	borderWidth: 1,
1761	center: ['50%', '50%'],
1762	colorByPoint: true, // always true for pies
1763	dataLabels: {
1764		// align: null,
1765		// connectorWidth: 1,
1766		// connectorColor: point.color,
1767		// connectorPadding: 5,
1768		distance: 30,
1769		enabled: true,
1770		formatter: function () {
1771			return this.point.name;
1772		},
1773		// softConnector: true,
1774		y: 5
1775	},
1776	//innerSize: 0,
1777	legendType: 'point',
1778	marker: null, // point options are specified in the base options
1779	size: '75%',
1780	showInLegend: false,
1781	slicedOffset: 10,
1782	states: {
1783		hover: {
1784			brightness: 0.1,
1785			shadow: false
1786		}
1787	}
1788
1789});
1790
1791// set the default time methods
1792setTimeMethods();
1793
1794
1795
1796/**
1797 * Set the time methods globally based on the useUTC option. Time method can be either
1798 * local time or UTC (default).
1799 */
1800function setTimeMethods() {
1801	var useUTC = defaultOptions.global.useUTC,
1802		GET = useUTC ? 'getUTC' : 'get',
1803		SET = useUTC ? 'setUTC' : 'set';
1804
1805	makeTime = useUTC ? Date.UTC : function (year, month, date, hours, minutes, seconds) {
1806		return new Date(
1807			year,
1808			month,
1809			pick(date, 1),
1810			pick(hours, 0),
1811			pick(minutes, 0),
1812			pick(seconds, 0)
1813		).getTime();
1814	};
1815	getMinutes =  GET + 'Minutes';
1816	getHours =    GET + 'Hours';
1817	getDay =      GET + 'Day';
1818	getDate =     GET + 'Date';
1819	getMonth =    GET + 'Month';
1820	getFullYear = GET + 'FullYear';
1821	setMinutes =  SET + 'Minutes';
1822	setHours =    SET + 'Hours';
1823	setDate =     SET + 'Date';
1824	setMonth =    SET + 'Month';
1825	setFullYear = SET + 'FullYear';
1826
1827}
1828
1829/**
1830 * Merge the default options with custom options and return the new options structure
1831 * @param {Object} options The new custom options
1832 */
1833function setOptions(options) {
1834	
1835	// Pull out axis options and apply them to the respective default axis options 
1836	defaultXAxisOptions = merge(defaultXAxisOptions, options.xAxis);
1837	defaultYAxisOptions = merge(defaultYAxisOptions, options.yAxis);
1838	options.xAxis = options.yAxis = UNDEFINED;
1839	
1840	// Merge in the default options
1841	defaultOptions = merge(defaultOptions, options);
1842	
1843	// Apply UTC
1844	setTimeMethods();
1845
1846	return defaultOptions;
1847}
1848
1849/**
1850 * Get the updated default options. Merely exposing defaultOptions for outside modules
1851 * isn't enough because the setOptions method creates a new object.
1852 */
1853function getOptions() {
1854	return defaultOptions;
1855}
1856
1857
1858
1859/**
1860 * Handle color operations. The object methods are chainable.
1861 * @param {String} input The input color in either rbga or hex format
1862 */
1863var Color = function (input) {
1864	// declare variables
1865	var rgba = [], result;
1866
1867	/**
1868	 * Parse the input color to rgba array
1869	 * @param {String} input
1870	 */
1871	function init(input) {
1872
1873		// rgba
1874		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);
1875		if (result) {
1876			rgba = [pInt(result[1]), pInt(result[2]), pInt(result[3]), parseFloat(result[4], 10)];
1877		} else { // hex
1878			result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(input);
1879			if (result) {
1880				rgba = [pInt(result[1], 16), pInt(result[2], 16), pInt(result[3], 16), 1];
1881			}
1882		}
1883
1884	}
1885	/**
1886	 * Return the color a specified format
1887	 * @param {String} format
1888	 */
1889	function get(format) {
1890		var ret;
1891
1892		// it's NaN if gradient colors on a column chart
1893		if (rgba && !isNaN(rgba[0])) {
1894			if (format === 'rgb') {
1895				ret = 'rgb(' + rgba[0] + ',' + rgba[1] + ',' + rgba[2] + ')';
1896			} else if (format === 'a') {
1897				ret = rgba[3];
1898			} else {
1899				ret = 'rgba(' + rgba.join(',') + ')';
1900			}
1901		} else {
1902			ret = input;
1903		}
1904		return ret;
1905	}
1906
1907	/**
1908	 * Brighten the color
1909	 * @param {Number} alpha
1910	 */
1911	function brighten(alpha) {
1912		if (isNumber(alpha) && alpha !== 0) {
1913			var i;
1914			for (i = 0; i < 3; i++) {
1915				rgba[i] += pInt(alpha * 255);
1916
1917				if (rgba[i] < 0) {
1918					rgba[i] = 0;
1919				}
1920				if (rgba[i] > 255) {
1921					rgba[i] = 255;
1922				}
1923			}
1924		}
1925		return this;
1926	}
1927	/**
1928	 * Set the color's opacity to a given alpha value
1929	 * @param {Number} alpha
1930	 */
1931	function setOpacity(alpha) {
1932		rgba[3] = alpha;
1933		return this;
1934	}
1935
1936	// initialize: parse the input
1937	init(input);
1938
1939	// public methods
1940	return {
1941		get: get,
1942		brighten: brighten,
1943		setOpacity: setOpacity
1944	};
1945};
1946
1947
1948/**
1949 * A wrapper object for SVG elements
1950 */
1951function SVGElement() {}
1952
1953SVGElement.prototype = {
1954	/**
1955	 * Initialize the SVG renderer
1956	 * @param {Object} renderer
1957	 * @param {String}
1957 nodeName
1958	 */
1959	init: function (renderer, nodeName) {
1960		var wrapper = this;
1961		wrapper.element = nodeName === 'span' ?
1962			createElement(nodeName) :
1963			doc.createElementNS(SVG_NS, nodeName);
1964		wrapper.renderer = renderer;
1965		/**
1966		 * A collection of attribute setters. These methods, if defined, are called right before a certain
1967		 * attribute is set on an element wrapper. Returning false prevents the default attribute
1968		 * setter to run. Returning a value causes the default setter to set that value. Used in
1969		 * Renderer.label.
1970		 */
1971		wrapper.attrSetters = {};
1972	},
1973	/**
1974	 * Animate a given attribute
1975	 * @param {Object} params
1976	 * @param {Number} options The same options as in jQuery animation
1977	 * @param {Function} complete Function to perform at the end of animation
1978	 */
1979	animate: function (params, options, complete) {
1980		var animOptions = pick(options, globalAnimation, true);
1981		stop(this); // stop regardless of animation actually running, or reverting to .attr (#607)
1982		if (animOptions) {
1983			animOptions = merge(animOptions);
1984			if (complete) { // allows using a callback with the global animation without overwriting it
1985				animOptions.complete = complete;
1986			}
1987			animate(this, params, animOptions);
1988		} else {
1989			this.attr(params);
1990			if (complete) {
1991				complete();
1992			}
1993		}
1994	},
1995	/**
1996	 * Set or get a given attribute
1997	 * @param {Object|String} hash
1998	 * @param {Mixed|Undefined} val
1999	 */
2000	attr: function (hash, val) {
2001		var wrapper = this,
2002			key,
2003			value,
2004			result,
2005			i,
2006			child,
2007			element = wrapper.element,
2008			nodeName = element.nodeName,
2009			renderer = wrapper.renderer,
2010			skipAttr,
2011			attrSetters = wrapper.attrSetters,
2012			shadows = wrapper.shadows,
2013			hasSetSymbolSize,
2014			ret = wrapper;
2015
2016		// single key-value pair
2017		if (isString(hash) && defined(val)) {
2018			key = hash;
2019			hash = {};
2020			hash[key] = val;
2021		}
2022
2023		// used as a getter: first argument is a string, second is undefined
2024		if (isString(hash)) {
2025			key = hash;
2026			if (nodeName === 'circle') {
2027				key = { x: 'cx', y: 'cy' }[key] || key;
2028			} else if (key === 'strokeWidth') {
2029				key = 'stroke-width';
2030			}
2031			ret = attr(element, key) || wrapper[key] || 0;
2032
2033			if (key !== 'd' && key !== 'visibility') { // 'd' is string in animation step
2034				ret = parseFloat(ret);
2035			}
2036
2037		// setter
2038		} else {
2039
2040			for (key in hash) {
2041				skipAttr = false; // reset
2042				value = hash[key];
2043
2044				// check for a specific attribute setter
2045				result = attrSetters[key] && attrSetters[key](value, key);
2046
2047				if (result !== false) {
2048
2049					if (result !== UNDEFINED) {
2050						value = result; // the attribute setter has returned a new value to set
2051					}
2052
2053					// paths
2054					if (key === 'd') {
2055						if (value && value.join) { // join path
2056							value = value.join(' ');
2057						}
2058						if (/(NaN| {2}|^$)/.test(value)) {
2059							value = 'M 0 0';
2060						}
2061						wrapper.d = value; // shortcut for animations
2062
2063					// update child tspans x values
2064					} else if (key === 'x' && nodeName === 'text') {
2065						for (i = 0; i < element.childNodes.length; i++) {
2066							child = element.childNodes[i];
2067							// if the x values are equal, the tspan represents a linebreak
2068							if (attr(child, 'x') === attr(element, 'x')) {
2069								//child.setAttribute('x', value);
2070								attr(child, 'x', value);
2071							}
2072						}
2073
2074						if (wrapper.rotation) {
2075							attr(element, 'transform', 'rotate(' + wrapper.rotation + ' ' + value + ' ' +
2076								pInt(hash.y || attr(element, 'y')) + ')');
2077						}
2078
2079					// apply gradients
2080					} else if (key === 'fill') {
2081						value = renderer.color(value, element, key);
2082
2083					// circle x and y
2084					} else if (nodeName === 'circle' && (key === 'x' || key === 'y')) {
2085						key = { x: 'cx', y: 'cy' }[key] || key;
2086
2087					// rectangle border radius
2088					} else if (nodeName === 'rect' && key === 'r') {
2089						attr(element, {
2090							rx: value,
2091							ry: value
2092						});
2093						skipAttr = true;
2094
2095					// translation and text rotation
2096					} else if (key === 'translateX' || key === 'translateY' || key === 'rotation' || key === 'verticalAlign') {
2097						wrapper[key] = value;
2098						wrapper.updateTransform();
2099						skipAttr = true;
2100
2101					// apply opacity as subnode (required by legacy WebKit and Batik)
2102					} else if (key === 'stroke') {
2103						value = renderer.color(value, element, key);
2104
2105					// emulate VML's dashstyle implementation
2106					} else if (key === 'dashstyle') {
2107						key = 'stroke-dasharray';
2108						value = value && value.toLowerCase();
2109						if (value === 'solid') {
2110							value = NONE;
2111						} else if (value) {
2112							value = value
2113								.replace('shortdashdotdot', '3,1,1,1,1,1,')
2114								.replace('shortdashdot', '3,1,1,1')
2115								.replace('shortdot', '1,1,')
2116								.replace('shortdash', '3,1,')
2117								.replace('longdash', '8,3,')
2118								.replace(/dot/g, '1,3,')
2119								.replace('dash', '4,3,')
2120								.replace(/,$/, '')
2121								.split(','); // ending comma
2122
2123							i = value.length;
2124							while (i--) {
2125								value[i] = pInt(value[i]) * hash['stroke-width'];
2126							}
2127							value = value.join(',');
2128						}
2129
2130					// special
2131					} else if (key === 'isTracker') {
2132						wrapper[key] = value;
2133
2134					// IE9/MooTools combo: MooTools returns objects instead of numbers and IE9 Beta 2
2135					// is unable to cast them. Test again with final IE9.
2136					} else if (key === 'width') {
2137						value = pInt(value);
2138
2139					// Text alignment
2140					} else if (key === 'align') {
2141						key = 'text-anchor';
2142						value = { left: 'start', center: 'middle', right: 'end' }[value];
2143
2144					// Title requires a subnode, #431
2145					} else if (key === 'title') {
2146						var title = doc.createElementNS(SVG_NS, 'title');
2147						title.appendChild(doc.createTextNode(value));
2148						element.appendChild(title);
2149					}
2150
2151					// jQuery animate changes case
2152					if (key === 'strokeWidth') {
2153						key = 'stroke-width';
2154					}
2155
2156					// Chrome/Win < 6 bug (http://code.google.com/p/chromium/issues/detail?id=15461)
2157					if (isWebKit && key === 'stroke-width' && value === 0) {
2158						value = 0.000001;
2159					}
2160
2161					// symbols
2162					if (wrapper.symbolName && /^(x|y|r|start|end|innerR|anchorX|anchorY)/.test(key)) {
2163
2164
2165						if (!hasSetSymbolSize) {
2166							wrapper.symbolAttr(hash);
2167							hasSetSymbolSize = true;
2168						}
2169						skipAttr = true;
2170					}
2171
2172					// let the shadow follow the main element
2173					if (shadows && /^(width|height|visibility|x|y|d|transform)$/.test(key)) {
2174						i = shadows.length;
2175						while (i--) {
2176							attr(shadows[i], key, value);
2177						}
2178					}
2179
2180					// validate heights
2181					if ((key === 'width' || key === 'height') && nodeName === 'rect' && value < 0) {
2182						value = 0;
2183					}
2184
2185
2186
2187
2188					if (key === 'text') {
2189						// only one node allowed
2190						wrapper.textStr = value;
2191						if (wrapper.added) {
2192							renderer.buildText(wrapper);
2193						}
2194					} else if (!skipAttr) {
2195						attr(element, key, value);
2196					}
2197
2198				}
2199
2200			}
2201
2202		}
2203		
2204		// Workaround for our #732, WebKit's issue https://bugs.webkit.org/show_bug.cgi?id=78385
2205		// TODO: If the WebKit team fix this bug before the final release of Chrome 18, remove the workaround.
2206		if (isWebKit && /Chrome\/(18|19)/.test(userAgent)) {
2207			if (nodeName === 'text' && (hash.x !== UNDEFINED || hash.y !== UNDEFINED)) {
2208				var parent = element.parentNode,
2209					next = element.nextSibling;
2210			
2211				if (parent) {
2212					parent.removeChild(element);
2213					if (next) {
2214						parent.insertBefore(element, next);
2215					} else {
2216						parent.appendChild(element);
2217					}
2218				}
2219			}
2220		}
2221		// End of workaround for #732
2222		
2223		return ret;
2224	},
2225
2226	/**
2227	 * If one of the symbol size affecting parameters are changed,
2228	 * check all the others only once for each call to an element's
2229	 * .attr() method
2230	 * @param {Object} hash
2231	 */
2232	symbolAttr: function (hash) {
2233		var wrapper = this;
2234
2235		each(['x', 'y', 'r', 'start', 'end', 'width', 'height', 'innerR', 'anchorX', 'anchorY'], function (key) {
2236			wrapper[key] = pick(hash[key], wrapper[key]);
2237		});
2238
2239		wrapper.attr({
2240			d: wrapper.renderer.symbols[wrapper.symbolName](wrapper.x, wrapper.y, wrapper.width, wrapper.height, wrapper)
2241		});
2242	},
2243
2244	/**
2245	 * Apply a clipping path to this object
2246	 * @param {String} id
2247	 */
2248	clip: function (clipRect) {
2249		return this.attr('clip-path', 'url(' + this.renderer.url + '#' + clipRect.id + ')');
2250	},
2251
2252	/**
2253	 * Calculate the coordinates needed for drawing a rectangle crisply and return the
2254	 * calculated attributes
2255	 * @param {Number} strokeWidth
2256	 * @param {Number} x
2257	 * @param {Number} y
2258	 * @param {Number} width
2259	 * @param {Number} height
2260	 */
2261	crisp: function (strokeWidth, x, y, width, height) {
2262
2263		var wrapper = this,
2264			key,
2265			attribs = {},
2266			values = {},
2267			normalizer;
2268
2269		strokeWidth = strokeWidth || wrapper.strokeWidth || (wrapper.attr && wrapper.attr('stroke-width')) || 0;
2270		normalizer = mathRound(strokeWidth) % 2 / 2; // mathRound because strokeWidth can sometimes have roundoff errors
2271
2272		// normalize for crisp edges
2273		values.x = mathFloor(x || wrapper.x || 0) + normalizer;
2274		values.y = mathFloor(y || wrapper.y || 0) + normalizer;
2275		values.width = mathFloor((width || wrapper.width || 0) - 2 * normalizer);
2276		values.height = mathFloor((height || wrapper.height || 0) - 2 * normalizer);
2277		values.strokeWidth = strokeWidth;
2278
2279		for (key in values) {
2280			if (wrapper[key] !== values[key]) { // only set attribute if changed
2281				wrapper[key] = attribs[key] = values[key];
2282			}
2283		}
2284
2285		return attribs;
2286	},
2287
2288	/**
2289	 * Set styles for the element
2290	 * @param {Object} styles
2291	 */
2292	css: function (styles) {
2293		/*jslint unparam: true*//* allow unused param a in the regexp function below */
2294		var elemWrapper = this,
2295			elem = elemWrapper.element,
2296			textWidth = styles && styles.width && elem.nodeName === 'text',
2297			n,
2298			serializedCss = '',
2299			hyphenate = function (a, b) { return '-' + b.toLowerCase(); };
2300		/*jslint unparam: false*/
2301
2302		// convert legacy
2303		if (styles && styles.color) {
2304			styles.fill = styles.color;
2305		}
2306
2307		// Merge the new styles with the old ones
2308		styles = extend(
2309			elemWrapper.styles,
2310			styles
2311		);
2312
2313		// store object
2314		elemWrapper.styles = styles;
2315
2316		// serialize and set style attribute
2317		if (isIE && !hasSVG) { // legacy IE doesn't support setting style attribute
2318			if (textWidth) {
2319				delete styles.width;
2320			}
2321			css(elemWrapper.element, styles);
2322		} else {
2323			for (n in styles) {
2324				serializedCss += n.replace(/([A-Z])/g, hyphenate) + ':' + styles[n] + ';';
2325			}
2326			elemWrapper.attr({
2327				style: serializedCss
2328			});
2329		}
2330
2331
2332		// re-build text
2333		if (textWidth && elemWrapper.added) {
2334			elemWrapper.renderer.buildText(elemWrapper);
2335		}
2336
2337		return elemWrapper;
2338	},
2339
2340	/**
2341	 * Add an event listener
2342	 * @param {String} eventType
2343	 * @param {Function} handler
2344	 */
2345	on: function (eventType, handler) {
2346		var fn = handler;
2347		// touch
2348		if (hasTouch && eventType === 'click') {
2349			eventType = 'touchstart';
2350			fn = function (e) {
2351				e.preventDefault();
2352				handler();
2353			};
2354		}
2355		// simplest possible event model for internal use
2356		this.element['on' + eventType] = fn;
2357		return this;
2358	},
2359
2360
2361	/**
2362	 * Move an object and its children by x and y values
2363	 * @param {Number} x
2364	 * @param {Number} y
2365	 */
2366	translate: function (x, y) {
2367		return this.attr({
2368			translateX: x,
2369			translateY: y
2370		});
2371	},
2372
2373	/**
2374	 * Invert a group, rotate and flip
2375	 */
2376	invert: function () {
2377		var wrapper = this;
2378		wrapper.inverted = true;
2379		wrapper.updateTransform();
2380		return wrapper;
2381	},
2382
2383	/**
2384	 * Apply CSS to HTML elements. This is used in text within SVG rendering and
2385	 * by the VML renderer
2386	 */
2387	htmlCss: function (styles) {
2388		var wrapper = this,
2389			element = wrapper.element,
2390			textWidth = styles && element.tagName === 'SPAN' && styles.width;
2391
2392		if (textWidth) {
2393			delete styles.width;
2394			wrapper.textWidth = textWidth;
2395			wrapper.updateTransform();
2396		}
2397
2398		wrapper.styles = extend(wrapper.styles, styles);
2399		css(wrapper.element, styles);
2400
2401		return wrapper;
2402	},
2403
2404
2405
2406	/**
2407	 * VML and useHTML method for calculating the bounding box based on offsets
2408	 * @param {Boolean} refresh Whether to force a fresh value from the DOM or to
2409	 * use the cached value
2410	 *
2411	 * @return {Object} A hash containing values for x, y, width and height
2412	 */
2413
2414	htmlGetBBox: function (refresh) {
2415		var wrapper = this,
2416			element = wrapper.element,
2417			bBox = wrapper.bBox;
2418
2419		// faking getBBox in exported SVG in legacy IE
2420		if (!bBox || refresh) {
2421			// faking getBBox in exported SVG in legacy IE
2422			if (element.nodeName === 'text') {
2423				element.style.position = ABSOLUTE;
2424			}
2425
2426			bBox = wrapper.bBox = {
2427				x: element.offsetLeft,
2428				y: element.offsetTop,
2429				width: element.offsetWidth,
2430				height: element.offsetHeight
2431			};
2432		}
2433
2434		return bBox;
2435	},
2436
2437	/**
2438	 * VML override private method to update elements based on internal
2439	 * properties based on SVG transform
2440	 */
2441	htmlUpdateTransform: function () {
2442		// aligning non added elements is expensive
2443		if (!this.added) {
2444			this.alignOnAdd = true;
2445			return;
2446		}
2447
2448		var wrapper = this,
2449			renderer = wrapper.renderer,
2450			elem = wrapper.element,
2451			translateX = wrapper.translateX || 0,
2452			translateY = wrapper.translateY || 0,
2453			x = wrapper.x || 0,
2454			y = wrapper.y || 0,
2455			align = wrapper.textAlign || 'left',
2456			alignCorrection = { left: 0, center: 0.5, right: 1 }[align],
2457			nonLeft = align && align !== 'left',
2458			shadows = wrapper.shadows;
2459
2460		// apply translate
2461		if (translateX || translateY) {
2462			css(elem, {
2463				marginLeft: translateX,
2464				marginTop: translateY
2465			});
2466			if (shadows) { // used in labels/tooltip
2467				each(shadows, function (shadow) {
2468					css(shadow, {
2469						marginLeft: translateX + 1,
2470						marginTop: translateY + 1
2471					});
2472				});
2473			}
2474		}
2475
2476		// apply inversion
2477		if (wrapper.inverted) { // wrapper is a group
2478			each(elem.childNodes, function (child) {
2479				renderer.invertChild(child, elem);
2480			});
2481		}
2482
2483		if (elem.tagName === 'SPAN') {
2484
2485			var width, height,
2486				rotation = wrapper.rotation,
2487				baseline,
2488				radians = 0,
2489				costheta = 1,
2490				sintheta = 0,
2491				quad,
2492				textWidth = pInt(wrapper.textWidth),
2493				xCorr = wrapper.xCorr || 0,
2494				yCorr = wrapper.yCorr || 0,
2495				currentTextTransform = [rotation, align, elem.innerHTML, wrapper.textWidth].join(',');
2496
2497			if (currentTextTransform !== wrapper.cTT) { // do the calculations and DOM access only if properties changed
2498
2499				if (defined(rotation)) {
2500					radians = rotation * deg2rad; // deg to rad
2501					costheta = mathCos(radians);
2502					sintheta = mathSin(radians);
2503
2504					// Adjust for alignment and rotation. Rotation of useHTML content is not yet implemented
2505					// but it can probably be implemented for Firefox 3.5+ on user request. FF3.5+
2506					// has support for CSS3 transform. The getBBox method also needs to be updated
2507					// to compensate for the rotation, like it currently does for SVG.
2508					// Test case: http://highcharts.com/tests/?file=text-rotation
2509					css(elem, {
2510						filter: rotation ? ['progid:DXImageTransform.Microsoft.Matrix(M11=', costheta,
2511							', M12=', -sintheta, ', M21=', sintheta, ', M22=', costheta,
2512							', sizingMethod=\'auto expand\')'].join('') : NONE
2513					});
2514				}
2515
2516				width = pick(wrapper.elemWidth, elem.offsetWidth);
2517				height = pick(wrapper.elemHeight, elem.offsetHeight);
2518
2519				// update textWidth
2520				if (width > textWidth) {
2521					css(elem, {
2522						width: textWidth + PX,
2523						display: 'block',
2524						whiteSpace: 'normal'
2525					});
2526					width = textWidth;
2527				}
2528
2529				// correct x and y
2530				baseline = renderer.fontMetrics(elem.style.fontSize).b;
2531				xCorr = costheta < 0 && -width;
2532				yCorr = sintheta < 0 && -height;
2533
2534				// correct for baseline and corners spilling out after rotation
2535				quad = costheta * sintheta < 0;
2536				xCorr += sintheta * baseline * (quad ? 1 - alignCorrection : alignCorrection);
2537				yCorr -= costheta * baseline * (rotation ? (quad ? alignCorrection : 1 - alignCorrection) : 1);
2538
2539				// correct for the length/height of the text
2540				if (nonLeft) {
2541					xCorr -= width * alignCorrection * (costheta < 0 ? -1 : 1);
2542					if (rotation) {
2543						yCorr -= height * alignCorrection * (sintheta < 0 ? -1 : 1);
2544					}
2545					css(elem, {
2546						textAlign: align
2547					});
2548				}
2549
2550				// record correction
2551				wrapper.xCorr = xCorr;
2552				wrapper.yCorr = yCorr;
2553			}
2554
2555			// apply position with correction
2556			css(elem, {
2557				left: (x + xCorr) + PX,
2558				top: (y + yCorr) + PX
2559			});
2560
2561			// record current text transform
2562			wrapper.cTT = currentTextTransform;
2563		}
2564	},
2565
2566	/**
2567	 * Private method to update the transform attribute based on internal
2568	 * properties
2569	 */
2570	updateTransform: function () {
2571		var wrapper = this,
2572			translateX = wrapper.translateX || 0,
2573			translateY = wrapper.translateY || 0,
2574			inverted = wrapper.inverted,
2575			rotation = wrapper.rotation,
2576			transform = [];
2577
2578		// flipping affects translate as adjustment for flipping around the group's axis
2579		if (inverted) {
2580			translateX += wrapper.attr('width');
2581			translateY += wrapper.attr('height');
2582		}
2583
2584		// apply translate
2585		if (translateX || translateY) {
2586			transform.push('translate(' + translateX + ',' + translateY + ')');
2587		}
2588
2589		// apply rotation
2590		if (inverted) {
2591			transform.push('rotate(90) scale(-1,1)');
2592		} else if (rotation) { // text rotation
2593			transform.push('rotate(' + rotation + ' ' + wrapper.x + ' ' + wrapper.y + ')');
2594		}
2595
2596		if (transform.length) {
2597			attr(wrapper.element, 'transform', transform.join(' '));
2598		}
2599	},
2600	/**
2601	 * Bring the element to the front
2602	 */
2603	toFront: function () {
2604		var element = this.element;
2605		element.parentNode.appendChild(element);
2606		return this;
2607	},
2608
2609
2610	/**
2611	 * Break down alignment options like align, verticalAlign, x and y
2612	 * to x and y relative to the chart.
2613	 *
2614	 * @param {Object} alignOptions
2615	 * @param {Boolean} alignByTranslate
2616	 * @param {Object} box The box to align to, needs a width and height
2617	 *
2618	 */
2619	align: function (alignOptions, alignByTranslate, box) {
2620		var elemWrapper = this;
2621
2622		if (!alignOptions) { // called on resize
2623			alignOptions = elemWrapper.alignOptions;
2624			alignByTranslate = elemWrapper.alignByTranslate;
2625		} else { // first call on instanciate
2626			elemWrapper.alignOptions = alignOptions;
2627			elemWrapper.alignByTranslate = alignByTranslate;
2628			if (!box) { // boxes other than renderer handle this internally
2629				elemWrapper.renderer.alignedObjects.push(elemWrapper);
2630			}
2631		}
2632
2633		box = pick(box, elemWrapper.renderer);
2634
2635		var align = alignOptions.align,
2636			vAlign = alignOptions.verticalAlign,
2637			x = (box.x || 0) + (alignOptions.x || 0), // default: left align
2638			y = (box.y || 0) + (alignOptions.y || 0), // default: top align
2639			attribs = {};
2640
2641
2642		// align
2643		if (/^(right|center)$/.test(align)) {
2644			x += (box.width - (alignOptions.width || 0)) /
2645					{ right: 1, center: 2 }[align];
2646		}
2647		attribs[alignByTranslate ? 'translateX' : 'x'] = mathRound(x);
2648
2649
2650		// vertical align
2651		if (/^(bottom|middle)$/.test(vAlign)) {
2652			y += (box.height - (alignOptions.height || 0)) /
2653					({ bottom: 1, middle: 2 }[vAlign] || 1);
2654
2655		}
2656		attribs[alignByTranslate ? 'translateY' : 'y'] = mathRound(y);
2657
2658		// animate only if already placed
2659		elemWrapper[elemWrapper.placed ? 'animate' : 'attr'](attribs);
2660		elemWrapper.placed = true;
2661		elemWrapper.alignAttr = attribs;
2662
2663		return elemWrapper;
2664	},
2665
2666	/**
2667	 * Get the bounding box (width, height, x and y) for the element
2668	 */
2669	getBBox: function (refresh) {
2670		var wrapper = this,
2671			bBox,
2672			width,
2673			height,
2674			rotation = wrapper.rotation,
2675			element = wrapper.element,
2676			rad = rotation * deg2rad;
2677
2678		// SVG elements
2679		if (element.namespaceURI === SVG_NS) {
2680			try { // Fails in Firefox if the container has display: none.
2681				
2682				bBox = element.getBBox ?
2683					// SVG: use extend because IE9 is not allowed to change width and height in case
2684					// of rotation (below)
2685					extend({}, element.getBBox()) :
2686					// Canvas renderer: // TODO: can this be removed now that we're checking for the SVG NS?
2687					{
2688						width: element.offsetWidth,
2689						height: element.offsetHeight
2690					};
2691			} catch (e) {}
2692			
2693			// If the bBox is not set, the try-catch block above failed. The other condition
2694			// is for Opera that returns a width of -Infinity on hidden elements.
2695			if (!bBox || bBox.width < 0) {
2696				bBox = { width: 0, height: 0 };
2697			}
2698			
2699			width = bBox.width;
2700			height = bBox.height;
2701
2702			// adjust for rotated text
2703			if (rotation) {
2704				bBox.width = mathAbs(height * mathSin(rad)) + mathAbs(width * mathCos(rad));
2705				bBox.height = mathAbs(height * mathCos(rad)) + mathAbs(width * mathSin(rad));
2706			}
2707
2708		// VML Renderer or useHTML within SVG
2709		} else {
2710			bBox = wrapper.htmlGetBBox(refresh);
2711		}
2712
2713		return bBox;
2714	},
2715
2716	/**
2717	 * Show the element
2718	 */
2719	show: function () {
2720		return this.attr({ visibility: VISIBLE });
2721	},
2722
2723	/**
2724	 * Hide the element
2725	 */
2726	hide: function () {
2727		return this.attr({ visibility: HIDDEN });
2728	},
2729
2730	/**
2731	 * Add the element
2732	 * @param {Object|Undefined} parent Can be an element, an element wrapper or undefined
2733	 *    to append the element to the renderer.box.
2734	 */
2735	add: function (parent) {
2736
2737		var renderer = this.renderer,
2738			parentWrapper = parent || renderer,
2739			parentNode = parentWrapper.element || renderer.box,
2740			childNodes = parentNode.childNodes,
2741			element = this.element,
2742			zIndex = attr(element, 'zIndex'),
2743			otherElement,
2744			otherZIndex,
2745			i,
2746			inserted;
2747
2748		// mark as inverted
2749		this.parentInverted = parent && parent.inverted;
2750
2751		// build formatted text
2752		if (this.textStr !== undefined) {
2753			renderer.buildText(this);
2754		}
2755
2756		// mark the container as having z indexed children
2757		if (zIndex) {
2758			parentWrapper.handleZ = true;
2759			zIndex = pInt(zIndex);
2760		}
2761
2762		// insert according to this and other elements' zIndex
2763		if (parentWrapper.handleZ) { // this element or any of its siblings has a z index
2764			for (i = 0; i < childNodes.length; i++) {
2765				otherElement = childNodes[i];
2766				otherZIndex = attr(otherElement, 'zIndex');
2767				if (otherElement !== element && (
2768						// insert before the first element with a higher zIndex
2769						pInt(otherZIndex) > zIndex ||
2770						// if no zIndex given, insert before the first element with a zIndex
2771						(!defined(zIndex) && defined(otherZIndex))
2772
2773						)) {
2774					parentNode.insertBefore(element, otherElement);
2775					inserted = true;
2776					break;
2777				}
2778			}
2779		}
2780
2781		// default: append at the end
2782		if (!inserted) {
2783			parentNode.appendChild(element);
2784		}
2785
2786		// mark as added
2787		this.added = true;
2788
2789		// fire an event for internal hooks
2790		fireEvent(this, 'add');
2791
2792		return this;
2793	},
2794
2795	/**
2796	 * Removes a child either by removeChild or move to garbageBin.
2797	 * Issue 490; in VML removeChild results in Orphaned nodes according to sIEve, discardElement does not.
2798	 */
2799	safeRemoveChild: function (element) {
2800		var parentNode = element.parentNode;
2801		if (parentNode) {
2802			parentNode.removeChild(element);
2803		}
2804	},
2805
2806	/**
2807	 * Destroy the element and element wrapper
2808	 */
2809	destroy: function () {
2810		var wrapper = this,
2811			element = wrapper.element || {},
2812			shadows = wrapper.shadows,
2813			box = wrapper.box,
2814			key,
2815			i;
2816
2817		// remove events
2818		element.onclick = element.onmouseout = element.onmouseover = element.onmousemove = null;
2819		stop(wrapper); // stop running animations
2820
2821		if (wrapper.clipPath) {
2822			wrapper.clipPath = wrapper.clipPath.destroy();
2823		}
2824
2825		// Destroy stops in case this is a gradient object
2826		if (wrapper.stops) {
2827			for (i = 0; i < wrapper.stops.length; i++) {
2828				wrapper.stops[i] = wrapper.stops[i].destroy();
2829			}
2830			wrapper.stops = null;
2831		}
2832
2833		// remove element
2834		wrapper.safeRemoveChild(element);
2835
2836		// destroy shadows
2837		if (shadows) {
2838			each(shadows, function (shadow) {
2839				wrapper.safeRemoveChild(shadow);
2840			});
2841		}
2842
2843		// destroy label box
2844		if (box) {
2845			box.destroy();
2846		}
2847
2848		// remove from alignObjects
2849		erase(wrapper.renderer.alignedObjects, wrapper);
2850
2851		for (key in wrapper) {
2852			delete wrapper[key];
2853		}
2854
2855		return null;
2856	},
2857
2858	/**
2859	 * Empty a group element
2860	 */
2861	empty: function () {
2862		var element = this.element,
2863			childNodes = element.childNodes,
2864			i = childNodes.length;
2865
2866		while (i--) {
2867			element.removeChild(childNodes[i]);
2868		}
2869	},
2870
2871	/**
2872	 * Add a shadow to the element. Must be done after the element is added to the DOM
2873	 * @param {Boolean} apply
2874	 */
2875	shadow: function (apply, group) {
2876		var shadows = [],
2877			i,
2878			shadow,
2879			element = this.element,
2880
2881			// compensate for inverted plot area
2882			transform = this.parentInverted ? '(-1,-1)' : '(1,1)';
2883
2884
2885		if (apply) {
2886			for (i = 1; i <= 3; i++) {
2887				shadow = element.cloneNode(0);
2888				attr(shadow, {
2889					'isShadow': 'true',
2890					'stroke': 'rgb(0, 0, 0)',
2891					'stroke-opacity': 0.05 * i,
2892					'stroke-width': 7 - 2 * i,
2893					'transform': 'translate' + transform,
2894					'fill': NONE
2895				});
2896
2897				if (group) {
2898					group.element.appendChild(shadow);
2899				} else {
2900					element.parentNode.insertBefore(shadow, element);
2901				}
2902
2903				shadows.push(shadow);
2904			}
2905
2906			this.shadows = shadows;
2907		}
2908		return this;
2909
2910	}
2911};
2912
2913
2914/**
2915 * The default SVG renderer
2916 */
2917var SVGRenderer = function () {
2918	this.init.apply(this, arguments);
2919};
2920SVGRenderer.prototype = {
2921	Element: SVGElement,
2922
2923	/**
2924	 * Initialize the SVGRenderer
2925	 * @param {Object} container
2926	 * @param {Number} width
2927	 * @param {Number} height
2928	 * @param {Boolean} forExport
2929	 */
2930	init: function (container, width, height, forExport) {
2931		var renderer = this,
2932			loc = location,
2933			boxWrapper;
2934
2935		boxWrapper = renderer.createElement('svg')
2936			.attr({
2937				xmlns: SVG_NS,
2938				version: '1.1'
2939			});
2940		container.appendChild(boxWrapper.element);
2941
2942		// object properties
2943		renderer.isSVG = true;
2944		renderer.box = boxWrapper.element;
2945		renderer.boxWrapper = boxWrapper;
2946		renderer.alignedObjects = [];
2947		renderer.url = isIE ? '' : loc.href.replace(/#.*?$/, '')
2948			.replace(/([\('\)])/g, '\\$1'); // Page url used for internal references. #24, #672.
2949		renderer.defs = this.createElement('defs').add();
2950		renderer.forExport = forExport;
2951		renderer.gradients = {}; // Object where gradient SvgElements are stored
2952
2953		renderer.setSize(width, height, false);
2954	},
2955
2956	/**
2957	 * Destroys the renderer and its allocated members.
2958	 */
2959	destroy: function () {
2960		var renderer = this,
2961			rendererDefs = renderer.defs;
2962		renderer.box = null;
2963		renderer.boxWrapper = renderer.boxWrapper.destroy();
2964
2965		// Call destroy on all gradient elements
2966		destroyObjectProperties(renderer.gradients || {});
2967		renderer.gradients = null;
2968
2969		// Defs are null in VMLRenderer
2970		// Otherwise, destroy them here.
2971		if (rendererDefs) {
2972			renderer.defs = rendererDefs.destroy();
2973		}
2974
2975		renderer.alignedObjects = null;
2976
2977		return null;
2978	},
2979
2980	/**
2981	 * Create a wrapper for an SVG element
2982	 * @param {Object} nodeName
2983	 */
2984	createElement: function (nodeName) {
2985		var wrapper = new this.Element();
2986		wrapper.init(this, nodeName);
2987		return wrapper;
2988	},
2989
2990	/**
2991	 * Dummy function for use in canvas renderer
2992	 */
2993	draw: function () {},
2994
2995	/**
2996	 * Parse a simple HTML string into SVG tspans
2997	 *
2998	 * @param {Object} textNode The parent text SVG node
2999	 */
3000	buildText: function (wrapper) {
3001		var textNode = wrapper.element,
3002			lines = pick(wrapper.textStr, '').toString()
3003				.replace(/<(b|strong)>/g, '<span style="font-weight:bold">')
3004				.replace(/<(i|em)>/g, '<span style="font-style:italic">')
3005				.replace(/<a/g, '<span')
3006				.replace(/<\/(b|strong|i|em|a)>/g, '</span>')
3007				.split(/<br.*?>/g),
3008			childNodes = textNode.childNodes,
3009			styleRegex = /style="([^"]+)"/,
3010			hrefRegex = /href="([^"]+)"/,
3011			parentX = attr(textNode, 'x'),
3012			textStyles = wrapper.styles,
3013			width = textStyles && pInt(textStyles.width),
3014			textLineHeight = textStyles && textStyles.lineHeight,
3015			lastLine,
3016			GET_COMPUTED_STYLE = 'getComputedStyle',
3017			i = childNodes.length;
3018
3019		// remove old text
3020		while (i--) {
3021			textNode.removeChild(childNodes[i]);
3022		}
3023
3024		if (width && !wrapper.added) {
3025			this.box.appendChild(textNode); // attach it to the DOM to read offset width
3026		}
3027
3028		// remove empty line at end
3029		if (lines[lines.length - 1] === '') {
3030			lines.pop();
3031		}
3032
3033		// build the lines
3034		each(lines, function (line, lineNo) {
3035			var spans, spanNo = 0, lineHeight;
3036
3037			line = line.replace(/<span/g, '|||<span').replace(/<\/span>/g, '</span>|||');
3038			spans = line.split('|||');
3039
3040			each(spans, function (span) {
3041				if (span !== '' || spans.length === 1) {
3042					var attributes = {},
3043						tspan = doc.createElementNS(SVG_NS, 'tspan');
3044					if (styleRegex.test(span)) {
3045						attr(
3046							tspan,
3047							'style',
3048							span.match(styleRegex)[1].replace(/(;| |^)color([ :])/, '$1fill$2')
3049						);
3050					}
3051					if (hrefRegex.test(span)) {
3052						attr(tspan, 'onclick', 'location.href=\"' + span.match(hrefRegex)[1] + '\"');
3053						css(tspan, { cursor: 'pointer' });
3054					}
3055
3056					span = (span.replace(/<(.|\n)*?>/g, '') || ' ')
3057						.replace(/&lt;/g, '<')
3058						.replace(/&gt;/g, '>');
3059
3060					// issue #38 workaround.
3061					/*if (reverse) {
3062						arr = [];
3063						i = span.length;
3064						while (i--) {
3065							arr.push(span.charAt(i));
3066						}
3067						span = arr.join('');
3068					}*/
3069
3070					// add the text node
3071					tspan.appendChild(doc.createTextNode(span));
3072
3073					if (!spanNo) { // first span in a line, align it to the left
3074						attributes.x = parentX;
3075					} else {
3076						// Firefox ignores spaces at the front or end of the tspan
3077						attributes.dx = 3; // space
3078					}
3079
3080					// first span on subsequent line, add the line height
3081					if (!spanNo) {
3082						if (lineNo) {
3083
3084							// allow getting the right offset height in exporting in IE
3085							if (!hasSVG && wrapper.renderer.forExport) {
3086								css(tspan, { display: 'block' });
3087							}
3088
3089							// Webkit and opera sometimes return 'normal' as the line height. In that
3090							// case, webkit uses offsetHeight, while Opera falls back to 18
3091							lineHeight = win[GET_COMPUTED_STYLE] &&
3092								pInt(win[GET_COMPUTED_STYLE](lastLine, null).getPropertyValue('line-height'));
3093
3094							if (!lineHeight || isNaN(lineHeight)) {
3095								lineHeight = textLineHeight || lastLine.offsetHeight || 18;
3096							}
3097							attr(tspan, 'dy', lineHeight);
3098						}
3099						lastLine = tspan; // record for use in next line
3100					}
3101
3102					// add attributes
3103					attr(tspan, attributes);
3104
3105					// append it
3106					textNode.appendChild(tspan);
3107
3108					spanNo++;
3109
3110					// check width and apply soft breaks
3111					if (width) {
3112						var words = span.replace(/-/g, '- ').split(' '),
3113							tooLong,
3114							actualWidth,
3115							rest = [];
3116
3117						while (words.length || rest.length) {
3118							actualWidth = wrapper.getBBox().width;
3119							tooLong = actualWidth > width;
3120							if (!tooLong || words.length === 1) { // new line needed
3121								words = rest;
3122								rest = [];
3123								if (words.length) {
3124									tspan = doc.createElementNS(SVG_NS, 'tspan');
3125									attr(tspan, {
3126										dy: textLineHeight || 16,
3127										x: parentX
3128									});
3129									textNode.appendChild(tspan);
3130
3131									if (actualWidth > width) { // a single word is pressing it out
3132										width = actualWidth;
3133									}
3134								}
3135							} else { // append to existing line tspan
3136								tspan.removeChild(tspan.firstChild);
3137								rest.unshift(words.pop());
3138							}
3139							if (words.length) {
3140								tspan.appendChild(doc.createTextNode(words.join(' ').replace(/- /g, '-')));
3141							}
3142						}
3143					}
3144				}
3145			});
3146		});
3147	},
3148
3149	/**
3150	 * Create a button with preset states
3151	 * @param {String} text
3152	 * @param {Number} x
3153	 * @param {Number} y
3154	 * @param {Function} callback
3155	 * @param {Object} normalState
3156	 * @param {Object} hoverState
3157	 * @param {Object} pressedState
3158	 */
3159	button: function (text, x, y, callback, normalState, hoverState, pressedState) {
3160		var label = this.label(text, x, y),
3161			curState = 0,
3162			stateOptions,
3163			stateStyle,
3164			normalStyle,
3165			hoverStyle,
3166			pressedStyle,
3167			STYLE = 'style',
3168			verticalGradient = { x1: 0, y1: 0, x2: 0, y2: 1 };
3169
3170		// prepare the attributes
3171		/*jslint white: true*/
3172		normalState = merge(hash(
3173			STROKE_WIDTH, 1,
3174			STROKE, '#999',
3175			FILL, hash(
3176				LINEAR_GRADIENT, verticalGradient,
3177				STOPS, [
3178					[0, '#FFF'],
3179					[1, '#DDD']
3180				]
3181			),
3182			'r', 3,
3183			'padding', 3,
3184			STYLE, hash(
3185				'color', 'black'
3186			)
3187		), normalState);
3188		/*jslint white: false*/
3189		normalStyle = normalState[STYLE];
3190		delete normalState[STYLE];
3191
3192		/*jslint white: true*/
3193		hoverState = merge(normalState, hash(
3194			STROKE, '#68A',
3195			FILL, hash(
3196				LINEAR_GRADIENT, verticalGradient,
3197				STOPS, [
3198					[0, '#FFF'],
3199					[1, '#ACF']
3200				]
3201			)
3202		), hoverState);
3203		/*jslint white: false*/
3204		hoverStyle = hoverState[STYLE];
3205		delete hoverState[STYLE];
3206
3207		/*jslint white: true*/
3208		pressedState = merge(normalState, hash(
3209			STROKE, '#68A',
3210			FILL, hash(
3211				LINEAR_GRADIENT, verticalGradient,
3212				STOPS, [
3213					[0, '#9BD'],
3214					[1, '#CDF']
3215				]
3216			)
3217		), pressedState);
3218		/*jslint white: false*/
3219		pressedStyle = pressedState[STYLE];
3220		delete pressedState[STYLE];
3221
3222		// add the events
3223		addEvent(label.element, 'mouseenter', function () {
3224			label.attr(hoverState)
3225				.css(hoverStyle);
3226		});
3227		addEvent(label.element, 'mouseleave', function () {
3228			stateOptions = [normalState, hoverState, pressedState][curState];
3229			stateStyle = [normalStyle, hoverStyle, pressedStyle][curState];
3230			label.attr(stateOptions)
3231				.css(stateStyle);
3232		});
3233
3234		label.setState = function (state) {
3235			curState = state;
3236			if (!state) {
3237				label.attr(normalState)
3238					.css(normalStyle);
3239			} else if (state === 2) {
3240				label.attr(pressedState)
3241					.css(pressedStyle);
3242			}
3243		};
3244
3245		return label
3246			.on('click', function () {
3247				callback.call(label);
3248			})
3249			.attr(normalState)
3250			.css(extend({ cursor: 'default' }, normalStyle));
3251	},
3252
3253	/**
3254	 * Make a straight line crisper by not spilling out to neighbour pixels
3255	 * @param {Array} points
3256	 * @param {Number} width
3257	 */
3258	crispLine: function (points, width) {
3259		// points format: [M, 0, 0, L, 100, 0]
3260		// normalize to a crisp line
3261		if (points[1] === points[4]) {
3262			points[1] = points[4] = mathRound(points[1]) + (width % 2 / 2);
3263		}
3264		if (points[2] === points[5]) {
3265			points[2] = points[5] = mathRound(points[2]) + (width % 2 / 2);
3266		}
3267		return points;
3268	},
3269
3270
3271	/**
3272	 * Draw a path
3273	 * @param {Array} path An SVG path in array form
3274	 */
3275	path: function (path) {
3276		return this.createElement('path').attr({
3277			d: path,
3278			fill: NONE
3279		});
3280	},
3281
3282	/**
3283	 * Draw and return an SVG circle
3284	 * @param {Number} x The x position
3285	 * @param {Number} y The y position
3286	 * @param {Number} r The radius
3287	 */
3288	circle: function (x, y, r) {
3289		var attr = isObject(x) ?
3290			x :
3291			{
3292				x: x,
3293				y: y,
3294				r: r
3295			};
3296
3297		return this.createElement('circle').attr(attr);
3298	},
3299
3300	/**
3301	 * Draw and return an arc
3302	 * @param {Number} x X position
3303	 * @param {Number} y Y position
3304	 * @param {Number} r Radius
3305	 * @param {Number} innerR Inner radius like used in donut charts
3306	 * @param {Number} start Starting angle
3307	 * @param {Number} end Ending angle
3308	 */
3309	arc: function (x, y, r, innerR, start, end) {
3310		// arcs are defined as symbols for the ability to set
3311		// attributes in attr and animate
3312
3313		if (isObject(x)) {
3314			y = x.y;
3315			r = x.r;
3316			innerR = x.innerR;
3317			start = x.start;
3318			end = x.end;
3319			x = x.x;
3320		}
3321		return this.symbol('arc', x || 0, y || 0, r || 0, r || 0, {
3322			innerR: innerR || 0,
3323			start: start || 0,
3324			end: end || 0
3325		});
3326	},
3327
3328	/**
3329	 * Draw and return a rectangle
3330	 * @param {Number} x Left position
3331	 * @param {Number} y Top position
3332	 * @param {Number} width
3333	 * @param {Number} height
3334	 * @param {Number} r Border corner radius
3335	 * @param {Number} strokeWidth A stroke width can be supplied to allow crisp drawing
3336	 */
3337	rect: function (x, y, width, height, r, strokeWidth) {
3338		if (isObject(x)) {
3339			y = x.y;
3340			width = x.width;
3341			height = x.height;
3342			r = x.r;
3343			strokeWidth = x.strokeWidth;
3344			x = x.x;
3345		}
3346		var wrapper = this.createElement('rect').attr({
3347			rx: r,
3348			ry: r,
3349			fill: NONE
3350		});
3351
3352		return wrapper.attr(wrapper.crisp(strokeWidth, x, y, mathMax(width, 0), mathMax(height, 0)));
3353	},
3354
3355	/**
3356	 * Resize the box and re-align all aligned elements
3357	 * @param {Object} width
3358	 * @param {Object} height
3359	 * @param {Boolean} animate
3360	 *
3361	 */
3362	setSize: function (width, height, animate) {
3363		var renderer = this,
3364			alignedObjects = renderer.alignedObjects,
3365			i = alignedObjects.length;
3366
3367		renderer.width = width;
3368		renderer.height = height;
3369
3370		renderer.boxWrapper[pick(animate, true) ? 'animate' : 'attr']({
3371			width: width,
3372			height: height
3373		});
3374
3375		while (i--) {
3376			alignedObjects[i].align();
3377		}
3378	},
3379
3380	/**
3381	 * Create a group
3382	 * @param {String} name The group will be given a class name of 'highcharts-{name}'.
3383	 *     This can be used for styling and scripting.
3384	 */
3385	g: function (name) {
3386		var elem = this.createElement('g');
3387		return defined(name) ? elem.attr({ 'class': PREFIX + name }) : elem;
3388	},
3389
3390	/**
3391	 * Display an image
3392	 * @param {String} src
3393	 * @param {Number} x
3394	 * @param {Number} y
3395	 * @param {Number} width
3396	 * @param {Number} height
3397	 */
3398	image: function (src, x, y, width, height) {
3399		var attribs = {
3400				preserveAspectRatio: NONE
3401			},
3402			elemWrapper;
3403
3404		// optional properties
3405		if (arguments.length > 1) {
3406			extend(attribs, {
3407				x: x,
3408				y: y,
3409				width: width,
3410				height: height
3411			});
3412		}
3413
3414		elemWrapper = this.createElement('image').attr(attribs);
3415
3416		// set the href in the xlink namespace
3417		if (elemWrapper.element.setAttributeNS) {
3418			elemWrapper.element.setAttributeNS('http://www.w3.org/1999/xlink',
3419				'href', src);
3420		} else {
3421			// could be exporting in IE
3422			// using href throws "not supported" in ie7 and under, requries regex shim to fix later
3423			elemWrapper.element.setAttribute('hc-svg-href', src);
3424	}
3425
3426		return elemWrapper;
3427	},
3428
3429	/**
3430	 * Draw a symbol out of pre-defined shape paths from the namespace 'symbol' object.
3431	 *
3432	 * @param {Object} symbol
3433	 * @param {Object} x
3434	 * @param {Object} y
3435	 * @param {Object} radius
3436	 * @param {Object} options
3437	 */
3438	symbol: function (symbol, x, y, width, height, options) {
3439
3440		var obj,
3441
3442			// get the symbol definition function
3443			symbolFn = this.symbols[symbol],
3444
3445			// check if there's a path defined for this symbol
3446			path = symbolFn && symbolFn(
3447				mathRound(x),
3448				mathRound(y),
3449				width,
3450				height,
3451				options
3452			),
3453
3454			imageRegex = /^url\((.*?)\)$/,
3455			imageSrc,
3456			imageSize;
3457
3458		if (path) {
3459
3460			obj = this.path(path);
3461			// expando properties for use in animate and attr
3462			extend(obj, {
3463				symbolName: symbol,
3464				x: x,
3465				y: y,
3466				width: width,
3467				height: height
3468			});
3469			if (options) {
3470				extend(obj, options);
3471			}
3472
3473
3474		// image symbols
3475		} else if (imageRegex.test(symbol)) {
3476
3477			var centerImage = function (img, size) {
3478				img.attr({
3479					width: size[0],
3480					height: size[1]
3481				}).translate(
3482					-mathRound(size[0] / 2),
3483					-mathRound(size[1] / 2)
3484				);
3485			};
3486
3487			imageSrc = symbol.match(imageRegex)[1];
3488			imageSize = symbolSizes[imageSrc];
3489
3490			// create the image synchronously, add attribs async
3491			obj = this.image(imageSrc)
3492				.attr({
3493					x: x,
3494					y: y
3495				});
3496
3497			if (imageSize) {
3498				centerImage(obj, imageSize);
3499			} else {
3500				// initialize image to be 0 size so export will still function if there's no cached sizes
3501				obj.attr({ width: 0, height: 0 });
3502
3503				// create a dummy JavaScript image to get the width and height
3504				createElement('img', {
3505					onload: function () {
3506						var img = this;
3507
3508						centerImage(obj, symbolSizes[imageSrc] = [img.width, img.height]);
3509					},
3510					src: imageSrc
3511				});
3512			}
3513		}
3514
3515		return obj;
3516	},
3517
3518	/**
3519	 * An extendable collection of functions for defining symbol paths.
3520	 */
3521	symbols: {
3522		'circle': function (x, y, w, h) {
3523			var cpw = 0.166 * w;
3524			return [
3525				M, x + w / 2, y,
3526				'C', x + w + cpw, y, x + w + cpw, y + h, x + w / 2, y + h,
3527				'C', x - cpw, y + h, x - cpw, y, x + w / 2, y,
3528				'Z'
3529			];
3530		},
3531
3532		'square': function (x, y, w, h) {
3533			return [
3534				M, x, y,
3535				L, x + w, y,
3536				x + w, y + h,
3537				x, y + h,
3538				'Z'
3539			];
3540		},
3541
3542		'triangle': function (x, y, w, h) {
3543			return [
3544				M, x + w / 2, y,
3545				L, x + w, y + h,
3546				x, y + h,
3547				'Z'
3548			];
3549		},
3550
3551		'triangle-down': function (x, y, w, h) {
3552			return [
3553				M, x, y,
3554				L, x + w, y,
3555				x + w / 2, y + h,
3556				'Z'
3557			];
3558		},
3559		'diamond': function (x, y, w, h) {
3560			return [
3561				M, x + w / 2, y,
3562				L, x + w, y + h / 2,
3563				x + w / 2, y + h,
3564				x, y + h / 2,
3565				'Z'
3566			];
3567		},
3568		'arc': function (x, y, w, h, options) {
3569			var start = options.start,
3570				radius = options.r || w || h,
3571				end = options.end - 0.000001, // to prevent cos and sin of start and end from becoming equal on 360 arcs
3572				innerRadius = options.innerR,
3573				cosStart = mathCos(start),
3574				sinStart = mathSin(start),
3575				cosEnd = mathCos(end),
3576				sinEnd = mathSin(end),
3577				longArc = options.end - start < mathPI ? 0 : 1;
3578
3579			return [
3580				M,
3581				x + radius * cosStart,
3582				y + radius * sinStart,
3583				'A', // arcTo
3584				radius, // x radius
3585				radius, // y radius
3586				0, // slanting
3587				longArc, // long or short arc
3588				1, // clockwise
3589				x + radius * cosEnd,
3590				y + radius * sinEnd,
3591				L,
3592				x + innerRadius * cosEnd,
3593				y + innerRadius * sinEnd,
3594				'A', // arcTo
3595				innerRadius, // x radius
3596				innerRadius, // y radius
3597				0, // slanting
3598				longArc, // long or short arc
3599				0, // clockwise
3600				x + innerRadius * cosStart,
3601				y + innerRadius * sinStart,
3602
3603				'Z' // close
3604			];
3605		}
3606	},
3607
3608	/**
3609	 * Define a clipping rectangle
3610	 * @param {String} id
3611	 * @param {Number} x
3612	 * @param {Number} y
3613	 * @param {Number} width
3614	 * @param {Number} height
3615	 */
3616	clipRect: function (x, y, width, height) {
3617		var wrapper,
3618			id = PREFIX + idCounter++,
3619
3620			clipPath = this.createElement('clipPath').attr({
3621				id: id
3622			}).add(this.defs);
3623
3624		wrapper = this.rect(x, y, width, height, 0).add(clipPath);
3625		wrapper.id = id;
3626		wrapper.clipPath = clipPath;
3627
3628		return wrapper;
3629	},
3630
3631
3632	/**
3633	 * Take a color and return it if it's a string, make it a gradient if it's a
3634	 * gradient configuration object. Prior to Highstock, an array was used to define
3635	 * a linear gradient with pixel positions relative to the SVG. In newer versions
3636	 * we change the coordinates to apply relative to the shape, using coordinates
3637	 * 0-1 within the shape. To preserve backwards compatibility, linearGradient
3638	 * in this definition is an object of x1, y1, x2 and y2.
3639	 *
3640	 * @param {Object} color The color or config object
3641	 */
3642	color: function (color, elem, prop) {
3643		var colorObject,
3644			regexRgba = /^rgba/;
3645		if (color && color.linearGradient) {
3646			var renderer = this,
3647				linearGradient = color[LINEAR_GRADIENT],
3648				relativeToShape = !isArray(linearGradient), // keep backwards compatibility
3649				id,
3650				gradients = renderer.gradients,
3651				gradientObject,
3652				x1 = linearGradient.x1 || linearGradient[0] || 0,
3653				y1 = linearGradient.y1 || linearGradient[1] || 0,
3654				x2 = linearGradient.x2 || linearGradient[2] || 0,
3655				y2 = linearGradient.y2 || linearGradient[3] || 0,
3656				stopColor,
3657				stopOpacity,
3658				// Create a unique key in order to reuse gradient objects. #671.
3659				key = [relativeToShape, x1, y1, x2, y2, color.stops.join(',')].join(',');
3660
3661			// If the gradient with the same setup is already created, reuse it
3662			if (gradients[key]) {
3663				id = attr(gradients[key].element, 'id');
3664
3665			// If not, create a new one and keep the reference.
3666			} else {
3667				id = PREFIX + idCounter++;
3668				gradientObject = renderer.createElement(LINEAR_GRADIENT)
3669					.attr(extend({
3670						id: id,
3671						x1: x1,
3672						y1: y1,
3673						x2: x2,
3674						y2: y2
3675					}, relativeToShape ? null : { gradientUnits: 'userSpaceOnUse' }))
3676					.add(renderer.defs);
3677
3678				// The gradient needs to keep a list of stops to be able to destroy them
3679				gradientObject.stops = [];
3680				each(color.stops, function (stop) {
3681					var stopObject;
3682					if (regexRgba.test(stop[1])) {
3683						colorObject = Color(stop[1]);
3684						stopColor = colorObject.get('rgb');
3685						stopOpacity = colorObject.get('a');
3686					} else {
3687						stopColor = stop[1];
3688						stopOpacity = 1;
3689					}
3690					stopObject = renderer.createElement('stop').attr({
3691						offset: stop[0],
3692						'stop-color': stopColor,
3693						'stop-opacity': stopOpacity
3694					}).add(gradientObject);
3695
3696					// Add the stop element to the gradient
3697					gradientObject.stops.push(stopObject);
3698				});
3699
3700				// Keep a reference to the gradient object so it is possible to reuse it and
3701				// destroy it later
3702				gradients[key] = gradientObject;
3703			}
3704
3705			return 'url(' + this.url + '#' + id + ')';
3706
3707		// Webkit and Batik can't show rgba.
3708		} else if (regexRgba.test(color)) {
3709			colorObject = Color(color);
3710			attr(elem, prop + '-opacity', colorObject.get('a'));
3711
3712			return colorObject.get('rgb');
3713
3714
3715		} else {
3716			// Remove the opacity attribute added above. Does not throw if the attribute is not there.
3717			elem.removeAttribute(prop + '-opacity');
3718
3719			return color;
3720		}
3721
3722	},
3723
3724
3725	/**
3726	 * Add text to the SVG object
3727	 * @param {String} str
3728	 * @param {Number} x Left position
3729	 * @param {Number} y Top position
3730	 * @param {Boolean} useHTML Use HTML to render the text
3731	 */
3732	text: function (str, x, y, useHTML) {
3733
3734		// declare variables
3735		var renderer = this,
3736			defaultChartStyle = defaultOptions.chart.style,
3737			wrapper;
3738
3739		if (useHTML && !renderer.forExport) {
3740			return renderer.html(str, x, y);
3741		}
3742
3743		x = mathRound(pick(x, 0));
3744		y = mathRound(pick(y, 0));
3745
3746		wrapper = renderer.createElement('text')
3747			.attr({
3748				x: x,
3749				y: y,
3750				text: str
3751			})
3752			.css({
3753				fontFamily: defaultChartStyle.fontFamily,
3754				fontSize: defaultChartStyle.fontSize
3755			});
3756
3757		wrapper.x = x;
3758		wrapper.y = y;
3759		return wrapper;
3760	},
3761
3762
3763	/**
3764	 * Create HTML text node. This is used by the VML renderer as well as the SVG
3765	 * renderer through the useHTML option.
3766	 *
3767	 * @param {String} str
3768	 * @param {Number} x
3769	 * @param {Number} y
3770	 */
3771	html: function (str, x, y) {
3772		var defaultChartStyle = defaultOptions.chart.style,
3773			wrapper = this.createElement('span'),
3774			attrSetters = wrapper.attrSetters,
3775			element = wrapper.element,
3776			renderer = wrapper.renderer;
3777
3778		// Text setter
3779		attrSetters.text = function (value) {
3780			element.innerHTML = value;
3781			return false;
3782		};
3783
3784		// Various setters which rely on update transform
3785		attrSetters.x = attrSetters.y = attrSetters.align = function (value, key) {
3786			if (key === 'align') {
3787				key = 'textAlign'; // Do not overwrite the SVGElement.align method. Same as VML.
3788			}
3789			wrapper[key] = value;
3790			wrapper.htmlUpdateTransform();
3791			return false;
3792		};
3793
3794		// Set the default attributes
3795		wrapper.attr({
3796				text: str,
3797				x: mathRound(x),
3798				y: mathRound(y)
3799			})
3800			.css({
3801				position: ABSOLUTE,
3802				whiteSpace: 'nowrap',
3803				fontFamily: defaultChartStyle.fontFamily,
3804				fontSize: defaultChartStyle.fontSize
3805			});
3806
3807		// Use the HTML specific .css method
3808		wrapper.css = wrapper.htmlCss;
3809
3810		// This is specific for HTML within SVG
3811		if (renderer.isSVG) {
3812			wrapper.add = function (svgGroupWrapper) {
3813
3814				var htmlGroup,
3815					htmlGroupStyle,
3816					container = renderer.box.parentNode;
3817
3818				// Create a mock group to hold the HTML elements
3819				if (svgGroupWrapper) {
3820					htmlGroup = svgGroupWrapper.div;
3821					if (!htmlGroup) {
3822						htmlGroup = svgGroupWrapper.div = createElement(DIV, {
3823							className: attr(svgGroupWrapper.element, 'class')
3824						}, {
3825							position: ABSOLUTE,
3826							left: svgGroupWrapper.attr('translateX') + PX,
3827							top: svgGroupWrapper.attr('translateY') + PX
3828						}, container);
3829
3830						// Ensure dynamic updating position
3831						htmlGroupStyle = htmlGroup.style;
3832						extend(svgGroupWrapper.attrSetters, {
3833							translateX: function (value) {
3834								htmlGroupStyle.left = value + PX;
3835							},
3836							translateY: function (value) {
3837								htmlGroupStyle.top = value + PX;
3838							},
3839							visibility: function (value, key) {
3840								htmlGroupStyle[key] = value;
3841							}
3842						});
3843
3844					}
3845				} else {
3846					htmlGroup = container;
3847				}
3848
3849				htmlGroup.appendChild(element);
3850
3851				// Shared with VML:
3852				wrapper.added = true;
3853				if (wrapper.alignOnAdd) {
3854					wrapper.htmlUpdateTransform();
3855				}
3856
3857				return wrapper;
3858			};
3859		}
3860		return wrapper;
3861	},
3862
3863	/**
3864	 * Utility to return the baseline offset and total line height from the font size
3865	 */
3866	fontMetrics: function (fontSize) {
3867		fontSize = pInt(fontSize || 11);
3868		
3869		// Empirical values found by comparing font size and bounding box height.
3870		// Applies to the default font family. http://jsfiddle.net/highcharts/7xvn7/
3871		var lineHeight = fontSize < 24 ? fontSize + 4 : mathRound(fontSize * 1.2),
3872			baseline = mathRound(lineHeight * 0.8);
3873		
3874		return {
3875			h: lineHeight, 
3876			b: baseline
3877		};
3878	},
3879
3880	/**
3881	 * Add a label, a text item that can hold a colored or gradient background
3882	 * as well as a border and shadow.
3883	 * @param {string} str
3884	 * @param {Number} x
3885	 * @param {Number} y
3886	 * @param {String} shape
3887	 * @param {Number} anchorX In case the shape has a pointer, like a flag, this is the
3888	 *    coordinates it should be pinned to
3889	 * @param {Number} anchorY
3890	 * @param {Boolean} baseline Whether to position the label relative to the text baseline,
3891	 *    like renderer.text, or to the upper border of the rectangle. 
3892	 */
3893	label: function (str, x, y, shape, anchorX, anchorY, useHTML, baseline) {
3894
3895		var renderer = this,
3896			wrapper = renderer.g(),
3897			text = renderer.text('', 0, 0, useHTML)
3898				.attr({
3899					zIndex: 1
3900				})
3901				.add(wrapper),
3902			box,
3903			bBox,
3904			align = 'left',
3905			padding = 3,
3906			width,
3907			height,
3908			wrapperX,
3909			wrapperY,
3910			crispAdjust = 0,
3911			deferredAttr = {},
3912			baselineOffset,
3913			attrSetters = wrapper.attrSetters;
3914
3915		/**
3916		 * This function runs after the label is added to the DOM (when the bounding box is
3917		 * available), and after the text of the label is updated to detect the new bounding
3918		 * box and reflect it in the border box.
3919		 */
3920		function updateBoxSize() {
3921			var boxY,
3922				style = text.element.style;
3923				
3924			bBox = (width === undefined || height === undefined || wrapper.styles.textAlign) &&
3925				text.getBBox(true);
3926			wrapper.width = (width || bBox.width) + 2 * padding;
3927			wrapper.height = (height || bBox.height) + 2 * padding;
3928			
3929			// update the label-scoped y offset
3930			baselineOffset = padding + renderer.fontMetrics(style && style.fontSize).b;
3931			
3932			
3933			// create the border box if it is not already present
3934			if (!box) {
3935				boxY = baseline ? -baselineOffset : 0;
3936			
3937				wrapper.box = box = shape ?
3938					renderer.symbol(shape, 0, boxY, wrapper.width, wrapper.height) :
3939					renderer.rect(0, boxY, wrapper.width, wrapper.height, 0, deferredAttr[STROKE_WIDTH]);
3940				box.add(wrapper);
3941			}
3942
3943			// apply the box attributes
3944			box.attr(merge({
3945				width: wrapper.width,
3946				height: wrapper.height
3947			}, deferredAttr));
3948			deferredAttr = null;
3949		}
3950
3951		/**
3952		 * This function runs after setting text or padding, but only if padding is changed
3953		 */
3954		function updateTextPadding() {
3955			var styles = wrapper.styles,
3956				textAlign = styles && styles.textAlign,
3957				x = padding,
3958				y;
3959			
3960			// determin y based on the baseline
3961			y = baseline ? 0 : baselineOffset;
3962
3963			// compensate for alignment
3964			if (defined(width) && (textAlign === 'center' || textAlign === 'right')) {
3965				x += { center: 0.5, right: 1 }[textAlign] * (width - bBox.width);
3966			}
3967
3968			// update if anything changed
3969			if (x !== text.x || y !== text.y) {
3970				text.attr({
3971					x: x,
3972					y: y
3973				});
3974			}
3975
3976			// record current values
3977			text.x = x;
3978			text.y = y;
3979		}
3980
3981		/**
3982		 * Set a box attribute, or defer it if the box is not yet created
3983		 * @param {Object} key
3984		 * @param {Object} value
3985		 */
3986		function boxAttr(key, value) {
3987			if (box) {
3988				box.attr(key, value);
3989			} else {
3990				deferredAttr[key] = value;
3991			}
3992		}
3993
3994		function getSizeAfterAdd() {
3995			wrapper.attr({
3996				text: str, // alignment is available now
3997				x: x,
3998				y: y,
3999				anchorX: anchorX,
4000				anchorY: anchorY
4001			});
4002		}
4003
4004		/**
4005		 * After the text element is added, get the desired size of the border box
4006		 * and add it before the text in the DOM.
4007		 */
4008		addEvent(wrapper, 'add', getSizeAfterAdd);
4009
4010		/*
4011		 * Add specific attribute setters.
4012		 */
4013
4014		// only change local variables
4015		attrSetters.width = function (value) {
4016			width = value;
4017			return false;
4018		};
4019		attrSetters.height = function (value) {
4020			height = value;
4021			return false;
4022		};
4023		attrSetters.padding = function (value) {
4024			if (defined(value) && value !== padding) {
4025				padding = value;
4026				updateTextPadding();
4027			}
4028
4029			return false;
4030		};
4031
4032		// change local variable and set attribue as well
4033		attrSetters.align = function (value) {
4034			align = value;
4035			return false; // prevent setting text-anchor on the group
4036		};
4037		
4038		// apply these to the box and the text alike
4039		attrSetters.text = function (value, key) {
4040			text.attr(key, value);
4041			updateBoxSize();
4042			updateTextPadding();
4043			return false;
4044		};
4045
4046		// apply these to the box but not to the text
4047		attrSetters[STROKE_WIDTH] = function (value, key) {
4048			crispAdjust = value % 2 / 2;
4049			boxAttr(key, value);
4050			return false;
4051		};
4052		attrSetters.stroke = attrSetters.fill = attrSetters.r = function (value, key) {
4053			boxAttr(key, value);
4054			return false;
4055		};
4056		attrSetters.anchorX = function (value, key) {
4057			anchorX = value;
4058			boxAttr(key, value + crispAdjust - wrapperX);
4059			return false;
4060		};
4061		attrSetters.anchorY = function (value, key) {
4062			anchorY = value;
4063			boxAttr(key, value - wrapperY);
4064			return false;
4065		};
4066		
4067		// rename attributes
4068		attrSetters.x = function (value) {
4069			value -= { left: 0, center: 0.5, right: 1 }[align] * ((width || bBox.width) + padding);
4070			wrapperX = wrapper.x = mathRound(value); // wrapper.x is for animation getter
4071			
4072			wrapper.attr('translateX', wrapperX);
4073			return false;
4074		};
4075		attrSetters.y = function (value) {
4076			wrapperY = wrapper.y = mathRound(value);
4077			wrapper.attr('translateY', value);
4078			return false;
4079		};
4080
4081		// Redirect certain methods to either the box or the text
4082		var baseCss = wrapper.css;
4083		return extend(wrapper, {
4084			/**
4085			 * Pick up some properties and apply them to the text instead of the wrapper
4086			 */
4087			css: function (styles) {
4088				if (styles) {
4089					var textStyles = {};
4090					styles = merge({}, styles); // create a copy to avoid altering the original object (#537)
4091					each(['fontSize', 'fontWeight', 'fontFamily', 'color', 'lineHeight', 'width'], function (prop) {
4092						if (styles[prop] !== UNDEFINED) {
4093							textStyles[prop] = styles[prop];
4094							delete styles[prop];
4095						}
4096					});
4097					text.css(textStyles);
4098				}
4099				return baseCss.call(wrapper, styles);
4100			},
4101			/**
4102			 * Return the bounding box of the box, not the group
4103			 */
4104			getBBox: function () {
4105				return box.getBBox();
4106			},
4107			/**
4108			 * Apply the shadow to the box
4109			 */
4110			shadow: function (b) {
4111				box.shadow(b);
4112				return wrapper;
4113			},
4114			/**
4115			 * Destroy and release memory.
4116			 */
4117			destroy: function () {
4118				removeEvent(wrapper, 'add', getSizeAfterAdd);
4119
4120				// Added by button implementation
4121				removeEvent(wrapper.element, 'mouseenter');
4122				removeEvent(wrapper.element, 'mouseleave');
4123
4124				if (text) {
4125					// Destroy the text element
4126					text = text.destroy();
4127				}
4128				// Call base implementation to destroy the rest
4129				SVGElement.prototype.destroy.call(wrapper);
4130			}
4131		});
4132	}
4133}; // end SVGRenderer
4134
4135
4136// general renderer
4137Renderer = SVGRenderer;
4138
4139
4140/* ****************************************************************************
4141 *                                                                            *
4142 * START OF INTERNET EXPLORER <= 8 SPECIFIC CODE                              *
4143 *                                                                            *
4144 * For applications and websites that don't need IE support, like platform    *
4145 * targeted mobile apps and web apps, this code can be removed.               *
4146 *                                                                            *
4147 *****************************************************************************/
4148
4149/**
4150 * @constructor
4151 */
4152var VMLRenderer;
4153if (!hasSVG && !useCanVG) {
4154
4155/**
4156 * The VML element wrapper.
4157 */
4158var VMLElement = {
4159
4160	/**
4161	 * Initialize a new VML element wrapper. It builds the markup as a string
4162	 * to minimize DOM traffic.
4163	 * @param {Object} renderer
4164	 * @param {Object} nodeName
4165	 */
4166	init: function (renderer, nodeName) {
4167		var wrapper = this,
4168			markup =  ['<', nodeName, ' filled="f" stroked="f"'],
4169			style = ['position: ', ABSOLUTE, ';'];
4170
4171		// divs and shapes need size
4172		if (nodeName === 'shape' || nodeName === DIV) {
4173			style.push('left:0;top:0;width:10px;height:10px;');
4174		}
4175		if (docMode8) {
4176			style.push('visibility: ', nodeName === DIV ? HIDDEN : VISIBLE);
4177		}
4178
4179		markup.push(' style="', style.join(''), '"/>');
4180
4181		// create element with default attributes and style
4182		if (nodeName) {
4183			markup = nodeName === DIV || nodeName === 'span' || nodeName === 'img' ?
4184				markup.join('')
4185				: renderer.prepVML(markup);
4186			wrapper.element = createElement(markup);
4187		}
4188
4189		wrapper.renderer = renderer;
4190		wrapper.attrSetters = {};
4191	},
4192
4193	/**
4194	 * Add the node to the given parent
4195	 * @param {Object} parent
4196	 */
4197	add: function (parent) {
4198		var wrapper = this,
4199			renderer = wrapper.renderer,
4200			element = wrapper.element,
4201			box = renderer.box,
4202			inverted = parent && parent.inverted,
4203
4204			// get the parent node
4205			parentNode = parent ?
4206				parent.element || parent :
4207				box;
4208
4209
4210		// if the parent group is inverted, apply inversion on all children
4211		if (inverted) { // only on groups
4212			renderer.invertChild(element, parentNode);
4213		}
4214
4215		// issue #140 workaround - related to #61 and #74
4216		if (docMode8 && parentNode.gVis === HIDDEN) {
4217			css(element, { visibility: HIDDEN });
4218		}
4219
4220		// append it
4221		parentNode.appendChild(element);
4222
4223		// align text after adding to be able to read offset
4224		wrapper.added = true;
4225		if (wrapper.alignOnAdd && !wrapper.deferUpdateTransform) {
4226			wrapper.updateTransform();
4227		}
4228
4229		// fire an event for internal hooks
4230		fireEvent(wrapper, 'add');
4231
4232		return wrapper;
4233	},
4234
4235	/**
4236	 * In IE8 documentMode 8, we need to recursively set the visibility down in the DOM
4237	 * tree for nested groups. Related to #61, #586.
4238	 */
4239	toggleChildren: function (element, visibility) {
4240		var childNodes = element.childNodes,
4241			i = childNodes.length;
4242			
4243		while (i--) {
4244			
4245			// apply the visibility
4246			css(childNodes[i], { visibility: visibility });
4247			
4248			// we have a nested group, apply it to its children again
4249			if (childNodes[i].nodeName === 'DIV') {
4250				this.toggleChildren(childNodes[i], visibility);
4251			}
4252		}
4253	},
4254
4255	/**
4256	 * VML always uses htmlUpdateTransform
4257	 */
4258	updateTransform: SVGElement.prototype.htmlUpdateTransform,
4259
4260	/**
4261	 * Get or set attributes
4262	 */
4263	attr: function (hash, val) {
4264		var wrapper = this,
4265			key,
4266			value,
4267			i,
4268			result,
4269			element = wrapper.element || {},
4270			elemStyle = element.style,
4271			nodeName = element.nodeName,
4272			renderer = wrapper.renderer,
4273			symbolName = wrapper.symbolName,
4274			hasSetSymbolSize,
4275			shadows = wrapper.shadows,
4276			skipAttr,
4277			attrSetters = wrapper.attrSetters,
4278			ret = wrapper;
4279
4280		// single key-value pair
4281		if (isString(hash) && defined(val)) {
4282			key = hash;
4283			hash = {};
4284			hash[key] = val;
4285		}
4286
4287		// used as a getter, val is undefined
4288		if (isString(hash)) {
4289			key = hash;
4290			if (key === 'strokeWidth' || key === 'stroke-width') {
4291				ret = wrapper.strokeweight;
4292			} else {
4293				ret = wrapper[key];
4294			}
4295
4296		// setter
4297		} else {
4298			for (key in hash) {
4299				value = hash[key];
4300				skipAttr = false;
4301
4302				// check for a specific attribute setter
4303				result = attrSetters[key] && attrSetters[key](value, key);
4304
4305				if (result !== false && value !== null) { // #620
4306
4307					if (result !== UNDEFINED) {
4308						value = result; // the attribute setter has returned a new value to set
4309					}
4310
4311
4312					// prepare paths
4313					// symbols
4314					if (symbolName && /^(x|y|r|start|end|width|height|innerR|anchorX|anchorY)/.test(key)) {
4315						// if one of the symbol size affecting parameters are changed,
4316						// check all the others only once for each call to an element's
4317						// .attr() method
4318						if (!hasSetSymbolSize) {
4319							wrapper.symbolAttr(hash);
4320
4321							hasSetSymbolSize = true;
4322						}
4323						skipAttr = true;
4324
4325					} else if (key === 'd') {
4326						value = value || [];
4327						wrapper.d = value.join(' '); // used in getter for animation
4328
4329						// convert paths
4330						i = value.length;
4331						var convertedPath = [];
4332						while (i--) {
4333
4334							// Multiply by 10 to allow subpixel precision.
4335							// Substracting half a pixel seems to make the coordinates
4336							// align with SVG, but this hasn't been tested thoroughly
4337							if (isNumber(value[i])) {
4338								convertedPath[i] = mathRound(value[i] * 10) - 5;
4339							} else if (value[i] === 'Z') { // close the path
4340								convertedPath[i] = 'x';
4341							} else {
4342								convertedPath[i] = value[i];
4343							}
4344
4345						}
4346						value = convertedPath.join(' ') || 'x';
4347						element.path = value;
4348
4349						// update shadows
4350						if (shadows) {
4351							i = shadows.length;
4352							while (i--) {
4353								shadows[i].path = value;
4354							}
4355						}
4356						skipAttr = true;
4357
4358					// directly mapped to css
4359					} else if (key === 'zIndex' || key === 'visibility') {
4360
4361						// workaround for #61 and #586
4362						if (docMode8 && key === 'visibility' && nodeName === 'DIV') {
4363							element.gVis = value;
4364							wrapper.toggleChildren(element, value);
4365							if (value === VISIBLE) { // #74
4366								value = null;
4367							}
4368						}
4369
4370						if (value) {
4371							elemStyle[key] = value;
4372						}
4373
4374
4375
4376						skipAttr = true;
4377
4378					// width and height
4379					} else if (key === 'width' || key === 'height') {
4380						
4381						value = mathMax(0, value); // don't set width or height below zero (#311)
4382						
4383						this[key] = value; // used in getter
4384
4385						// clipping rectangle special
4386						if (wrapper.updateClipping) {
4387							wrapper[key] = value;
4388							wrapper.updateClipping();
4389						} else {
4390							// normal
4391							elemStyle[key] = value;
4392						}
4393
4394						skipAttr = true;
4395
4396					// x and y
4397					} else if (key === 'x' || key === 'y') {
4398
4399						wrapper[key] = value; // used in getter
4400						elemStyle[{ x: 'left', y: 'top' }[key]] = value;
4401
4402					// class name
4403					} else if (key === 'class') {
4404						// IE8 Standards mode has problems retrieving the className
4405						element.className = value;
4406
4407					// stroke
4408					} else if (key === 'stroke') {
4409
4410						value = renderer.color(value, element, key);
4411
4412						key = 'strokecolor';
4413
4414					// stroke width
4415					} else if (key === 'stroke-width' || key === 'strokeWidth') {
4416						element.stroked = value ? true : false;
4417						key = 'strokeweight';
4418						wrapper[key] = value; // used in getter, issue #113
4419						if (isNumber(value)) {
4420							value += PX;
4421						}
4422
4423					// dashStyle
4424					} else if (key === 'dashstyle') {
4425						var strokeElem = element.getElementsByTagName('stroke')[0] ||
4426							createElement(renderer.prepVML(['<stroke/>']), null, null, element);
4427						strokeElem[key] = value || 'solid';
4428						wrapper.dashstyle = value; /* because changing stroke-width will change the dash length
4429							and cause an epileptic effect */
4430						skipAttr = true;
4431
4432					// fill
4433					} else if (key === 'fill') {
4434
4435						if (nodeName === 'SPAN') { // text color
4436							elemStyle.color = value;
4437						} else {
4438							element.filled = value !== NONE ? true : false;
4439
4440							value = renderer.color(value, element, key);
4441
4442							key = 'fillcolor';
4443						}
4444
4445					// translation for animation
4446					} else if (key === 'translateX' || key === 'translateY' || key === 'rotation') {
4447						wrapper[key] = value;
4448						wrapper.updateTransform();
4449
4450						skipAttr = true;
4451
4452					// text for rotated and non-rotated elements
4453					} else if (key === 'text') {
4454						this.bBox = null;
4455						element.innerHTML = value;
4456						skipAttr = true;
4457					}
4458
4459					// let the shadow follow the main element
4460					if (shadows && key === 'visibility') {
4461						i = shadows.length;
4462						while (i--) {
4463							shadows[i].style[key] = value;
4464						}
4465					}
4466
4467
4468
4469					if (!skipAttr) {
4470						if (docMode8) { // IE8 setAttribute bug
4471							element[key] = value;
4472						} else {
4473							attr(element, key, value);
4474						}
4475					}
4476
4477				}
4478			}
4479		}
4480		return ret;
4481	},
4482
4483	/**
4484	 * Set the element's clipping to a predefined rectangle
4485	 *
4486	 * @param {String} id The id of the clip rectangle
4487	 */
4488	clip: function (clipRect) {
4489		var wrapper = this,
4490			clipMembers = clipRect.members;
4491
4492		clipMembers.push(wrapper);
4493		wrapper.destroyClip = function () {
4494			erase(clipMembers, wrapper);
4495		};
4496		return wrapper.css(clipRect.getCSS(wrapper.inverted));
4497	},
4498
4499	/**
4500	 * Set styles for the element
4501	 * @param {Object} styles
4502	 */
4503	css: SVGElement.prototype.htmlCss,
4504
4505	/**
4506	 * Removes a child either by removeChild or move to garbageBin.
4507	 * Issue 490; in VML removeChild results in Orphaned nodes according to sIEve, discardElement does not.
4508	 */
4509	safeRemoveChild: function (element) {
4510		// discardElement will detach the node from its parent before attaching it
4511		// to the garbage bin. Therefore it is important that the node is attached and have parent.
4512		var parentNode = element.parentNode;
4513		if (parentNode) {
4514			discardElement(element);
4515		}
4516	},
4517
4518	/**
4519	 * Extend element.destroy by removing it from the clip members array
4520	 */
4521	destroy: function () {
4522		var wrapper = this;
4523
4524		if (wrapper.destroyClip) {
4525			wrapper.destroyClip();
4526		}
4527
4528		return SVGElement.prototype.destroy.apply(wrapper);
4529	},
4530
4531	/**
4532	 * Remove all child nodes of a group, except the v:group element
4533	 */
4534	empty: function () {
4535		var element = this.element,
4536			childNodes = element.childNodes,
4537			i = childNodes.length,
4538			node;
4539
4540		while (i--) {
4541			node = childNodes[i];
4542			node.parentNode.removeChild(node);
4543		}
4544	},
4545
4546	/**
4547	 * Add an event listener. VML override for normalizing event parameters.
4548	 * @param {String} eventType
4549	 * @param {Function} handler
4550	 */
4551	on: function (eventType, handler) {
4552		// simplest possible event model for internal use
4553		this.element['on' + eventType] = function () {
4554			var evt = win.event;
4555			evt.target = evt.srcElement;
4556			handler(evt);
4557		};
4558		return this;
4559	},
4560
4561	/**
4562	 * Apply a drop shadow by copying elements and giving them different strokes
4563	 * @param {Boolean} apply
4564	 */
4565	shadow: function (apply, group) {
4566		var shadows = [],
4567			i,
4568			element = this.element,
4569			renderer = this.renderer,
4570			shadow,
4571			elemStyle = element.style,
4572			markup,
4573			path = element.path;
4574
4575		// some times empty paths are not strings
4576		if (path && typeof path.value !== 'string') {
4577			path = 'x';
4578		}
4579
4580		if (apply) {
4581			for (i = 1; i <= 3; i++) {
4582				markup = ['<shape isShadow="true" strokeweight="', (7 - 2 * i),
4583					'" filled="false" path="', path,
4584					'" coordsize="100,100" style="', element.style.cssText, '" />'];
4585				shadow = createElement(renderer.prepVML(markup),
4586					null, {
4587						left: pInt(elemStyle.left) + 1,
4588						top: pInt(elemStyle.top) + 1
4589					}
4590				);
4591
4592				// apply the opacity
4593				markup = ['<stroke color="black" opacity="', (0.05 * i), '"/>'];
4594				createElement(renderer.prepVML(markup), null, null, shadow);
4595
4596
4597				// insert it
4598				if (group) {
4599					group.element.appendChild(shadow);
4600				} else {
4601					element.parentNode.insertBefore(shadow, element);
4602				}
4603
4604				// record it
4605				shadows.push(shadow);
4606
4607			}
4608
4609			this.shadows = shadows;
4610		}
4611		return this;
4612
4613	}
4614};
4615VMLElement = extendClass(SVGElement, VMLElement);
4616
4617/**
4618 * The VML renderer
4619 */
4620var VMLRendererExtension = { // inherit SVGRenderer
4621
4622	Element: VMLElement,
4623	isIE8: userAgent.indexOf('MSIE 8.0') > -1,
4624
4625
4626	/**
4627	 * Initialize the VMLRenderer
4628	 * @param {Object} container
4629	 * @param {Number} width
4630	 * @param {Number} height
4631	 */
4632	init: function (container, width, height) {
4633		var renderer = this,
4634			boxWrapper,
4635			box;
4636
4637		renderer.alignedObjects = [];
4638
4639		boxWrapper = renderer.createElement(DIV);
4640		box = boxWrapper.element;
4641		box.style.position = RELATIVE; // for freeform drawing using renderer directly
4642		container.appendChild(boxWrapper.element);
4643
4644
4645		// generate the containing box
4646		renderer.box = box;
4647		renderer.boxWrapper = boxWrapper;
4648
4649
4650		renderer.setSize(width, height, false);
4651
4652		// The only way to make IE6 and IE7 print is to use a global namespace. However,
4653		// with IE8 the only way to make the dynamic shapes visible in screen and print mode
4654		// seems to be to add the xmlns attribute and the behaviour style inline.
4655		if (!doc.namespaces.hcv) {
4656
4657			doc.namespaces.add('hcv', 'urn:schemas-microsoft-com:vml');
4658
4659			// setup default css
4660			doc.createStyleSheet().cssText =
4661				'hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke' +
4662				'{ behavior:url(#default#VML); display: inline-block; } ';
4663
4664		}
4665	},
4666
4667	/**
4668	 * Define a clipping rectangle. In VML it is accomplished by storing the values
4669	 * for setting the CSS style to all associated members.
4670	 *
4671	 * @param {Number} x
4672	 * @param {Number} y
4673	 * @param {Number} width
4674	 * @param {Number} height
4675	 */
4676	clipRect: function (x, y, width, height) {
4677
4678		// create a dummy element
4679		var clipRect = this.createElement();
4680
4681		// mimic a rectangle with its style object for automatic updating in attr
4682		return extend(clipRect, {
4683			members: [],
4684			left: x,
4685			top: y,
4686			width: width,
4687			height: height,
4688			getCSS: function (inverted) {
4689				var rect = this,//clipRect.element.style,
4690					top = rect.top,
4691					left = rect.left,
4692					right = left + rect.width,
4693					bottom = top + rect.height,
4694					ret = {
4695						clip: 'rect(' +
4696							mathRound(inverted ? left : top) + 'px,' +
4697							mathRound(inverted ? bottom : right) + 'px,' +
4698							mathRound(inverted ? right : bottom) + 'px,' +
4699							mathRound(inverted ? top : left) + 'px)'
4700					};
4701
4702				// issue 74 workaround
4703				if (!inverted && docMode8) {
4704					extend(ret, {
4705						width: right + PX,
4706						height: bottom + PX
4707					});
4708				}
4709				return ret;
4710			},
4711
4712			// used in attr and animation to update the clipping of all members
4713			updateClipping: function () {
4714				each(clipRect.members, function (member) {
4715					member.css(clipRect.getCSS(member.inverted));
4716				});
4717			}
4718		});
4719
4720	},
4721
4722
4723	/**
4724	 * Take a color and return it if it's a string, make it a gradient if it's a
4725	 * gradient configuration object, and apply opacity.
4726	 *
4727	 * @param {Object} color The color or config object
4728	 */
4729	color: function (color, elem, prop) {
4730		var colorObject,
4731			regexRgba = /^rgba/,
4732			markup;
4733
4734		if (color && color[LINEAR_GRADIENT]) {
4735
4736			var stopColor,
4737				stopOpacity,
4738				linearGradient = color[LINEAR_GRADIENT],
4739				x1 = linearGradient.x1 || linearGradient[0] || 0,
4740				y1 = linearGradient.y1 || linearGradient[1] || 0,
4741				x2 = linearGradient.x2 || linearGradient[2] || 0,
4742				y2 = linearGradient.y2 || linearGradient[3] || 0,
4743				angle,
4744				color1,
4745				opacity1,
4746				color2,
4747				opacity2;
4748
4749			each(color.stops, function (stop, i) {
4750				if (regexRgba.test(stop[1])) {
4751					colorObject = Color(stop[1]);
4752					stopColor = colorObject.get('rgb');
4753					stopOpacity = colorObject.get('a');
4754				} else {
4755					stopColor = stop[1];
4756					stopOpacity = 1;
4757				}
4758
4759				if (!i) { // first
4760					color1 = stopColor;
4761					opacity1 = stopOpacity;
4762				} else {
4763					color2 = stopColor;
4764					opacity2 = stopOpacity;
4765				}
4766			});
4767
4768			// Apply the gradient to fills only.
4769			if (prop === 'fill') {
4770				// calculate the angle based on the linear vector
4771				angle = 90  - math.atan(
4772					(y2 - y1) / // y vector
4773					(x2 - x1) // x vector
4774					) * 180 / mathPI;
4775	
4776	
4777				// when colors attribute is used, the meanings of opacity and o:opacity2
4778				// are reversed.
4779				markup = ['<fill colors="0% ', color1, ',100% ', color2, '" angle="', angle,
4780					'" opacity="', opacity2, '" o:opacity2="', opacity1,
4781					'" type="gradient" focus="100%" method="sigma" />'];
4782				createElement(this.prepVML(markup), null, null, elem);
4783			
4784			// Gradients are not supported for VML stroke, return the first color. #722.
4785			} else {
4786				return stopColor;
4787			}
4788
4789
4790		// if the color is an rgba color, split it and add a fill node
4791		// to hold the opacity component
4792		} else if (regexRgba.test(color) && elem.tagName !== 'IMG') {
4793
4794			colorObject = Color(color);
4795
4796			markup = ['<', prop, ' opacity="', colorObject.get('a'), '"/>'];
4797			createElement(this.prepVML(markup), null, null, elem);
4798
4799			return colorObject.get('rgb');
4800
4801
4802		} else {
4803			var strokeNodes = elem.getElementsByTagName(prop);
4804			if (strokeNodes.length) {
4805				strokeNodes[0].opacity = 1;
4806			}
4807			return color;
4808		}
4809
4810	},
4811
4812	/**
4813	 * Take a VML string and prepare it for either IE8 or IE6/IE7.
4814	 * @param {Array} markup A string array of the VML markup to prepare
4815	 */
4816	prepVML: function (markup) {
4817		var vmlStyle = 'display:inline-block;behavior:url(#default#VML);',
4818			isIE8 = this.isIE8;
4819
4820		markup = markup.join('');
4821
4822		if (isIE8) { // add xmlns and style inline
4823			markup = markup.replace('/>', ' xmlns="urn:schemas-microsoft-com:vml" />');
4824			if (markup.indexOf('style="') === -1) {
4825				markup = markup.replace('/>', ' style="' + vmlStyle + '" />');
4826			} else {
4827				markup = markup.replace('style="', 'style="' + vmlStyle);
4828			}
4829
4830		} else { // add namespace
4831			markup = markup.replace('<', '<hcv:');
4832		}
4833
4834		return markup;
4835	},
4836
4837	/**
4838	 * Create rotated and aligned text
4839	 * @param {String} str
4840	 * @param {Number} x
4841	 * @param {Number} y
4842	 */
4843	text: SVGRenderer.prototype.html,
4844
4845	/**
4846	 * Create and return a path element
4847	 * @param {Array} path
4848	 */
4849	path: function (path) {
4850		// create the shape
4851		return this.createElement('shape').attr({
4852			// subpixel precision down to 0.1 (width and height = 10px)
4853			coordsize: '100 100',
4854			d: path
4855		});
4856	},
4857
4858	/**
4859	 * Create and return a circle element. In VML circles are implemented as
4860	 * shapes, which is faster than v:oval
4861	 * @param {Number} x
4862	 * @param {Number} y
4863	 * @param {Number} r
4864	 */
4865	circle: function (x, y, r) {
4866		return this.symbol('circle').attr({ x: x - r, y: y - r, width: 2 * r, height: 2 * r });
4867	},
4868
4869	/**
4870	 * Create a group using an outer div and an inner v:group to allow rotating
4871	 * and flipping. A simple v:group would have problems with positioning
4872	 * child HTML elements and CSS clip.
4873	 *
4874	 * @param {String} name The name of the group
4875	 */
4876	g: function (name) {
4877		var wrapper,
4878			attribs;
4879
4880		// set the class name
4881		if (name) {
4882			attribs = { 'className': PREFIX + name, 'class': PREFIX + name };
4883		}
4884
4885		// the div to hold HTML and clipping
4886		wrapper = this.createElement(DIV).attr(attribs);
4887
4888		return wrapper;
4889	},
4890
4891	/**
4892	 * VML override to create a regular HTML image
4893	 * @param {String} src
4894	 * @param {Number} x
4895	 * @param {Number} y
4896	 * @param {Number} width
4897	 * @param {Number} height
4898	 */
4899	image: function (src, x, y, width, height) {
4900		var obj = this.createElement('img')
4901			.attr({ src: src });
4902
4903		if (arguments.length > 1) {
4904			obj.css({
4905				left: x,
4906				top: y,
4907				width: width,
4908				height: height
4909			});
4910		}
4911		return obj;
4912	},
4913
4914	/**
4915	 * VML uses a shape for rect to overcome bugs and rotation problems
4916	 */
4917	rect: function (x, y, width, height, r, strokeWidth) {
4918
4919		if (isObject(x)) {
4920			y = x.y;
4921			width = x.width;
4922			height = x.height;
4923			strokeWidth = x.strokeWidth;
4924			x = x.x;
4925		}
4926		var wrapper = this.symbol('rect');
4927		wrapper.r = r;
4928
4929		return wrapper.attr(wrapper.crisp(strokeWidth, x, y, mathMax(width, 0), mathMax(height, 0)));
4930	},
4931
4932	/**
4933	 * In the VML renderer, each child of an inverted div (group) is inverted
4934	 * @param {Object} element
4935	 * @param {Object} parentNode
4936	 */
4937	invertChild: function (element, parentNode) {
4938		var parentStyle = parentNode.style;
4939
4940		css(element, {
4941			flip: 'x',
4942			left: pInt(parentStyle.width) - 10,
4943			top: pInt(parentStyle.height) - 10,
4944			rotation: -90
4945		});
4946	},
4947
4948	/**
4949	 * Symbol definitions that override the parent SVG renderer's symbols
4950	 *
4951	 */
4952	symbols: {
4953		// VML specific arc function
4954		arc: function (x, y, w, h, options) {
4955			var start = options.start,
4956				end = options.end,
4957				radius = options.r || w || h,
4958				cosStart = mathCos(start),
4959				sinStart = mathSin(start),
4960				cosEnd = mathCos(end),
4961				sinEnd = mathSin(end),
4962				innerRadius = options.innerR,
4963				circleCorrection = 0.08 / radius, // #760
4964				innerCorrection = (innerRadius && 0.25 / innerRadius) || 0;
4965
4966			if (end - start === 0) { // no angle, don't show it.
4967				return ['x'];
4968
4969			} else if (2 * mathPI - end + start < circleCorrection) { // full circle
4970				// empirical correction found by trying out the limits for different radii
4971				cosEnd = -circleCorrection;
4972			} else if (end - start < innerCorrection) { // issue #186, another mysterious VML arc problem
4973				cosEnd = mathCos(start + innerCorrection);
4974			}
4975
4976			return [
4977				'wa', // clockwise arc to
4978				x - radius, // left
4979				y - radius, // top
4980				x + radius, // right
4981				y + radius, // bottom
4982				x + radius * cosStart, // start x
4983				y + radius * sinStart, // start y
4984				x + radius * cosEnd, // end x
4985				y + radius * sinEnd, // end y
4986
4987
4988				'at', // anti clockwise arc to
4989				x - innerRadius, // left
4990				y - innerRadius, // top
4991				x + innerRadius, // right
4992				y + innerRadius, // bottom
4993				x + innerRadius * cosEnd, // start x
4994				y + innerRadius * sinEnd, // start y
4995				x + innerRadius * cosStart, // end x
4996				y + innerRadius * sinStart, // end y
4997
4998				'x', // finish path
4999				'e' // close
5000			];
5001
5002		},
5003		// Add circle symbol path. This performs significantly faster than v:oval.
5004		circle: function (x, y, w, h) {
5005
5006			return [
5007				'wa', // clockwisearcto
5008				x, // left
5009				y, // top
5010				x + w, // right
5011				y + h, // bottom
5012				x + w, // start x
5013				y + h / 2,     // start y
5014				x + w, // end x
5015				y + h / 2,     // end y
5016				//'x', // finish path
5017				'e' // close
5018			];
5019		},
5020		/**
5021		 * Add rectangle symbol path which eases rotation and omits arcsize problems
5022		 * compared to the built-in VML roundrect shape
5023		 *
5024		 * @param {Number} left Left position
5025		 * @param {Number} top Top position
5026		 * @param {Number} r Border radius
5027		 * @param {Object} options Width and height
5028		 */
5029
5030		rect: function (left, top, width, height, options) {
5031			/*for (var n in r) {
5032				logTime && console .log(n)
5033				}*/
5034
5035			if (!defined(options)) {
5036				return [];
5037			}
5038			var right = left + width,
5039				bottom = top + height,
5040				r = mathMin(options.r || 0, width, height);
5041
5042			return [
5043				M,
5044				left + r, top,
5045
5046				L,
5047				right - r, top,
5048				'wa',
5049				right - 2 * r, top,
5050				right, top + 2 * r,
5051				right - r, top,
5052				right, top + r,
5053
5054				L,
5055				right, bottom - r,
5056				'wa',
5057				right - 2 * r, bottom - 2 * r,
5058				right, bottom,
5059				right, bottom - r,
5060				right - r, bottom,
5061
5062				L,
5063				left + r, bottom,
5064				'wa',
5065				left, bottom - 2 * r,
5066				left + 2 * r, bottom,
5067				left + r, bottom,
5068				left, bottom - r,
5069
5070				L,
5071				left, top + r,
5072				'wa',
5073				left, top,
5074				left + 2 * r, top + 2 * r,
5075				left, top + r,
5076				left + r, top,
5077
5078
5079				'x',
5080				'e'
5081			];
5082
5083		}
5084	}
5085};
5086VMLRenderer = function () {
5087	this.init.apply(this, arguments);
5088};
5089VMLRenderer.prototype = merge(SVGRenderer.prototype, VMLRendererExtension);
5090
5091	// general renderer
5092	Renderer = VMLRenderer;
5093}
5094
5095/* ****************************************************************************
5096 *                                                                            *
5097 * END OF INTERNET EXPLORER <= 8 SPECIFIC CODE                                *
5098 *                                                                            *
5099 *****************************************************************************/
5100/* ****************************************************************************
5101 *                                                                            *
5102 * START OF ANDROID < 3 SPECIFIC CODE. THIS CAN BE REMOVED IF YOU'RE NOT      *
5103 * TARGETING THAT SYSTEM.                                                     *
5104 *                                                                            *
5105 *****************************************************************************/
5106var CanVGRenderer,
5107	CanVGController;
5108
5109if (useCanVG) {
5110	/**
5111	 * The CanVGRenderer is empty from start to keep the source footprint small.
5112	 * When requested, the CanVGController downloads the rest of the source packaged
5113	 * together with the canvg library.
5114	 */
5115	CanVGRenderer = function () {
5116		// Empty constructor
5117	};
5118
5119	/**
5120	 * Handles on demand download of canvg rendering support.
5121	 */
5122	CanVGController = (function () {
5123		// List of renderering calls
5124		var deferredRenderCalls = [];
5125
5126		/**
5127		 * When downloaded, we are ready to draw deferred charts.
5128		 */
5129		function drawDeferred() {
5130			var callLength = deferredRenderCalls.length,
5131				callIndex;
5132
5133			// Draw all pending render calls
5134			for (callIndex = 0; callIndex < callLength; callIndex++) {
5135				deferredRenderCalls[callIndex]();
5136			}
5137			// Clear the list
5138			deferredRenderCalls = [];
5139		}
5140
5141		return {
5142			push: function (func, scriptLocation) {
5143				// Only get the script once
5144				if (deferredRenderCalls.length === 0) {
5145					getScript(scriptLocation, drawDeferred);
5146				}
5147				// Register render call
5148				deferredRenderCalls.push(func);
5149			}
5150		};
5151	}());
5152} // end CanVGRenderer
5153
5154/* ****************************************************************************
5155 *                                                                            *
5156 * END OF ANDROID < 3 SPECIFIC CODE                                           *
5157 *                                                                            *
5158 *****************************************************************************/
5159
5160/**
5161 * General renderer
5162 */
5163Renderer = VMLRenderer || CanVGRenderer || SVGRenderer;
5164
5165/**
5166 * The chart class
5167 * @param {Object} options
5168 * @param {Function} callback Function to run when the chart has loaded
5169 */
5170function Chart(userOptions, callback) {
5171
5172	// Handle regular options
5173	var options,
5174		seriesOptions = userOptions.series;
5174 // skip merging data points to increase performance
5175	userOptions.series = null;
5176	options = merge(defaultOptions, userOptions); // do the merge
5177	options.series = userOptions.series = seriesOptions; // set back the series data
5178	
5179	var optionsChart = options.chart,
5180		optionsMargin = optionsChart.margin,
5181		margin = isObject(optionsMargin) ?
5182			optionsMargin :
5183			[optionsMargin, optionsMargin, optionsMargin, optionsMargin],
5184		optionsMarginTop = pick(optionsChart.marginTop, margin[0]),
5185		optionsMarginRight = pick(optionsChart.marginRight, margin[1]),
5186		optionsMarginBottom = pick(optionsChart.marginBottom, margin[2]),
5187		optionsMarginLeft = pick(optionsChart.marginLeft, margin[3]),
5188		spacingTop = optionsChart.spacingTop,
5189		spacingRight = optionsChart.spacingRight,
5190		spacingBottom = optionsChart.spacingBottom,
5191		spacingLeft = optionsChart.spacingLeft,
5192		spacingBox,
5193		chartTitleOptions,
5194		chartSubtitleOptions,
5195		plotTop,
5196		marginRight,
5197		marginBottom,
5198		plotLeft,
5199		axisOffset,
5200		renderTo,
5201		renderToClone,
5202		container,
5203		containerId,
5204		containerWidth,
5205		containerHeight,
5206		chartWidth,
5207		chartHeight,
5208		oldChartWidth,
5209		oldChartHeight,
5210		chartBackground,
5211		plotBackground,
5212		plotBGImage,
5213		plotBorder,
5214		chart = this,
5215		chartEvents = optionsChart.events,
5216		runChartClick = chartEvents && !!chartEvents.click,
5217		eventType,
5218		isInsidePlot, // function
5219		tooltip,
5220		mouseIsDown,
5221		loadingDiv,
5222		loadingSpan,
5223		loadingShown,
5224		plotHeight,
5225		plotWidth,
5226		tracker,
5227		trackerGroup,
5228		legend,
5229		legendWidth,
5230		legendHeight,
5231		chartPosition,
5232		hasCartesianSeries = optionsChart.showAxes,
5233		isResizing = 0,
5234		axes = [],
5235		maxTicks, // handle the greatest amount of ticks on grouped axes
5236		series = [],
5237		inverted,
5238		renderer,
5239		tooltipTick,
5240		tooltipInterval,
5241		hoverX,
5242		drawChartBox, // function
5243		getMargins, // function
5244		resetMargins, // function
5245		setChartSize, // function
5246		resize,
5247		zoom, // function
5248		zoomOut; // function
5249
5250
5251	/**
5252	 * Create a new axis object
5253	 * @param {Object} options
5254	 */
5255	function Axis(userOptions) {
5256
5257		// Define variables
5258		var isXAxis = userOptions.isX,
5259			opposite = userOptions.opposite, // needed in setOptions
5260			horiz = inverted ? !isXAxis : isXAxis,
5261			side = horiz ?
5262				(opposite ? 0 : 2) : // top : bottom
5263				(opposite ? 1 : 3),  // right : left
5264			stacks = {},
5265
5266			options = merge(
5267				isXAxis ? defaultXAxisOptions : defaultYAxisOptions,
5268				[defaultTopAxisOptions, defaultRightAxisOptions,
5269					defaultBottomAxisOptions, defaultLeftAxisOptions][side],
5270				userOptions
5271			),
5272
5273			axis = this,
5274			axisTitle,
5275			type = options.type,
5276			isDatetimeAxis = type === 'datetime',
5277			isLog = type === 'logarithmic',
5278			offset = options.offset || 0,
5279			xOrY = isXAxis ? 'x' : 'y',
5280			axisLength = 0,
5281			oldAxisLength,
5282			transA, // translation factor
5283			transB, // translation addend
5284			oldTransA, // used for prerendering
5285			axisLeft,
5286			axisTop,
5287			axisWidth,
5288			axisHeight,
5289			axisBottom,
5290			axisRight,
5291			translate, // fn
5292			setAxisTranslation, // fn
5293			getPlotLinePath, // fn
5294			axisGroup,
5295			gridGroup,
5296			axisLine,
5297			dataMin,
5298			dataMax,
5299			minRange = options.minRange || options.maxZoom,
5300			range = options.range,
5301			userMin,
5302			userMax,
5303			oldUserMin,
5304			oldUserMax,
5305			max = null,
5306			min = null,
5307			oldMin,
5308			oldMax,
5309			minPadding = options.minPadding,
5310			maxPadding = options.maxPadding,
5311			minPixelPadding = 0,
5312			isLinked = defined(options.linkedTo),
5313			linkedParent,
5314			ignoreMinPadding, // can be set to true by a column or bar series
5315			ignoreMaxPadding,
5316			usePercentage,
5317			events = options.events,
5318			eventType,
5319			plotLinesAndBands = [],
5320			tickInterval,
5321			minorTickInterval,
5322			magnitude,
5323			tickPositions, // array containing predefined positions
5324			tickPositioner = options.tickPositioner,
5325			ticks = {},
5326			minorTicks = {},
5327			alternateBands = {},
5328			tickAmount,
5329			labelOffset,
5330			axisTitleMargin,// = options.title.margin,
5331			categories = options.categories,
5332			labelFormatter = options.labels.formatter ||  // can be overwritten by dynamic format
5333				function () {
5334					var value = this.value,
5335						dateTimeLabelFormat = this.dateTimeLabelFormat,
5336						ret;
5337
5338					if (dateTimeLabelFormat) { // datetime axis
5339						ret = dateFormat(dateTimeLabelFormat, value);
5340
5341					} else if (tickInterval % 1000000 === 0) { // use M abbreviation
5342						ret = (value / 1000000) + 'M';
5343
5344					} else if (tickInterval % 1000 === 0) { // use k abbreviation
5345						ret = (value / 1000) + 'k';
5346
5347					} else if (!categories && value >= 1000) { // add thousands separators
5348						ret = numberFormat(value, 0);
5349
5350					} else { // strings (categories) and small numbers
5351						ret = value;
5352					}
5353					return ret;
5354				},
5355
5356			staggerLines = horiz && options.labels.staggerLines,
5357			reversed = options.reversed,
5358			tickmarkOffset = (categories && options.tickmarkPlacement === 'between') ? 0.5 : 0;
5359
5360		/**
5361		 * The Tick class
5362		 */
5363		function Tick(pos, type) {
5364			var tick = this;
5365			tick.pos = pos;
5366			tick.type = type || '';
5367			tick.isNew = true;
5368
5369			if (!type) {
5370				tick.addLabel();
5371			}
5372		}
5373		Tick.prototype = {
5374
5375			/**
5376			 * Write the tick label
5377			 */
5378			addLabel: function () {
5379				var tick = this,
5380					pos = tick.pos,
5381					labelOptions = options.labels,
5382					str,
5383					width = (categories && horiz && categories.length &&
5384						!labelOptions.step && !labelOptions.staggerLines &&
5385						!labelOptions.rotation &&
5386						plotWidth / categories.length) ||
5387						(!horiz && plotWidth / 2),
5388					isFirst = pos === tickPositions[0],
5389					isLast = pos === tickPositions[tickPositions.length - 1],
5390					css,
5391					value = categories && defined(categories[pos]) ? categories[pos] : pos,
5392					label = tick.label,
5393					tickPositionInfo = tickPositions.info,
5394					dateTimeLabelFormat;
5395
5396				// Set the datetime label format. If a higher rank is set for this position, use that. If not,
5397				// use the general format.
5398				if (isDatetimeAxis && tickPositionInfo) {
5399					dateTimeLabelFormat = options.dateTimeLabelFormats[tickPositionInfo.higherRanks[pos] || tickPositionInfo.unitName];
5400				}
5401
5402				// set properties for access in render method
5403				tick.isFirst = isFirst;
5404				tick.isLast = isLast;
5405
5406				// get the string
5407				str = labelFormatter.call({
5408					axis: axis,
5409					chart: chart,
5410					isFirst: isFirst,
5411					isLast: isLast,
5412					dateTimeLabelFormat: dateTimeLabelFormat,
5413					value: isLog ? correctFloat(lin2log(value)) : value
5414				});
5415
5416
5417				// prepare CSS
5418				css = width && { width: mathMax(1, mathRound(width - 2 * (labelOptions.padding || 10))) + PX };
5419				css = extend(css, labelOptions.style);
5420
5421				// first call
5422				if (!defined(label)) {
5423					tick.label =
5424						defined(str) && labelOptions.enabled ?
5425							renderer.text(
5426									str,
5427									0,
5428									0,
5429									labelOptions.useHTML
5430								)
5431								.attr({
5432									align: labelOptions.align,
5433									rotation: labelOptions.rotation
5434								})
5435								// without position absolute, IE export sometimes is wrong
5436								.css(css)
5437								.add(axisGroup) :
5438							null;
5439
5440				// update
5441				} else if (label) {
5442					label.attr({
5443							text: str
5444						})
5445						.css(css);
5446				}
5447			},
5448			/**
5449			 * Get the offset height or width of the label
5450			 */
5451			getLabelSize: function () {
5452				var label = this.label;
5453				return label ?
5454					((this.labelBBox = label.getBBox()))[horiz ? 'height' : 'width'] :
5455					0;
5456			},
5457			
5458			/**
5459			 * Find how far the labels extend to the right and left of the tick's x position. Used for anti-collision
5460			 * detection with overflow logic.
5461			 */
5462			getLabelSides: function () {
5463				var bBox = this.labelBBox, // assume getLabelSize has run at this point
5464					labelOptions = options.labels,
5465					width = bBox.width,
5466					leftSide = width * { left: 0, center: 0.5, right: 1 }[labelOptions.align] - labelOptions.x;
5467					
5468				return [-leftSide, width - leftSide];				
5469			},
5470			
5471			/**
5472			 * Handle the label overflow by adjusting the labels to the left and right edge, or
5473			 * hide them if they collide into the neighbour label.
5474			 */
5475			handleOverflow: function (index) {
5476				var show = true,
5477					isFirst = this.isFirst,
5478					isLast = this.isLast,
5479					label = this.label,
5480					x = label.x;
5481					
5482				if (isFirst || isLast) {
5483					
5484					var sides = this.getLabelSides(),
5485						leftSide = sides[0],
5486						rightSide = sides[1],
5487						plotLeft = chart.plotLeft,
5488						plotRight = plotLeft + axis.len,
5489						neighbour = ticks[tickPositions[index + (isFirst ? 1 : -1)]],
5490						neighbourEdge = neighbour && neighbour.label.x + neighbour.getLabelSides()[isFirst ? 0 : 1];
5491					
5492					if ((isFirst && !reversed) || (isLast && reversed)) {
5493						// Is the label spilling out to the left of the plot area?
5494						if (x + leftSide < plotLeft) {
5495							
5496							// Align it to plot left
5497							x = plotLeft - leftSide;
5498							
5499							// Hide it if it now overlaps the neighbour label
5500							if (neighbour && x + rightSide > neighbourEdge) {
5501								show = false;
5502							}
5503						}
5504										
5505					} else {
5506						// Is the label spilling out to the right of the plot area?
5507						if (x + rightSide > plotRight) {
5508							
5509							// Align it to plot right
5510							x = plotRight - rightSide;
5511							
5512							// Hide it if it now overlaps the neighbour label
5513							if (neighbour && x + leftSide < neighbourEdge) {
5514								show = false;
5515							}
5516							
5517						}
5518					}
5519					
5520					// Set the modified x position of the label
5521					label.x = x;
5522				}
5523				return show;
5524			},
5525			
5526			/**
5527			 * Put everything in place
5528			 *
5529			 * @param index {Number}
5530			 * @param old {Boolean} Use old coordinates to prepare an animation into new position
5531			 */
5532			render: function (index, old) {
5533				var tick = this,
5534					type = tick.type,
5535					label = tick.label,
5536					pos = tick.pos,
5537					labelOptions = options.labels,
5538					gridLine = tick.gridLine,
5539					gridPrefix = type ? type + 'Grid' : 'grid',
5540					tickPrefix = type ? type + 'Tick' : 'tick',
5541					gridLineWidth = options[gridPrefix + 'LineWidth'],
5542					gridLineColor = options[gridPrefix + 'LineColor'],
5543					dashStyle = options[gridPrefix + 'LineDashStyle'],
5544					tickLength = options[tickPrefix + 'Length'],
5545					tickWidth = options[tickPrefix + 'Width'] || 0,
5546					tickColor = options[tickPrefix + 'Color'],
5547					tickPosition = options[tickPrefix + 'Position'],
5548					gridLinePath,
5549					mark = tick.mark,
5550					markPath,
5551					step = labelOptions.step,
5552					cHeight = (old && oldChartHeight) || chartHeight,
5553					attribs,
5554					show = true,
5555					x,
5556					y;
5557
5558				// get x and y position for ticks and labels
5559				x = horiz ?
5560					translate(pos + tickmarkOffset, null, null, old) + transB :
5561					axisLeft + offset + (opposite ? ((old && oldChartWidth) || chartWidth) - axisRight - axisLeft : 0);
5562
5563				y = horiz ?
5564					cHeight - axisBottom + offset - (opposite ? axisHeight : 0) :
5565					cHeight - translate(pos + tickmarkOffset, null, null, old) - transB;
5566
5567				// create the grid line
5568				if (gridLineWidth) {
5569					gridLinePath = getPlotLinePath(pos + tickmarkOffset, gridLineWidth, old);
5570
5571					if (gridLine === UNDEFINED) {
5572						attribs = {
5573							stroke: gridLineColor,
5574							'stroke-width': gridLineWidth
5575						};
5576						if (dashStyle) {
5577							attribs.dashstyle = dashStyle;
5578						}
5579						if (!type) {
5580							attribs.zIndex = 1;
5581						}
5582						tick.gridLine = gridLine =
5583							gridLineWidth ?
5584								renderer.path(gridLinePath)
5585									.attr(attribs).add(gridGroup) :
5586								null;
5587					}
5588
5589					// If the parameter 'old' is set, the current call will be followed
5590					// by another call, therefore do not do any animations this time
5591					if (!old && gridLine && gridLinePath) {
5592						gridLine.animate({
5593							d: gridLinePath
5594						});
5595					}
5596				}
5597
5598				// create the tick mark
5599				if (tickWidth) {
5600
5601					// negate the length
5602					if (tickPosition === 'inside') {
5603						tickLength = -tickLength;
5604					}
5605					if (opposite) {
5606						tickLength = -tickLength;
5607					}
5608
5609					markPath = renderer.crispLine([
5610						M,
5611						x,
5612						y,
5613						L,
5614						x + (horiz ? 0 : -tickLength),
5615						y + (horiz ? tickLength : 0)
5616					], tickWidth);
5617
5618					if (mark) { // updating
5619						mark.animate({
5620							d: markPath
5621						});
5622					} else { // first time
5623						tick.mark = renderer.path(
5624							markPath
5625						).attr({
5626							stroke: tickColor,
5627							'stroke-width': tickWidth
5628						}).add(axisGroup);
5629					}
5630				}
5631
5632				// the label is created on init - now move it into place
5633				if (label && !isNaN(x)) {
5634					x = x + labelOptions.x - (tickmarkOffset && horiz ?
5635						tickmarkOffset * transA * (reversed ? -1 : 1) : 0);
5636					y = y + labelOptions.y - (tickmarkOffset && !horiz ?
5637						tickmarkOffset * transA * (reversed ? 1 : -1) : 0);
5638
5639					// vertically centered
5640					if (!defined(labelOptions.y)) {
5641						y += pInt(label.styles.lineHeight) * 0.9 - label.getBBox().height / 2;
5642					}
5643
5644
5645					// correct for staggered labels
5646					if (staggerLines) {
5647						y += (index / (step || 1) % staggerLines) * 16;
5648					}
5649					
5650					// Cache x and y to be able to read final position before animation
5651					label.x = x;
5652					label.y = y;
5653
5654					// apply show first and show last
5655					if ((tick.isFirst && !pick(options.showFirstLabel, 1)) ||
5656							(tick.isLast && !pick(options.showLastLabel, 1))) {
5657						show = false;
5658						
5659					// Handle label overflow and show or hide accordingly
5660					} else if (!staggerLines && horiz && labelOptions.overflow === 'justify' && !tick.handleOverflow(index)) {						
5661						show = false;
5662					}
5663
5664					// apply step
5665					if (step && index % step) {
5666						// show those indices dividable by step
5667						show = false;
5668					}
5669					
5670					// Set the new position, and show or hide
5671					if (show) {
5672						label[tick.isNew ? 'attr' : 'animate']({
5673							x: label.x,
5674							y: label.y
5675						});
5676						label.show();
5677						tick.isNew = false;
5678					} else {
5679						label.hide();
5680					}
5681				}
5682
5683				
5684			},
5685			
5686			/**
5687			 * Destructor for the tick prototype
5688			 */
5689			destroy: function () {
5690				destroyObjectProperties(this);
5691			}
5692		};
5693
5694		/**
5695		 * The object wrapper for plot lines and plot bands
5696		 * @param {Object} options
5697		 */
5698		function PlotLineOrBand(options) {
5699			var plotLine = this;
5700			if (options) {
5701				plotLine.options = options;
5702				plotLine.id = options.id;
5703			}
5704
5705			//plotLine.render()
5706			return plotLine;
5707		}
5708
5709		PlotLineOrBand.prototype = {
5710
5711		/**
5712		 * Render the plot line or plot band. If it is already existing,
5713		 * move it.
5714		 */
5715		render: function () {
5716			var plotLine = this,
5717				halfPointRange = (axis.pointRange || 0) / 2,
5718				options = plotLine.options,
5719				optionsLabel = options.label,
5720				label = plotLine.label,
5721				width = options.width,
5722				to = options.to,
5723				from = options.from,
5724				value = options.value,
5725				toPath, // bands only
5726				dashStyle = options.dashStyle,
5727				svgElem = plotLine.svgElem,
5728				path = [],
5729				addEvent,
5730				eventType,
5731				xs,
5732				ys,
5733				x,
5734				y,
5735				color = options.color,
5736				zIndex = options.zIndex,
5737				events = options.events,
5738				attribs;
5739
5740			// logarithmic conversion
5741			if (isLog) {
5742				from = log2lin(from);
5743				to = log2lin(to);
5744				value = log2lin(value);
5745			}
5746
5747			// plot line
5748			if (width) {
5749				path = getPlotLinePath(value, width);
5750				attribs = {
5751					stroke: color,
5752					'stroke-width': width
5753				};
5754				if (dashStyle) {
5755					attribs.dashstyle = dashStyle;
5756				}
5757			} else if (defined(from) && defined(to)) { // plot band
5758				// keep within plot area
5759				from = mathMax(from, min - halfPointRange);
5760				to = mathMin(to, max + halfPointRange);
5761
5762				toPath = getPlotLinePath(to);
5763				path = getPlotLinePath(from);
5764				if (path && toPath) {
5765					path.push(
5766						toPath[4],
5767						toPath[5],
5768						toPath[1],
5769						toPath[2]
5770					);
5771				} else { // outside the axis area
5772					path = null;
5773				}
5774				attribs = {
5775					fill: color
5776				};
5777			} else {
5778				return;
5779			}
5780			// zIndex
5781			if (defined(zIndex)) {
5782				attribs.zIndex = zIndex;
5783			}
5784
5785			// common for lines and bands
5786			if (svgElem) {
5787				if (path) {
5788					svgElem.animate({
5789						d: path
5790					}, null, svgElem.onGetPath);
5791				} else {
5792					svgElem.hide();
5793					svgElem.onGetPath = function () {
5794						svgElem.show();
5795					};
5796				}
5797			} else if (path && path.length) {
5798				plotLine.svgElem = svgElem = renderer.path(path)
5799					.attr(attribs).add();
5800
5801				// events
5802				if (events) {
5803					addEvent = function (eventType) {
5804						svgElem.on(eventType, function (e) {
5805							events[eventType].apply(plotLine, [e]);
5806						});
5807					};
5808					for (eventType in events) {
5809						addEvent(eventType);
5810					}
5811				}
5812			}
5813
5814			// the plot band/line label
5815			if (optionsLabel && defined(optionsLabel.text) && path && path.length && axisWidth > 0 && axisHeight > 0) {
5816				// apply defaults
5817				optionsLabel = merge({
5818					align: horiz && toPath && 'center',
5819					x: horiz ? !toPath && 4 : 10,
5820					verticalAlign : !horiz && toPath && 'middle',
5821					y: horiz ? toPath ? 16 : 10 : toPath ? 6 : -4,
5822					rotation: horiz && !toPath && 90
5823				}, optionsLabel);
5824
5825				// add the SVG element
5826				if (!label) {
5827					plotLine.label = label = renderer.text(
5828							optionsLabel.text,
5829							0,
5830							0
5831						)
5832						.attr({
5833							align: optionsLabel.textAlign || optionsLabel.align,
5834							rotation: optionsLabel.rotation,
5835							zIndex: zIndex
5836						})
5837						.css(optionsLabel.style)
5838						.add();
5839				}
5840
5841				// get the bounding box and align the label
5842				xs = [path[1], path[4], pick(path[6], path[1])];
5843				ys = [path[2], path[5], pick(path[7], path[2])];
5844				x = arrayMin(xs);
5845				y = arrayMin(ys);
5846
5847				label.align(optionsLabel, false, {
5848					x: x,
5849					y: y,
5850					width: arrayMax(xs) - x,
5851					height: arrayMax(ys) - y
5852				});
5853				label.show();
5854
5855			} else if (label) { // move out of sight
5856				label.hide();
5857			}
5858
5859			// chainable
5860			return plotLine;
5861		},
5862
5863		/**
5864		 * Remove the plot line or band
5865		 */
5866		destroy: function () {
5867			var obj = this;
5868
5869			destroyObjectProperties(obj);
5870
5871			// remove it from the lookup
5872			erase(plotLinesAndBands, obj);
5873		}
5874		};
5875
5876		/**
5877		 * The class for stack items
5878		 */
5879		function StackItem(options, isNegative, x, stackOption) {
5880			var stackItem = this;
5881
5882			// Tells if the stack is negative
5883			stackItem.isNegative = isNegative;
5884
5885			// Save the options to be able to style the label
5886			stackItem.options = options;
5887
5888			// Save the x value to be able to position the label later
5889			stackItem.x = x;
5890
5891			// Save the stack option on the series configuration object
5892			stackItem.stack = stackOption;
5893
5894			// The align options and text align varies on whether the stack is negative and
5895			// if the chart is inverted or not.
5896			// First test the user supplied value, then use the dynamic.
5897			stackItem.alignOptions = {
5898				align: options.align || (inverted ? (isNegative ? 'left' : 'right') : 'center'),
5899				verticalAlign: options.verticalAlign || (inverted ? 'middle' : (isNegative ? 'bottom' : 'top')),
5900				y: pick(options.y, inverted ? 4 : (isNegative ? 14 : -6)),
5901				x: pick(options.x, inverted ? (isNegative ? -6 : 6) : 0)
5902			};
5903
5904			stackItem.textAlign = options.textAlign || (inverted ? (isNegative ? 'right' : 'left') : 'center');
5905		}
5906
5907		StackItem.prototype = {
5908			destroy: function () {
5909				destroyObjectProperties(this);
5910			},
5911
5912			/**
5913			 * Sets the total of this stack. Should be called when a serie is hidden or shown
5914			 * since that will affect the total of other stacks.
5915			 */
5916			setTotal: function (total) {
5917				this.total = total;
5918				this.cum = total;
5919			},
5920
5921			/**
5922			 * Renders the stack total label and adds it to the stack label group.
5923			 */
5924			render: function (group) {
5925				var stackItem = this,									// aliased this
5926					str = stackItem.options.formatter.call(stackItem);  // format the text in the label
5927
5928				// Change the text to reflect the new total and set visibility to hidden in case the serie is hidden
5929				if (stackItem.label) {
5930					stackItem.label.attr({text: str, visibility: HIDDEN});
5931				// Create new label
5932				} else {
5933					stackItem.label =
5934						chart.renderer.text(str, 0, 0)				// dummy positions, actual position updated with setOffset method in columnseries
5935							.css(stackItem.options.style)			// apply style
5936							.attr({align: stackItem.textAlign,			// fix the text-anchor
5937								rotation: stackItem.options.rotation,	// rotation
5938								visibility: HIDDEN })					// hidden until setOffset is called
5939							.add(group);							// add to the labels-group
5940				}
5941			},
5942
5943			/**
5944			 * Sets the offset that the stack has from the x value and repositions the label.
5945			 */
5946			setOffset: function (xOffset, xWidth) {
5947				var stackItem = this,										// aliased this
5948					neg = stackItem.isNegative,								// special treatment is needed for negative stacks
5949					y = axis.translate(stackItem.total, 0, 0, 0, 1),		// stack value translated mapped to chart coordinates
5950					yZero = axis.translate(0),								// stack origin
5951					h = mathAbs(y - yZero),									// stack height
5952					x = chart.xAxis[0].translate(stackItem.x) + xOffset,	// stack x position
5953					plotHeight = chart.plotHeight,
5954					stackBox = {	// this is the box for the complete stack
5955							x: inverted ? (neg ? y : y - h) : x,
5956							y: inverted ? plotHeight - x - xWidth : (neg ? (plotHeight - y - h) : plotHeight - y),
5957							width: inverted ? h : xWidth,
5958							height: inverted ? xWidth : h
5959					};
5960
5961				if (stackItem.label) {
5962					stackItem.label
5963						.align(stackItem.alignOptions, null, stackBox)	// align the label to the box
5964						.attr({visibility: VISIBLE});					// set visibility
5965				}
5966			}
5967		};
5968
5969		/**
5970		 * Get the minimum and maximum for the series of each axis
5971		 */
5972		function getSeriesExtremes() {
5973			var posStack = [],
5974				negStack = [],
5975				i;
5976
5977			// reset dataMin and dataMax in case we're redrawing
5978			dataMin = dataMax = null;
5979
5980			// loop through this axis' series
5981			each(axis.series, function (series) {
5982
5983				if (series.visible || !optionsChart.ignoreHiddenSeries) {
5984
5985					var seriesOptions = series.options,
5986						stacking,
5987						posPointStack,
5988						negPointStack,
5989						stackKey,
5990						stackOption,
5991						negKey,
5992						xData,
5993						yData,
5994						x,
5995						y,
5996						threshold = seriesOptions.threshold,
5997						yDataLength,
5998						activeYData = [],
5999						activeCounter = 0;
6000						
6001					// Validate threshold in logarithmic axes
6002					if (isLog && threshold <= 0) {
6003						threshold = seriesOptions.threshold = null;
6004					}
6005
6006					// Get dataMin and dataMax for X axes
6007					if (isXAxis) {
6008						xData = series.xData;
6009						if (xData.length) {
6010							dataMin = mathMin(pick(dataMin, xData[0]), arrayMin(xData));
6011							dataMax = mathMax(pick(dataMax, xData[0]), arrayMax(xData));
6012						}
6013
6014					// Get dataMin and dataMax for Y axes, as well as handle stacking and processed data
6015					} else {
6016						var isNegative,
6017							pointStack,
6018							key,
6019							cropped = series.cropped,
6020							xExtremes = series.xAxis.getExtremes(),
6021							//findPointRange,
6022							//pointRange,
6023							j,
6024							hasModifyValue = !!series.modifyValue;
6025
6026
6027						// Handle stacking
6028						stacking = seriesOptions.stacking;
6029						usePercentage = stacking === 'percent';
6030
6031						// create a stack for this particular series type
6032						if (stacking) {
6033							stackOption = seriesOptions.stack;
6034							stackKey = series.type + pick(stackOption, '');
6035							negKey = '-' + stackKey;
6036							series.stackKey = stackKey; // used in translate
6037
6038							posPointStack = posStack[stackKey] || []; // contains the total values for each x
6039							posStack[stackKey] = posPointStack;
6040
6041							negPointStack = negStack[negKey] || [];
6042							negStack[negKey] = negPointStack;
6043						}
6044						if (usePercentage) {
6045							dataMin = 0;
6046							dataMax = 99;
6047						}
6048
6049
6050						// processData can alter series.pointRange, so this goes after
6051						//findPointRange = series.pointRange === null;
6052
6053						xData = series.processedXData;
6054						yData = series.processedYData;
6055						yDataLength = yData.length;
6056
6057						// loop over the non-null y values and read them into a local array
6058						for (i = 0; i < yDataLength; i++) {
6059							x = xData[i];
6060							y = yData[i];
6061							if (y !== null && y !== UNDEFINED) {
6062
6063								// read stacked values into a stack based on the x value,
6064								// the sign of y and the stack key
6065								if (stacking) {
6066									isNegative = y < threshold;
6067									pointStack = isNegative ? negPointStack : posPointStack;
6068									key = isNegative ? negKey : stackKey;
6069
6070									y = pointStack[x] =
6071										defined(pointStack[x]) ?
6072										pointStack[x] + y : y;
6073
6074
6075									// add the series
6076									if (!stacks[key]) {
6077										stacks[key] = {};
6078									}
6079
6080									// If the StackItem is there, just update the values,
6081									// if not, create one first
6082									if (!stacks[key][x]) {
6083										stacks[key][x] = new StackItem(options.stackLabels, isNegative, x, stackOption);
6084									}
6085									stacks[key][x].setTotal(y);
6086
6087
6088								// general hook, used for Highstock compare values feature
6089								} else if (hasModifyValue) {
6090									y = series.modifyValue(y);
6091								}
6092
6093								// get the smallest distance between points
6094								/*if (i) {
6095									distance = mathAbs(xData[i] - xData[i - 1]);
6096									pointRange = pointRange === UNDEFINED ? distance : mathMin(distance, pointRange);
6097								}*/
6098
6099								// for points within the visible range, including the first point outside the
6100								// visible range, consider y extremes
6101								if (cropped || ((xData[i + 1] || x) >= xExtremes.min && (xData[i - 1] || x) <= xExtremes.max)) {
6102
6103									j = y.length;
6104									if (j) { // array, like ohlc data
6105										while (j--) {
6106											if (y[j] !== null) {
6107												activeYData[activeCounter++] = y[j];
6108											}
6109										}
6110									} else {
6111										activeYData[activeCounter++] = y;
6112									}
6113								}
6114							}
6115						}
6116
6117						// record the least unit distance
6118						/*if (findPointRange) {
6119							series.pointRange = pointRange || 1;
6120						}
6121						series.closestPointRange = pointRange;*/
6122
6123						// Get the dataMin and dataMax so far. If percentage is used, the min and max are
6124						// always 0 and 100. If the length of activeYData is 0, continue with null values.
6125						if (!usePercentage && activeYData.length) {
6126							dataMin = mathMin(pick(dataMin, activeYData[0]), arrayMin(activeYData));
6127							dataMax = mathMax(pick(dataMax, activeYData[0]), arrayMax(activeYData));
6128						}
6129
6130						// Adjust to threshold
6131						if (defined(threshold)) {
6132							if (dataMin >= threshold) {
6133								dataMin = threshold;
6134								ignoreMinPadding = true;
6135							} else if (dataMax < threshold) {
6136								dataMax = threshold;
6137								ignoreMaxPadding = true;
6138							}
6139						}
6140					}
6141				}
6142			});
6143
6144		}
6145
6146		/**
6147		 * Translate from axis value to pixel position on the chart, or back
6148		 *
6149		 */
6150		translate = function (val, backwards, cvsCoord, old, handleLog) {
6151			
6152			var sign = 1,
6153				cvsOffset = 0,
6154				localA = old ? oldTransA : transA,
6155				localMin = old ? oldMin : min,
6156				returnValue,
6157				postTranslate = options.ordinal || (isLog && handleLog);
6158
6159			if (!localA) {
6160				localA = transA;
6161			}
6162
6163			if (cvsCoord) {
6164				sign *= -1; // canvas coordinates inverts the value
6165				cvsOffset = axisLength;
6166			}
6167			if (reversed) { // reversed axis
6168				sign *= -1;
6169				cvsOffset -= sign * axisLength;
6170			}
6171
6172			if (backwards) { // reverse translation
6173				if (reversed) {
6174					val = axisLength - val;
6175				}
6176				returnValue = val / localA + localMin; // from chart pixel to value
6177				if (postTranslate) { // log and ordinal axes
6178					returnValue = axis.lin2val(returnValue);
6179				}
6180
6181			} else { // normal translation, from axis value to pixel, relative to plot
6182				if (postTranslate) { // log and ordinal axes
6183					val = axis.val2lin(val);
6184				}
6185
6186				returnValue = sign * (val - localMin) * localA + cvsOffset + (sign * minPixelPadding);
6187			}
6188
6189			return returnValue;
6190		};
6191
6192		/**
6193		 * Create the path for a plot line that goes from the given value on
6194		 * this axis, across the plot to the opposite side
6195		 * @param {Number} value
6196		 * @param {Number} lineWidth Used for calculation crisp line
6197		 * @param {Number] old Use old coordinates (for resizing and rescaling)
6198		 */
6199		getPlotLinePath = function (value, lineWidth, old) {
6200			var x1,
6201				y1,
6202				x2,
6203				y2,
6204				translatedValue = translate(value, null, null, old),
6205				cHeight = (old && oldChartHeight) || chartHeight,
6206				cWidth = (old && oldChartWidth) || chartWidth,
6207				skip;
6208
6209			x1 = x2 = mathRound(translatedValue + transB);
6210			y1 = y2 = mathRound(cHeight - translatedValue - transB);
6211
6212			if (isNaN(translatedValue)) { // no min or max
6213				skip = true;
6214
6215			} else if (horiz) {
6216				y1 = axisTop;
6217				y2 = cHeight - axisBottom;
6218				if (x1 < axisLeft || x1 > axisLeft + axisWidth) {
6219					skip = true;
6220				}
6221			} else {
6222				x1 = axisLeft;
6223				x2 = cWidth - axisRight;
6224
6225				if (y1 < axisTop || y1 > axisTop + axisHeight) {
6226					skip = true;
6227				}
6228			}
6229			return skip ?
6230				null :
6231				renderer.crispLine([M, x1, y1, L, x2, y2], lineWidth || 0);
6232		};
6233
6234		/**
6235		 * Set the tick positions of a linear axis to round values like whole tens or every five.
6236		 */
6237		function getLinearTickPositions(tickInterval, min, max) {
6238
6239			var pos,
6240				lastPos,
6241				roundedMin = correctFloat(mathFloor(min / tickInterval) * tickInterval),
6242				roundedMax = correctFloat(mathCeil(max / tickInterval) * tickInterval),
6243				tickPositions = [];
6244
6245			// Populate the intermediate values
6246			pos = roundedMin;
6247			while (pos <= roundedMax) {
6248
6249				// Place the tick on the rounded value
6250				tickPositions.push(pos);
6251
6252				// Always add the raw tickInterval, not the corrected one.
6253				pos = correctFloat(pos + tickInterval);
6254
6255				// If the interval is not big enough in the current min - max range to actually increase
6256				// the loop variable, we need to break out to prevent endless loop. Issue #619
6257				if (pos === lastPos) {
6258					break;
6259				}
6260
6261				// Record the last value
6262				lastPos = pos;
6263			}
6264			return tickPositions;
6265		}
6266		
6267		/**
6268		 * Set the tick positions of a logarithmic axis
6269		 */
6270		function getLogTickPositions(interval, min, max, minor) {
6271			
6272			// Since we use this method for both major and minor ticks,
6273			// use a local variable and return the result
6274			var positions = []; 
6275			
6276			// Reset
6277			if (!minor) {
6278				axis._minorAutoInterval = null;
6279			}
6280			
6281			// First case: All ticks fall on whole logarithms: 1, 10, 100 etc.
6282			if (interval >= 0.5) {
6283				interval = mathRound(interval);
6284				positions = getLinearTickPositions(interval, min, max);
6285				
6286			// Second case: We need intermediary ticks. For example 
6287			// 1, 2, 4, 6, 8, 10, 20, 40 etc. 
6288			} else if (interval >= 0.08) {
6289				var roundedMin = mathFloor(min),
6290					intermediate,
6291					i,
6292					j,
6293					len,
6294					pos,
6295					lastPos,
6296					break2;
6297					
6298				if (interval > 0.3) {
6299					intermediate = [1, 2, 4];
6300				} else if (interval > 0.15) { // 0.2 equals five minor ticks per 1, 10, 100 etc
6301					intermediate = [1, 2, 4, 6, 8];
6302				} else { // 0.1 equals ten minor ticks per 1, 10, 100 etc
6303					intermediate = [1, 2, 3, 4, 5, 6, 7, 8, 9];
6304				}
6305				
6306				for (i = roundedMin; i < max + 1 && !break2; i++) {
6307					len = intermediate.length;
6308					for (j = 0; j < len && !break2; j++) {
6309						pos = log2lin(lin2log(i) * intermediate[j]);
6310						
6311						if (pos > min) {
6312							positions.push(lastPos);
6313						}
6314						
6315						if (lastPos > max) {
6316							break2 = true;
6317						}
6318						lastPos = pos;
6319					}
6320				}
6321				
6322			// Third case: We are so deep in between whole logarithmic values that
6323			// we might as well handle the tick positions like a linear axis. For
6324			// example 1.01, 1.02, 1.03, 1.04.
6325			} else {
6326				var realMin = lin2log(min),
6327					realMax = lin2log(max),
6328					tickIntervalOption = options[minor ? 'minorTickInterval' : 'tickInterval'],
6329					filteredTickIntervalOption = tickIntervalOption === 'auto' ? null : tickIntervalOption,
6330					tickPixelIntervalOption = options.tickPixelInterval / (minor ? 5 : 1),
6331					totalPixelLength = minor ? axisLength / tickPositions.length : axisLength;
6332				
6333				interval = pick(
6334					filteredTickIntervalOption,
6335					axis._minorAutoInterval,
6336					(realMax - realMin) * tickPixelIntervalOption / (totalPixelLength || 1)
6337				);
6338				
6339				interval = normalizeTickInterval(
6340					interval, 
6341					null, 
6342					math.pow(10, mathFloor(math.log(interval) / math.LN10))
6343				);
6344				
6345				positions = map(getLinearTickPositions(
6346					interval, 
6347					realMin,
6348					realMax	
6349				), log2lin);
6350				
6351				if (!minor) {
6352					axis._minorAutoInterval = interval / 5;
6353				}
6354			}
6355			
6356			// Set the axis-level tickInterval variable 
6357			if (!minor) {
6358				tickInterval = interval;
6359			}
6360			return positions;
6361		}
6362		
6363		/**
6364		 * Return the minor tick positions. For logarithmic axes, reuse the same logic
6365		 * as for major ticks.
6366		 */
6367		function getMinorTickPositions() {
6368			var minorTickPositions = [],
6369				pos,
6370				i,
6371				len;
6372			
6373			if (isLog) {
6374				len = tickPositions.length;
6375				for (i = 1; i < len; i++) {
6376					minorTickPositions = minorTickPositions.concat(
6377						getLogTickPositions(minorTickInterval, tickPositions[i - 1], tickPositions[i], true)
6378					);	
6379				}
6380			
6381			} else {			
6382				for (pos = min + (tickPositions[0] - min) % minorTickInterval; pos <= max; pos += minorTickInterval) {
6383					minorTickPositions.push(pos);	
6384				}
6385			}
6386			
6387			return minorTickPositions;
6388		}
6389
6390		/**
6391		 * Adjust the min and max for the minimum range. Keep in mind that the series data is 
6392		 * not yet processed, so we don't have information on data cropping and grouping, or 
6393		 * updated axis.pointRange or series.pointRange. The data can't be processed until
6394		 * we have finally established min and max.
6395		 */
6396		function adjustForMinRange() {
6397			var zoomOffset,
6398				spaceAvailable = dataMax - dataMin >= minRange,
6399				closestDataRange,
6400				i,
6401				distance,
6402				xData,
6403				loopLength,
6404				minArgs,
6405				maxArgs;
6406				
6407			// Set the automatic minimum range based on the closest point distance
6408			if (isXAxis && minRange === UNDEFINED && !isLog) {
6409				
6410				if (defined(options.min) || defined(options.max)) {
6411					minRange = null; // don't do this again
6412
6413				} else {
6414
6415					// Find the closest distance between raw data points, as opposed to
6416					// closestPointRange that applies to processed points (cropped and grouped)
6417					each(axis.series, function (series) {
6418						xData = series.xData;
6419						loopLength = series.xIncrement ? 1 : xData.length - 1;
6420						for (i = loopLength; i > 0; i--) {
6421							distance = xData[i] - xData[i - 1];
6422							if (closestDataRange === UNDEFINED || distance < closestDataRange) {
6423								closestDataRange = distance;
6424							}
6425						}
6426					});
6427					minRange = mathMin(closestDataRange * 5, dataMax - dataMin);
6428				}
6429			}
6430			
6431			// if minRange is exceeded, adjust
6432			if (max - min < minRange) {
6433
6434				zoomOffset = (minRange - max + min) / 2;
6435
6436				// if min and max options have been set, don't go beyond it
6437				minArgs = [min - zoomOffset, pick(options.min, min - zoomOffset)];
6438				if (spaceAvailable) { // if space is available, stay within the data range
6439					minArgs[2] = dataMin;
6440				}
6441				min = arrayMax(minArgs);
6442
6443				maxArgs = [min + minRange, pick(options.max, min + minRange)];
6444				if (spaceAvailable) { // if space is availabe, stay within the data range
6445					maxArgs[2] = dataMax;
6446				}
6447				
6448				max = arrayMin(maxArgs);
6449
6450				// now if the max is adjusted, adjust the min back
6451				if (max - min < minRange) {
6452					minArgs[0] = max - minRange;
6453					minArgs[1] = pick(options.min, max - minRange);
6454					min = arrayMax(minArgs);
6455				}
6456			}
6457		}
6458
6459		/**
6460		 * Set the tick positions to round values and optionally extend the extremes
6461		 * to the nearest tick
6462		 */
6463		function setTickPositions(secondPass) {
6464
6465			var length,
6466				linkedParentExtremes,
6467				tickIntervalOption = options.tickInterval,
6468				tickPixelIntervalOption = options.tickPixelInterval;
6469
6470			// linked axis gets the extremes from the parent axis
6471			if (isLinked) {
6472				linkedParent = chart[isXAxis ? 'xAxis' : 'yAxis'][options.linkedTo];
6473				linkedParentExtremes = linkedParent.getExtremes();
6474				min = pick(linkedParentExtremes.min, linkedParentExtremes.dataMin);
6475				max = pick(linkedParentExtremes.max, linkedParentExtremes.dataMax);
6476				if (options.type !== linkedParent.options.type) {
6477					error(11, 1); // Can't link axes of different type
6478				}
6479			} else { // initial min and max from the extreme data values
6480				min = pick(userMin, options.min, dataMin);
6481				max = pick(userMax, options.max, dataMax);
6482			}
6483
6484			if (isLog) {
6485				if (!secondPass && mathMin(min, dataMin) <= 0) {
6486					error(10, 1); // Can't plot negative values on log axis
6487				}
6488				min = log2lin(min);
6489				max = log2lin(max);
6490			}
6491
6492			// handle zoomed range
6493			if (range) {
6494				userMin = min = mathMax(min, max - range); // #618
6495				userMax = max;
6496				if (secondPass) {
6497					range = null;  // don't use it when running setExtremes
6498				}
6499			}
6500
6501			// adjust min and max for the minimum range
6502			adjustForMinRange();
6503
6504			// pad the values to get clear of the chart's edges
6505			if (!categories && !usePercentage && !isLinked && defined(min) && defined(max)) {
6506				length = (max - min) || 1;
6507				if (!defined(options.min) && !defined(userMin) && minPadding && (dataMin < 0 || !ignoreMinPadding)) {
6508					min -= length * minPadding;
6509				}
6510				if (!defined(options.max) && !defined(userMax)  && maxPadding && (dataMax > 0 || !ignoreMaxPadding)) {
6511					max += length * maxPadding;
6512				}
6513			}
6514
6515			// get tickInterval
6516			if (min === max || min === undefined || max === undefined) {
6517				tickInterval = 1;
6518			} else if (isLinked && !tickIntervalOption &&
6519					tickPixelIntervalOption === linkedParent.options.tickPixelInterval) {
6520				tickInterval = linkedParent.tickInterval;
6521			} else {
6522				tickInterval = pick(
6523					tickIntervalOption,
6524					categories ? // for categoried axis, 1 is default, for linear axis use tickPix
6525						1 :
6526						(max - min) * tickPixelIntervalOption / (axisLength || 1)
6527				);
6528			}
6529
6530			// Now we're finished detecting min and max, crop and group series data. This
6531			// is in turn needed in order to find tick positions in ordinal axes. 
6532			if (isXAxis && !secondPass) {
6533				each(axis.series, function (series) {
6534					series.processData(min !== oldMin || max !== oldMax);             
6535				});
6536			}
6537
6538			// set the translation factor used in translate function
6539			setAxisTranslation();
6540
6541			// hook for ordinal axes. To do: merge with below
6542			if (axis.beforeSetTickPositions) {
6543				axis.beforeSetTickPositions();
6544			}
6545			
6546			// hook for extensions, used in Highstock ordinal axes
6547			if (axis.postProcessTickInterval) {
6548				tickInterval = axis.postProcessTickInterval(tickInterval);				
6549			}
6550
6551			// for linear axes, get magnitude and normalize the interval
6552			if (!isDatetimeAxis && !isLog) { // linear
6553				magnitude = math.pow(10, mathFloor(math.log(tickInterval) / math.LN10));
6554				if (!defined(options.tickInterval)) {
6555					tickInterval = normalizeTickInterval(tickInterval, null, magnitude, options);
6556				}
6557			}
6558
6559			// record the tick interval for linked axis
6560			axis.tickInterval = tickInterval;
6561
6562			// get minorTickInterval
6563			minorTickInterval = options.minorTickInterval === 'auto' && tickInterval ?
6564					tickInterval / 5 : options.minorTickInterval;
6565
6566			// find the tick positions
6567			tickPositions = options.tickPositions || (tickPositioner && tickPositioner.apply(axis, [min, max]));
6568			if (!tickPositions) {
6569				if (isDatetimeAxis) {
6570					tickPositions = (axis.getNonLinearTimeTicks || getTimeTicks)(
6571						normalizeTimeTickInterval(tickInterval, options.units),
6572						min,
6573						max,
6574						options.startOfWeek,
6575						axis.ordinalPositions,
6576						axis.closestPointRange,
6577						true
6578					);
6579				} else if (isLog) {
6580					tickPositions = getLogTickPositions(tickInterval, min, max);
6581				} else {
6582					tickPositions = getLinearTickPositions(tickInterval, min, max);
6583				}
6584			}
6585
6586			if (!isLinked) {
6587
6588				// reset min/max or remove extremes based on start/end on tick
6589				var roundedMin = tickPositions[0],
6590					roundedMax = tickPositions[tickPositions.length - 1];
6591
6592				if (options.startOnTick) {
6593					min = roundedMin;
6594				} else if (min > roundedMin) {
6595					tickPositions.shift();
6596				}
6597
6598				if (options.endOnTick) {
6599					max = roundedMax;
6600				} else if (max < roundedMax) {
6601					tickPositions.pop();
6602				}
6603
6604				// record the greatest number of ticks for multi axis
6605				if (!maxTicks) { // first call, or maxTicks have been reset after a zoom operation
6606					maxTicks = {
6607						x: 0,
6608						y: 0
6609					};
6610				}
6611
6612				if (!isDatetimeAxis && tickPositions.length > maxTicks[xOrY] && options.alignTicks !== false) {
6613					maxTicks[xOrY] = tickPositions.length;
6614				}
6615			}
6616		}
6617
6618		/**
6619		 * When using multiple axes, adjust the number of ticks to match the highest
6620		 * number of ticks in that group
6621		 */
6622		function adjustTickAmount() {
6623
6624			if (maxTicks && maxTicks[xOrY] && !isDatetimeAxis && !categories && !isLinked && options.alignTicks !== false) { // only apply to linear scale
6625				var oldTickAmount = tickAmount,
6626					calculatedTickAmount = tickPositions.length;
6627
6628				// set the axis-level tickAmount to use below
6629				tickAmount = maxTicks[xOrY];
6630
6631				if (calculatedTickAmount < tickAmount) {
6632					while (tickPositions.length < tickAmount) {
6633						tickPositions.push(correctFloat(
6634							tickPositions[tickPositions.length - 1] + tickInterval
6635						));
6636					}
6637					transA *= (calculatedTickAmount - 1) / (tickAmount - 1);
6638					max = tickPositions[tickPositions.length - 1];
6639
6640				}
6641				if (defined(oldTickAmount) && tickAmount !== oldTickAmount) {
6642					axis.isDirty = true;
6643				}
6644			}
6645
6646
6647		}
6648
6649		/**
6650		 * Set the scale based on data min and max, user set min and max or options
6651		 *
6652		 */
6653		function setScale() {
6654			var type,
6655				i,
6656				isDirtyData,
6657				isDirtyAxisLength;
6658				
6659			oldMin = min;
6660			oldMax = max;
6661			oldAxisLength = axisLength;
6662
6663			// set the new axisLength
6664			axisLength = horiz ? axisWidth : axisHeight;
6665			isDirtyAxisLength = axisLength !== oldAxisLength;
6666
6667			// is there new data?
6668			each(axis.series, function (series) {
6669				if (series.isDirtyData || series.isDirty ||
6670						series.xAxis.isDirty) { // when x axis is dirty, we need new data extremes for y as well
6671					isDirtyData = true;
6672				}
6673			});
6674
6675			// do we really need to go through all this?
6676			if (isDirtyAxisLength || isDirtyData || isLinked ||
6677				userMin !== oldUserMin || userMax !== oldUserMax) {
6678
6679				// get data extremes if needed
6680				getSeriesExtremes();
6681
6682				// get fixed positions based on tickInterval
6683				setTickPositions();
6684
6685				// record old values to decide whether a rescale is necessary later on (#540)
6686				oldUserMin = userMin;
6687				oldUserMax = userMax;
6688
6689				// reset stacks
6690				if (!isXAxis) {
6691					for (type in stacks) {
6692						for (i in stacks[type]) {
6693							stacks[type][i].cum = stacks[type][i].total;
6694						}
6695					}
6696				}
6697
6698				// Mark as dirty if it is not already set to dirty and extremes have changed. #595.
6699				if (!axis.isDirty) {
6700					axis.isDirty = isDirtyAxisLength || min !== oldMin || max !== oldMax;
6701				}
6702			}
6703		}
6704
6705		/**
6706		 * Set the extremes and optionally redraw
6707		 * @param {Number} newMin
6708		 * @param {Number} newMax
6709		 * @param {Boolean} redraw
6710		 * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
6711		 *    configuration
6712		 * @param {Object} eventArguments 
6713		 *
6714		 */
6715		function setExtremes(newMin, newMax, redraw, animation, eventArguments) {
6716
6717			redraw = pick(redraw, true); // defaults to true
6718			
6719			// Extend the arguments with min and max
6720			eventArguments = extend(eventArguments, {
6721				min: newMin,
6722				max: newMax
6723			});
6724
6725			// Fire the event
6726			fireEvent(axis, 'setExtremes', eventArguments, function () { // the default event handler
6727
6728				userMin = newMin;
6729				userMax = newMax;
6730				
6731				// Mark for running afterSetExtremes
6732				axis.isDirtyExtremes = true;
6733				
6734				// redraw
6735				if (redraw) {
6736					chart.redraw(animation);
6737				}
6738			});
6739		}
6740		
6741		/**
6742		 * Update translation information
6743		 */
6744		setAxisTranslation = function () {
6745			var range = max - min,
6746				pointRange = 0,
6747				closestPointRange,
6748				seriesClosestPointRange;
6749			
6750			// adjust translation for padding
6751			if (isXAxis) {
6752				if (isLinked) {
6753					pointRange = linkedParent.pointRange;
6754				} else {
6755					each(axis.series, function (series) {
6756						pointRange = mathMax(pointRange, series.pointRange);
6757						seriesClosestPointRange = series.closestPointRange;
6758						if (!series.noSharedTooltip && defined(seriesClosestPointRange)) {
6759							closestPointRange = defined(closestPointRange) ?
6760								mathMin(closestPointRange, seriesClosestPointRange) :
6761								seriesClosestPointRange;
6762						}
6763					});
6764				}
6765				
6766				// pointRange means the width reserved for each point, like in a column chart
6767				axis.pointRange = pointRange;
6768
6769				// closestPointRange means the closest distance between points. In columns
6770				// it is mostly equal to pointRange, but in lines pointRange is 0 while closestPointRange
6771				// is some other value
6772				axis.closestPointRange = closestPointRange;
6773			}
6774
6775			// secondary values
6776			oldTransA = transA;
6777			axis.translationSlope = transA = axisLength / ((range + pointRange) || 1);
6778			transB = horiz ? axisLeft : axisBottom; // translation addend
6779			minPixelPadding = transA * (pointRange / 2);
6780		};
6781
6782		/**
6783		 * Update the axis metrics
6784		 */
6785		function setAxisSize() {
6786
6787			var offsetLeft = options.offsetLeft || 0,
6788				offsetRight = options.offsetRight || 0;
6789
6790			// basic values
6791			axisLeft = pick(options.left, plotLeft + offsetLeft);
6792			axisTop = pick(options.top, plotTop);
6793			axisWidth = pick(options.width, plotWidth - offsetLeft + offsetRight);
6794			axisHeight = pick(options.height, plotHeight);
6795			axisBottom = chartHeight - axisHeight - axisTop;
6796			axisRight = chartWidth - axisWidth - axisLeft;
6797			axisLength = horiz ? axisWidth : axisHeight;
6798
6799			// expose to use in Series object and navigator
6800			axis.left = axisLeft;
6801			axis.top = axisTop;
6802			axis.len = axisLength;
6803
6804		}
6805
6806		/**
6807		 * Get the actual axis extremes
6808		 */
6809		function getExtremes() {
6810			return {
6811				min: isLog ? correctFloat(lin2log(min)) : min,
6812				max: isLog ? correctFloat(lin2log(max)) : max,
6813				dataMin: dataMin,
6814				dataMax: dataMax,
6815				userMin: userMin,
6816				userMax: userMax
6817			};
6818		}
6819
6820		/**
6821		 * Get the zero plane either based on zero or on the min or max value.
6822		 * Used in bar and area plots
6823		 */
6824		function getThreshold(threshold) {
6825			var realMin = isLog ? lin2log(min) : min,
6826				realMax = isLog ? lin2log(max) : max;
6827			
6828			if (realMin > threshold || threshold === null) {
6829				threshold = realMin;
6830			} else if (realMax < threshold) {
6831				threshold = realMax;
6832			}
6833
6834			return translate(threshold, 0, 1, 0, 1);
6835		}
6836
6837		/**
6838		 * Add a plot band or plot line after render time
6839		 *
6840		 * @param options {Object} The plotBand or plotLine configuration object
6841		 */
6842		function addPlotBandOrLine(options) {
6843			var obj = new PlotLineOrBand(options).render();
6844			plotLinesAndBands.push(obj);
6845			return obj;
6846		}
6847
6848		/**
6849		 * Render the tick labels to a preliminary position to get their sizes
6850		 */
6851		function getOffset() {
6852
6853			var hasData = axis.series.length && defined(min) && defined(max),
6854				showAxis = hasData || pick(options.showEmpty, true),
6855				titleOffset = 0,
6856				titleOffsetOption,
6857				titleMargin = 0,
6858				axisTitleOptions = options.title,
6859				labelOptions = options.labels,
6860				directionFactor = [-1, 1, 1, -1][side],
6861				n;
6862
6863			if (!axisGroup) {
6864				axisGroup = renderer.g('axis')
6865					.attr({ zIndex: 7 })
6866					.add();
6867				gridGroup = renderer.g('grid')
6868					.attr({ zIndex: options.gridZIndex || 1 })
6869					.add();
6870			}
6871
6872			labelOffset = 0; // reset
6873
6874			if (hasData || isLinked) {
6875				each(tickPositions, function (pos) {
6876					if (!ticks[pos]) {
6877						ticks[pos] = new Tick(pos);
6878					} else {
6879						ticks[pos].addLabel(); // update labels depending on tick interval
6880					}
6881
6882				});
6883
6884				each(tickPositions, function (pos) {
6885					// left side must be align: right and right side must have align: left for labels
6886					if (side === 0 || side === 2 || { 1: 'left', 3: 'right' }[side] === labelOptions.align) {
6887
6888						// get the highest offset
6889						labelOffset = mathMax(
6890							ticks[pos].getLabelSize(),
6891							labelOffset
6892						);
6893					}
6894
6895				});
6896
6897				if (staggerLines) {
6898					labelOffset += (staggerLines - 1) * 16;
6899				}
6900
6901			} else { // doesn't have data
6902				for (n in ticks) {
6903					ticks[n].destroy();
6904					delete ticks[n];
6905				}
6906			}
6907
6908			if (axisTitleOptions && axisTitleOptions.text) {
6909				if (!axisTitle) {
6910					axisTitle = axis.axisTitle = renderer.text(
6911						axisTitleOptions.text,
6912						0,
6913						0,
6914						axisTitleOptions.useHTML
6915					)
6916					.attr({
6917						zIndex: 7,
6918						rotation: axisTitleOptions.rotation || 0,
6919						align:
6920							axisTitleOptions.textAlign ||
6921							{ low: 'left', middle: 'center', high: 'right' }[axisTitleOptions.align]
6922					})
6923					.css(axisTitleOptions.style)
6924					.add();
6925					axisTitle.isNew = true;
6926				}
6927
6928				if (showAxis) {
6929					titleOffset = axisTitle.getBBox()[horiz ? 'height' : 'width'];
6930					titleMargin = pick(axisTitleOptions.margin, horiz ? 5 : 10);
6931					titleOffsetOption = axisTitleOptions.offset;
6932				}
6933
6934				// hide or show the title depending on whether showEmpty is set
6935				axisTitle[showAxis ? 'show' : 'hide']();
6936
6937
6938			}
6939
6940			// handle automatic or user set offset
6941			offset = directionFactor * pick(options.offset, axisOffset[side]);
6942
6943			axisTitleMargin =
6944				pick(titleOffsetOption,
6945					labelOffset + titleMargin +
6946					(side !== 2 && labelOffset && directionFactor * options.labels[horiz ? 'y' : 'x'])
6947				);
6948
6949			axisOffset[side] = mathMax(
6950				axisOffset[side],
6951				axisTitleMargin + titleOffset + directionFactor * offset
6952			);
6953
6954		}
6955
6956		/**
6957		 * Render the axis
6958		 */
6959		function render() {
6960			var axisTitleOptions = options.title,
6961				stackLabelOptions = options.stackLabels,
6962				alternateGridColor = options.alternateGridColor,
6963				lineWidth = options.lineWidth,
6964				lineLeft,
6965				lineTop,
6966				linePath,
6967				hasRendered = chart.hasRendered,
6968				slideInTicks = hasRendered && defined(oldMin) && !isNaN(oldMin),
6969				hasData = axis.series.length && defined(min) && defined(max),
6970				showAxis = hasData || pick(options.showEmpty, true),
6971				from,
6972				to;
6973
6974			// If the series has data draw the ticks. Else only the line and title
6975			if (hasData || isLinked) {
6976
6977				// minor ticks
6978				if (minorTickInterval && !categories) {
6979					each(getMinorTickPositions(), function (pos) {
6980						if (!minorTicks[pos]) {
6981							minorTicks[pos] = new Tick(pos, 'minor');
6982						}
6983
6984						// render new ticks in old position
6985						if (slideInTicks && minorTicks[pos].isNew) {
6986							minorTicks[pos].render(null, true);
6987						}
6988
6989
6990						minorTicks[pos].isActive = true;
6991						minorTicks[pos].render();
6992					});
6993				}
6994
6995				// Major ticks. Pull out the first item and render it last so that
6996				// we can get the position of the neighbour label. #808.
6997				each(tickPositions.slice(1).concat([tickPositions[0]]), function (pos, i) {
6998					
6999					// Reorganize the indices
7000					i = (i === tickPositions.length - 1) ? 0 : i + 1;
7001					
7002					// linked axes need an extra check to find out if
7003					if (!isLinked || (pos >= min && pos <= max)) {
7004
7005						if (!ticks[pos]) {
7006							ticks[pos] = new Tick(pos);
7007						}
7008
7009						// render new ticks in old position
7010						if (slideInTicks && ticks[pos].isNew) {
7011							ticks[pos].render(i, true);
7012						}
7013
7014						ticks[pos].isActive = true;
7015						ticks[pos].render(i);
7016					}
7017
7018				});
7019
7020				// alternate grid color
7021				if (alternateGridColor) {
7022					each(tickPositions, function (pos, i) {
7023						if (i % 2 === 0 && pos < max) {
7024							if (!alternateBands[pos]) {
7025								alternateBands[pos] = new PlotLineOrBand();
7026							}
7027							from = pos;
7028							to = tickPositions[i + 1] !== UNDEFINED ? tickPositions[i + 1] : max;
7029							alternateBands[pos].options = {
7030								from: isLog ? lin2log(from) : from,
7031								to: isLog ? lin2log(to) : to,
7032								color: alternateGridColor
7033							};
7034							alternateBands[pos].render();
7035							alternateBands[pos].isActive = true;
7036						}
7037					});
7038				}
7039
7040				// custom plot lines and bands
7041				if (!axis._addedPlotLB) { // only first time
7042					each((options.plotLines || []).concat(options.plotBands || []), function (plotLineOptions) {
7043						//plotLinesAndBands.push(new PlotLineOrBand(plotLineOptions).render());
7044						addPlotBandOrLine(plotLineOptions);
7045					});
7046					axis._addedPlotLB = true;
7047				}
7048
7049
7050
7051			} // end if hasData
7052
7053			// remove inactive ticks
7054			each([ticks, minorTicks, alternateBands], function (coll) {
7055				var pos;
7056				for (pos in coll) {
7057					if (!coll[pos].isActive) {
7058						coll[pos].destroy();
7059						delete coll[pos];
7060					} else {
7061						coll[pos].isActive = false; // reset
7062					}
7063				}
7064			});
7065
7066
7067
7068
7069			// Static items. As the axis group is cleared on subsequent calls
7070			// to render, these items are added outside the group.
7071			// axis line
7072			if (lineWidth) {
7073				lineLeft = axisLeft + (opposite ? axisWidth : 0) + offset;
7074				lineTop = chartHeight - axisBottom - (opposite ? axisHeight : 0) + offset;
7075
7076				linePath = renderer.crispLine([
7077						M,
7078						horiz ?
7079							axisLeft :
7080							lineLeft,
7081						horiz ?
7082							lineTop :
7083							axisTop,
7084						L,
7085						horiz ?
7086							chartWidth - axisRight :
7087							lineLeft,
7088						horiz ?
7089							lineTop :
7090							chartHeight - axisBottom
7091					], lineWidth);
7092				if (!axisLine) {
7093					axisLine = renderer.path(linePath)
7094						.attr({
7095							stroke: options.lineColor,
7096							'stroke-width': lineWidth,
7097							zIndex: 7
7098						})
7099						.add();
7100				} else {
7101					axisLine.animate({ d: linePath });
7102				}
7103
7104				// show or hide the line depending on options.showEmpty
7105				axisLine[showAxis ? 'show' : 'hide']();
7106
7107			}
7108
7109			if (axisTitle && showAxis) {
7110				// compute anchor points for each of the title align options
7111				var margin = horiz ? axisLeft : axisTop,
7112					fontSize = pInt(axisTitleOptions.style.fontSize || 12),
7113				// the position in the length direction of the axis
7114				alongAxis = {
7115					low: margin + (horiz ? 0 : axisLength),
7116					middle: margin + axisLength / 2,
7117					high: margin + (horiz ? axisLength : 0)
7118				}[axisTitleOptions.align],
7119
7120				// the position in the perpendicular direction of the axis
7121				offAxis = (horiz ? axisTop + axisHeight : axisLeft) +
7122					(horiz ? 1 : -1) * // horizontal axis reverses the margin
7123					(opposite ? -1 : 1) * // so does opposite axes
7124					axisTitleMargin +
7125					(side === 2 ? fontSize : 0);
7126
7127				axisTitle[axisTitle.isNew ? 'attr' : 'animate']({
7128					x: horiz ?
7129						alongAxis :
7130						offAxis + (opposite ? axisWidth : 0) + offset +
7131							(axisTitleOptions.x || 0), // x
7132					y: horiz ?
7133						offAxis - (opposite ? axisHeight : 0) + offset :
7134						alongAxis + (axisTitleOptions.y || 0) // y
7135				});
7136				axisTitle.isNew = false;
7137			}
7138
7139			// Stacked totals:
7140			if (stackLabelOptions && stackLabelOptions.enabled) {
7141				var stackKey, oneStack, stackCategory,
7142					stackTotalGroup = axis.stackTotalGroup;
7143
7144				// Create a separate group for the stack total labels
7145				if (!stackTotalGroup) {
7146					axis.stackTotalGroup = stackTotalGroup =
7147						renderer.g('stack-labels')
7148							.attr({
7149								visibility: VISIBLE,
7150								zIndex: 6
7151							})
7152							.translate(plotLeft, plotTop)
7153							.add();
7154				}
7155
7156				// Render each stack total
7157				for (stackKey in stacks) {
7158					oneStack = stacks[stackKey];
7159					for (stackCategory in oneStack) {
7160						oneStack[stackCategory].render(stackTotalGroup);
7161					}
7162				}
7163			}
7164			// End stacked totals
7165
7166			axis.isDirty = false;
7167		}
7168
7169		/**
7170		 * Remove a plot band or plot line from the chart by id
7171		 * @param {Object} id
7172		 */
7173		function removePlotBandOrLine(id) {
7174			var i = plotLinesAndBands.length;
7175			while (i--) {
7176				if (plotLinesAndBands[i].id === id) {
7177					plotLinesAndBands[i].destroy();
7178				}
7179			}
7180		}
7181		
7182		/**
7183		 * Update the axis title by options
7184		 */
7185		function setTitle(newTitleOptions, redraw) {
7186			options.title = merge(options.title, newTitleOptions);
7187			
7188			axisTitle = axisTitle.destroy();
7189			axis.isDirty = true;
7190			
7191			if (pick(redraw, true)) {
7192				chart.redraw();
7193			}
7194		}
7195
7196		/**
7197		 * Redraw the axis to reflect changes in the data or axis extremes
7198		 */
7199		function redraw() {
7200
7201			// hide tooltip and hover states
7202			if (tracker.resetTracker) {
7203				tracker.resetTracker();
7204			}
7205
7206			// render the axis
7207			render();
7208
7209			// move plot lines and bands
7210			each(plotLinesAndBands, function (plotLine) {
7211				plotLine.render();
7212			});
7213
7214			// mark associated series as dirty and ready for redraw
7215			each(axis.series, function (series) {
7216				series.isDirty = true;
7217			});
7218
7219		}
7220
7221		/**
7222		 * Set new axis categories and optionally redraw
7223		 * @param {Array} newCategories
7224		 * @param {Boolean} doRedraw
7225		 */
7226		function setCategories(newCategories, doRedraw) {
7227				// set the categories
7228				axis.categories = userOptions.categories = categories = newCategories;
7229
7230				// force reindexing tooltips
7231				each(axis.series, function (series) {
7232					series.translate();
7233					series.setTooltipPoints(true);
7234				});
7235
7236
7237				// optionally redraw
7238				axis.isDirty = true;
7239
7240				if (pick(doRedraw, true)) {
7241					chart.redraw();
7242				}
7243		}
7244
7245		/**
7246		 * Destroys an Axis instance.
7247		 */
7248		function destroy() {
7249			var stackKey;
7250
7251			// Remove the events
7252			removeEvent(axis);
7253
7254			// Destroy each stack total
7255			for (stackKey in stacks) {
7256				destroyObjectProperties(stacks[stackKey]);
7257
7258				stacks[stackKey] = null;
7259			}
7260
7261			// Destroy stack total group
7262			if (axis.stackTotalGroup) {
7263				axis.stackTotalGroup = axis.stackTotalGroup.destroy();
7264			}
7265
7266			// Destroy collections
7267			each([ticks, minorTicks, alternateBands, plotLinesAndBands], function (coll) {
7268				destroyObjectProperties(coll);
7269			});
7270
7271			// Destroy local variables
7272			each([axisLine, axisGroup, gridGroup, axisTitle], function (obj) {
7273				if (obj) {
7274					obj.destroy();
7275				}
7276			});
7277			axisLine = axisGroup = gridGroup = axisTitle = null;
7278		}
7279
7280
7281		// Run Axis
7282
7283		// Register
7284		axes.push(axis);
7285		chart[isXAxis ? 'xAxis' : 'yAxis'].push(axis);
7286
7287		// inverted charts have reversed xAxes as default
7288		if (inverted && isXAxis && reversed === UNDEFINED) {
7289			reversed = true;
7290		}
7291
7292
7293		// expose some variables
7294		extend(axis, {
7295			addPlotBand: addPlotBandOrLine,
7296			addPlotLine: addPlotBandOrLine,
7297			adjustTickAmount: adjustTickAmount,
7298			categories: categories,
7299			getExtremes: getExtremes,
7300			getPlotLinePath: getPlotLinePath,
7301			getThreshold: getThreshold,
7302			isXAxis: isXAxis,
7303			options: options,
7304			plotLinesAndBands: plotLinesAndBands,
7305			getOffset: getOffset,
7306			render: render,
7307			setAxisSize: setAxisSize,
7308			setAxisTranslation: setAxisTranslation,
7309			setCategories: setCategories,
7310			setExtremes: setExtremes,
7311			setScale: setScale,
7312			setTickPositions: setTickPositions,
7313			translate: translate,
7314			redraw: redraw,
7315			removePlotBand: removePlotBandOrLine,
7316			removePlotLine: removePlotBandOrLine,
7317			reversed: reversed,
7318			setTitle: setTitle,
7319			series: [], // populated by Series
7320			stacks: stacks,
7321			destroy: destroy
7322		});
7323
7324		// register event listeners
7325		for (eventType in events) {
7326			addEvent(axis, eventType, events[eventType]);
7327		}
7328
7329		// extend logarithmic axis
7330		if (isLog) {
7331			axis.val2lin = log2lin;
7332			axis.lin2val = lin2log;
7333		}
7334
7335	} // end Axis
7336
7337
7338	/**
7339	 * The tooltip object
7340	 * @param {Object} options Tooltip options
7341	 */
7342	function Tooltip(options) {
7343		var currentSeries,
7344			borderWidth = options.borderWidth,
7345			crosshairsOptions = options.crosshairs,
7346			crosshairs = [],
7347			style = options.style,
7348			shared = options.shared,
7349			padding = pInt(style.padding),
7350			tooltipIsHidden = true,
7351			currentX = 0,
7352			currentY = 0;
7353
7354		// remove padding CSS and apply padding on box instead
7355		style.padding = 0;
7356
7357		// create the label
7358		var label = renderer.label('', 0, 0, null, null, null, options.useHTML)
7359			.attr({
7360				padding: padding,
7361				fill: options.backgroundColor,
7362				'stroke-width': borderWidth,
7363				r: options.borderRadius,
7364				zIndex: 8
7365			})
7366			.css(style)
7367			.hide()
7368			.add();
7369
7370		// When using canVG the shadow shows up as a gray circle
7371		// even if the tooltip is hidden.
7372		if (!useCanVG) {
7373			label.shadow(options.shadow);
7374		}
7375
7376		/**
7377		 * Destroy the tooltip and its elements.
7378		 */
7379		function destroy() {
7380			each(crosshairs, function (crosshair) {
7381				if (crosshair) {
7382					crosshair.destroy();
7383				}
7384			});
7385
7386			// Destroy and clear local variables
7387			if (label) {
7388				label = label.destroy();
7389			}
7390		}
7391
7392		/**
7393		 * In case no user defined formatter is given, this will be used
7394		 */
7395		function defaultFormatter() {
7396			var pThis = this,
7397				items = pThis.points || splat(pThis),
7398				series = items[0].series,
7399				s;
7400
7401			// build the header
7402			s = [series.tooltipHeaderFormatter(items[0].key)];
7403
7404			// build the values
7405			each(items, function (item) {
7406				series = item.series;
7407				s.push((series.tooltipFormatter && series.tooltipFormatter(item)) ||
7408					item.point.tooltipFormatter(series.tooltipOptions.pointFormat));
7409			});
7410			
7411			// footer
7412			s.push(options.footerFormat || '');
7413			
7414			return s.join('');
7415		}
7416
7417		/**
7418		 * Provide a soft movement for the tooltip
7419		 *
7420		 * @param {Number} finalX
7421		 * @param {Number} finalY
7422		 */
7423		function move(finalX, finalY) {
7424
7425			// get intermediate values for animation
7426			currentX = tooltipIsHidden ? finalX : (2 * currentX + finalX) / 3;
7427			currentY = tooltipIsHidden ? finalY : (currentY + finalY) / 2;
7428
7429			// move to the intermediate value
7430			label.attr({ x: currentX, y: currentY });
7431
7432			// run on next tick of the mouse tracker
7433			if (mathAbs(finalX - currentX) > 1 || mathAbs(finalY - currentY) > 1) {
7434				tooltipTick = function () {
7435					move(finalX, finalY);
7436				};
7437			} else {
7438				tooltipTick = null;
7439			}
7440		}
7441
7442		/**
7443		 * Hide the tooltip
7444		 */
7445		function hide() {
7446			if (!tooltipIsHidden) {
7447				var hoverPoints = chart.hoverPoints;
7448
7449				label.hide();
7450
7451				// hide previous hoverPoints and set new
7452				if (hoverPoints) {
7453					each(hoverPoints, function (point) {
7454						point.setState();
7455					});
7456				}
7457				chart.hoverPoints = null;
7458
7459
7460				tooltipIsHidden = true;
7461			}
7462
7463		}
7464
7465		/**
7466		 * Hide the crosshairs
7467		 */
7468		function hideCrosshairs() {
7469			each(crosshairs, function (crosshair) {
7470				if (crosshair) {
7471					crosshair.hide();
7472				}
7473			});
7474		}
7475
7476		/**
7477		 * Refresh the tooltip's text and position.
7478		 * @param {Object} point
7479		 *
7480		 */
7481		function refresh(point) {
7482			var x,
7483				y,
7484				show,
7485				plotX,
7486				plotY,
7487				textConfig = {},
7488				text,
7489				pointConfig = [],
7490				tooltipPos = point.tooltipPos,
7491				formatter = options.formatter || defaultFormatter,
7492				hoverPoints = chart.hoverPoints,
7493				placedTooltipPoint,
7494				borderColor;
7495
7496			// shared tooltip, array is sent over
7497			if (shared && !(point.series && point.series.noSharedTooltip)) {
7498				plotY = 0;
7499
7500				// hide previous hoverPoints and set new
7501				if (hoverPoints) {
7502					each(hoverPoints, function (point) {
7503						point.setState();
7504					});
7505				}
7506				chart.hoverPoints = point;
7507
7508				each(point, function (item) {
7509					item.setState(HOVER_STATE);
7510					plotY += item.plotY; // for average
7511
7512					pointConfig.push(item.getLabelConfig());
7513				});
7514
7515				plotX = point[0].plotX;
7516				plotY = mathRound(plotY) / point.length; // mathRound because Opera 10 has problems here
7517
7518				textConfig = {
7519					x: point[0].category
7520				};
7521				textConfig.points = pointConfig;
7522				point = point[0];
7523
7524			// single point tooltip
7525			} else {
7526				textConfig = point.getLabelConfig();
7527			}
7528			text = formatter.call(textConfig);
7529
7530			// register the current series
7531			currentSeries = point.series;
7532
7533			// get the reference point coordinates (pie charts use tooltipPos)
7534			plotX = pick(plotX, point.plotX);
7535			plotY = pick(plotY, point.plotY);
7536
7537			x = mathRound(tooltipPos ? tooltipPos[0] : (inverted ? plotWidth - plotY : plotX));
7538			y = mathRound(tooltipPos ? tooltipPos[1] : (inverted ? plotHeight - plotX : plotY));
7539
7540
7541			// For line type series, hide tooltip if the point falls outside the plot
7542			show = shared || !currentSeries.isCartesian || currentSeries.tooltipOutsidePlot || isInsidePlot(x, y);
7543
7544			// update the inner HTML
7545			if (text === false || !show) {
7546				hide();
7547			} else {
7548
7549				// show it
7550				if (tooltipIsHidden) {
7551					label.show();
7552					tooltipIsHidden = false;
7553				}
7554
7555				// update text
7556				label.attr({
7557					text: text
7558				});
7559
7560				// set the stroke color of the box
7561				borderColor = options.borderColor || point.color || currentSeries.color || '#606060';
7562				label.attr({
7563					stroke: borderColor
7564				});
7565
7566				placedTooltipPoint = placeBox(
7567					label.width,
7568					label.height,
7569					plotLeft,
7570					plotTop,
7571					plotWidth,
7572					plotHeight,
7573					{x: x, y: y},
7574					pick(options.distance, 12),
7575					inverted
7576				);
7577
7578				// do the move
7579				move(mathRound(placedTooltipPoint.x), mathRound(placedTooltipPoint.y));
7580			}
7581
7582
7583			// crosshairs
7584			if (crosshairsOptions) {
7585				crosshairsOptions = splat(crosshairsOptions); // [x, y]
7586
7587				var path,
7588					i = crosshairsOptions.length,
7589					attribs,
7590					axis;
7591
7592				while (i--) {
7593					axis = point.series[i ? 'yAxis' : 'xAxis'];
7594					if (crosshairsOptions[i] && axis) {
7595						path = axis.getPlotLinePath(
7596							i ? pick(point.stackY, point.y) : point.x, // #814 
7597							1
7598						);
7599						if (crosshairs[i]) {
7600							crosshairs[i].attr({ d: path, visibility: VISIBLE });
7601
7602						} else {
7603							attribs = {
7604								'stroke-width': crosshairsOptions[i].width || 1,
7605								stroke: crosshairsOptions[i].color || '#C0C0C0',
7606								zIndex: crosshairsOptions[i].zIndex || 2
7607							};
7608							if (crosshairsOptions[i].dashStyle) {
7609								attribs.dashstyle = crosshairsOptions[i].dashStyle;
7610							}
7611							crosshairs[i] = renderer.path(path)
7612								.attr(attribs)
7613								.add();
7614						}
7615					}
7616				}
7617			}
7618			fireEvent(chart, 'tooltipRefresh', {
7619					text: text,
7620					x: x + plotLeft,
7621					y: y + plotTop,
7622					borderColor: borderColor
7623				});
7624		}
7625
7626
7627
7628		// public members
7629		return {
7630			shared: shared,
7631			refresh: refresh,
7632			hide: hide,
7633			hideCrosshairs: hideCrosshairs,
7634			destroy: destroy
7635		};
7636	}
7637
7638	/**
7639	 * The mouse tracker object
7640	 * @param {Object} options
7641	 */
7642	function MouseTracker(options) {
7643
7644
7645		var mouseDownX,
7646			mouseDownY,
7647			hasDragged,
7648			selectionMarker,
7649			zoomType = useCanVG ? '' : optionsChart.zoomType,
7650			zoomX = /x/.test(zoomType),
7651			zoomY = /y/.test(zoomType),
7652			zoomHor = (zoomX && !inverted) || (zoomY && inverted),
7653			zoomVert = (zoomY && !inverted) || (zoomX && inverted);
7654
7655		/**
7656		 * Add crossbrowser support for chartX and chartY
7657		 * @param {Object} e The event object in standard browsers
7658		 */
7659		function normalizeMouseEvent(e) {
7660			var ePos,
7661				chartPosLeft,
7662				chartPosTop,
7663				chartX,
7664				chartY;
7665
7666			// common IE normalizing
7667			e = e || win.event;
7668			if (!e.target) {
7669				e.target = e.srcElement;
7670			}
7671
7672			// jQuery only copies over some properties. IE needs e.x and iOS needs touches.
7673			if (e.originalEvent) {
7674				e = e.originalEvent;
7675			}
7676
7677			// The same for MooTools. It renames e.pageX to e.page.x. #445.
7678			if (e.event) {
7679				e = e.event;
7680			}
7681
7682			// iOS
7683			ePos = e.touches ? e.touches.item(0) : e;
7684
7685			// get mouse position
7686			chartPosition = offset(container);
7687			chartPosLeft = chartPosition.left;
7688			chartPosTop = chartPosition.top;
7689
7690			// chartX and chartY
7691			if (isIE) { // IE including IE9 that has pageX but in a different meaning
7692				chartX = e.x;
7693				chartY = e.y;
7694			} else {
7695				chartX = ePos.pageX - chartPosLeft;
7696				chartY = ePos.pageY - chartPosTop;
7697			}
7698
7699			return extend(e, {
7700				chartX: mathRound(chartX),
7701				chartY: mathRound(chartY)
7702			});
7703		}
7704
7705		/**
7706		 * Get the click position in terms of axis values.
7707		 *
7708		 * @param {Object} e A mouse event
7709		 */
7710		function getMouseCoordinates(e) {
7711			var coordinates = {
7712				xAxis: [],
7713				yAxis: []
7714			};
7715			each(axes, function (axis) {
7716				var translate = axis.translate,
7717					isXAxis = axis.isXAxis,
7718					isHorizontal = inverted ? !isXAxis : isXAxis;
7719
7720				coordinates[isXAxis ? 'xAxis' : 'yAxis'].push({
7721					axis: axis,
7722					value: translate(
7723						isHorizontal ?
7724							e.chartX - plotLeft  :
7725							plotHeight - e.chartY + plotTop,
7726						true
7727					)
7728				});
7729			});
7730			return coordinates;
7731		}
7732
7733		/**
7734		 * With line type charts with a single tracker, get the point closest to the mouse
7735		 */
7736		function onmousemove(e) {
7737			var point,
7738				points,
7739				hoverPoint = chart.hoverPoint,
7740				hoverSeries = chart.hoverSeries,
7741				i,
7742				j,
7743				distance = chartWidth,
7744				index = inverted ? e.chartY : e.chartX - plotLeft; // wtf?
7745
7746			// shared tooltip
7747			if (tooltip && options.shared && !(hoverSeries && hoverSeries.noSharedTooltip)) {
7748				points = [];
7749
7750				// loop over all series and find the ones with points closest to the mouse
7751				i = series.length;
7752				for (j = 0; j < i; j++) {
7753					if (series[j].visible &&
7754							series[j].options.enableMouseTracking !== false &&
7755							!series[j].noSharedTooltip && series[j].tooltipPoints.length) {
7756						point = series[j].tooltipPoints[index];
7757						point._dist = mathAbs(index - point.plotX);
7758						distance = mathMin(distance, point._dist);
7759						points.push(point);
7760					}
7761				}
7762				// remove furthest points
7763				i = points.length;
7764				while (i--) {
7765					if (points[i]._dist > distance) {
7766						points.splice(i, 1);
7767					}
7768				}
7769				// refresh the tooltip if necessary
7770				if (points.length && (points[0].plotX !== hoverX)) {
7771					tooltip.refresh(points);
7772					hoverX = points[0].plotX;
7773				}
7774			}
7775
7776			// separate tooltip and general mouse events
7777			if (hoverSeries && hoverSeries.tracker) { // only use for line-type series with common tracker
7778
7779				// get the point
7780				point = hoverSeries.tooltipPoints[index];
7781
7782				// a new point is hovered, refresh the tooltip
7783				if (point && point !== hoverPoint) {
7784
7785					// trigger the events
7786					point.onMouseOver();
7787
7788				}
7789			}
7790		}
7791
7792
7793
7794		/**
7795		 * Reset the tracking by hiding the tooltip, the hover series state and the hover point
7796		 */
7797		function resetTracker() {
7798			var hoverSeries = chart.hoverSeries,
7799				hoverPoint = chart.hoverPoint;
7800
7801			if (hoverPoint) {
7802				hoverPoint.onMouseOut();
7803			}
7804
7805			if (hoverSeries) {
7806				hoverSeries.onMouseOut();
7807			}
7808
7809			if (tooltip) {
7810				tooltip.hide();
7811				tooltip.hideCrosshairs();
7812			}
7813
7814			hoverX = null;
7815		}
7816
7817		/**
7818		 * Mouse up or outside the plot area
7819		 */
7820		function drop() {
7821			if (selectionMarker) {
7822				var selectionData = {
7823						xAxis: [],
7824						yAxis: []
7825					},
7826					selectionBox = selectionMarker.getBBox(),
7827					selectionLeft = selectionBox.x - plotLeft,
7828					selectionTop = selectionBox.y - plotTop;
7829
7830
7831				// a selection has been made
7832				if (hasDragged) {
7833
7834					// record each axis' min and max
7835					each(axes, function (axis) {
7836						if (axis.options.zoomEnabled !== false) {
7837							var translate = axis.translate,
7838								isXAxis = axis.isXAxis,
7839								isHorizontal = inverted ? !isXAxis : isXAxis,
7840								selectionMin = translate(
7841									isHorizontal ?
7842										selectionLeft :
7843										plotHeight - selectionTop - selectionBox.height,
7844									true,
7845									0,
7846									0,
7847									1
7848								),
7849								selectionMax = translate(
7850									isHorizontal ?
7851										selectionLeft + selectionBox.width :
7852										plotHeight - selectionTop,
7853									true,
7854									0,
7855									0,
7856									1
7857								);
7858
7859								selectionData[isXAxis ? 'xAxis' : 'yAxis'].push({
7860									axis: axis,
7861									min: mathMin(selectionMin, selectionMax), // for reversed axes,
7862									max: mathMax(selectionMin, selectionMax)
7863								});
7864						}
7865					});
7866					fireEvent(chart, 'selection', selectionData, zoom);
7867
7868				}
7869				selectionMarker = selectionMarker.destroy();
7870			}
7871
7872			css(container, { cursor: 'auto' });
7873
7874			chart.mouseIsDown = mouseIsDown = hasDragged = false;
7875			removeEvent(doc, hasTouch ? 'touchend' : 'mouseup', drop);
7876
7877		}
7878
7879		/**
7880		 * Special handler for mouse move that will hide the tooltip when the mouse leaves the plotarea.
7881		 */
7882		function hideTooltipOnMouseMove(e) {
7883			var pageX = defined(e.pageX) ? e.pageX : e.page.x, // In mootools the event is wrapped and the page x/y position is named e.page.x
7884				pageY = defined(e.pageX) ? e.pageY : e.page.y; // Ref: http://mootools.net/docs/core/Types/DOMEvent
7885
7886			if (chartPosition &&
7887					!isInsidePlot(pageX - chartPosition.left - plotLeft,
7888						pageY - chartPosition.top - plotTop)) {
7889				resetTracker();
7890			}
7891		}
7892
7893		/**
7894		 * When mouse leaves the container, hide the tooltip.
7895		 */
7896		function hideTooltipOnMouseLeave() {
7897			resetTracker();
7898			chartPosition = null; // also reset the chart position, used in #149 fix
7899		}
7900
7901		/**
7902		 * Set the JS events on the container element
7903		 */
7904		function setDOMEvents() {
7905			var lastWasOutsidePlot = true;
7906			/*
7907			 * Record the starting position of a dragoperation
7908			 */
7909			container.onmousedown = function (e) {
7910				e = normalizeMouseEvent(e);
7911
7912				// issue #295, dragging not always working in Firefox
7913				if (!hasTouch && e.preventDefault) {
7914					e.preventDefault();
7915				}
7916
7917				// record the start position
7918				chart.mouseIsDown = mouseIsDown = true;
7919				chart.mouseDownX = mouseDownX = e.chartX;
7920				mouseDownY = e.chartY;
7921
7922				addEvent(doc, hasTouch ? 'touchend' : 'mouseup', drop);
7923			};
7924
7925			// The mousemove, touchmove and touchstart event handler
7926			var mouseMove = function (e) {
7927
7928				// let the system handle multitouch operations like two finger scroll
7929				// and pinching
7930				if (e && e.touches && e.touches.length > 1) {
7931					return;
7932				}
7933
7934				// normalize
7935				e = normalizeMouseEvent(e);
7936				if (!hasTouch) { // not for touch devices
7937					e.returnValue = false;
7938				}
7939
7940				var chartX = e.chartX,
7941					chartY = e.chartY,
7942					isOutsidePlot = !isInsidePlot(chartX - plotLeft, chartY - plotTop);
7943
7944				// on touch devices, only trigger click if a handler is defined
7945				if (hasTouch && e.type === 'touchstart') {
7946					if (attr(e.target, 'isTracker')) {
7947						if (!chart.runTrackerClick) {
7948							e.preventDefault();
7949						}
7950					} else if (!runChartClick && !isOutsidePlot) {
7951						e.preventDefault();
7952					}
7953				}
7954
7955				// cancel on mouse outside
7956				if (isOutsidePlot) {
7957
7958					/*if (!lastWasOutsidePlot) {
7959						// reset the tracker
7960						resetTracker();
7961					}*/
7962
7963					// drop the selection if any and reset mouseIsDown and hasDragged
7964					//drop();
7965					if (chartX < plotLeft) {
7966						chartX = plotLeft;
7967					} else if (chartX > plotLeft + plotWidth) {
7968						chartX = plotLeft + plotWidth;
7969					}
7970
7971					if (chartY < plotTop) {
7972						chartY = plotTop;
7973					} else if (chartY > plotTop + plotHeight) {
7974						chartY = plotTop + plotHeight;
7975					}
7976
7977				}
7978
7979				if (mouseIsDown && e.type !== 'touchstart') { // make selection
7980
7981					// determine if the mouse has moved more than 10px
7982					hasDragged = Math.sqrt(
7983						Math.pow(mouseDownX - chartX, 2) +
7984						Math.pow(mouseDownY - chartY, 2)
7985					);
7986					if (hasDragged > 10) {
7987						var clickedInside = isInsidePlot(mouseDownX - plotLeft, mouseDownY - plotTop);
7988
7989						// make a selection
7990						if (hasCartesianSeries && (zoomX || zoomY) && clickedInside) {
7991							if (!selectionMarker) {
7992								selectionMarker = renderer.rect(
7993									plotLeft,
7994									plotTop,
7995									zoomHor ? 1 : plotWidth,
7996									zoomVert ? 1 : plotHeight,
7997									0
7998								)
7999								.attr({
8000									fill: optionsChart.selectionMarkerFill || 'rgba(69,114,167,0.25)',
8001									zIndex: 7
8002								})
8003								.add();
8004							}
8005						}
8006
8007						// adjust the width of the selection marker
8008						if (selectionMarker && zoomHor) {
8009							var xSize = chartX - mouseDownX;
8010							selectionMarker.attr({
8011								width: mathAbs(xSize),
8012								x: (xSize > 0 ? 0 : xSize) + mouseDownX
8013							});
8014						}
8015						// adjust the height of the selection marker
8016						if (selectionMarker && zoomVert) {
8017							var ySize = chartY - mouseDownY;
8018							selectionMarker.attr({
8019								height: mathAbs(ySize),
8020								y: (ySize > 0 ? 0 : ySize) + mouseDownY
8021							});
8022						}
8023
8024						// panning
8025						if (clickedInside && !selectionMarker && optionsChart.panning) {
8026							chart.pan(chartX);
8027						}
8028					}
8029
8030				} else if (!isOutsidePlot) {
8031					// show the tooltip
8032					onmousemove(e);
8033				}
8034
8035				lastWasOutsidePlot = isOutsidePlot;
8036
8037				// when outside plot, allow touch-drag by returning true
8038				return isOutsidePlot || !hasCartesianSeries;
8039			};
8040
8041			/*
8042			 * When the mouse enters the container, run mouseMove
8043			 */
8044			container.onmousemove = mouseMove;
8045
8046			/*
8047			 * When the mouse leaves the container, hide the tracking (tooltip).
8048			 */
8049			addEvent(container, 'mouseleave', hideTooltipOnMouseLeave);
8050
8051			// issue #149 workaround
8052			// The mouseleave event above does not always fire. Whenever the mouse is moving
8053			// outside the plotarea, hide the tooltip
8054			addEvent(doc, 'mousemove', hideTooltipOnMouseMove);
8055
8056			container.ontouchstart = function (e) {
8057				// For touch devices, use touchmove to zoom
8058				if (zoomX || zoomY) {
8059					container.onmousedown(e);
8060				}
8061				// Show tooltip and prevent the lower mouse pseudo event
8062				mouseMove(e);
8063			};
8064
8065			/*
8066			 * Allow dragging the finger over the chart to read the values on touch
8067			 * devices
8068			 */
8069			container.ontouchmove = mouseMove;
8070
8071			/*
8072			 * Allow dragging the finger over the chart to read the values on touch
8073			 * devices
8074			 */
8075			container.ontouchend = function () {
8076				if (hasDragged) {
8077					resetTracker();
8078				}
8079			};
8080
8081
8082			// MooTools 1.2.3 doesn't fire this in IE when using addEvent
8083			container.onclick = function (e) {
8084				var hoverPoint = chart.hoverPoint;
8085				e = normalizeMouseEvent(e);
8086
8087				e.cancelBubble = true; // IE specific
8088
8089
8090				if (!hasDragged) {
8091					
8092					// Detect clicks on trackers or tracker groups, #783 
8093					if (hoverPoint && (attr(e.target, 'isTracker') || attr(e.target.parentNode, 'isTracker'))) {
8094						var plotX = hoverPoint.plotX,
8095							plotY = hoverPoint.plotY;
8096
8097						// add page position info
8098						extend(hoverPoint, {
8099							pageX: chartPosition.left + plotLeft +
8100								(inverted ? plotWidth - plotY : plotX),
8101							pageY: chartPosition.top + plotTop +
8102								(inverted ? plotHeight - plotX : plotY)
8103						});
8104
8105						// the series click event
8106						fireEvent(hoverPoint.series, 'click', extend(e, {
8107							point: hoverPoint
8108						}));
8109
8110						// the point click event
8111						hoverPoint.firePointEvent('click', e);
8112
8113					} else {
8114						extend(e, getMouseCoordinates(e));
8115
8116						// fire a click event in the chart
8117						if (isInsidePlot(e.chartX - plotLeft, e.chartY - plotTop)) {
8118							fireEvent(chart, 'click', e);
8119						}
8120					}
8121
8122
8123				}
8124				// reset mouseIsDown and hasDragged
8125				hasDragged = false;
8126			};
8127
8128		}
8129
8130		/**
8131		 * Destroys the MouseTracker object and disconnects DOM events.
8132		 */
8133		function destroy() {
8134			// Destroy the tracker group element
8135			if (chart.trackerGroup) {
8136				chart.trackerGroup = trackerGroup = chart.trackerGroup.destroy();
8137			}
8138
8139			removeEvent(container, 'mouseleave', hideTooltipOnMouseLeave);
8140			removeEvent(doc, 'mousemove', hideTooltipOnMouseMove);
8141			container.onclick = container.onmousedown = container.onmousemove = container.ontouchstart = container.ontouchend = container.ontouchmove = null;
8142		}
8143
8144		
8145		// Run MouseTracker
8146		
8147		if (!trackerGroup) {
8148			chart.trackerGroup = trackerGroup = renderer.g('tracker')
8149				.attr({ zIndex: 9 })
8150				.add();
8151		}
8152		
8153		if (options.enabled) {
8154			chart.tooltip = tooltip = Tooltip(options);
8155
8156			// set the fixed interval ticking for the smooth tooltip
8157			tooltipInterval = setInterval(function () {
8158				if (tooltipTick) {
8159					tooltipTick();
8160				}
8161			}, 32);
8162		}
8163
8164		setDOMEvents();
8165
8166		// expose properties
8167		extend(this, {
8168			zoomX: zoomX,
8169			zoomY: zoomY,
8170			resetTracker: resetTracker,
8171			normalizeMouseEvent: normalizeMouseEvent,
8172			destroy: destroy
8173		});
8174	}
8175
8176
8177
8178	/**
8179	 * The overview of the chart's series
8180	 */
8181	var Legend = function () {
8182
8183		var options = chart.options.legend;
8184
8185		if (!options.enabled) {
8186			return;
8187		}
8188
8189		var horizontal = options.layout === 'horizontal',
8190			symbolWidth = options.symbolWidth,
8191			symbolPadding = options.symbolPadding,
8192			allItems,
8193			style = options.style,
8194			itemStyle = options.itemStyle,
8195			itemHoverStyle = options.itemHoverStyle,
8196			itemHiddenStyle = merge(itemStyle, options.itemHiddenStyle),
8197			padding = options.padding || pInt(style.padding),
8198			ltr = !options.rtl,
8199			itemMarginTop = options.itemMarginTop || 0,
8200			itemMarginBottom = options.itemMarginBottom || 0,
8201			y = 18,
8202			maxItemWidth = 0,
8203			initialItemX = 4 + padding + symbolWidth + symbolPadding,
8204			initialItemY = padding + itemMarginTop + y - 5, // 5 is the number of pixels above the text
8205			itemX,
8206			itemY,
8207			lastItemY,
8208			itemHeight = 0,
8209			box,
8210			legendBorderWidth = options.borderWidth,
8211			legendBackgroundColor = options.backgroundColor,
8212			legendGroup,
8213			offsetWidth,
8214			widthOption = options.width,
8215			series = chart.series,
8216			reversedLegend = options.reversed;
8217
8218
8219
8220		/**
8221		 * Set the colors for the legend item
8222		 * @param {Object} item A Series or Point instance
8223		 * @param {Object} visible Dimmed or colored
8224		 */
8225		function colorizeItem(item, visible) {
8226			var legendItem = item.legendItem,
8227				legendLine = item.legendLine,
8228				legendSymbol = item.legendSymbol,
8229				hiddenColor = itemHiddenStyle.color,
8230				textColor = visible ? options.itemStyle.color : hiddenColor,
8231				symbolColor = visible ? item.color : hiddenColor;
8232
8233			if (legendItem) {
8234				legendItem.css({ fill: textColor });
8235			}
8236			if (legendLine) {
8237				legendLine.attr({ stroke: symbolColor });
8238			}
8239			if (legendSymbol) {
8240				legendSymbol.attr({
8241					stroke: symbolColor,
8242					fill: symbolColor
8243				});
8244			}
8245		}
8246
8247		/**
8248		 * Position the legend item
8249		 * @param {Object} item A Series or Point instance
8250		 * @param {Object} visible Dimmed or colored
8251		 */
8252		function positionItem(item) {
8253			var legendItem = item.legendItem,
8254				legendLine = item.legendLine,
8255				legendItemPos = item._legendItemPos,
8256				itemX = legendItemPos[0],
8257				itemY = legendItemPos[1],
8258				legendSymbol = item.legendSymbol,
8259				symbolX,
8260				checkbox = item.checkbox;
8261			
8262			if (legendItem) {
8263				legendItem.attr({
8264					x: ltr ? itemX : legendWidth - itemX,
8265					y: itemY
8266				});
8267			}
8268			if (legendLine) {
8269				legendLine.translate(
8270					ltr ? itemX : legendWidth - itemX,
8271					itemY - 4
8272				);
8273			}
8274			if (legendSymbol) {
8275				symbolX = itemX + legendSymbol.xOff;
8276				legendSymbol.attr({
8277					x: ltr ? symbolX : legendWidth - symbolX,
8278					y: itemY + legendSymbol.yOff
8279				});
8280			}
8281			if (checkbox) {
8282				checkbox.x = itemX;
8283				checkbox.y = itemY;
8284			}
8285		}
8286
8287		/**
8288		 * Destroy a single legend item
8289		 * @param {Object} item The series or point
8290		 */
8291		function destroyItem(item) {
8292			var checkbox = item.checkbox;
8293
8294			// destroy SVG elements
8295			each(['legendItem', 'legendLine', 'legendSymbol'], function (key) {
8296				if (item[key]) {
8297					item[key].destroy();
8298				}
8299			});
8300
8301			if (checkbox) {
8302				discardElement(item.checkbox);
8303			}
8304
8305
8306		}
8307
8308		/**
8309		 * Destroys the legend.
8310		 */
8311		function destroy() {
8312			if (box) {
8313				box = box.destroy();
8314			}
8315
8316			if (legendGroup) {
8317				legendGroup = legendGroup.destroy();
8318			}
8319		}
8320
8321		/**
8322		 * Position the checkboxes after the width is determined
8323		 */
8324		function positionCheckboxes() {
8325			each(allItems, function (item) {
8326				var checkbox = item.checkbox,
8327					alignAttr = legendGroup.alignAttr;
8328				if (checkbox) {
8329					css(checkbox, {
8330						left: (alignAttr.translateX + item.legendItemWidth + checkbox.x - 40) + PX,
8331						top: (alignAttr.translateY + checkbox.y - 11) + PX
8332					});
8333				}
8334			});
8335		}
8336
8337		/**
8338		 * Render a single specific legend item
8339		 * @param {Object} item A series or point
8340		 */
8341		function renderItem(item) {
8342			var bBox,
8343				itemWidth,
8344				legendSymbol,
8345				symbolX,
8346				symbolY,
8347				simpleSymbol,
8348				radius,
8349				li = item.legendItem,
8350				series = item.series || item,
8351				itemOptions = series.options,
8352				strokeWidth = (itemOptions && itemOptions.borderWidth) || 0;
8353
8354
8355			if (!li) { // generate it once, later move it
8356
8357				// let these series types use a simple symbol
8358				simpleSymbol = /^(bar|pie|area|column)$/.test(series.type);
8359
8360				// generate the list item text
8361				item.legendItem = li = renderer.text(
8362						options.labelFormatter.call(item),
8363						0,
8364						0,
8365						options.useHTML
8366					)
8367					.css(item.visible ? itemStyle : itemHiddenStyle)
8368					.on('mouseover', function () {
8369						item.setState(HOVER_STATE);
8370						li.css(itemHoverStyle);
8371					})
8372					.on('mouseout', function () {
8373						li.css(item.visible ? itemStyle : itemHiddenStyle);
8374						item.setState();
8375					})
8376					.on('click', function () {
8377						var strLegendItemClick = 'legendItemClick',
8378							fnLegendItemClick = function () {
8379								item.setVisible();
8380							};
8381
8382						// click the name or symbol
8383						if (item.firePointEvent) { // point
8384							item.firePointEvent(strLegendItemClick, null, fnLegendItemClick);
8385						} else {
8386							fireEvent(item, strLegendItemClick, null, fnLegendItemClick);
8387						}
8388					})
8389					.attr({
8390						align: ltr ? 'left' : 'right',
8391						zIndex: 2
8392					})
8393					.add(legendGroup);
8394
8395				// draw the line
8396				if (!simpleSymbol && itemOptions && itemOptions.lineWidth) {
8397					var attrs = {
8398							'stroke-width': itemOptions.lineWidth,
8399							zIndex: 2
8400						};
8401					if (itemOptions.dashStyle) {
8402						attrs.dashstyle = itemOptions.dashStyle;
8403					}
8404					item.legendLine = renderer.path([
8405						M,
8406						(-symbolWidth - symbolPadding) * (ltr ? 1 : -1),
8407						0,
8408						L,
8409						(-symbolPadding) * (ltr ? 1 : -1),
8410						0
8411					])
8412					.attr(attrs)
8413					.add(legendGroup);
8414				}
8415
8416				// draw a simple symbol
8417				if (simpleSymbol) { // bar|pie|area|column
8418
8419					legendSymbol = renderer.rect(
8420						(symbolX = -symbolWidth - symbolPadding),
8421						(symbolY = -11),
8422						symbolWidth,
8423						12,
8424						2
8425					).attr({
8426						//'stroke-width': 0,
8427						zIndex: 3
8428					}).add(legendGroup);
8429					
8430					if (!ltr) {
8431						symbolX += symbolWidth;
8432					}
8433					
8434				} else if (itemOptions && itemOptions.marker && itemOptions.marker.enabled) { // draw the marker
8435					radius = itemOptions.marker.radius;
8436					legendSymbol = renderer.symbol(
8437						item.symbol,
8438						(symbolX = -symbolWidth / 2 - symbolPadding - radius),
8439						(symbolY = -4 - radius),
8440						2 * radius,
8441						2 * radius
8442					)
8443					.attr(item.pointAttr[NORMAL_STATE])
8444					.attr({ zIndex: 3 })
8445					.add(legendGroup);
8446					
8447					if (!ltr) {
8448						symbolX += symbolWidth / 2;
8449					}
8450
8451				}
8452				if (legendSymbol) {
8453					
8454					legendSymbol.xOff = symbolX + (strokeWidth % 2 / 2);
8455					legendSymbol.yOff = symbolY + (strokeWidth % 2 / 2);
8456				}
8457
8458				item.legendSymbol = legendSymbol;
8459
8460				// colorize the items
8461				colorizeItem(item, item.visible);
8462
8463
8464				// add the HTML checkbox on top
8465				if (itemOptions && itemOptions.showCheckbox) {
8466					item.checkbox = createElement('input', {
8467						type: 'checkbox',
8468						checked: item.selected,
8469						defaultChecked: item.selected // required by IE7
8470					}, options.itemCheckboxStyle, container);
8471
8472					addEvent(item.checkbox, 'click', function (event) {
8473						var target = event.target;
8474						fireEvent(item, 'checkboxClick', {
8475								checked: target.checked
8476							},
8477							function () {
8478								item.select();
8479							}
8480						);
8481					});
8482				}
8483			}
8484
8485
8486			// calculate the positions for the next line
8487			bBox = li.getBBox();
8488
8489			itemWidth = item.legendItemWidth =
8490				options.itemWidth || symbolWidth + symbolPadding + bBox.width + padding;
8491			itemHeight = bBox.height;
8492
8493			// if the item exceeds the width, start a new line
8494			if (horizontal && itemX - initialItemX + itemWidth >
8495					(widthOption || (chartWidth - 2 * padding - initialItemX))) {
8496				itemX = initialItemX;
8497				itemY += itemMarginTop + itemHeight + itemMarginBottom;
8498			}
8499			
8500			// If the item exceeds the height, start a new column
8501			if (!horizontal && itemY + options.y + itemHeight > chartHeight - spacingTop - spacingBottom) {
8502				itemY = initialItemY;
8503				itemX += maxItemWidth;
8504				maxItemWidth = 0;
8505			}
8506
8507			// Set the edge positions
8508			maxItemWidth = mathMax(maxItemWidth, itemWidth);
8509			lastItemY = mathMax(lastItemY, itemY + itemMarginBottom);
8510			
8511			// cache the position of the newly generated or reordered items
8512			item._legendItemPos = [itemX, itemY];
8513
8514			// advance
8515			if (horizontal) {
8516				itemX += itemWidth;
8517			} else {
8518				itemY += itemMarginTop + itemHeight + itemMarginBottom;
8519			}
8520
8521			// the width of the widest item
8522			offsetWidth = widthOption || mathMax(
8523				(itemX - initialItemX) + (horizontal ? 0 : itemWidth),
8524				offsetWidth
8525			);
8526
8527		}
8528
8529		/**
8530		 * Render the legend. This method can be called both before and after
8531		 * chart.render. If called after, it will only rearrange items instead
8532		 * of creating new ones.
8533		 */
8534		function renderLegend() {
8535			itemX = initialItemX;
8536			itemY = initialItemY;
8537			offsetWidth = 0;
8538			lastItemY = 0;
8539
8540			if (!legendGroup) {
8541				legendGroup = renderer.g('legend')
8542					// #414, #759. Trackers will be drawn above the legend, but we have 
8543					// to sacrifice that because tooltips need to be above the legend
8544					// and trackers above tooltips
8545					.attr({ zIndex: 7 }) 
8546					.add();
8547			}
8548
8549
8550			// add each series or point
8551			allItems = [];
8552			each(series, function (serie) {
8553				var seriesOptions = serie.options;
8554
8555				if (!seriesOptions.showInLegend) {
8556					return;
8557				}
8558
8559				// use points or series for the legend item depending on legendType
8560				allItems = allItems.concat(
8561						serie.legendItems ||
8562						(seriesOptions.legendType === 'point' ?
8563								serie.data :
8564								serie)
8565				);
8566
8567			});
8568
8569			// sort by legendIndex
8570			stableSort(allItems, function (a, b) {
8571				return (a.options.legendIndex || 0) - (b.options.legendIndex || 0);
8572			});
8573
8574			// reversed legend
8575			if (reversedLegend) {
8576				allItems.reverse();
8577			}
8578
8579			// render the items
8580			each(allItems, renderItem);
8581
8582
8583			// Draw the border
8584			legendWidth = widthOption || offsetWidth;
8585			legendHeight = lastItemY - y + itemHeight;
8586
8587			if (legendBorderWidth || legendBackgroundColor) {
8588				legendWidth += 2 * padding;
8589				legendHeight += 2 * padding;
8590
8591				if (!box) {
8592					box = renderer.rect(
8593						0,
8594						0,
8595						legendWidth,
8596						legendHeight,
8597						options.borderRadius,
8598						legendBorderWidth || 0
8599					).attr({
8600						stroke: options.borderColor,
8601						'stroke-width': legendBorderWidth || 0,
8602						fill: legendBackgroundColor || NONE
8603					})
8604					.add(legendGroup)
8605					.shadow(options.shadow);
8606					box.isNew = true;
8607
8608				} else if (legendWidth > 0 && legendHeight > 0) {
8609					box[box.isNew ? 'attr' : 'animate'](
8610						box.crisp(null, null, null, legendWidth, legendHeight)
8611					);
8612					box.isNew = false;
8613				}
8614
8615				// hide the border if no items
8616				box[allItems.length ? 'show' : 'hide']();
8617			}
8618			
8619			// Now that the legend width and height are extablished, put the items in the 
8620			// final position
8621			each(allItems, positionItem);
8622
8623			// 1.x compatibility: positioning based on style
8624			var props = ['left', 'right', 'top', 'bottom'],
8625				prop,
8626				i = 4;
8627			while (i--) {
8628				prop = props[i];
8629				if (style[prop] && style[prop] !== 'auto') {
8630					options[i < 2 ? 'align' : 'verticalAlign'] = prop;
8631					options[i < 2 ? 'x' : 'y'] = pInt(style[prop]) * (i % 2 ? -1 : 1);
8632				}
8633			}
8634
8635			if (allItems.length) {
8636				legendGroup.align(extend(options, {
8637					width: legendWidth,
8638					height: legendHeight
8639				}), true, spacingBox);
8640			}
8641
8642			if (!isResizing) {
8643				positionCheckboxes();
8644			}
8645		}
8646
8647
8648		// run legend
8649		renderLegend();
8650
8651		// move checkboxes
8652		addEvent(chart, 'endResize', positionCheckboxes);
8653
8654		// expose
8655		return {
8656			colorizeItem: colorizeItem,
8657			destroyItem: destroyItem,
8658			renderLegend: renderLegend,
8659			destroy: destroy
8660		};
8661	};
8662
8663
8664
8665
8666
8667
8668	/**
8669	 * Initialize an individual series, called internally before render time
8670	 */
8671	function initSeries(options) {
8672		var type = options.type || optionsChart.type || optionsChart.defaultSeriesType,
8673			typeClass = seriesTypes[type],
8674			serie,
8675			hasRendered = chart.hasRendered;
8676
8677		// an inverted chart can't take a column series and vice versa
8678		if (hasRendered) {
8679			if (inverted && type === 'column') {
8680				typeClass = seriesTypes.bar;
8681			} else if (!inverted && type === 'bar') {
8682				typeClass = seriesTypes.column;
8683			}
8684		}
8685
8686		serie = new typeClass();
8687
8688		serie.init(chart, options);
8689
8690		// set internal chart properties
8691		if (!hasRendered && serie.inverted) {
8692			inverted = true;
8693		}
8694		if (serie.isCartesian) {
8695			hasCartesianSeries = serie.isCartesian;
8696		}
8697
8698		series.push(serie);
8699
8700		return serie;
8701	}
8702
8703	/**
8704	 * Add a series dynamically after  time
8705	 *
8706	 * @param {Object} options The config options
8707	 * @param {Boolean} redraw Whether to redraw the chart after adding. Defaults to true.
8708	 * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
8709	 *    configuration
8710	 *
8711	 * @return {Object} series The newly created series object
8712	 */
8713	function addSeries(options, redraw, animation) {
8714		var series;
8715
8716		if (options) {
8717			setAnimation(animation, chart);
8718			redraw = pick(redraw, true); // defaults to true
8719
8720			fireEvent(chart, 'addSeries', { options: options }, function () {
8721				series = initSeries(options);
8722				series.isDirty = true;
8723
8724				chart.isDirtyLegend = true; // the series array is out of sync with the display
8725				if (redraw) {
8726					chart.redraw();
8727				}
8728			});
8729		}
8730
8731		return series;
8732	}
8733
8734	/**
8735	 * Check whether a given point is within the plot area
8736	 *
8737	 * @param {Number} x Pixel x relative to the plot area
8738	 * @param {Number} y Pixel y relative to the plot area
8739	 */
8740	isInsidePlot = function (x, y) {
8741		return x >= 0 &&
8742			x <= plotWidth &&
8743			y >= 0 &&
8744			y <= plotHeight;
8745	};
8746
8747	/**
8748	 * Adjust all axes tick amounts
8749	 */
8750	function adjustTickAmounts() {
8751		if (optionsChart.alignTicks !== false) {
8752			each(axes, function (axis) {
8753				axis.adjustTickAmount();
8754			});
8755		}
8756		maxTicks = null;
8757	}
8758
8759	/**
8760	 * Redraw legend, axes or series based on updated data
8761	 *
8762	 * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
8763	 *    configuration
8764	 */
8765	function redraw(animation) {
8766		var redrawLegend = chart.isDirtyLegend,
8767			hasStackedSeries,
8768			isDirtyBox = chart.isDirtyBox, // todo: check if it has actually changed?
8769			seriesLength = series.length,
8770			i = seriesLength,
8771			clipRect = chart.clipRect,
8772			serie;
8773
8774		setAnimation(animation, chart);
8775
8776		// link stacked series
8777		while (i--) {
8778			serie = series[i];
8779			if (serie.isDirty && serie.options.stacking) {
8780				hasStackedSeries = true;
8781				break;
8782			}
8783		}
8784		if (hasStackedSeries) { // mark others as dirty
8785			i = seriesLength;
8786			while (i--) {
8787				serie = series[i];
8788				if (serie.options.stacking) {
8789					serie.isDirty = true;
8790				}
8791			}
8792		}
8793
8794		// handle updated data in the series
8795		each(series, function (serie) {
8796			if (serie.isDirty) { // prepare the data so axis can read it
8797				if (serie.options.legendType === 'point') {
8798					redrawLegend = true;
8799				}
8800			}
8801		});
8802
8803		// handle added or removed series
8804		if (redrawLegend && legend.renderLegend) { // series or pie points are added or removed
8805			// draw legend graphics
8806			legend.renderLegend();
8807
8808			chart.isDirtyLegend = false;
8809		}
8810
8811
8812		if (hasCartesianSeries) {
8813			if (!isResizing) {
8814
8815				// reset maxTicks
8816				maxTicks = null;
8817
8818				// set axes scales
8819				each(axes, function (axis) {
8820					axis.setScale();
8821				});
8822			}
8823			adjustTickAmounts();
8824			getMargins();
8825
8826			// redraw axes
8827			each(axes, function (axis) {
8828				
8829				// Fire 'afterSetExtremes' only if extremes are set
8830				if (axis.isDirtyExtremes) { // #821
8831					axis.isDirtyExtremes = false;
8832					fireEvent(axis, 'afterSetExtremes', axis.getExtremes()); // #747, #751
8833				}
8834								
8835				if (axis.isDirty || isDirtyBox) {					
8836					axis.redraw();
8837					isDirtyBox = true; // #792
8838				}
8839			});
8840
8841
8842		}
8843
8844		// the plot areas size has changed
8845		if (isDirtyBox) {
8846			drawChartBox();
8847
8848			// move clip rect
8849			if (clipRect) {
8850				stop(clipRect);
8851				clipRect.animate({ // for chart resize
8852					width: chart.plotSizeX,
8853					height: chart.plotSizeY + 1
8854				});
8855			}
8856
8857		}
8858
8859
8860		// redraw affected series
8861		each(series, function (serie) {
8862			if (serie.isDirty && serie.visible &&
8863					(!serie.isCartesian || serie.xAxis)) { // issue #153
8864				serie.redraw();
8865			}
8866		});
8867
8868
8869		// hide tooltip and hover states
8870		if (tracker && tracker.resetTracker) {
8871			tracker.resetTracker();
8872		}
8873
8874		// redraw if canvas
8875		renderer.draw();
8876
8877		// fire the event
8878		fireEvent(chart, 'redraw'); // jQuery breaks this when calling it from addEvent. Overwrites chart.redraw
8879	}
8880
8881
8882
8883	/**
8884	 * Dim the chart and show a loading text or symbol
8885	 * @param {String} str An optional text to show in the loading label instead of the default one
8886	 */
8887	function showLoading(str) {
8888		var loadingOptions = options.loading;
8889
8890		// create the layer at the first call
8891		if (!loadingDiv) {
8892			loadingDiv = createElement(DIV, {
8893				className: PREFIX + 'loading'
8894			}, extend(loadingOptions.style, {
8895				left: plotLeft + PX,
8896				top: plotTop + PX,
8897				width: plotWidth + PX,
8898				height: plotHeight + PX,
8899				zIndex: 10,
8900				display: NONE
8901			}), container);
8902
8903			loadingSpan = createElement(
8904				'span',
8905				null,
8906				loadingOptions.labelStyle,
8907				loadingDiv
8908			);
8909
8910		}
8911
8912		// update text
8913		loadingSpan.innerHTML = str || options.lang.loading;
8914
8915		// show it
8916		if (!loadingShown) {
8917			css(loadingDiv, { opacity: 0, display: '' });
8918			animate(loadingDiv, {
8919				opacity: loadingOptions.style.opacity
8920			}, {
8921				duration: loadingOptions.showDuration || 0
8922			});
8923			loadingShown = true;
8924		}
8925	}
8926	/**
8927	 * Hide the loading layer
8928	 */
8929	function hideLoading() {
8930		if (loadingDiv) {
8931			animate(loadingDiv, {
8932				opacity: 0
8933			}, {
8934				duration: options.loading.hideDuration || 100,
8935				complete: function () {
8936					css(loadingDiv, { display: NONE });
8937				}
8938			});
8939		}
8940		loadingShown = false;
8941	}
8942
8943	/**
8944	 * Get an axis, series or point object by id.
8945	 * @param id {String} The id as given in the configuration options
8946	 */
8947	function get(id) {
8948		var i,
8949			j,
8950			points;
8951
8952		// search axes
8953		for (i = 0; i < axes.length; i++) {
8954			if (axes[i].options.id === id) {
8955				return axes[i];
8956			}
8957		}
8958
8959		// search series
8960		for (i = 0; i < series.length; i++) {
8961			if (series[i].options.id === id) {
8962				return series[i];
8963			}
8964		}
8965
8966		// search points
8967		for (i = 0; i < series.length; i++) {
8968			points = series[i].points || [];
8969			for (j = 0; j < points.length; j++) {
8970				if (points[j].id === id) {
8971					return points[j];
8972				}
8973			}
8974		}
8975		return null;
8976	}
8977
8978	/**
8979	 * Create the Axis instances based on the config options
8980	 */
8981	function getAxes() {
8982		var xAxisOptions = options.xAxis || {},
8983			yAxisOptions = options.yAxis || {},
8984			optionsArray,
8985			axis;
8986
8987		// make sure the options are arrays and add some members
8988		xAxisOptions = splat(xAxisOptions);
8989		each(xAxisOptions, function (axis, i) {
8990			axis.index = i;
8991			axis.isX = true;
8992		});
8993
8994		yAxisOptions = splat(yAxisOptions);
8995		each(yAxisOptions, function (axis, i) {
8996			axis.index = i;
8997		});
8998
8999		// concatenate all axis options into one array
9000		optionsArray = xAxisOptions.concat(yAxisOptions);
9001
9002		each(optionsArray, function (axisOptions) {
9003			axis = new Axis(axisOptions);
9004		});
9005
9006		adjustTickAmounts();
9007	}
9008
9009
9010	/**
9011	 * Get the currently selected points from all series
9012	 */
9013	function getSelectedPoints() {
9014		var points = [];
9015		each(series, function (serie) {
9016			points = points.concat(grep(serie.points, function (point) {
9017				return point.selected;
9018			}));
9019		});
9020		return points;
9021	}
9022
9023	/**
9024	 * Get the currently selected series
9025	 */
9026	function getSelectedSeries() {
9027		return grep(series, function (serie) {
9028			return serie.selected;
9029		});
9030	}
9031
9032	/**
9033	 * Display the zoom button
9034	 */
9035	function showResetZoom() {
9036		var lang = defaultOptions.lang,
9037			btnOptions = optionsChart.resetZoomButton,
9038			theme = btnOptions.theme,
9039			states = theme.states,
9040			box = btnOptions.relativeTo === 'chart' ? null : {
9041				x: plotLeft,
9042				y: plotTop,
9043				width: plotWidth,
9044				height: plotHeight
9045			};
9046		chart.resetZoomButton = renderer.button(lang.resetZoom, null, null, zoomOut, theme, states && states.hover)
9047			.attr({
9048				align: btnOptions.position.align,
9049				title: lang.resetZoomTitle
9050			})
9051			.add()
9052			.align(btnOptions.position, false, box);
9053	}
9054
9055	/**
9056	 * Zoom out to 1:1
9057	 */
9058	zoomOut = function () {
9059		var resetZoomButton = chart.resetZoomButton;
9060
9061		fireEvent(chart, 'selection', { resetSelection: true }, zoom);
9062		if (resetZoomButton) {
9063			chart.resetZoomButton = resetZoomButton.destroy();
9064		}
9065	};
9066	/**
9067	 * Zoom into a given portion of the chart given by axis coordinates
9068	 * @param {Object} event
9069	 */
9070	zoom = function (event) {
9071
9072		// add button to reset selection
9073		var hasZoomed;
9074
9075		if (chart.resetZoomEnabled !== false && !chart.resetZoomButton) { // hook for Stock charts etc.
9076			showResetZoom();
9077		}
9078
9079		// if zoom is called with no arguments, reset the axes
9080		if (!event || event.resetSelection) {
9081			each(axes, function (axis) {
9082				if (axis.options.zoomEnabled !== false) {
9083					axis.setExtremes(null, null, false);
9084					hasZoomed = true;
9085				}
9086			});
9087		} else { // else, zoom in on all axes
9088			each(event.xAxis.concat(event.yAxis), function (axisData) {
9089				var axis = axisData.axis;
9090
9091				// don't zoom more than minRange
9092				if (chart.tracker[axis.isXAxis ? 'zoomX' : 'zoomY']) {
9093					axis.setExtremes(axisData.min, axisData.max, false);
9094					hasZoomed = true;
9095				}
9096			});
9097		}
9098
9099		// Redraw
9100		if (hasZoomed) {
9101			redraw( 
9102				pick(optionsChart.animation, chart.pointCount < 100) // animation
9103			);
9104		}
9105	};
9106
9107	/**
9108	 * Pan the chart by dragging the mouse across the pane. This function is called
9109	 * on mouse move, and the distance to pan is computed from chartX compared to
9110	 * the first chartX position in the dragging operation.
9111	 */
9112	chart.pan = function (chartX) {
9113
9114		var xAxis = chart.xAxis[0],
9115			mouseDownX = chart.mouseDownX,
9116			halfPointRange = xAxis.pointRange / 2,
9117			extremes = xAxis.getExtremes(),
9118			newMin = xAxis.translate(mouseDownX - chartX, true) + halfPointRange,
9119			newMax = xAxis.translate(mouseDownX + plotWidth - chartX, true) - halfPointRange,
9120			hoverPoints = chart.hoverPoints;
9121
9122		// remove active points for shared tooltip
9123		if (hoverPoints) {
9124			each(hoverPoints, function (point) {
9125				point.setState();
9126			});
9127		}
9128
9129		if (newMin > mathMin(extremes.dataMin, extremes.min) && newMax < mathMax(extremes.dataMax, extremes.max)) {
9130			xAxis.setExtremes(newMin, newMax, true, false);
9131		}
9132
9133		chart.mouseDownX = chartX; // set new reference for next run
9134		css(container, { cursor: 'move' });
9135	};
9136
9137	/**
9138	 * Show the title and subtitle of the chart
9139	 *
9140	 * @param titleOptions {Object} New title options
9141	 * @param subtitleOptions {Object} New subtitle options
9142	 *
9143	 */
9144	function setTitle(titleOptions, subtitleOptions) {
9145
9146		chartTitleOptions = merge(options.title, titleOptions);
9147		chartSubtitleOptions = merge(options.subtitle, subtitleOptions);
9148
9149		// add title and subtitle
9150		each([
9151			['title', titleOptions, chartTitleOptions],
9152			['subtitle', subtitleOptions, chartSubtitleOptions]
9153		], function (arr) {
9154			var name = arr[0],
9155				title = chart[name],
9156				titleOptions = arr[1],
9157				chartTitleOptions = arr[2];
9158
9159			if (title && titleOptions) {
9160				title = title.destroy(); // remove old
9161			}
9162			if (chartTitleOptions && chartTitleOptions.text && !title) {
9163				chart[name] = renderer.text(
9164					chartTitleOptions.text,
9165					0,
9166					0,
9167					chartTitleOptions.useHTML
9168				)
9169				.attr({
9170					align: chartTitleOptions.align,
9171					'class': PREFIX + name,
9172					zIndex: chartTitleOptions.zIndex || 4
9173				})
9174				.css(chartTitleOptions.style)
9175				.add()
9176				.align(chartTitleOptions, false, spacingBox);
9177			}
9178		});
9179
9180	}
9181
9182	/**
9183	 * Get chart width and height according to options and container size
9184	 */
9185	function getChartSize() {
9186
9187		containerWidth = (renderToClone || renderTo).offsetWidth;
9188		containerHeight = (renderToClone || renderTo).offsetHeight;
9189		chart.chartWidth = chartWidth = optionsChart.width || containerWidth || 600;
9190		chart.chartHeight = chartHeight = optionsChart.height ||
9191			// the offsetHeight of an empty container is 0 in standard browsers, but 19 in IE7:
9192			(containerHeight > 19 ? containerHeight : 400);
9193	}
9194
9195
9196	/**
9197	 * Get the containing element, determine the size and create the inner container
9198	 * div to hold the chart
9199	 */
9200	function getContainer() {
9201		renderTo = optionsChart.renderTo;
9202		containerId = PREFIX + idCounter++;
9203
9204		if (isString(renderTo)) {
9205			renderTo = doc.getElementById(renderTo);
9206		}
9207		
9208		// Display an error if the renderTo is wrong
9209		if (!renderTo) {
9210			error(13, true);
9211		}
9212
9213		// remove previous chart
9214		renderTo.innerHTML = '';
9215
9216		// If the container doesn't have an offsetWidth, it has or is a child of a node
9217		// that has display:none. We need to temporarily move it out to a visible
9218		// state to determine the size, else the legend and tooltips won't render
9219		// properly
9220		if (!renderTo.offsetWidth) {
9221			renderToClone = renderTo.cloneNode(0);
9222			css(renderToClone, {
9223				position: ABSOLUTE,
9224				top: '-9999px',
9225				display: ''
9226			});
9227			doc.body.appendChild(renderToClone);
9228		}
9229
9230		// get the width and height
9231		getChartSize();
9232
9233		// create the inner container
9234		chart.container = container = createElement(DIV, {
9235				className: PREFIX + 'container' +
9236					(optionsChart.className ? ' ' + optionsChart.className : ''),
9237				id: containerId
9238			}, extend({
9239				position: RELATIVE,
9240				overflow: HIDDEN, // needed for context menu (avoid scrollbars) and
9241					// content overflow in IE
9242				width: chartWidth + PX,
9243				height: chartHeight + PX,
9244				textAlign: 'left',
9245				lineHeight: 'normal' // #427
9246			}, optionsChart.style),
9247			renderToClone || renderTo
9248		);
9249
9250		chart.renderer = renderer =
9251			optionsChart.forExport ? // force SVG, used for SVG export
9252				new SVGRenderer(container, chartWidth, chartHeight, true) :
9253				new Renderer(container, chartWidth, chartHeight);
9254
9255		if (useCanVG) {
9256			// If we need canvg library, extend and configure the renderer
9257			// to get the tracker for translating mouse events
9258			renderer.create(chart, container, chartWidth, chartHeight);
9259		}
9260
9261		// Issue 110 workaround:
9262		// In Firefox, if a div is positioned by percentage, its pixel position may land
9263		// between pixels. The container itself doesn't display this, but an SVG element
9264		// inside this container will be drawn at subpixel precision. In order to draw
9265		// sharp lines, this must be compensated for. This doesn't seem to work inside
9266		// iframes though (like in jsFiddle).
9267		var subPixelFix, rect;
9268		if (isFirefox && container.getBoundingClientRect) {
9269			subPixelFix = function () {
9270				css(container, { left: 0, top: 0 });
9271				rect = container.getBoundingClientRect();
9272				css(container, {
9273					left: (-(rect.left - pInt(rect.left))) + PX,
9274					top: (-(rect.top - pInt(rect.top))) + PX
9275				});
9276			};
9277
9278			// run the fix now
9279			subPixelFix();
9280
9281			// run it on resize
9282			addEvent(win, 'resize', subPixelFix);
9283
9284			// remove it on chart destroy
9285			addEvent(chart, 'destroy', function () {
9286				removeEvent(win, 'resize', subPixelFix);
9287			});
9288		}
9289	}
9290
9291	/**
9292	 * Calculate margins by rendering axis labels in a preliminary position. Title,
9293	 * subtitle and legend have already been rendered at this stage, but will be
9294	 * moved into their final positions
9295	 */
9296	getMargins = function () {
9297		var legendOptions = options.legend,
9298			legendMargin = pick(legendOptions.margin, 10),
9299			legendX = legendOptions.x,
9300			legendY = legendOptions.y,
9301			align = legendOptions.align,
9302			verticalAlign = legendOptions.verticalAlign,
9303			titleOffset;
9304
9305		resetMargins();
9306
9307		// adjust for title and subtitle
9308		if ((chart.title || chart.subtitle) && !defined(optionsMarginTop)) {
9309			titleOffset = mathMax(
9310				(chart.title && !chartTitleOptions.floating && !chartTitleOptions.verticalAlign && chartTitleOptions.y) || 0,
9311				(chart.subtitle && !chartSubtitleOptions.floating && !chartSubtitleOptions.verticalAlign && chartSubtitleOptions.y) || 0
9312			);
9313			if (titleOffset) {
9314				plotTop = mathMax(plotTop, titleOffset + pick(chartTitleOptions.margin, 15) + spacingTop);
9315			}
9316		}
9317		// adjust for legend
9318		if (legendOptions.enabled && !legendOptions.floating) {
9319			if (align === 'right') { // horizontal alignment handled first
9320				if (!defined(optionsMarginRight)) {
9321					marginRight = mathMax(
9322						marginRight,
9323						legendWidth - legendX + legendMargin + spacingRight
9324					);
9325				}
9326			} else if (align === 'left') {
9327				if (!defined(optionsMarginLeft)) {
9328					plotLeft = mathMax(
9329						plotLeft,
9330						legendWidth + legendX + legendMargin + spacingLeft
9331					);
9332				}
9333
9334			} else if (verticalAlign === 'top') {
9335				if (!defined(optionsMarginTop)) {
9336					plotTop = mathMax(
9337						plotTop,
9338						legendHeight + legendY + legendMargin + spacingTop
9339					);
9340				}
9341
9342			} else if (verticalAlign === 'bottom') {
9343				if (!defined(optionsMarginBottom)) {
9344					marginBottom = mathMax(
9345						marginBottom,
9346						legendHeight - legendY + legendMargin + spacingBottom
9347					);
9348				}
9349			}
9350		}
9351
9352		// adjust for scroller
9353		if (chart.extraBottomMargin) {
9354			marginBottom += chart.extraBottomMargin;
9355		}
9356		if (chart.extraTopMargin) {
9357			plotTop += chart.extraTopMargin;
9358		}
9359
9360		// pre-render axes to get labels offset width
9361		if (hasCartesianSeries) {
9362			each(axes, function (axis) {
9363				axis.getOffset();
9364			});
9365		}
9366
9367		if (!defined(optionsMarginLeft)) {
9368			plotLeft += axisOffset[3];
9369		}
9370		if (!defined(optionsMarginTop)) {
9371			plotTop += axisOffset[0];
9372		}
9373		if (!defined(optionsMarginBottom)) {
9374			marginBottom += axisOffset[2];
9375		}
9376		if (!defined(optionsMarginRight)) {
9377			marginRight += axisOffset[1];
9378		}
9379
9380		setChartSize();
9381
9382	};
9383
9384	/**
9385	 * Add the event handlers necessary for auto resizing
9386	 *
9387	 */
9388	function initReflow() {
9389		var reflowTimeout;
9390		function reflow(e) {
9391			var width = optionsChart.width || renderTo.offsetWidth,
9392				height = optionsChart.height || renderTo.offsetHeight,
9393				target = e ? e.target : win; // #805 - MooTools doesn't supply e
9394				
9395			// Width and height checks for display:none. Target is doc in IE8 and Opera,
9396			// win in Firefox, Chrome and IE9.
9397			if (width && height && (target === win || target === doc)) {
9398				
9399				if (width !== containerWidth || height !== containerHeight) {
9400					clearTimeout(reflowTimeout);
9401					reflowTimeout = setTimeout(function () {
9402						resize(width, height, false);
9403					}, 100);
9404				}
9405				containerWidth = width;
9406				containerHeight = height;
9407			}
9408		}
9409		addEvent(win, 'resize', reflow);
9410		addEvent(chart, 'destroy', function () {
9411			removeEvent(win, 'resize', reflow);
9412		});
9413	}
9414
9415	/**
9416	 * Fires endResize event on chart instance.
9417	 */
9418	function fireEndResize() {
9419		if (chart) {
9420			fireEvent(chart, 'endResize', null, function () {
9421				isResizing -= 1;
9422			});
9423		}
9424	}
9425
9426	/**
9427	 * Resize the chart to a given width and height
9428	 * @param {Number} width
9429	 * @param {Number} height
9430	 * @param {Object|Boolean} animation
9431	 */
9432	resize = function (width, height, animation) {
9433		var chartTitle = chart.title,
9434			chartSubtitle = chart.subtitle;
9435
9436		isResizing += 1;
9437
9438		// set the animation for the current process
9439		setAnimation(animation, chart);
9440
9441		oldChartHeight = chartHeight;
9442		oldChartWidth = chartWidth;
9443		if (defined(width)) {
9444			chart.chartWidth = chartWidth = mathRound(width);
9445		}
9446		if (defined(height)) {
9447			chart.chartHeight = chartHeight = mathRound(height);
9448		}
9449
9450		css(container, {
9451			width: chartWidth + PX,
9452			height: chartHeight + PX
9453		});
9454		renderer.setSize(chartWidth, chartHeight, animation);
9455
9456		// update axis lengths for more correct tick intervals:
9457		plotWidth = chartWidth - plotLeft - marginRight;
9458		plotHeight = chartHeight - plotTop - marginBottom;
9459
9460		// handle axes
9461		maxTicks = null;
9462		each(axes, function (axis) {
9463			axis.isDirty = true;
9464			axis.setScale();
9465		});
9466
9467		// make sure non-cartesian series are also handled
9468		each(series, function (serie) {
9469			serie.isDirty = true;
9470		});
9471
9472		chart.isDirtyLegend = true; // force legend redraw
9473		chart.isDirtyBox = true; // force redraw of plot and chart border
9474
9475		getMargins();
9476
9477		// move titles
9478		if (chartTitle) {
9479			chartTitle.align(null, null, spacingBox);
9480		}
9481		if (chartSubtitle) {
9482			chartSubtitle.align(null, null, spacingBox);
9483		}
9484
9485		redraw(animation);
9486
9487
9488		oldChartHeight = null;
9489		fireEvent(chart, 'resize');
9490
9491		// fire endResize and set isResizing back
9492		// If animation is disabled, fire without delay
9493		if (globalAnimation === false) {
9494			fireEndResize();
9495		} else { // else set a timeout with the animation duration
9496			setTimeout(fireEndResize, (globalAnimation && globalAnimation.duration) || 500);
9497		}
9498	};
9499
9500	/**
9501	 * Set the public chart properties. This is done before and after the pre-render
9502	 * to determine margin sizes
9503	 */
9504	setChartSize = function () {
9505
9506		chart.plotLeft = plotLeft = mathRound(plotLeft);
9507		chart.plotTop = plotTop = mathRound(plotTop);
9508		chart.plotWidth = plotWidth = mathRound(chartWidth - plotLeft - marginRight);
9509		chart.plotHeight = plotHeight = mathRound(chartHeight - plotTop - marginBottom);
9510
9511		chart.plotSizeX = inverted ? plotHeight : plotWidth;
9512		chart.plotSizeY = inverted ? plotWidth : plotHeight;
9513
9514		spacingBox = {
9515			x: spacingLeft,
9516			y: spacingTop,
9517			width: chartWidth - spacingLeft - spacingRight,
9518			height: chartHeight - spacingTop - spacingBottom
9519		};
9520
9521		each(axes, function (axis) {
9522			axis.setAxisSize();
9523			axis.setAxisTranslation();
9524		});
9525	};
9526
9527	/**
9528	 * Initial margins before auto size margins are applied
9529	 */
9530	resetMargins = function () {
9531		plotTop = pick(optionsMarginTop, spacingTop);
9532		marginRight = pick(optionsMarginRight, spacingRight);
9533		marginBottom = pick(optionsMarginBottom, spacingBottom);
9534		plotLeft = pick(optionsMarginLeft, spacingLeft);
9535		axisOffset = [0, 0, 0, 0]; // top, right, bottom, left
9536	};
9537
9538	/**
9539	 * Draw the borders and backgrounds for chart and plot area
9540	 */
9541	drawChartBox = function () {
9542		var chartBorderWidth = optionsChart.borderWidth || 0,
9543			chartBackgroundColor = optionsChart.backgroundColor,
9544			plotBackgroundColor = optionsChart.plotBackgroundColor,
9545			plotBackgroundImage = optionsChart.plotBackgroundImage,
9546			mgn,
9547			plotSize = {
9548				x: plotLeft,
9549				y: plotTop,
9550				width: plotWidth,
9551				height: plotHeight
9552			};
9553
9554		// Chart area
9555		mgn = chartBorderWidth + (optionsChart.shadow ? 8 : 0);
9556
9557		if (chartBorderWidth || chartBackgroundColor) {
9558			if (!chartBackground) {
9559				chartBackground = renderer.rect(mgn / 2, mgn / 2, chartWidth - mgn, chartHeight - mgn,
9560						optionsChart.borderRadius, chartBorderWidth)
9561					.attr({
9562						stroke: optionsChart.borderColor,
9563						'stroke-width': chartBorderWidth,
9564						fill: chartBackgroundColor || NONE
9565					})
9566					.add()
9567					.shadow(optionsChart.shadow);
9568			} else { // resize
9569				chartBackground.animate(
9570					chartBackground.crisp(null, null, null, chartWidth - mgn, chartHeight - mgn)
9571				);
9572			}
9573		}
9574
9575
9576		// Plot background
9577		if (plotBackgroundColor) {
9578			if (!plotBackground) {
9579				plotBackground = renderer.rect(plotLeft, plotTop, plotWidth, plotHeight, 0)
9580					.attr({
9581						fill: plotBackgroundColor
9582					})
9583					.add()
9584					.shadow(optionsChart.plotShadow);
9585			} else {
9586				plotBackground.animate(plotSize);
9587			}
9588		}
9589		if (plotBackgroundImage) {
9590			if (!plotBGImage) {
9591				plotBGImage = renderer.image(plotBackgroundImage, plotLeft, plotTop, plotWidth, plotHeight)
9592					.add();
9593			} else {
9594				plotBGImage.animate(plotSize);
9595			}
9596		}
9597
9598		// Plot area border
9599		if (optionsChart.plotBorderWidth) {
9600			if (!plotBorder) {
9601				plotBorder = renderer.rect(plotLeft, plotTop, plotWidth, plotHeight, 0, optionsChart.plotBorderWidth)
9602					.attr({
9603						stroke: optionsChart.plotBorderColor,
9604						'stroke-width': optionsChart.plotBorderWidth,
9605						zIndex: 4
9606					})
9607					.add();
9608			} else {
9609				plotBorder.animate(
9610					plotBorder.crisp(null, plotLeft, plotTop, plotWidth, plotHeight)
9611				);
9612			}
9613		}
9614
9615		// reset
9616		chart.isDirtyBox = false;
9617	};
9618
9619	/**
9620	 * Detect whether the chart is inverted, either by setting the chart.inverted option
9621	 * or adding a bar series to the configuration options
9622	 */
9623	function setInverted() {
9624		var BAR = 'bar',
9625			isInverted = (
9626				inverted || // it is set before
9627				optionsChart.inverted ||
9628				optionsChart.type === BAR || // default series type
9629				optionsChart.defaultSeriesType === BAR // backwards compatible
9630			),
9631			seriesOptions = options.series,
9632			i = seriesOptions && seriesOptions.length;
9633
9634		// check if a bar series is present in the config options
9635		while (!isInverted && i--) {
9636			if (seriesOptions[i].type === BAR) {
9637				isInverted = true;
9638			}
9639		}
9640
9641		// set the chart property and the chart scope variable
9642		chart.inverted = inverted = isInverted;
9643	}
9644
9645	/**
9646	 * Render all graphics for the chart
9647	 */
9648	function render() {
9649		var labels = options.labels,
9650			credits = options.credits,
9651			creditsHref;
9652
9653		// Title
9654		setTitle();
9655
9656
9657		// Legend
9658		legend = chart.legend = new Legend();
9659
9660		// Get margins by pre-rendering axes
9661		// set axes scales
9662		each(axes, function (axis) {
9663			axis.setScale();
9664		});
9665		getMargins();
9666		each(axes, function (axis) {
9667			axis.setTickPositions(true); // update to reflect the new margins
9668		});
9669		adjustTickAmounts();
9670		getMargins(); // second pass to check for new labels
9671
9672
9673		// Draw the borders and backgrounds
9674		drawChartBox();
9675
9676		// Axes
9677		if (hasCartesianSeries) {
9678			each(axes, function (axis) {
9679				axis.render();
9680			});
9681		}
9682
9683
9684		// The series
9685		if (!chart.seriesGroup) {
9686			chart.seriesGroup = renderer.g('series-group')
9687				.attr({ zIndex: 3 })
9688				.add();
9689		}
9690		each(series, function (serie) {
9691			serie.translate();
9692			serie.setTooltipPoints();
9693			serie.render();
9694		});
9695
9696
9697		// Labels
9698		if (labels.items) {
9699			each(labels.items, function () {
9700				var style = extend(labels.style, this.style),
9701					x = pInt(style.left) + plotLeft,
9702					y = pInt(style.top) + plotTop + 12;
9703
9704				// delete to prevent rewriting in IE
9705				delete style.left;
9706				delete style.top;
9707
9708				renderer.text(
9709					this.html,
9710					x,
9711					y
9712				)
9713				.attr({ zIndex: 2 })
9714				.css(style)
9715				.add();
9716
9717			});
9718		}
9719
9720		// Credits
9721		if (credits.enabled && !chart.credits) {
9722			creditsHref = credits.href;
9723			chart.credits = renderer.text(
9724				credits.text,
9725				0,
9726				0
9727			)
9728			.on('click', function () {
9729				if (creditsHref) {
9730					location.href = creditsHref;
9731				}
9732			})
9733			.attr({
9734				align: credits.position.align,
9735				zIndex: 8
9736			})
9737			.css(credits.style)
9738			.add()
9739			.align(credits.position);
9740		}
9741
9742		// Set flag
9743		chart.hasRendered = true;
9744
9745	}
9746
9747	/**
9748	 * Clean up memory usage
9749	 */
9750	function destroy() {
9751		var i,
9752			parentNode = container && container.parentNode;
9753
9754		// If the chart is destroyed already, do nothing.
9755		// This will happen if if a script invokes chart.destroy and
9756		// then it will be called again on win.unload
9757		if (chart === null) {
9758			return;
9759		}
9760
9761		// fire the chart.destoy event
9762		fireEvent(chart, 'destroy');
9763
9764		// remove events
9765		removeEvent(chart);
9766
9767		// ==== Destroy collections:
9768		// Destroy axes
9769		i = axes.length;
9770		while (i--) {
9771			axes[i] = axes[i].destroy();
9772		}
9773
9774		// Destroy each series
9775		i = series.length;
9776		while (i--) {
9777			series[i] = series[i].destroy();
9778		}
9779
9780		// ==== Destroy chart properties:
9781		each(['title', 'subtitle', 'seriesGroup', 'clipRect', 'credits', 'tracker', 'scroller', 'rangeSelector'], function (name) {
9782			var prop = chart[name];
9783
9784			if (prop) {
9785				chart[name] = prop.destroy();
9786			}
9787		});
9788
9789		// ==== Destroy local variables:
9790		each([chartBackground, plotBorder, plotBackground, legend, tooltip, renderer, tracker], function (obj) {
9791			if (obj && obj.destroy) {
9792				obj.destroy();
9793			}
9794		});
9795		chartBackground = plotBorder = plotBackground = legend = tooltip = renderer = tracker = null;
9796
9797		// remove container and all SVG
9798		if (container) { // can break in IE when destroyed before finished loading
9799			container.innerHTML = '';
9800			removeEvent(container);
9801			if (parentNode) {
9802				discardElement(container);
9803			}
9804
9805			// IE6 leak
9806			container = null;
9807		}
9808
9809		// memory and CPU leak
9810		clearInterval(tooltipInterval);
9811
9812		// clean it all up
9813		for (i in chart) {
9814			delete chart[i];
9815		}
9816
9817		chart = null;
9818		options = null;
9819	}
9820	/**
9821	 * Prepare for first rendering after all data are loaded
9822	 */
9823	function firstRender() {
9824		// VML namespaces can't be added until after complete. Listening
9825		// for Perini's doScroll hack is not enough.
9826		var ONREADYSTATECHANGE = 'onreadystatechange',
9827		COMPLETE = 'complete';
9828		// Note: in spite of JSLint's complaints, win == win.top is required
9829		/*jslint eqeq: true*/
9830		if ((!hasSVG && (win == win.top && doc.readyState !== COMPLETE)) || (useCanVG && !win.canvg)) {
9831		/*jslint eqeq: false*/
9832			if (useCanVG) {
9833				// Delay rendering until canvg library is downloaded and ready
9834				CanVGController.push(firstRender, options.global.canvasToolsURL);
9835			} else {
9836				doc.attachEvent(ONREADYSTATECHANGE, function () {
9837					doc.detachEvent(ONREADYSTATECHANGE, firstRender);
9838					if (doc.readyState === COMPLETE) {
9839						firstRender();
9840					}
9841				});
9842			}
9843			return;
9844		}
9845
9846		// create the container
9847		getContainer();
9848
9849		// Run an early event after the container and renderer are established
9850		fireEvent(chart, 'init');
9851
9852		// Initialize range selector for stock charts
9853		if (Highcharts.RangeSelector && options.rangeSelector.enabled) {
9854			chart.rangeSelector = new Highcharts.RangeSelector(chart);
9855		}
9856
9857		resetMargins();
9858		setChartSize();
9859
9860		// Set the common inversion and transformation for inverted series after initSeries
9861		setInverted();
9862
9863		// get axes
9864		getAxes();
9865
9866		// Initialize the series
9867		each(options.series || [], function (serieOptions) {
9868			initSeries(serieOptions);
9869		});
9870
9871		// Run an event where series and axes can be added
9872		//fireEvent(chart, 'beforeRender');
9873
9874		// Initialize scroller for stock charts
9875		if (Highcharts.Scroller && (options.navigator.enabled || options.scrollbar.enabled)) {
9876			chart.scroller = new Highcharts.Scroller(chart);
9877		}
9878
9879		chart.render = render;
9880
9881		// depends on inverted and on margins being set
9882		chart.tracker = tracker = new MouseTracker(options.tooltip);
9883
9884
9885		render();
9886
9887		// add canvas
9888		renderer.draw();
9889		// run callbacks
9890		if (callback) {
9891			callback.apply(chart, [chart]);
9892		}
9893		each(chart.callbacks, function (fn) {
9894			fn.apply(chart, [chart]);
9895		});
9896		
9897		
9898		// If the chart was rendered outside the top container, put it back in
9899		if (renderToClone) {
9900			renderTo.appendChild(container);
9901			discardElement(renderToClone);
9902		}
9903
9904		fireEvent(chart, 'load');
9905
9906	}
9907
9908	// Run chart
9909
9910	// Set up auto resize
9911	if (optionsChart.reflow !== false) {
9912		addEvent(chart, 'load', initReflow);
9913	}
9914
9915	// Chart event handlers
9916	if (chartEvents) {
9917		for (eventType in chartEvents) {
9918			addEvent(chart, eventType, chartEvents[eventType]);
9919		}
9920	}
9921
9922
9923	chart.options = options;
9924	chart.series = series;
9925
9926
9927	chart.xAxis = [];
9928	chart.yAxis = [];
9929
9930
9931
9932
9933	// Expose methods and variables
9934	chart.addSeries = addSeries;
9935	chart.animation = useCanVG ? false : pick(optionsChart.animation, true);
9936	chart.Axis = Axis;
9937	chart.destroy = destroy;
9938	chart.get = get;
9939	chart.getSelectedPoints = getSelectedPoints;
9940	chart.getSelectedSeries = getSelectedSeries;
9941	chart.hideLoading = hideLoading;
9942	chart.initSeries = initSeries;
9943	chart.isInsidePlot = isInsidePlot;
9944	chart.redraw = redraw;
9945	chart.setSize = resize;
9946	chart.setTitle = setTitle;
9947	chart.showLoading = showLoading;
9948	chart.pointCount = 0;
9949	chart.counters = new ChartCounters();
9950	/*
9951	if ($) $(function () {
9952		$container = $('#container');
9953		var origChartWidth,
9954			origChartHeight;
9955		if ($container) {
9956			$('<button>+</button>')
9957				.insertBefore($container)
9958				.click(function () {
9959					if (origChartWidth === UNDEFINED) {
9960						origChartWidth = chartWidth;
9961						origChartHeight = chartHeight;
9962					}
9963					chart.resize(chartWidth *= 1.1, chartHeight *= 1.1);
9964				});
9965			$('<button>-</button>')
9966				.insertBefore($container)
9967				.click(function () {
9968					if (origChartWidth === UNDEFINED) {
9969						origChartWidth = chartWidth;
9970						origChartHeight = chartHeight;
9971					}
9972					chart.resize(chartWidth *= 0.9, chartHeight *= 0.9);
9973				});
9974			$('<button>1:1</button>')
9975				.insertBefore($container)
9976				.click(function () {
9977					if (origChartWidth === UNDEFINED) {
9978						origChartWidth = chartWidth;
9979						origChartHeight = chartHeight;
9980					}
9981					chart.resize(origChartWidth, origChartHeight);
9982				});
9983		}
9984	})
9985	*/
9986
9987
9988
9989
9990	firstRender();
9991
9992
9993} // end Chart
9994
9995// Hook for exporting module
9996Chart.prototype.callbacks = [];
9997/**
9998 * The Point object and prototype. Inheritable and used as base for PiePoint
9999 */
10000var Point = function () {};
10001Point.prototype = {
10002
10003	/**
10004	 * Initialize the point
10005	 * @param {Object} series The series object containing this point
10006	 * @param {Object} options The data in either number, array or object format
10007	 */
10008	init: function (series, options, x) {
10009		var point = this,
10010			counters = series.chart.counters,
10011			defaultColors;
10012		point.series = series;
10013		point.applyOptions(options, x);
10014		point.pointAttr = {};
10015
10016		if (series.options.colorByPoint) {
10017			defaultColors = series.chart.options.colors;
10018			if (!point.options) {
10019				point.options = {};
10020			}
10021			point.color = point.options.color = point.color || defaultColors[counters.color++];
10022
10023			// loop back to zero
10024			counters.wrapColor(defaultColors.length);
10025		}
10026
10027		series.chart.pointCount++;
10028		return point;
10029	},
10030	/**
10031	 * Apply the options containing the x and y data and possible some extra properties.
10032	 * This is called on point init or from point.update.
10033	 *
10034	 * @param {Object} options
10035	 */
10036	applyOptions: function (options, x) {
10037		var point = this,
10038			series = point.series,
10039			optionsType = typeof options;
10040
10041		point.config = options;
10042
10043		// onedimensional array input
10044		if (optionsType === 'number' || options === null) {
10045			point.y = options;
10046		} else if (typeof options[0] === 'number') { // two-dimentional array
10047			point.x = options[0];
10048			point.y = options[1];
10049		} else if (optionsType === 'object' && typeof options.length !== 'number') { // object input
10050			// copy options directly to point
10051			extend(point, options);
10052			point.options = options;
10053			
10054			// This is the fastest way to detect if there are individual point dataLabels that need 
10055			// to be considered in drawDataLabels. These can only occur in object configs.
10056			if (options.dataLabels) {
10057				series._hasPointLabels = true;
10058			}
10059		} else if (typeof options[0] === 'string') { // categorized data with name in first position
10060			point.name = options[0];
10061			point.y = options[1];
10062		}
10063		
10064		/*
10065		 * If no x is set by now, get auto incremented value. All points must have an
10066		 * x value, however the y value can be null to create a gap in the series
10067		 */
10068		// todo: skip this? It is only used in applyOptions, in translate it should not be used
10069		if (point.x === UNDEFINED) {
10070			point.x = x === UNDEFINED ? series.autoIncrement() : x;
10071		}
10072		
10073		
10074
10075	},
10076
10077	/**
10078	 * Destroy a point to clear memory. Its reference still stays in series.data.
10079	 */
10080	destroy: function () {
10081		var point = this,
10082			series = point.series,
10083			hoverPoints = series.chart.hoverPoints,
10084			prop;
10085
10086		series.chart.pointCount--;
10087
10088		if (hoverPoints) {
10089			point.setState();
10090			erase(hoverPoints, point);
10091		}
10092		if (point === series.chart.hoverPoint) {
10093			point.onMouseOut();
10094		}
10095		series.chart.hoverPoints = null;
10096
10097		// remove all events
10098		if (point.graphic || point.dataLabel) { // removeEvent and destroyElements are performance expensive
10099			removeEvent(point);
10100			point.destroyElements();
10101		}
10102
10103		if (point.legendItem) { // pies have legend items
10104			point.series.chart.legend.destroyItem(point);
10105		}
10106
10107		for (prop in point) {
10108			point[prop] = null;
10109		}
10110
10111
10112	},
10113
10114	/**
10115	 * Destroy SVG elements associated with the point
10116	 */
10117	destroyElements: function () {
10118		var point = this,
10119			props = ['graphic', 'tracker', 'dataLabel', 'group', 'connector', 'shadowGroup'],
10120			prop,
10121			i = 6;
10122		while (i--) {
10123			prop = props[i];
10124			if (point[prop]) {
10125				point[prop] = point[prop].destroy();
10126			}
10127		}
10128	},
10129
10130	/**
10131	 * Return the configuration hash needed for the data label and tooltip formatters
10132	 */
10133	getLabelConfig: function () {
10134		var point = this;
10135		return {
10136			x: point.category,
10137			y: point.y,
10138			key: point.name || point.category,
10139			series: point.series,
10140			point: point,
10141			percentage: point.percentage,
10142			total: point.total || point.stackTotal
10143		};
10144	},
10145
10146	/**
10147	 * Toggle the selection status of a point
10148	 * @param {Boolean} selected Whether to select or unselect the point.
10149	 * @param {Boolean} accumulate Whether to add to the previous selection. By default,
10150	 *     this happens if the control key (Cmd on Mac) was pressed during clicking.
10151	 */
10152	select: function (selected, accumulate) {
10153		var point = this,
10154			series = point.series,
10155			chart = series.chart;
10156
10157		selected = pick(selected, !point.selected);
10158
10159		// fire the event with the defalut handler
10160		point.firePointEvent(selected ? 'select' : 'unselect', { accumulate: accumulate }, function () {
10161			point.selected = selected;
10162			point.setState(selected && SELECT_STATE);
10163
10164			// unselect all other points unless Ctrl or Cmd + click
10165			if (!accumulate) {
10166				each(chart.getSelectedPoints(), function (loopPoint) {
10167					if (loopPoint.selected && loopPoint !== point) {
10168						loopPoint.selected = false;
10169						loopPoint.setState(NORMAL_STATE);
10170						loopPoint.firePointEvent('unselect');
10171					}
10172				});
10173			}
10174		});
10175	},
10176
10177	onMouseOver: function () {
10178		var point = this,
10179			series = point.series,
10180			chart = series.chart,
10181			tooltip = chart.tooltip,
10182			hoverPoint = chart.hoverPoint;
10183
10184		// set normal state to previous series
10185		if (hoverPoint && hoverPoint !== point) {
10186			hoverPoint.onMouseOut();
10187		}
10188
10189		// trigger the event
10190		point.firePointEvent('mouseOver');
10191
10192		// update the tooltip
10193		if (tooltip && (!tooltip.shared || series.noSharedTooltip)) {
10194			tooltip.refresh(point);
10195		}
10196
10197		// hover this
10198		point.setState(HOVER_STATE);
10199		chart.hoverPoint = point;
10200	},
10201
10202	onMouseOut: function () {
10203		var point = this;
10204		point.firePointEvent('mouseOut');
10205
10206		point.setState();
10207		point.series.chart.hoverPoint = null;
10208	},
10209
10210	/**
10211	 * Extendable method for formatting each point's tooltip line
10212	 *
10213	 * @return {String} A string to be concatenated in to the common tooltip text
10214	 */
10215	tooltipFormatter: function (pointFormat) {
10216		var point = this,
10217			series = point.series,
10218			seriesTooltipOptions = series.tooltipOptions,
10219			split = String(point.y).split('.'),
10220			originalDecimals = split[1] ? split[1].length : 0,
10221			match = pointFormat.match(/\{(series|point)\.[a-zA-Z]+\}/g),
10222			splitter = /[{\.}]/,
10223			obj,
10224			key,
10225			replacement,
10226			parts,
10227			prop,
10228			i;
10229
10230		// loop over the variables defined on the form {series.name}, {point.y} etc
10231		for (i in match) {
10232			key = match[i];
10233			if (isString(key) && key !== pointFormat) { // IE matches more than just the variables
10234				
10235				// Split it further into parts
10236				parts = (' ' + key).split(splitter); // add empty string because IE and the rest handles it differently
10237				obj = { 'point': point, 'series': series }[parts[1]];
10238				prop = parts[2];
10239				
10240				// Add some preformatting
10241				if (obj === point && (prop === 'y' || prop === 'open' || prop === 'high' || 
10242						prop === 'low' || prop === 'close')) { 
10243					replacement = (seriesTooltipOptions.valuePrefix || seriesTooltipOptions.yPrefix || '') + 
10244						numberFormat(point[prop], pick(seriesTooltipOptions.valueDecimals, seriesTooltipOptions.yDecimals, originalDecimals)) +
10245						(seriesTooltipOptions.valueSuffix || seriesTooltipOptions.ySuffix || '');
10246				
10247				// Automatic replacement
10248				} else {
10249					replacement = obj[prop];
10250				}
10251				
10252				pointFormat = pointFormat.replace(key, replacement);
10253			}
10254		}
10255		
10256		return pointFormat;
10257	},
10258
10259	/**
10260	 * Update the point with new options (typically x/y data) and optionally redraw the series.
10261	 *
10262	 * @param {Object} options Point options as defined in the series.data array
10263	 * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
10264	 * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
10265	 *    configuration
10266	 *
10267	 */
10268	update: function (options, redraw, animation) {
10269		var point = this,
10270			series = point.series,
10271			graphic = point.graphic,
10272			i,
10273			data = series.data,
10274			dataLength = data.length,
10275			chart = series.chart;
10276
10277		redraw = pick(redraw, true);
10278
10279		// fire the event with a default handler of doing the update
10280		point.firePointEvent('update', { options: options }, function () {
10281
10282			point.applyOptions(options);
10283
10284			// update visuals
10285			if (isObject(options)) {
10286				series.getAttribs();
10287				if (graphic) {
10288					graphic.attr(point.pointAttr[series.state]);
10289				}
10290			}
10291
10292			// record changes in the parallel arrays
10293			for (i = 0; i < dataLength; i++) {
10294				if (data[i] === point) {
10295					series.xData[i] = point.x;
10296					series.yData[i] = point.y;
10297					series.options.data[i] = options;
10298					break;
10299				}
10300			}
10301
10302			// redraw
10303			series.isDirty = true;
10304			series.isDirtyData = true;
10305			if (redraw) {
10306				chart.redraw(animation);
10307			}
10308		});
10309	},
10310
10311	/**
10312	 * Remove a point and optionally redraw the series and if necessary the axes
10313	 * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
10314	 * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
10315	 *    configuration
10316	 */
10317	remove: function (redraw, animation) {
10318		var point = this,
10319			series = point.series,
10320			chart = series.chart,
10321			i,
10322			data = series.data,
10323			dataLength = data.length;
10324
10325		setAnimation(animation, chart);
10326		redraw = pick(redraw, true);
10327
10328		// fire the event with a default handler of removing the point
10329		point.firePointEvent('remove', null, function () {
10330
10331			//erase(series.data, point);
10332
10333			for (i = 0; i < dataLength; i++) {
10334				if (data[i] === point) {
10335
10336					// splice all the parallel arrays
10337					data.splice(i, 1);
10338					series.options.data.splice(i, 1);
10339					series.xData.splice(i, 1);
10340					series.yData.splice(i, 1);
10341					break;
10342				}
10343			}
10344
10345			point.destroy();
10346
10347
10348			// redraw
10349			series.isDirty = true;
10350			series.isDirtyData = true;
10351			if (redraw) {
10352				chart.redraw();
10353			}
10354		});
10355
10356
10357	},
10358
10359	/**
10360	 * Fire an event on the Point object. Must not be renamed to fireEvent, as this
10361	 * causes a name clash in MooTools
10362	 * @param {String} eventType
10363	 * @param {Object} eventArgs Additional event arguments
10364	 * @param {Function} defaultFunction Default event handler
10365	 */
10366	firePointEvent: function (eventType, eventArgs, defaultFunction) {
10367		var point = this,
10368			series = this.series,
10369			seriesOptions = series.options;
10370
10371		// load event handlers on demand to save time on mouseover/out
10372		if (seriesOptions.point.events[eventType] || (point.options && point.options.events && point.options.events[eventType])) {
10373			this.importEvents();
10374		}
10375
10376		// add default handler if in selection mode
10377		if (eventType === 'click' && seriesOptions.allowPointSelect) {
10378			defaultFunction = function (event) {
10379				// Control key is for Windows, meta (= Cmd key) for Mac, Shift for Opera
10380				point.select(null, event.ctrlKey || event.metaKey || event.shiftKey);
10381			};
10382		}
10383
10384		fireEvent(this, eventType, eventArgs, defaultFunction);
10385	},
10386	/**
10387	 * Import events from the series' and point's options. Only do it on
10388	 * demand, to save processing time on hovering.
10389	 */
10390	importEvents: function () {
10391		if (!this.hasImportedEvents) {
10392			var point = this,
10393				options = merge(point.series.options.point, point.options),
10394				events = options.events,
10395				eventType;
10396
10397			point.events = events;
10398
10399			for (eventType in events) {
10400				addEvent(point, eventType, events[eventType]);
10401			}
10402			this.hasImportedEvents = true;
10403
10404		}
10405	},
10406
10407	/**
10408	 * Set the point's state
10409	 * @param {String} state
10410	 */
10411	setState: function (state) {
10412		var point = this,
10413			plotX = point.plotX,
10414			plotY = point.plotY,
10415			series = point.series,
10416			stateOptions = series.options.states,
10417			markerOptions = defaultPlotOptions[series.type].marker && series.options.marker,
10418			normalDisabled = markerOptions && !markerOptions.enabled,
10419			markerStateOptions = markerOptions && markerOptions.states[state],
10420			stateDisabled = markerStateOptions && markerStateOptions.enabled === false,
10421			stateMarkerGraphic = series.stateMarkerGraphic,
10422			chart = series.chart,
10423			radius,
10424			pointAttr = point.pointAttr;
10425
10426		state = state || NORMAL_STATE; // empty string
10427
10428		if (
10429				// already has this state
10430				state === point.state ||
10431				// selected points don't respond to hover
10432				(point.selected && state !== SELECT_STATE) ||
10433				// series' state options is disabled
10434				(stateOptions[state] && stateOptions[state].enabled === false) ||
10435				// point marker's state options is disabled
10436				(state && (stateDisabled || (normalDisabled && !markerStateOptions.enabled)))
10437
10438			) {
10439			return;
10440		}
10441
10442		// apply hover styles to the existing point
10443		if (point.graphic) {
10444			radius = markerOptions && point.graphic.symbolName && pointAttr[state].r;
10445			point.graphic.attr(merge(
10446				pointAttr[state],
10447				radius ? { // new symbol attributes (#507, #612)
10448					x: plotX - radius,
10449					y: plotY - radius,
10450					width: 2 * radius,
10451					height: 2 * radius
10452				} : {}
10453			));
10454		} else {
10455			// if a graphic is not applied to each point in the normal state, create a shared
10456			// graphic for the hover state
10457			if (state) {
10458				if (!stateMarkerGraphic) {
10459					radius = markerOptions.radius;
10460					series.stateMarkerGraphic = stateMarkerGraphic = chart.renderer.symbol(
10461						series.symbol,
10462						-radius,
10463						-radius,
10464						2 * radius,
10465						2 * radius
10466					)
10467					.attr(pointAttr[state])
10468					.add(series.group);
10469				}
10470
10471				stateMarkerGraphic.translate(
10472					plotX,
10473					plotY
10474				);
10475			}
10476
10477			if (stateMarkerGraphic) {
10478				stateMarkerGraphic[state ? 'show' : 'hide']();
10479			}
10480		}
10481
10482		point.state = state;
10483	}
10484};
10485
10486/**
10487 * @classDescription The base function which all other series types inherit from. The data in the series is stored
10488 * in various arrays.
10489 *
10490 * - First, series.options.data contains all the original config options for
10491 * each point whether added by options or methods like series.addPoint.
10492 * - Next, series.data contains those values converted to points, but in case the series data length
10493 * exceeds the cropThreshold, or if the data is grouped, series.data doesn't contain all the points. It
10494 * only contains the points that have been created on demand.
10495 * - Then there's series.points that contains all currently visible point objects. In case of cropping,
10496 * the cropped-away points are not part of this array. The series.points array starts at series.cropStart
10497 * compared to series.data and series.options.data. If however the series data is grouped, these can't
10498 * be correlated one to one.
10499 * - series.xData and series.processedXData contain clean x values, equivalent to series.data and series.points.
10500 * - series.yData and series.processedYData contain clean x values, equivalent to series.data and series.points.
10501 *
10502 * @param {Object} chart
10503 * @param {Object} options
10504 */
10505var Series = function () {};
10506
10507Series.prototype = {
10508
10509	isCartesian: true,
10510	type: 'line',
10511	pointClass: Point,
10512	sorted: true, // requires the data to be sorted
10513	pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
10514		stroke: 'lineColor',
10515		'stroke-width': 'lineWidth',
10516		fill: 'fillColor',
10517		r: 'radius'
10518	},
10519	init: function (chart, options) {
10520		var series = this,
10521			eventType,
10522			events,
10523			//pointEvent,
10524			index = chart.series.length;
10525
10526		series.chart = chart;
10527		series.options = options = series.setOptions(options); // merge with plotOptions
10528		
10529		// bind the axes
10530		series.bindAxes();
10531
10532		// set some variables
10533		extend(series, {
10534			index: index,
10535			name: options.name || 'Series ' + (index + 1),
10536			state: NORMAL_STATE,
10537			pointAttr: {},
10538			visible: options.visible !== false, // true by default
10539			selected: options.selected === true // false by default
10540		});
10541		
10542		// special
10543		if (useCanVG) {
10544			options.animation = false;
10545		}
10546
10547		// register event listeners
10548		events = options.events;
10549		for (eventType in events) {
10550			addEvent(series, eventType, events[eventType]);
10551		}
10552		if (
10553			(events && events.click) ||
10554			(options.point && options.point.events && options.point.events.click) ||
10555			options.allowPointSelect
10556		) {
10557			chart.runTrackerClick = true;
10558		}
10559
10560		series.getColor();
10561		series.getSymbol();
10562
10563		// set the data
10564		series.setData(options.data, false);
10565
10566	},
10567	
10568	
10569	
10570	/**
10571	 * Set the xAxis and yAxis properties of cartesian series, and register the series
10572	 * in the axis.series array
10573	 */
10574	bindAxes: function () {
10575		var series = this,
10576			seriesOptions = series.options,
10577			chart = series.chart,
10578			axisOptions;
10579			
10580		if (series.isCartesian) {
10581			
10582			each(['xAxis', 'yAxis'], function (AXIS) { // repeat for xAxis and yAxis
10583				
10584				each(chart[AXIS], function (axis) { // loop through the chart's axis objects
10585					
10586					axisOptions = axis.options;
10587					
10588					// apply if the series xAxis or yAxis option mathches the number of the 
10589					// axis, or if undefined, use the first axis
10590					if ((seriesOptions[AXIS] === axisOptions.index) ||
10591							(seriesOptions[AXIS] === UNDEFINED && axisOptions.index === 0)) {
10592						
10593						// register this series in the axis.series lookup
10594						axis.series.push(series);
10595						
10596						// set this series.xAxis or series.yAxis reference
10597						series[AXIS] = axis;
10598						
10599						// mark dirty for redraw
10600						axis.isDirty = true;
10601					}
10602				});
10603				
10604			});
10605		}
10606	},
10607
10608
10609	/**
10610	 * Return an auto incremented x value based on the pointStart and pointInterval options.
10611	 * This is only used if an x value is not given for the point that calls autoIncrement.
10612	 */
10613	autoIncrement: function () {
10614		var series = this,
10615			options = series.options,
10616			xIncrement = series.xIncrement;
10617
10618		xIncrement = pick(xIncrement, options.pointStart, 0);
10619
10620		series.pointInterval = pick(series.pointInterval, options.pointInterval, 1);
10621
10622		series.xIncrement = xIncrement + series.pointInterval;
10623		return xIncrement;
10624	},
10625
10626	/**
10627	 * Divide the series data into segments divided by null values.
10628	 */
10629	getSegments: function () {
10630		var series = this,
10631			lastNull = -1,
10632			segments = [],
10633			i,
10634			points = series.points,
10635			pointsLength = points.length;
10636
10637		if (pointsLength) { // no action required for []
10638			
10639			// if connect nulls, just remove null points
10640			if (series.options.connectNulls) {
10641				i = pointsLength;
10642				while (i--) {
10643					if (points[i].y === null) {
10644						points.splice(i, 1);
10645					}
10646				}
10647				if (points.length) {
10648					segments = [points];
10649				}
10650				
10651			// else, split on null points
10652			} else {
10653				each(points, function (point, i) {
10654					if (point.y === null) {
10655						if (i > lastNull + 1) {
10656							segments.push(points.slice(lastNull + 1, i));
10657						}
10658						lastNull = i;
10659					} else if (i === pointsLength - 1) { // last value
10660						segments.push(points.slice(lastNull + 1, i + 1));
10661					}
10662				});
10663			}
10664		}
10665		
10666		// register it
10667		series.segments = segments;
10668	},
10669	/**
10670	 * Set the series options by merging from the options tree
10671	 * @param {Object} itemOptions
10672	 */
10673	setOptions: function (itemOptions) {
10674		var series = this,
10675			chart = series.chart,
10676			chartOptions = chart.options,
10677			plotOptions = chartOptions.plotOptions,
10678			data = itemOptions.data,
10679			options;
10680
10681		itemOptions.data = null; // remove from merge to prevent looping over the data set
10682
10683		options = merge(
10684			plotOptions[this.type],
10685			plotOptions.series,
10686			itemOptions
10687		);
10688		
10689		// Re-insert the data array to the options and the original config (#717)
10690		options.data = itemOptions.data = data;
10691		
10692		// the tooltip options are merged between global and series specific options
10693		series.tooltipOptions = merge(chartOptions.tooltip, options.tooltip);
10694		
10695		return options;
10696
10697	},
10698	/**
10699	 * Get the series' color
10700	 */
10701	getColor: function () {
10702		var defaultColors = this.chart.options.colors,
10703			counters = this.chart.counters;
10704		this.color = this.options.color || defaultColors[counters.color++] || '#0000ff';
10705		counters.wrapColor(defaultColors.length);
10706	},
10707	/**
10708	 * Get the series' symbol
10709	 */
10710	getSymbol: function () {
10711		var series = this,
10712			seriesMarkerOption = series.options.marker,
10713			chart = series.chart,
10714			defaultSymbols = chart.options.symbols,
10715			counters = chart.counters;
10716		series.symbol = seriesMarkerOption.symbol || defaultSymbols[counters.symbol++];
10717		
10718		// don't substract radius in image symbols (#604)
10719		if (/^url/.test(series.symbol)) {
10720			seriesMarkerOption.radius = 0;
10721		}
10722		counters.wrapSymbol(defaultSymbols.length);
10723	},
10724
10725	/**
10726	 * Add a point dynamically after chart load time
10727	 * @param {Object} options Point options as given in series.data
10728	 * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
10729	 * @param {Boolean} shift If shift is true, a point is shifted off the start
10730	 *    of the series as one is appended to the end.
10731	 * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
10732	 *    configuration
10733	 */
10734	addPoint: function (options, redraw, shift, animation) {
10735		var series = this,
10736			data = series.data,
10737			graph = series.graph,
10738			area = series.area,
10739			chart = series.chart,
10740			xData = series.xData,
10741			yData = series.yData,
10742			currentShift = (graph && graph.shift) || 0,
10743			dataOptions = series.options.data,
10744			point;
10745			//point = (new series.pointClass()).init(series, options);
10746
10747		setAnimation(animation, chart);
10748
10749		// Make graph animate sideways
10750		if (graph && shift) { 
10751			graph.shift = currentShift + 1;
10752		}
10753		if (area) {
10754			if (shift) { // #780
10755				area.shift = currentShift + 1;
10756			}
10757			area.isArea = true; // needed in animation, both with and without shift
10758		}
10759		
10760		// Optional redraw, defaults to true
10761		redraw = pick(redraw, true);
10762
10763		// Get options and push the point to xData, yData and series.options. In series.generatePoints
10764		// the Point instance will be created on demand and pushed to the series.data array.
10765		point = { series: series };
10766		series.pointClass.prototype.applyOptions.apply(point, [options]);
10767		xData.push(point.x);
10768		yData.push(series.valueCount === 4 ? [point.open, point.high, point.low, point.close] : point.y);
10769		dataOptions.push(options);
10770
10771
10772		// Shift the first point off the parallel arrays
10773		// todo: consider series.removePoint(i) method
10774		if (shift) {
10775			if (data[0]) {
10776				data[0].remove(false);
10777			} else {
10778				data.shift();
10779				xData.shift();
10780				yData.shift();
10781				dataOptions.shift();
10782			}
10783		}
10784		series.getAttribs();
10785
10786		// redraw
10787		series.isDirty = true;
10788		series.isDirtyData = true;
10789		if (redraw) {
10790			chart.redraw();
10791		}
10792	},
10793
10794	/**
10795	 * Replace the series data with a new set of data
10796	 * @param {Object} data
10797	 * @param {Object} redraw
10798	 */
10799	setData: function (data, redraw) {
10800		var series = this,
10801			oldData = series.points,
10802			options = series.options,
10803			initialColor = series.initialColor,
10804			chart = series.chart,
10805			firstPoint = null,
10806			i;
10807
10808		// reset properties
10809		series.xIncrement = null;
10810		series.pointRange = (series.xAxis && series.xAxis.categories && 1) || options.pointRange;
10811		
10812		if (defined(initialColor)) { // reset colors for pie
10813			chart.counters.color = initialColor;
10814		}
10815		
10816		// parallel arrays
10817		var xData = [],
10818			yData = [],
10819			dataLength = data ? data.length : [],
10820			turboThreshold = options.turboThreshold || 1000,
10821			pt,
10822			ohlc = series.valueCount === 4;
10823
10824		// In turbo mode, only one- or twodimensional arrays of numbers are allowed. The
10825		// first value is tested, and we assume that all the rest are defined the same
10826		// way. Although the 'for' loops are similar, they are repeated inside each
10827		// if-else conditional for max performance.
10828		if (dataLength > turboThreshold) {
10829			
10830			// find the first non-null point
10831			i = 0;
10832			while (firstPoint === null && i < dataLength) {
10833				firstPoint = data[i];
10834				i++;
10835			}
10836		
10837		
10838			if (isNumber(firstPoint)) { // assume all points are numbers
10839				var x = pick(options.pointStart, 0),
10840					pointInterval = pick(options.pointInterval, 1);
10841
10842				for (i = 0; i < dataLength; i++) {
10843					xData[i] = x;
10844					yData[i] = data[i];
10845					x += pointInterval;
10846				}
10847				series.xIncrement = x;
10848			} else if (isArray(firstPoint)) { // assume all points are arrays
10849				if (ohlc) { // [x, o, h, l, c]
10850					for (i = 0; i < dataLength; i++) {
10851						pt = data[i];
10852						xData[i] = pt[0];
10853						yData[i] = pt.slice(1, 5);
10854					}
10855				} else { // [x, y]
10856					for (i = 0; i < dataLength; i++) {
10857						pt = data[i];
10858						xData[i] = pt[0];
10859						yData[i] = pt[1];
10860					}
10861				}
10862			} /* else {
10863				error(12); // Highcharts expects configs to be numbers or arrays in turbo mode
10864			}*/
10865		} else {
10866			for (i = 0; i < dataLength; i++) {
10867				pt = { series: series };
10868				series.pointClass.prototype.applyOptions.apply(pt, [data[i]]);
10869				xData[i] = pt.x;
10870				yData[i] = ohlc ? [pt.open, pt.high, pt.low, pt.close] : pt.y;
10871			}
10872		}
10873
10874		series.data = [];
10875		series.options.data = data;
10876		series.xData = xData;
10877		series.yData = yData;
10878
10879		// destroy old points
10880		i = (oldData && oldData.length) || 0;
10881		while (i--) {
10882			if (oldData[i] && oldData[i].destroy) {
10883				oldData[i].destroy();
10884			}
10885		}
10886
10887		// redraw
10888		series.isDirty = series.isDirtyData = chart.isDirtyBox = true;
10889		if (pick(redraw, true)) {
10890			chart.redraw(false);
10891		}
10892	},
10893
10894	/**
10895	 * Remove a series and optionally redraw the chart
10896	 *
10897	 * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
10898	 * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
10899	 *    configuration
10900	 */
10901
10902	remove: function (redraw, animation) {
10903		var series = this,
10904			chart = series.chart;
10905		redraw = pick(redraw, true);
10906
10907		if (!series.isRemoving) {  /* prevent triggering native event in jQuery
10908				(calling the remove function from the remove event) */
10909			series.isRemoving = true;
10910
10911			// fire the event with a default handler of removing the point
10912			fireEvent(series, 'remove', null, function () {
10913
10914
10915				// destroy elements
10916				series.destroy();
10917
10918
10919				// redraw
10920				chart.isDirtyLegend = chart.isDirtyBox = true;
10921				if (redraw) {
10922					chart.redraw(animation);
10923				}
10924			});
10925
10926		}
10927		series.isRemoving = false;
10928	},
10929
10930	/**
10931	 * Process the data by cropping away unused data points if the series is longer
10932	 * than the crop threshold. This saves computing time for lage series.
10933	 */
10934	processData: function (force) {
10935		var series = this,
10936			processedXData = series.xData, // copied during slice operation below
10937			processedYData = series.yData,
10938			dataLength = processedXData.length,
10939			cropStart = 0,
10940			cropEnd = dataLength,
10941			cropped,
10942			distance,
10943			closestPointRange,
10944			xAxis = series.xAxis,
10945			i, // loop variable
10946			options = series.options,
10947			cropThreshold = options.cropThreshold,
10948			isCartesian = series.isCartesian;
10949
10950		// If the series data or axes haven't changed, don't go through this. Return false to pass
10951		// the message on to override methods like in data grouping. 
10952		if (isCartesian && !series.isDirty && !xAxis.isDirty && !series.yAxis.isDirty && !force) {
10953			return false;
10954		}
10955
10956		// optionally filter out points outside the plot area
10957		if (isCartesian && series.sorted && (!cropThreshold || dataLength > cropThreshold || series.forceCrop)) {
10958			var extremes = xAxis.getExtremes(),
10959				min = extremes.min,
10960				max = extremes.max;
10961
10962			// it's outside current extremes
10963			if (processedXData[dataLength - 1] < min || processedXData[0] > max) {
10964				processedXData = [];
10965				processedYData = [];
10966			
10967			// only crop if it's actually spilling out
10968			} else if (processedXData[0] < min || processedXData[dataLength - 1] > max) {
10969
10970				// iterate up to find slice start
10971				for (i = 0; i < dataLength; i++) {
10972					if (processedXData[i] >= min) {
10973						cropStart = mathMax(0, i - 1);
10974						break;
10975					}
10976				}
10977				// proceed to find slice end
10978				for (; i < dataLength; i++) {
10979					if (processedXData[i] > max) {
10980						cropEnd = i + 1;
10981						break;
10982					}
10983					
10984				}
10985				processedXData = processedXData.slice(cropStart, cropEnd);
10986				processedYData = processedYData.slice(cropStart, cropEnd);
10987				cropped = true;
10988			}
10989		}
10990		
10991		
10992		// Find the closest distance between processed points
10993		for (i = processedXData.length - 1; i > 0; i--) {
10994			distance = processedXData[i] - processedXData[i - 1];
10995			if (distance > 0 && (closestPointRange === UNDEFINED || distance < closestPointRange)) {
10996				closestPointRange = distance;
10997			}
10998		}
10999		
11000		// Record the properties
11001		series.cropped = cropped; // undefined or true
11002		series.cropStart = cropStart;
11003		series.processedXData = processedXData;
11004		series.processedYData = processedYData;
11005		
11006		if (options.pointRange === null) { // null means auto, as for columns, candlesticks and OHLC
11007			series.pointRange = closestPointRange || 1;
11008		}
11009		series.closestPointRange = closestPointRange;
11010		
11011	},
11012
11013	/**
11014	 * Generate the data point after the data has been processed by cropping away
11015	 * unused points and optionally grouped in Highcharts Stock.
11016	 */
11017	generatePoints: function () {
11018		var series = this,
11019			options = series.options,
11020			dataOptions = options.data,
11021			data = series.data,
11022			dataLength,
11023			processedXData = series.processedXData,
11024			processedYData = series.processedYData,
11025			pointClass = series.pointClass,
11026			processedDataLength = processedXData.length,
11027			cropStart = series.cropStart || 0,
11028			cursor,
11029			hasGroupedData = series.hasGroupedData,
11030			point,
11031			points = [],
11032			i;
11033
11034		if (!data && !hasGroupedData) {
11035			var arr = [];
11036			arr.length = dataOptions.length;
11037			data = series.data = arr;
11038		}
11039
11040		for (i = 0; i < processedDataLength; i++) {
11041			cursor = cropStart + i;
11042			if (!hasGroupedData) {
11043				if (data[cursor]) {
11044					point = data[cursor];
11045				} else {
11046					data[cursor] = point = (new pointClass()).init(series, dataOptions[cursor], processedXData[i]);
11047				}
11048				points[i] = point;
11049			} else {
11050				// splat the y data in case of ohlc data array
11051				points[i] = (new pointClass()).init(series, [processedXData[i]].concat(splat(processedYData[i])));
11052			}
11053		}
11054
11055		// Hide cropped-away points - this only runs when the number of points is above cropThreshold, or when
11056		// swithching view from non-grouped data to grouped data (#637)	
11057		if (data && (processedDataLength !== (dataLength = data.length) || hasGroupedData)) {
11058			for (i = 0; i < dataLength; i++) {
11059				if (i === cropStart && !hasGroupedData) { // when has grouped data, clear all points
11060					i += processedDataLength;
11061				}
11062				if (data[i]) {
11063					data[i].destroyElements();
11064				}
11065			}
11066		}
11067
11068		series.data = data;
11069		series.points = points;
11070	},
11071
11072	/**
11073	 * Translate data points from raw data values to chart specific positioning data
11074	 * needed later in drawPoints, drawGraph and drawTracker.
11075	 */
11076	translate: function () {
11077		if (!this.processedXData) { // hidden series
11078			this.processData();
11079		}
11080		this.generatePoints();
11081		var series = this,
11082			chart = series.chart,
11083			options = series.options,
11084			stacking = options.stacking,
11085			xAxis = series.xAxis,
11086			categories = xAxis.categories,
11087			yAxis = series.yAxis,
11088			points = series.points,
11089			dataLength = points.length,
11090			hasModifyValue = !!series.modifyValue,
11091			isLastSeries,
11092			allStackSeries = yAxis.series,
11093			i = allStackSeries.length;
11094			
11095		// Is it the last visible series?
11096		while (i--) {
11097			if (allStackSeries[i].visible) {
11098				if (i === series.index) {
11099					isLastSeries = true;
11100				}
11101				break;
11102			}
11103		}
11104		
11105		// Translate each point
11106		for (i = 0; i < dataLength; i++) {
11107			var point = points[i],
11108				xValue = point.x,
11109				yValue = point.y,
11110				yBottom = point.low,
11111				stack = yAxis.stacks[(yValue < options.threshold ? '-' : '') + series.stackKey],
11112				pointStack,
11113				pointStackTotal;
11114				
11115			// get the plotX translation
11116			point.plotX = mathRound(xAxis.translate(xValue, 0, 0, 0, 1) * 10) / 10; // Math.round fixes #591
11117
11118			// calculate the bottom y value for stacked series
11119			if (stacking && series.visible && stack && stack[xValue]) {
11120				pointStack = stack[xValue];
11121				pointStackTotal = pointStack.total;
11122				pointStack.cum = yBottom = pointStack.cum - yValue; // start from top
11123				yValue = yBottom + yValue;
11124				
11125				if (isLastSeries) {
11126					yBottom = options.threshold;
11127				}
11128				
11129				if (stacking === 'percent') {
11130					yBottom = pointStackTotal ? yBottom * 100 / pointStackTotal : 0;
11131					yValue = pointStackTotal ? yValue * 100 / pointStackTotal : 0;
11132				}
11133
11134				point.percentage = pointStackTotal ? point.y * 100 / pointStackTotal : 0;
11135				point.stackTotal = pointStackTotal;
11136				point.stackY = yValue;
11137			}
11138
11139			// Set translated yBottom or remove it
11140			point.yBottom = defined(yBottom) ? 
11141				yAxis.translate(yBottom, 0, 1, 0, 1) :
11142				null;
11143			
11144			// general hook, used for Highstock compare mode
11145			if (hasModifyValue) {
11146				yValue = series.modifyValue(yValue, point);
11147			}
11148
11149			// Set the the plotY value, reset it for redraws
11150			point.plotY = (typeof yValue === 'number') ? 
11151				mathRound(yAxis.translate(yValue, 0, 1, 0, 1) * 10) / 10 : // Math.round fixes #591
11152				UNDEFINED;
11153
11154			// set client related positions for mouse tracking
11155			point.clientX = chart.inverted ?
11156				chart.plotHeight - point.plotX :
11157				point.plotX; // for mouse tracking
11158
11159			// some API data
11160			point.category = categories && categories[point.x] !== UNDEFINED ?
11161				categories[point.x] : point.x;
11162
11163
11164		}
11165
11166		// now that we have the cropped data, build the segments
11167		series.getSegments();
11168	},
11169	/**
11170	 * Memoize tooltip texts and positions
11171	 */
11172	setTooltipPoints: function (renew) {
11173		var series = this,
11174			chart = series.chart,
11175			inverted = chart.inverted,
11176			points = [],
11177			pointsLength,
11178			plotSize = mathRound((inverted ? chart.plotTop : chart.plotLeft) + chart.plotSizeX),
11179			low,
11180			high,
11181			xAxis = series.xAxis,
11182			point,
11183			i,
11184			tooltipPoints = []; // a lookup array for each pixel in the x dimension
11185
11186		// don't waste resources if tracker is disabled
11187		if (series.options.enableMouseTracking === false) {
11188			return;
11189		}
11190
11191		// renew
11192		if (renew) {
11193			series.tooltipPoints = null;
11194		}
11195
11196		// concat segments to overcome null values
11197		each(series.segments || series.points, function (segment) {
11198			points = points.concat(segment);
11199		});
11200
11201		// loop the concatenated points and apply each point to all the closest
11202		// pixel positions
11203		if (xAxis && xAxis.reversed) {
11204			points = points.reverse();//reverseArray(points);
11205		}
11206
11207		//each(points, function (point, i) {
11208		pointsLength = points.length;
11209		for (i = 0; i < pointsLength; i++) {
11210			point = points[i];
11211			low = points[i - 1] ? points[i - 1]._high + 1 : 0;
11212			high = point._high = points[i + 1] ?
11213				(mathFloor((point.plotX + (points[i + 1] ? points[i + 1].plotX : plotSize)) / 2)) :
11214				plotSize;
11215
11216			while (low <= high) {
11217				tooltipPoints[inverted ? plotSize - low++ : low++] = point;
11218			}
11219		}
11220		series.tooltipPoints = tooltipPoints;
11221	},
11222
11223	/**
11224	 * Format the header of the tooltip
11225	 */
11226	tooltipHeaderFormatter: function (key) {
11227		var series = this,
11228			tooltipOptions = series.tooltipOptions,
11229			xDateFormat = tooltipOptions.xDateFormat || '%A, %b %e, %Y',
11230			xAxis = series.xAxis,
11231			isDateTime = xAxis && xAxis.options.type === 'datetime';
11232		
11233		return tooltipOptions.headerFormat
11234			.replace('{point.key}', isDateTime ? dateFormat(xDateFormat, key) :  key)
11235			.replace('{series.name}', series.name)
11236			.replace('{series.color}', series.color);
11237	},
11238
11239	/**
11240	 * Series mouse over handler
11241	 */
11242	onMouseOver: function () {
11243		var series = this,
11244			chart = series.chart,
11245			hoverSeries = chart.hoverSeries;
11246
11247		if (!hasTouch && chart.mouseIsDown) {
11248			return;
11249		}
11250
11251		// set normal state to previous series
11252		if (hoverSeries && hoverSeries !== series) {
11253			hoverSeries.onMouseOut();
11254		}
11255
11256		// trigger the event, but to save processing time,
11257		// only if defined
11258		if (series.options.events.mouseOver) {
11259			fireEvent(series, 'mouseOver');
11260		}
11261
11262		// hover this
11263		series.setState(HOVER_STATE);
11264		chart.hoverSeries = series;
11265	},
11266
11267	/**
11268	 * Series mouse out handler
11269	 */
11270	onMouseOut: function () {
11271		// trigger the event only if listeners exist
11272		var series = this,
11273			options = series.options,
11274			chart = series.chart,
11275			tooltip = chart.tooltip,
11276			hoverPoint = chart.hoverPoint;
11277
11278		// trigger mouse out on the point, which must be in this series
11279		if (hoverPoint) {
11280			hoverPoint.onMouseOut();
11281		}
11282
11283		// fire the mouse out event
11284		if (series && options.events.mouseOut) {
11285			fireEvent(series, 'mouseOut');
11286		}
11287
11288
11289		// hide the tooltip
11290		if (tooltip && !options.stickyTracking && !tooltip.shared) {
11291			tooltip.hide();
11292		}
11293
11294		// set normal state
11295		series.setState();
11296		chart.hoverSeries = null;
11297	},
11298
11299	/**
11300	 * Animate in the series
11301	 */
11302	animate: function (init) {
11303		var series = this,
11304			chart = series.chart,
11305			clipRect = series.clipRect,
11306			animation = series.options.animation;
11307
11308		if (animation && !isObject(animation)) {
11309			animation = {};
11310		}
11311
11312		if (init) { // initialize the animation
11313			if (!clipRect.isAnimating) { // apply it only for one of the series
11314				clipRect.attr('width', 0);
11315				clipRect.isAnimating = true;
11316			}
11317
11318		} else { // run the animation
11319			clipRect.animate({
11320				width: chart.plotSizeX
11321			}, animation);
11322
11323			// delete this function to allow it only once
11324			this.animate = null;
11325		}
11326	},
11327
11328
11329	/**
11330	 * Draw the markers
11331	 */
11332	drawPoints: function () {
11333		var series = this,
11334			pointAttr,
11335			points = series.points,
11336			chart = series.chart,
11337			plotX,
11338			plotY,
11339			i,
11340			point,
11341			radius,
11342			symbol,
11343			isImage,
11344			graphic;
11345
11346		if (series.options.marker.enabled) {
11347			i = points.length;
11348			while (i--) {
11349				point = points[i];
11350				plotX = point.plotX;
11351				plotY = point.plotY;
11352				graphic = point.graphic;
11353
11354				// only draw the point if y is defined
11355				if (plotY !== UNDEFINED && !isNaN(plotY)) {
11356
11357					// shortcuts
11358					pointAttr = point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE];
11359					radius = pointAttr.r;
11360					symbol = pick(point.marker && point.marker.symbol, series.symbol);
11361					isImage = symbol.indexOf('url') === 0;
11362
11363					if (graphic) { // update
11364						graphic.animate(extend({
11365							x: plotX - radius,
11366							y: plotY - radius
11367						}, graphic.symbolName ? { // don't apply to image symbols #507
11368							width: 2 * radius,
11369							height: 2 * radius
11370						} : {}));
11371					} else if (radius > 0 || isImage) {
11372						point.graphic = chart.renderer.symbol(
11373							symbol,
11374							plotX - radius,
11375							plotY - radius,
11376							2 * radius,
11377							2 * radius
11378						)
11379						.attr(pointAttr)
11380						.add(series.group);
11381					}
11382				}
11383			}
11384		}
11385
11386	},
11387
11388	/**
11389	 * Convert state properties from API naming conventions to SVG attributes
11390	 *
11391	 * @param {Object} options API options object
11392	 * @param {Object} base1 SVG attribute object to inherit from
11393	 * @param {Object} base2 Second level SVG attribute object to inherit from
11394	 */
11395	convertAttribs: function (options, base1, base2, base3) {
11396		var conversion = this.pointAttrToOptions,
11397			attr,
11398			option,
11399			obj = {};
11400
11401		options = options || {};
11402		base1 = base1 || {};
11403		base2 = base2 || {};
11404		base3 = base3 || {};
11405
11406		for (attr in conversion) {
11407			option = conversion[attr];
11408			obj[attr] = pick(options[option], base1[attr], base2[attr], base3[attr]);
11409		}
11410		return obj;
11411	},
11412
11413	/**
11414	 * Get the state attributes. Each series type has its own set of attributes
11415	 * that are allowed to change on a point's state change. Series wide attributes are stored for
11416	 * all series, and additionally point specific attributes are stored for all
11417	 * points with individual marker options. If such options are not defined for the point,
11418	 * a reference to the series wide attributes is stored in point.pointAttr.
11419	 */
11420	getAttribs: function () {
11421		var series = this,
11422			normalOptions = defaultPlotOptions[series.type].marker ? series.options.marker : series.options,
11423			stateOptions = normalOptions.states,
11424			stateOptionsHover = stateOptions[HOVER_STATE],
11425			pointStateOptionsHover,
11426			seriesColor = series.color,
11427			normalDefaults = {
11428				stroke: seriesColor,
11429				fill: seriesColor
11430			},
11431			points = series.points,
11432			i,
11433			point,
11434			seriesPointAttr = [],
11435			pointAttr,
11436			pointAttrToOptions = series.pointAttrToOptions,
11437			hasPointSpecificOptions,
11438			key;
11439
11440		// series type specific modifications
11441		if (series.options.marker) { // line, spline, area, areaspline, scatter
11442
11443			// if no hover radius is given, default to normal radius + 2
11444			stateOptionsHover.radius = stateOptionsHover.radius || normalOptions.radius + 2;
11445			stateOptionsHover.lineWidth = stateOptionsHover.lineWidth || normalOptions.lineWidth + 1;
11446
11447		} else { // column, bar, pie
11448
11449			// if no hover color is given, brighten the normal color
11450			stateOptionsHover.color = stateOptionsHover.color ||
11451				Color(stateOptionsHover.color || seriesColor)
11452					.brighten(stateOptionsHover.brightness).get();
11453		}
11454
11455		// general point attributes for the series normal state
11456		seriesPointAttr[NORMAL_STATE] = series.convertAttribs(normalOptions, normalDefaults);
11457
11458		// HOVER_STATE and SELECT_STATE states inherit from normal state except the default radius
11459		each([HOVER_STATE, SELECT_STATE], function (state) {
11460			seriesPointAttr[state] =
11461					series.convertAttribs(stateOptions[state], seriesPointAttr[NORMAL_STATE]);
11462		});
11463
11464		// set it
11465		series.pointAttr = seriesPointAttr;
11466
11467
11468		// Generate the point-specific attribute collections if specific point
11469		// options are given. If not, create a referance to the series wide point
11470		// attributes
11471		i = points.length;
11472		while (i--) {
11473			point = points[i];
11474			normalOptions = (point.options && point.options.marker) || point.options;
11475			if (normalOptions && normalOptions.enabled === false) {
11476				normalOptions.radius = 0;
11477			}
11478			hasPointSpecificOptions = false;
11479
11480			// check if the point has specific visual options
11481			if (point.options) {
11482				for (key in pointAttrToOptions) {
11483					if (defined(normalOptions[pointAttrToOptions[key]])) {
11484						hasPointSpecificOptions = true;
11485					}
11486				}
11487			}
11488
11489
11490
11491			// a specific marker config object is defined for the individual point:
11492			// create it's own attribute collection
11493			if (hasPointSpecificOptions) {
11494
11495				pointAttr = [];
11496				stateOptions = normalOptions.states || {}; // reassign for individual point
11497				pointStateOptionsHover = stateOptions[HOVER_STATE] = stateOptions[HOVER_STATE] || {};
11498
11499				// if no hover color is given, brighten the normal color
11500				if (!series.options.marker) { // column, bar, point
11501					pointStateOptionsHover.color =
11502						Color(pointStateOptionsHover.color || point.options.color)
11503							.brighten(pointStateOptionsHover.brightness ||
11504								stateOptionsHover.brightness).get();
11505
11506				}
11507
11508				// normal point state inherits series wide normal state
11509				pointAttr[NORMAL_STATE] = series.convertAttribs(normalOptions, seriesPointAttr[NORMAL_STATE]);
11510
11511				// inherit from point normal and series hover
11512				pointAttr[HOVER_STATE] = series.convertAttribs(
11513					stateOptions[HOVER_STATE],
11514					seriesPointAttr[HOVER_STATE],
11515					pointAttr[NORMAL_STATE]
11516				);
11517				// inherit from point normal and series hover
11518				pointAttr[SELECT_STATE] = series.convertAttribs(
11519					stateOptions[SELECT_STATE],
11520					seriesPointAttr[SELECT_STATE],
11521					pointAttr[NORMAL_STATE]
11522				);
11523
11524
11525
11526			// no marker config object is created: copy a reference to the series-wide
11527			// attribute collection
11528			} else {
11529				pointAttr = seriesPointAttr;
11530			}
11531
11532			point.pointAttr = pointAttr;
11533
11534		}
11535
11536	},
11537
11538
11539	/**
11540	 * Clear DOM objects and free up memory
11541	 */
11542	destroy: function () {
11543		var series = this,
11544			chart = series.chart,
11545			seriesClipRect = series.clipRect,
11546			issue134 = /AppleWebKit\/533/.test(userAgent),
11547			destroy,
11548			i,
11549			data = series.data || [],
11550			point,
11551			prop,
11552			axis;
11553
11554		// add event hook
11555		fireEvent(series, 'destroy');
11556
11557		// remove all events
11558		removeEvent(series);
11559		
11560		// erase from axes
11561		each(['xAxis', 'yAxis'], function (AXIS) {
11562			axis = series[AXIS];
11563			if (axis) {
11564				erase(axis.series, series);
11565				axis.isDirty = true;
11566			}
11567		});
11568
11569		// remove legend items
11570		if (series.legendItem) {
11571			series.chart.legend.destroyItem(series);
11572		}
11573
11574		// destroy all points with their elements
11575		i = data.length;
11576		while (i--) {
11577			point = data[i];
11578			if (point && point.destroy) {
11579				point.destroy();
11580			}
11581		}
11582		series.points = null;
11583
11584		// If this series clipRect is not the global one (which is removed on chart.destroy) we
11585		// destroy it here.
11586		if (seriesClipRect && seriesClipRect !== chart.clipRect) {
11587			series.clipRect = seriesClipRect.destroy();
11588		}
11589
11590		// destroy all SVGElements associated to the series
11591		each(['area', 'graph', 'dataLabelsGroup', 'group', 'tracker'], function (prop) {
11592			if (series[prop]) {
11593
11594				// issue 134 workaround
11595				destroy = issue134 && prop === 'group' ?
11596					'hide' :
11597					'destroy';
11598
11599				series[prop][destroy]();
11600			}
11601		});
11602
11603		// remove from hoverSeries
11604		if (chart.hoverSeries === series) {
11605			chart.hoverSeries = null;
11606		}
11607		erase(chart.series, series);
11608
11609		// clear all members
11610		for (prop in series) {
11611			delete series[prop];
11612		}
11613	},
11614
11615	/**
11616	 * Draw the data labels
11617	 */
11618	drawDataLabels: function () {
11619		
11620		var series = this,
11621			seriesOptions = series.options,
11622			options = seriesOptions.dataLabels;
11623		
11624		if (options.enabled || series._hasPointLabels) {
11625			var x,
11626				y,
11627				points = series.points,
11628				pointOptions,
11629				generalOptions,
11630				str,
11631				dataLabelsGroup = series.dataLabelsGroup,
11632				chart = series.chart,
11633				xAxis = series.xAxis,
11634				groupLeft = xAxis ? xAxis.left : chart.plotLeft,
11635				yAxis = series.yAxis,
11636				groupTop = yAxis ? yAxis.top : chart.plotTop,
11637				renderer = chart.renderer,
11638				inverted = chart.inverted,
11639				seriesType = series.type,
11640				stacking = seriesOptions.stacking,
11641				isBarLike = seriesType === 'column' || seriesType === 'bar',
11642				vAlignIsNull = options.verticalAlign === null,
11643				yIsNull = options.y === null,
11644				fontMetrics = renderer.fontMetrics(options.style.fontSize), // height and baseline
11645				fontLineHeight = fontMetrics.h,
11646				fontBaseline = fontMetrics.b,
11647				dataLabel,
11648				enabled;
11649
11650			if (isBarLike) {
11651				var defaultYs = {
11652					top: fontBaseline, 
11653					middle: fontBaseline - fontLineHeight / 2, 
11654					bottom: -fontLineHeight + fontBaseline
11655				};
11656				if (stacking) {
11657					// In stacked series the default label placement is inside the bars
11658					if (vAlignIsNull) {
11659						options = merge(options, {verticalAlign: 'middle'});
11660					}
11661
11662					// If no y delta is specified, try to create a good default
11663					if (yIsNull) {
11664						options = merge(options, { y: defaultYs[options.verticalAlign]});
11665					}
11666				} else {
11667					// In non stacked series the default label placement is on top of the bars
11668					if (vAlignIsNull) {
11669						options = merge(options, {verticalAlign: 'top'});
11670					
11671					// If no y delta is specified, try to create a good default (like default bar)
11672					} else if (yIsNull) {
11673						options = merge(options, { y: defaultYs[options.verticalAlign]});
11674					}
11675					
11676				}
11677			}
11678
11679
11680			// create a separate group for the data labels to avoid rotation
11681			if (!dataLabelsGroup) {
11682				dataLabelsGroup = series.dataLabelsGroup =
11683					renderer.g('data-labels')
11684						.attr({
11685							visibility: series.visible ? VISIBLE : HIDDEN,
11686							zIndex: 6
11687						})
11688						.translate(groupLeft, groupTop)
11689						.add();
11690			} else {
11691				dataLabelsGroup.translate(groupLeft, groupTop);
11692			}
11693			
11694			// make the labels for each point
11695			generalOptions = options;
11696			each(points, function (point) {
11697				
11698				dataLabel = point.dataLabel;
11699				
11700				// Merge in individual options from point
11701				options = generalOptions; // reset changes from previous points
11702				pointOptions = point.options;
11703				if (pointOptions && pointOptions.dataLabels) {
11704					options = merge(options, pointOptions.dataLabels);
11705				}
11706				enabled = options.enabled;
11707				
11708				// Get the positions
11709				if (enabled) {
11710					var plotX = (point.barX && point.barX + point.barW / 2) || pick(point.plotX, -999),
11711						plotY = pick(point.plotY, -999),
11712						
11713						// if options.y is null, which happens by default on column charts, set the position
11714						// above or below the column depending on the threshold
11715						individualYDelta = options.y === null ? 
11716							(point.y >= seriesOptions.threshold ? 
11717								-fontLineHeight + fontBaseline : // below the threshold 
11718								fontBaseline) : // above the threshold
11719							options.y;
11720					
11721					x = (inverted ? chart.plotWidth - plotY : plotX) + options.x;
11722					y = mathRound((inverted ? chart.plotHeight - plotX : plotY) + individualYDelta);
11723					
11724				}
11725				
11726				// If the point is outside the plot area, destroy it. #678, #820
11727				if (dataLabel && series.isCartesian && (!chart.isInsidePlot(x, y) || !enabled)) {
11728					point.dataLabel = dataLabel.destroy();
11729				
11730				// Individual labels are disabled if the are explicitly disabled 
11731				// in the point options, or if they fall outside the plot area.
11732				} else if (enabled) {
11733					
11734					var align = options.align;
11735				
11736					// Get the string
11737					str = options.formatter.call(point.getLabelConfig(), options);
11738					
11739					// in columns, align the string to the column
11740					if (seriesType === 'column') {
11741						x += { left: -1, right: 1 }[align] * point.barW / 2 || 0;
11742					}
11743	
11744					if (!stacking && inverted && point.y < 0) {
11745						align = 'right';
11746						x -= 10;
11747					}
11748					
11749					// Determine the color
11750					options.style.color = pick(options.color, options.style.color, series.color, 'black');
11751	
11752					
11753					// update existing label
11754					if (dataLabel) {
11755						// vertically centered
11756						dataLabel
11757							.attr({
11758								text: str
11759							}).animate({
11760								x: x,
11761								y: y
11762							});
11763					// create new label
11764					} else if (defined(str)) {
11765						dataLabel = point.dataLabel = renderer[options.rotation ? 'text' : 'label']( // labels don't support rotation
11766							str,
11767							x,
11768							y,
11769							null,
11770							null,
11771							null,
11772							options.useHTML,
11773							true // baseline for backwards compat
11774						)
11775						.attr({
11776							align: align,
11777							fill: options.backgroundColor,
11778							stroke: options.borderColor,
11779							'stroke-width': options.borderWidth,
11780							r: options.borderRadius,
11781							rotation: options.rotation,
11782							padding: options.padding,
11783							zIndex: 1
11784						})
11785						.css(options.style)
11786						.add(dataLabelsGroup)
11787						.shadow(options.shadow);
11788					}
11789	
11790					if (isBarLike && seriesOptions.stacking && dataLabel) {
11791						var barX = point.barX,
11792							barY = point.barY,
11793							barW = point.barW,
11794							barH = point.barH;
11795	
11796						dataLabel.align(options, null,
11797							{
11798								x: inverted ? chart.plotWidth - barY - barH : barX,
11799								y: inverted ? chart.plotHeight - barX - barW : barY,
11800								width: inverted ? barH : barW,
11801								height: inverted ? barW : barH
11802							});
11803					}
11804					
11805					
11806				}
11807			});
11808		}
11809	},
11810
11811	/**
11812	 * Draw the actual graph
11813	 */
11814	drawGraph: function () {
11815		var series = this,
11816			options = series.options,
11817			chart = series.chart,
11818			graph = series.graph,
11819			graphPath = [],
11820			fillColor,
11821			area = series.area,
11822			group = series.group,
11823			color = options.lineColor || series.color,
11824			lineWidth = options.lineWidth,
11825			dashStyle =  options.dashStyle,
11826			segmentPath,
11827			renderer = chart.renderer,
11828			translatedThreshold = series.yAxis.getThreshold(options.threshold),
11829			useArea = /^area/.test(series.type),
11830			singlePoints = [], // used in drawTracker
11831			areaPath = [],
11832			attribs;
11833
11834
11835		// divide into segments and build graph and area paths
11836		each(series.segments, function (segment) {
11837			segmentPath = [];
11838
11839			// build the segment line
11840			each(segment, function (point, i) {
11841
11842				if (series.getPointSpline) { // generate the spline as defined in the SplineSeries object
11843					segmentPath.push.apply(segmentPath, series.getPointSpline(segment, point, i));
11844
11845				} else {
11846
11847					// moveTo or lineTo
11848					segmentPath.push(i ? L : M);
11849
11850					// step line?
11851					if (i && options.step) {
11852						var lastPoint = segment[i - 1];
11853						segmentPath.push(
11854							point.plotX,
11855							lastPoint.plotY
11856						);
11857					}
11858
11859					// normal line to next point
11860					segmentPath.push(
11861						point.plotX,
11862						point.plotY
11863					);
11864				}
11865			});
11866
11867			// add the segment to the graph, or a single point for tracking
11868			if (segment.length > 1) {
11869				graphPath = graphPath.concat(segmentPath);
11870			} else {
11871				singlePoints.push(segment[0]);
11872			}
11873
11874			// build the area
11875			if (useArea) {
11876				var areaSegmentPath = [],
11877					i,
11878					segLength = segmentPath.length;
11879				for (i = 0; i < segLength; i++) {
11880					areaSegmentPath.push(segmentPath[i]);
11881				}
11882				if (segLength === 3) { // for animation from 1 to two points
11883					areaSegmentPath.push(L, segmentPath[1], segmentPath[2]);
11884				}
11885				if (options.stacking && series.type !== 'areaspline') {
11886					
11887					// Follow stack back. Todo: implement areaspline. A general solution could be to 
11888					// reverse the entire graphPath of the previous series, though may be hard with
11889					// splines and with series with different extremes
11890					for (i = segment.length - 1; i >= 0; i--) {
11891					
11892						// step line?
11893						if (i < segment.length - 1 && options.step) {
11894							areaSegmentPath.push(segment[i + 1].plotX, segment[i].yBottom);
11895						}
11896						
11897						areaSegmentPath.push(segment[i].plotX, segment[i].yBottom);
11898					}
11899
11900				} else { // follow zero line back
11901					areaSegmentPath.push(
11902						L,
11903						segment[segment.length - 1].plotX,
11904						translatedThreshold,
11905						L,
11906						segment[0].plotX,
11907						translatedThreshold
11908					);
11909				}
11910				areaPath = areaPath.concat(areaSegmentPath);
11911			}
11912		});
11913
11914		// used in drawTracker:
11915		series.graphPath = graphPath;
11916		series.singlePoints = singlePoints;
11917
11918		// draw the area if area series or areaspline
11919		if (useArea) {
11920			fillColor = pick(
11921				options.fillColor,
11922				Color(series.color).setOpacity(options.fillOpacity || 0.75).get()
11923			);
11924			if (area) {
11925				area.animate({ d: areaPath });
11926
11927			} else {
11928				// draw the area
11929				series.area = series.chart.renderer.path(areaPath)
11930					.attr({
11931						fill: fillColor
11932					}).add(group);
11933			}
11934		}
11935
11936		// draw the graph
11937		if (graph) {
11938			stop(graph); // cancel running animations, #459
11939			graph.animate({ d: graphPath });
11940
11941		} else {
11942			if (lineWidth) {
11943				attribs = {
11944					'stroke': color,
11945					'stroke-width': lineWidth
11946				};
11947				if (dashStyle) {
11948					attribs.dashstyle = dashStyle;
11949				}
11950
11951				series.graph = renderer.path(graphPath)
11952					.attr(attribs).add(group).shadow(options.shadow);
11953			}
11954		}
11955	},
11956
11957	/**
11958	 * Initialize and perform group inversion on series.group and series.trackerGroup
11959	 */
11960	invertGroups: function () {
11961		var series = this,
11962			group = series.group,
11963			trackerGroup = series.trackerGroup,
11964			chart = series.chart;
11965		
11966		// A fixed size is needed for inversion to work
11967		function setInvert() {			
11968			var size = {
11969				width: series.yAxis.len,
11970				height: series.xAxis.len
11971			};
11972			
11973			// Set the series.group size
11974			group.attr(size).invert();
11975			
11976			// Set the tracker group size
11977			if (trackerGroup) {
11978				trackerGroup.attr(size).invert();
11979			}
11980		}
11981
11982		addEvent(chart, 'resize', setInvert); // do it on resize
11983		addEvent(series, 'destroy', function () {
11984			removeEvent(chart, 'resize', setInvert);
11985		});
11986
11987		// Do it now
11988		setInvert(); // do it now
11989		
11990		// On subsequent render and redraw, just do setInvert without setting up events again
11991		series.invertGroups = setInvert;
11992	},
11993
11994	/**
11995	 * Render the graph and markers
11996	 */
11997	render: function () {
11998		var series = this,
11999			chart = series.chart,
12000			group,
12001			options = series.options,
12002			doClip = options.clip !== false,
12003			animation = options.animation,
12004			doAnimation = animation && series.animate,
12005			duration = doAnimation ? (animation && animation.duration) || 500 : 0,
12006			clipRect = series.clipRect,
12007			renderer = chart.renderer;
12008
12009
12010		// Add plot area clipping rectangle. If this is before chart.hasRendered,
12011		// create one shared clipRect.
12012
12013		// Todo: since creating the clip property, the clipRect is created but
12014		// never used when clip is false. A better way would be that the animation
12015		// would run, then the clipRect destroyed.
12016		if (!clipRect) {
12017			clipRect = series.clipRect = !chart.hasRendered && chart.clipRect ?
12018				chart.clipRect :
12019				renderer.clipRect(0, 0, chart.plotSizeX, chart.plotSizeY + 1);
12020			if (!chart.clipRect) {
12021				chart.clipRect = clipRect;
12022			}
12023		}
12024		
12025
12026		// the group
12027		if (!series.group) {
12028			group = series.group = renderer.g('series');
12029
12030			group.attr({
12031					visibility: series.visible ? VISIBLE : HIDDEN,
12032					zIndex: options.zIndex
12033				})
12034				.translate(series.xAxis.left, series.yAxis.top)
12035				.add(chart.seriesGroup);
12036		}
12037
12038		series.drawDataLabels();
12039
12040		// initiate the animation
12041		if (doAnimation) {
12042			series.animate(true);
12043		}
12044
12045		// cache attributes for shapes
12046		series.getAttribs();
12047
12048		// draw the graph if any
12049		if (series.drawGraph) {
12050			series.drawGraph();
12051		}
12052
12053		// draw the points
12054		series.drawPoints();
12055
12056		// draw the mouse tracking area
12057		if (series.options.enableMouseTracking !== false) {
12058			series.drawTracker();
12059		}
12060		
12061		// Handle inverted series and tracker groups
12062		if (chart.inverted) {
12063			series.invertGroups();
12064		}
12065		
12066		// Do the initial clipping. This must be done after inverting for VML.
12067		if (doClip && !series.hasRendered) {
12068			group.clip(clipRect);
12069			if (series.trackerGroup) {
12070				series.trackerGroup.clip(chart.clipRect);
12071			}
12072		}
12073			
12074
12075		// run the animation
12076		if (doAnimation) {
12077			series.animate();
12078		}
12079
12080		// finish the individual clipRect
12081		setTimeout(function () {
12082			clipRect.isAnimating = false;
12083			group = series.group; // can be destroyed during the timeout
12084			if (group && clipRect !== chart.clipRect && clipRect.renderer) {
12085				if (doClip) {
12086					group.clip((series.clipRect = chart.clipRect));
12087				}
12088				clipRect.destroy();
12089			}
12090		}, duration);
12091
12092		series.isDirty = series.isDirtyData = false; // means data is in accordance with what you see
12093		// (See #322) series.isDirty = series.isDirtyData = false; // means data is in accordance with what you see
12094		series.hasRendered = true;
12095	},
12096
12097	/**
12098	 * Redraw the series after an update in the axes.
12099	 */
12100	redraw: function () {
12101		var series = this,
12102			chart = series.chart,
12103			wasDirtyData = series.isDirtyData, // cache it here as it is set to false in render, but used after
12104			group = series.group;
12105
12106		// reposition on resize
12107		if (group) {
12108			if (chart.inverted) {
12109				group.attr({
12110					width: chart.plotWidth,
12111					height: chart.plotHeight
12112				});
12113			}
12114
12115			group.animate({
12116				translateX: series.xAxis.left,
12117				translateY: series.yAxis.top
12118			});
12119		}
12120
12121		series.translate();
12122		series.setTooltipPoints(true);
12123
12124		series.render();
12125		if (wasDirtyData) {
12126			fireEvent(series, 'updatedData');
12127		}
12128	},
12129
12130	/**
12131	 * Set the state of the graph
12132	 */
12133	setState: function (state) {
12134		var series = this,
12135			options = series.options,
12136			graph = series.graph,
12137			stateOptions = options.states,
12138			lineWidth = options.lineWidth;
12139
12140		state = state || NORMAL_STATE;
12141
12142		if (series.state !== state) {
12143			series.state = state;
12144
12145			if (stateOptions[state] && stateOptions[state].enabled === false) {
12146				return;
12147			}
12148
12149			if (state) {
12150				lineWidth = stateOptions[state].lineWidth || lineWidth + 1;
12151			}
12152
12153			if (graph && !graph.dashstyle) { // hover is turned off for dashed lines in VML
12154				graph.attr({ // use attr because animate will cause any other animation on the graph to stop
12155					'stroke-width': lineWidth
12156				}, state ? 0 : 500);
12157			}
12158		}
12159	},
12160
12161	/**
12162	 * Set the visibility of the graph
12163	 *
12164	 * @param vis {Boolean} True to show the series, false to hide. If UNDEFINED,
12165	 *        the visibility is toggled.
12166	 */
12167	setVisible: function (vis, redraw) {
12168		var series = this,
12169			chart = series.chart,
12170			legendItem = series.legendItem,
12171			seriesGroup = series.group,
12172			seriesTracker = series.tracker,
12173			dataLabelsGroup = series.dataLabelsGroup,
12174			showOrHide,
12175			i,
12176			points = series.points,
12177			point,
12178			ignoreHiddenSeries = chart.options.chart.ignoreHiddenSeries,
12179			oldVisibility = series.visible;
12180
12181		// if called without an argument, toggle visibility
12182		series.visible = vis = vis === UNDEFINED ? !oldVisibility : vis;
12183		showOrHide = vis ? 'show' : 'hide';
12184
12185		// show or hide series
12186		if (seriesGroup) { // pies don't have one
12187			seriesGroup[showOrHide]();
12188		}
12189
12190		// show or hide trackers
12191		if (seriesTracker) {
12192			seriesTracker[showOrHide]();
12193		} else if (points) {
12194			i = points.length;
12195			while (i--) {
12196				point = points[i];
12197				if (point.tracker) {
12198					point.tracker[showOrHide]();
12199				}
12200			}
12201		}
12202
12203
12204		if (dataLabelsGroup) {
12205			dataLabelsGroup[showOrHide]();
12206		}
12207
12208		if (legendItem) {
12209			chart.legend.colorizeItem(series, vis);
12210		}
12211
12212
12213		// rescale or adapt to resized chart
12214		series.isDirty = true;
12215		// in a stack, all other series are affected
12216		if (series.options.stacking) {
12217			each(chart.series, function (otherSeries) {
12218				if (otherSeries.options.stacking && otherSeries.visible) {
12219					otherSeries.isDirty = true;
12220				}
12221			});
12222		}
12223
12224		if (ignoreHiddenSeries) {
12225			chart.isDirtyBox = true;
12226		}
12227		if (redraw !== false) {
12228			chart.redraw();
12229		}
12230
12231		fireEvent(series, showOrHide);
12232	},
12233
12234	/**
12235	 * Show the graph
12236	 */
12237	show: function () {
12238		this.setVisible(true);
12239	},
12240
12241	/**
12242	 * Hide the graph
12243	 */
12244	hide: function () {
12245		this.setVisible(false);
12246	},
12247
12248
12249	/**
12250	 * Set the selected state of the graph
12251	 *
12252	 * @param selected {Boolean} True to select the series, false to unselect. If
12253	 *        UNDEFINED, the selection state is toggled.
12254	 */
12255	select: function (selected) {
12256		var series = this;
12257		// if called without an argument, toggle
12258		series.selected = selected = (selected === UNDEFINED) ? !series.selected : selected;
12259
12260		if (series.checkbox) {
12261			series.checkbox.checked = selected;
12262		}
12263
12264		fireEvent(series, selected ? 'select' : 'unselect');
12265	},
12266
12267	/**
12268	 * Create a group that holds the tracking object or objects. This allows for
12269	 * individual clipping and placement of each series tracker.
12270	 */
12271	drawTrackerGroup: function () {
12272		var trackerGroup = this.trackerGroup,
12273			chart = this.chart;
12274		
12275		if (this.isCartesian) {
12276		
12277			// Generate it on first call
12278			if (!trackerGroup) {	
12279				this.trackerGroup = trackerGroup = chart.renderer.g()
12280					.attr({
12281						zIndex: this.options.zIndex || 1
12282					})
12283					.add(chart.trackerGroup);
12284					
12285			}
12286			// Place it on first and subsequent (redraw) calls
12287			trackerGroup.translate(this.xAxis.left, this.yAxis.top);
12288			
12289		}
12290		
12291		return trackerGroup;
12292	},
12293	
12294	/**
12295	 * Draw the tracker object that sits above all data labels and markers to
12296	 * track mouse events on the graph or points. For the line type charts
12297	 * the tracker uses the same graphPath, but with a greater stroke width
12298	 * for better control.
12299	 */
12300	drawTracker: function () {
12301		var series = this,
12302			options = series.options,
12303			trackerPath = [].concat(series.graphPath),
12304			trackerPathLength = trackerPath.length,
12305			chart = series.chart,
12306			renderer = chart.renderer,
12307			snap = chart.options.tooltip.snap,
12308			tracker = series.tracker,
12309			cursor = options.cursor,
12310			css = cursor && { cursor: cursor },
12311			singlePoints = series.singlePoints,
12312			trackerGroup = series.drawTrackerGroup(),
12313			singlePoint,
12314			i;
12315
12316		// Extend end points. A better way would be to use round linecaps,
12317		// but those are not clickable in VML.
12318		if (trackerPathLength) {
12319			i = trackerPathLength + 1;
12320			while (i--) {
12321				if (trackerPath[i] === M) { // extend left side
12322					trackerPath.splice(i + 1, 0, trackerPath[i + 1] - snap, trackerPath[i + 2], L);
12323				}
12324				if ((i && trackerPath[i] === M) || i === trackerPathLength) { // extend right side
12325					trackerPath.splice(i, 0, L, trackerPath[i - 2] + snap, trackerPath[i - 1]);
12326				}
12327			}
12328		}
12329
12330		// handle single points
12331		for (i = 0; i < singlePoints.length; i++) {
12332			singlePoint = singlePoints[i];
12333			trackerPath.push(M, singlePoint.plotX - snap, singlePoint.plotY,
12334				L, singlePoint.plotX + snap, singlePoint.plotY);
12335		}
12336		
12337		
12338
12339		// draw the tracker
12340		if (tracker) {
12341			tracker.attr({ d: trackerPath });
12342
12343		} else { // create
12344				
12345			series.tracker = renderer.path(trackerPath)
12346				.attr({
12347					isTracker: true,
12348					stroke: TRACKER_FILL,
12349					fill: NONE,
12350					'stroke-linejoin': 'bevel',
12351					'stroke-width' : options.lineWidth + 2 * snap,
12352					visibility: series.visible ? VISIBLE : HIDDEN
12353				})
12354				.on(hasTouch ? 'touchstart' : 'mouseover', function () {
12355					if (chart.hoverSeries !== series) {
12356						series.onMouseOver();
12357					}
12358				})
12359				.on('mouseout', function () {
12360					if (!options.stickyTracking) {
12361						series.onMouseOut();
12362					}
12363				})
12364				.css(css)
12365				.add(trackerGroup);
12366		}
12367
12368	}
12369
12370}; // end Series prototype
12371
12372
12373/**
12374 * LineSeries object
12375 */
12376var LineSeries = extendClass(Series);
12377seriesTypes.line = LineSeries;
12378
12379/**
12380 * AreaSeries object
12381 */
12382var AreaSeries = extendClass(Series, {
12383	type: 'area'
12384});
12385seriesTypes.area = AreaSeries;
12386
12387
12388
12389
12390/**
12391 * SplineSeries object
12392 */
12393var SplineSeries = extendClass(Series, {
12394	type: 'spline',
12395
12396	/**
12397	 * Draw the actual graph
12398	 */
12399	getPointSpline: function (segment, point, i) {
12400		var smoothing = 1.5, // 1 means control points midway between points, 2 means 1/3 from the point, 3 is 1/4 etc
12401			denom = smoothing + 1,
12402			plotX = point.plotX,
12403			plotY = point.plotY,
12404			lastPoint = segment[i - 1],
12405			nextPoint = segment[i + 1],
12406			leftContX,
12407			leftContY,
12408			rightContX,
12409			rightContY,
12410			ret;
12411
12412		// find control points
12413		if (i && i < segment.length - 1) {
12414			var lastX = lastPoint.plotX,
12415				lastY = lastPoint.plotY,
12416				nextX = nextPoint.plotX,
12417				nextY = nextPoint.plotY,
12418				correction;
12419
12420			leftContX = (smoothing * plotX + lastX) / denom;
12421			leftContY = (smoothing * plotY + lastY) / denom;
12422			rightContX = (smoothing * plotX + nextX) / denom;
12423			rightContY = (smoothing * plotY + nextY) / denom;
12424
12425			// have the two control points make a straight line through main point
12426			correction = ((rightContY - leftContY) * (rightContX - plotX)) /
12427				(rightContX - leftContX) + plotY - rightContY;
12428
12429			leftContY += correction;
12430			rightContY += correction;
12431
12432			// to prevent false extremes, check that control points are between
12433			// neighbouring points' y values
12434			if (leftContY > lastY && leftContY > plotY) {
12435				leftContY = mathMax(lastY, plotY);
12436				rightContY = 2 * plotY - leftContY; // mirror of left control point
12437			} else if (leftContY < lastY && leftContY < plotY) {
12438				leftContY = mathMin(lastY, plotY);
12439				rightContY = 2 * plotY - leftContY;
12440			}
12441			if (rightContY > nextY && rightContY > plotY) {
12442				rightContY = mathMax(nextY, plotY);
12443				leftContY = 2 * plotY - rightContY;
12444			} else if (rightContY < nextY && rightContY < plotY) {
12445				rightContY = mathMin(nextY, plotY);
12446				leftContY = 2 * plotY - rightContY;
12447			}
12448
12449			// record for drawing in next point
12450			point.rightContX = rightContX;
12451			point.rightContY = rightContY;
12452
12453		}
12454
12455		// moveTo or lineTo
12456		if (!i) {
12457			ret = [M, plotX, plotY];
12458		} else { // curve from last point to this
12459			ret = [
12460				'C',
12461				lastPoint.rightContX || lastPoint.plotX,
12462				lastPoint.rightContY || lastPoint.plotY,
12463				leftContX || plotX,
12464				leftContY || plotY,
12465				plotX,
12466				plotY
12467			];
12468			lastPoint.rightContX = lastPoint.rightContY = null; // reset for updating series later
12469		}
12470		return ret;
12471	}
12472});
12473seriesTypes.spline = SplineSeries;
12474
12475
12476
12477/**
12478 * AreaSplineSeries object
12479 */
12480var AreaSplineSeries = extendClass(SplineSeries, {
12481	type: 'areaspline'
12482});
12483seriesTypes.areaspline = AreaSplineSeries;
12484
12485/**
12486 * ColumnSeries object
12487 */
12488var ColumnSeries = extendClass(Series, {
12489	type: 'column',
12490	tooltipOutsidePlot: true,
12491	pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
12492		stroke: 'borderColor',
12493		'stroke-width': 'borderWidth',
12494		fill: 'color',
12495		r: 'borderRadius'
12496	},
12497	init: function () {
12498		Series.prototype.init.apply(this, arguments);
12499
12500		var series = this,
12501			chart = series.chart;
12502
12503		// if the series is added dynamically, force redraw of other
12504		// series affected by a new column
12505		if (chart.hasRendered) {
12506			each(chart.series, function (otherSeries) {
12507				if (otherSeries.type === series.type) {
12508					otherSeries.isDirty = true;
12509				}
12510			});
12511		}
12512	},
12513
12514	/**
12515	 * Translate each point to the plot area coordinate system and find shape positions
12516	 */
12517	translate: function () {
12518		var series = this,
12519			chart = series.chart,
12520			options = series.options,
12521			stacking = options.stacking,
12522			borderWidth = options.borderWidth,
12523			columnCount = 0,
12524			xAxis = series.xAxis,
12525			reversedXAxis = xAxis.reversed,
12526			stackGroups = {},
12527			stackKey,
12528			columnIndex;
12529
12530		Series.prototype.translate.apply(series);
12531
12532		// Get the total number of column type series.
12533		// This is called on every series. Consider moving this logic to a
12534		// chart.orderStacks() function and call it on init, addSeries and removeSeries
12535		each(chart.series, function (otherSeries) {
12536			if (otherSeries.type === series.type && otherSeries.visible &&
12537					series.options.group === otherSeries.options.group) { // used in Stock charts navigator series
12538				if (otherSeries.options.stacking) {
12539					stackKey = otherSeries.stackKey;
12540					if (stackGroups[stackKey] === UNDEFINED) {
12541						stackGroups[stackKey] = columnCount++;
12542					}
12543					columnIndex = stackGroups[stackKey];
12544				} else {
12545					columnIndex = columnCount++;
12546				}
12547				otherSeries.columnIndex = columnIndex;
12548			}
12549		});
12550
12551		// calculate the width and position of each column based on
12552		// the number of column series in the plot, the groupPadding
12553		// and the pointPadding options
12554		var points = series.points,
12555			categoryWidth = mathAbs(xAxis.translationSlope) * (xAxis.ordinalSlope || xAxis.closestPointRange || 1),
12556			groupPadding = categoryWidth * options.groupPadding,
12557			groupWidth = categoryWidth - 2 * groupPadding,
12558			pointOffsetWidth = groupWidth / columnCount,
12559			optionPointWidth = options.pointWidth,
12560			pointPadding = defined(optionPointWidth) ? (pointOffsetWidth - optionPointWidth) / 2 :
12561				pointOffsetWidth * options.pointPadding,
12562			pointWidth = mathCeil(mathMax(pick(optionPointWidth, pointOffsetWidth - 2 * pointPadding), 1 + 2 * borderWidth)),
12563			colIndex = (reversedXAxis ? columnCount -
12564				series.columnIndex : series.columnIndex) || 0,
12565			pointXOffset = pointPadding + (groupPadding + colIndex *
12566				pointOffsetWidth - (categoryWidth / 2)) *
12567				(reversedXAxis ? -1 : 1),
12568			threshold = options.threshold,
12569			translatedThreshold = series.yAxis.getThreshold(threshold),
12570			minPointLength = pick(options.minPointLength, 5);
12571
12572		// record the new values
12573		each(points, function (point) {
12574			var plotY = point.plotY,
12575				yBottom = pick(point.yBottom, translatedThreshold),
12576				barX = point.plotX + pointXOffset,
12577				barY = mathCeil(mathMin(plotY, yBottom)),
12578				barH = mathCeil(mathMax(plotY, yBottom) - barY),
12579				stack = series.yAxis.stacks[(point.y < 0 ? '-' : '') + series.stackKey],
12580				shapeArgs;
12581
12582			// Record the offset'ed position and width of the bar to be able to align the stacking total correctly
12583			if (stacking && series.visible && stack && stack[point.x]) {
12584				stack[point.x].setOffset(pointXOffset, pointWidth);
12585			}
12586
12587			// handle options.minPointLength
12588			if (mathAbs(barH) < minPointLength) {
12589				if (minPointLength) {
12590					barH = minPointLength;
12591					barY =
12592						mathAbs(barY - translatedThreshold) > minPointLength ? // stacked
12593							yBottom - minPointLength : // keep position
12594							translatedThreshold - (plotY <= translatedThreshold ? minPointLength : 0);
12595				}
12596			}
12597
12598			extend(point, {
12599				barX: barX,
12600				barY: barY,
12601				barW: pointWidth,
12602				barH: barH
12603			});
12604
12605			// create shape type and shape args that are reused in drawPoints and drawTracker
12606			point.shapeType = 'rect';
12607			shapeArgs = {
12608				x: barX,
12609				y: barY,
12610				width: pointWidth,
12611				height: barH,
12612				r: options.borderRadius,
12613				strokeWidth: borderWidth
12614			};
12615			
12616			if (borderWidth % 2) { // correct for shorting in crisp method, visible in stacked columns with 1px border
12617				shapeArgs.y -= 1;
12618				shapeArgs.height += 1;
12619			}
12620			point.shapeArgs = shapeArgs;
12621
12622			// make small columns responsive to mouse
12623			point.trackerArgs = mathAbs(barH) < 3 && merge(point.shapeArgs, {
12624				height: 6,
12625				y: barY - 3
12626			});
12627		});
12628
12629	},
12630
12631	getSymbol: function () {
12632	},
12633
12634	/**
12635	 * Columns have no graph
12636	 */
12637	drawGraph: function () {},
12638
12639	/**
12640	 * Draw the columns. For bars, the series.group is rotated, so the same coordinates
12641	 * apply for columns and bars. This method is inherited by scatter series.
12642	 *
12643	 */
12644	drawPoints: function () {
12645		var series = this,
12646			options = series.options,
12647			renderer = series.chart.renderer,
12648			graphic,
12649			shapeArgs;
12650
12651
12652		// draw the columns
12653		each(series.points, function (point) {
12654			var plotY = point.plotY;
12655			if (plotY !== UNDEFINED && !isNaN(plotY) && point.y !== null) {
12656				graphic = point.graphic;
12657				shapeArgs = point.shapeArgs;
12658				if (graphic) { // update
12659					stop(graphic);
12660					graphic.animate(renderer.Element.prototype.crisp.apply({}, [
12661						shapeArgs.strokeWidth,
12662						shapeArgs.x,
12663						shapeArgs.y,
12664						shapeArgs.width,
12665						shapeArgs.height
12666					]));
12667
12668				} else {
12669					point.graphic = graphic = renderer[point.shapeType](shapeArgs)
12670						.attr(point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE])
12671						.add(series.group)
12672						.shadow(options.shadow);
12673						
12674				}
12675
12676			}
12677		});
12678	},
12679	/**
12680	 * Draw the individual tracker elements.
12681	 * This method is inherited by scatter and pie charts too.
12682	 */
12683	drawTracker: function () {
12684		var series = this,
12685			chart = series.chart,
12686			renderer = chart.renderer,
12687			shapeArgs,
12688			tracker,
12689			trackerLabel = +new Date(),
12690			options = series.options,
12691			cursor = options.cursor,
12692			css = cursor && { cursor: cursor },
12693			trackerGroup = series.drawTrackerGroup(),
12694			rel;
12695			
12696		each(series.points, function (point) {
12697			tracker = point.tracker;
12698			shapeArgs = point.trackerArgs || point.shapeArgs;
12699			delete shapeArgs.strokeWidth;
12700			if (point.y !== null) {
12701				if (tracker) {// update
12702					tracker.attr(shapeArgs);
12703
12704				} else {
12705					point.tracker =
12706						renderer[point.shapeType](shapeArgs)
12707						.attr({
12708							isTracker: trackerLabel,
12709							fill: TRACKER_FILL,
12710							visibility: series.visible ? VISIBLE : HIDDEN
12711						})
12712						.on(hasTouch ? 'touchstart' : 'mouseover', function (event) {
12713							rel = event.relatedTarget || event.fromElement;
12714							if (chart.hoverSeries !== series && attr(rel, 'isTracker') !== trackerLabel) {
12715								series.onMouseOver();
12716							}
12717							point.onMouseOver();
12718
12719						})
12720						.on('mouseout', function (event) {
12721							if (!options.stickyTracking) {
12722								rel = event.relatedTarget || event.toElement;
12723								if (attr(rel, 'isTracker') !== trackerLabel) {
12724									series.onMouseOut();
12725								}
12726							}
12727						})
12728						.css(css)
12729						.add(point.group || trackerGroup); // pies have point group - see issue #118
12730				}
12731			}
12732		});
12733	},
12734
12735
12736	/**
12737	 * Animate the column heights one by one from zero
12738	 * @param {Boolean} init Whether to initialize the animation or run it
12739	 */
12740	animate: function (init) {
12741		var series = this,
12742			points = series.points,
12743			options = series.options;
12744
12745		if (!init) { // run the animation
12746			/*
12747			 * Note: Ideally the animation should be initialized by calling
12748			 * series.group.hide(), and then calling series.group.show()
12749			 * after the animation was started. But this rendered the shadows
12750			 * invisible in IE8 standards mode. If the columns flicker on large
12751			 * datasets, this is the cause.
12752			 */
12753
12754			each(points, function (point) {
12755				var graphic = point.graphic,
12756					shapeArgs = point.shapeArgs,
12757					yAxis = series.yAxis,
12758					threshold = options.threshold;
12759
12760				if (graphic) {
12761					// start values
12762					graphic.attr({
12763						height: 0,
12764						y: defined(threshold) ? 
12765							yAxis.getThreshold(threshold) :
12766							yAxis.translate(yAxis.getExtremes().min, 0, 1, 0, 1)
12767					});
12768
12769					// animate
12770					graphic.animate({
12771						height: shapeArgs.height,
12772						y: shapeArgs.y
12773					}, options.animation);
12774				}
12775			});
12776
12777
12778			// delete this function to allow it only once
12779			series.animate = null;
12780		}
12781
12782	},
12783	/**
12784	 * Remove this series from the chart
12785	 */
12786	remove: function () {
12787		var series = this,
12788			chart = series.chart;
12789
12790		// column and bar series affects other series of the same type
12791		// as they are either stacked or grouped
12792		if (chart.hasRendered) {
12793			each(chart.series, function (otherSeries) {
12794				if (otherSeries.type === series.type) {
12795					otherSeries.isDirty = true;
12796				}
12797			});
12798		}
12799
12800		Series.prototype.remove.apply(series, arguments);
12801	}
12802});
12803seriesTypes.column = ColumnSeries;
12804
12805var BarSeries = extendClass(ColumnSeries, {
12806	type: 'bar',
12807	init: function () {
12808		this.inverted = true;
12809		ColumnSeries.prototype.init.apply(this, arguments);
12810	}
12811});
12812seriesTypes.bar = BarSeries;
12813
12814/**
12815 * The scatter series class
12816 */
12817var ScatterSeries = extendClass(Series, {
12818	type: 'scatter',
12819	sorted: false,
12820	/**
12821	 * Extend the base Series' translate method by adding shape type and
12822	 * arguments for the point trackers
12823	 */
12824	translate: function () {
12825		var series = this;
12826
12827		Series.prototype.translate.apply(series);
12828
12829		each(series.points, function (point) {
12830			point.shapeType = 'circle';
12831			point.shapeArgs = {
12832				x: point.plotX,
12833				y: point.plotY,
12834				r: series.chart.options.tooltip.snap
12835			};
12836		});
12837	},
12838
12839	/**
12840	 * Add tracking event listener to the series group, so the point graphics
12841	 * themselves act as trackers
12842	 */
12843	drawTracker: function () {
12844		var series = this,
12845			cursor = series.options.cursor,
12846			css = cursor && { cursor: cursor },
12847			points = series.points,
12848			i = points.length,
12849			graphic;
12850
12851		// Set an expando property for the point index, used below
12852		while (i--) {
12853			graphic = points[i].graphic;
12854			if (graphic) { // doesn't exist for null points
12855				graphic.element._i = i; 
12856			}
12857		}
12858		
12859		// Add the event listeners, we need to do this only once
12860		if (!series._hasTracking) {
12861			series.group
12862				.attr({
12863					isTracker: true
12864				})
12865				.on(hasTouch ? 'touchstart' : 'mouseover', function (e) {
12866					series.onMouseOver();
12867					if (e.target._i !== UNDEFINED) { // undefined on graph in scatterchart
12868						points[e.target._i].onMouseOver();
12869					}
12870				})
12871				.on('mouseout', function () {
12872					if (!series.options.stickyTracking) {
12873						series.onMouseOut();
12874					}
12875				})
12876				.css(css);
12877		} else {
12878			series._hasTracking = true;
12879		}
12880
12881	}
12882});
12883seriesTypes.scatter = ScatterSeries;
12884
12885/**
12886 * Extended point object for pies
12887 */
12888var PiePoint = extendClass(Point, {
12889	/**
12890	 * Initiate the pie slice
12891	 */
12892	init: function () {
12893
12894		Point.prototype.init.apply(this, arguments);
12895
12896		var point = this,
12897			toggleSlice;
12898
12899		//visible: options.visible !== false,
12900		extend(point, {
12901			visible: point.visible !== false,
12902			name: pick(point.name, 'Slice')
12903		});
12904
12905		// add event listener for select
12906		toggleSlice = function () {
12907			point.slice();
12908		};
12909		addEvent(point, 'select', toggleSlice);
12910		addEvent(point, 'unselect', toggleSlice);
12911
12912		return point;
12913	},
12914
12915	/**
12916	 * Toggle the visibility of the pie slice
12917	 * @param {Boolean} vis Whether to show the slice or not. If undefined, the
12918	 *    visibility is toggled
12919	 */
12920	setVisible: function (vis) {
12921		var point = this,
12922			chart = point.series.chart,
12923			tracker = point.tracker,
12924			dataLabel = point.dataLabel,
12925			connector = point.connector,
12926			shadowGroup = point.shadowGroup,
12927			method;
12928
12929		// if called without an argument, toggle visibility
12930		point.visible = vis = vis === UNDEFINED ? !point.visible : vis;
12931
12932		method = vis ? 'show' : 'hide';
12933
12934		point.group[method]();
12935		if (tracker) {
12936			tracker[method]();
12937		}
12938		if (dataLabel) {
12939			dataLabel[method]();
12940		}
12941		if (connector) {
12942			connector[method]();
12943		}
12944		if (shadowGroup) {
12945			shadowGroup[method]();
12946		}
12947		if (point.legendItem) {
12948			chart.legend.colorizeItem(point, vis);
12949		}
12950	},
12951
12952	/**
12953	 * Set or toggle whether the slice is cut out from the pie
12954	 * @param {Boolean} sliced When undefined, the slice state is toggled
12955	 * @param {Boolean} redraw Whether to redraw the chart. True by default.
12956	 */
12957	slice: function (sliced, redraw, animation) {
12958		var point = this,
12959			series = point.series,
12960			chart = series.chart,
12961			slicedTranslation = point.slicedTranslation,
12962			translation;
12963
12964		setAnimation(animation, chart);
12965
12966		// redraw is true by default
12967		redraw = pick(redraw, true);
12968
12969		// if called without an argument, toggle
12970		sliced = point.sliced = defined(sliced) ? sliced : !point.sliced;
12971
12972		translation = {
12973			translateX: (sliced ? slicedTranslation[0] : chart.plotLeft),
12974			translateY: (sliced ? slicedTranslation[1] : chart.plotTop)
12975		};
12976		point.group.animate(translation);
12977		if (point.shadowGroup) {
12978			point.shadowGroup.animate(translation);
12979		}
12980
12981	}
12982});
12983
12984/**
12985 * The Pie series class
12986 */
12987var PieSeries = extendClass(Series, {
12988	type: 'pie',
12989	isCartesian: false,
12990	pointClass: PiePoint,
12991	pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
12992		stroke: 'borderColor',
12993		'stroke-width': 'borderWidth',
12994		fill: 'color'
12995	},
12996
12997	/**
12998	 * Pies have one color each point
12999	 */
13000	getColor: function () {
13001		// record first color for use in setData
13002		this.initialColor = this.chart.counters.color;
13003	},
13004
13005	/**
13006	 * Animate the column heights one by one from zero
13007	 */
13008	animate: function () {
13009		var series = this,
13010			points = series.points;
13011
13012		each(points, function (point) {
13013			var graphic = point.graphic,
13014				args = point.shapeArgs,
13015				up = -mathPI / 2;
13016
13017			if (graphic) {
13018				// start values
13019				graphic.attr({
13020					r: 0,
13021					start: up,
13022					end: up
13023				});
13024
13025				// animate
13026				graphic.animate({
13027					r: args.r,
13028					start: args.start,
13029					end: args.end
13030				}, series.options.animation);
13031			}
13032		});
13033
13034		// delete this function to allow it only once
13035		series.animate = null;
13036
13037	},
13038
13039	/**
13040	 * Extend the basic setData method by running processData and generatePoints immediately,
13041	 * in order to access the points from the legend.
13042	 */
13043	setData: function () {
13044		Series.prototype.setData.apply(this, arguments);
13045		this.processData();
13046		this.generatePoints();
13047	},
13048	/**
13049	 * Do translation for pie slices
13050	 */
13051	translate: function () {
13052		this.generatePoints();
13053		
13054		var total = 0,
13055			series = this,
13056			cumulative = -0.25, // start at top
13057			precision = 1000, // issue #172
13058			options = series.options,
13059			slicedOffset = options.slicedOffset,
13060			connectorOffset = slicedOffset + options.borderWidth,
13061			positions = options.center.concat([options.size, options.innerSize || 0]),
13062			chart = series.chart,
13063			plotWidth = chart.plotWidth,
13064			plotHeight = chart.plotHeight,
13065			start,
13066			end,
13067			angle,
13068			points = series.points,
13069			circ = 2 * mathPI,
13070			fraction,
13071			smallestSize = mathMin(plotWidth, plotHeight),
13072			isPercent,
13073			radiusX, // the x component of the radius vector for a given point
13074			radiusY,
13075			labelDistance = options.dataLabels.distance;
13076
13077		// get positions - either an integer or a percentage string must be given
13078		positions = map(positions, function (length, i) {
13079
13080			isPercent = /%$/.test(length);
13081			return isPercent ?
13082				// i == 0: centerX, relative to width
13083				// i == 1: centerY, relative to height
13084				// i == 2: size, relative to smallestSize
13085				// i == 4: innerSize, relative to smallestSize
13086				[plotWidth, plotHeight, smallestSize, smallestSize][i] *
13087					pInt(length) / 100 :
13088				length;
13089		});
13090
13091		// utility for getting the x value from a given y, used for anticollision logic in data labels
13092		series.getX = function (y, left) {
13093
13094			angle = math.asin((y - positions[1]) / (positions[2] / 2 + labelDistance));
13095
13096			return positions[0] +
13097				(left ? -1 : 1) *
13098				(mathCos(angle) * (positions[2] / 2 + labelDistance));
13099		};
13100
13101		// set center for later use
13102		series.center = positions;
13103
13104		// get the total sum
13105		each(points, function (point) {
13106			total += point.y;
13107		});
13108
13109		each(points, function (point) {
13110			// set start and end angle
13111			fraction = total ? point.y / total : 0;
13112			start = mathRound(cumulative * circ * precision) / precision;
13113			cumulative += fraction;
13114			end = mathRound(cumulative * circ * precision) / precision;
13115
13116			// set the shape
13117			point.shapeType = 'arc';
13118			point.shapeArgs = {
13119				x: positions[0],
13120				y: positions[1],
13121				r: positions[2] / 2,
13122				innerR: positions[3] / 2,
13123				start: start,
13124				end: end
13125			};
13126
13127			// center for the sliced out slice
13128			angle = (end + start) / 2;
13129			point.slicedTranslation = map([
13130				mathCos(angle) * slicedOffset + chart.plotLeft,
13131				mathSin(angle) * slicedOffset + chart.plotTop
13132			], mathRound);
13133
13134			// set the anchor point for tooltips
13135			radiusX = mathCos(angle) * positions[2] / 2;
13136			radiusY = mathSin(angle) * positions[2] / 2;
13137			point.tooltipPos = [
13138				positions[0] + radiusX * 0.7,
13139				positions[1] + radiusY * 0.7
13140			];
13141
13142			// set the anchor point for data labels
13143			point.labelPos = [
13144				positions[0] + radiusX + mathCos(angle) * labelDistance, // first break of connector
13145				positions[1] + radiusY + mathSin(angle) * labelDistance, // a/a
13146				positions[0] + radiusX + mathCos(angle) * connectorOffset, // second break, right outside pie
13147				positions[1] + radiusY + mathSin(angle) * connectorOffset, // a/a
13148				positions[0] + radiusX, // landing point for connector
13149				positions[1] + radiusY, // a/a
13150				labelDistance < 0 ? // alignment
13151					'center' :
13152					angle < circ / 4 ? 'left' : 'right', // alignment
13153				angle // center angle
13154			];
13155
13156			// API properties
13157			point.percentage = fraction * 100;
13158			point.total = total;
13159
13160		});
13161
13162
13163		this.setTooltipPoints();
13164	},
13165
13166	/**
13167	 * Render the slices
13168	 */
13169	render: function () {
13170		var series = this;
13171
13172		// cache attributes for shapes
13173		series.getAttribs();
13174
13175		this.drawPoints();
13176
13177		// draw the mouse tracking area
13178		if (series.options.enableMouseTracking !== false) {
13179			series.drawTracker();
13180		}
13181
13182		this.drawDataLabels();
13183
13184		if (series.options.animation && series.animate) {
13185			series.animate();
13186		}
13187
13188		// (See #322) series.isDirty = series.isDirtyData = false; // means data is in accordance with what you see
13189		series.isDirty = false; // means data is in accordance with what you see
13190	},
13191
13192	/**
13193	 * Draw the data points
13194	 */
13195	drawPoints: function () {
13196		var series = this,
13197			chart = series.chart,
13198			renderer = chart.renderer,
13199			groupTranslation,
13200			//center,
13201			graphic,
13202			group,
13203			shadow = series.options.shadow,
13204			shadowGroup,
13205			shapeArgs;
13206
13207		// draw the slices
13208		each(series.points, function (point) {
13209			graphic = point.graphic;
13210			shapeArgs = point.shapeArgs;
13211			group = point.group;
13212			shadowGroup = point.shadowGroup;
13213
13214			// put the shadow behind all points
13215			if (shadow && !shadowGroup) {
13216				shadowGroup = point.shadowGroup = renderer.g('shadow')
13217					.attr({ zIndex: 4 })
13218					.add();
13219			}
13220
13221			// create the group the first time
13222			if (!group) {
13223				group = point.group = renderer.g('point')
13224					.attr({ zIndex: 5 })
13225					.add();
13226			}
13227
13228			// if the point is sliced, use special translation, else use plot area traslation
13229			groupTranslation = point.sliced ? point.slicedTranslation : [chart.plotLeft, chart.plotTop];
13230			group.translate(groupTranslation[0], groupTranslation[1]);
13231			if (shadowGroup) {
13232				shadowGroup.translate(groupTranslation[0], groupTranslation[1]);
13233			}
13234
13235			// draw the slice
13236			if (graphic) {
13237				graphic.animate(shapeArgs);
13238			} else {
13239				point.graphic =
13240					renderer.arc(shapeArgs)
13241					.attr(extend(
13242						point.pointAttr[NORMAL_STATE],
13243						{ 'stroke-linejoin': 'round' }
13244					))
13245					.add(point.group)
13246					.shadow(shadow, shadowGroup);
13247			}
13248
13249			// detect point specific visibility
13250			if (point.visible === false) {
13251				point.setVisible(false);
13252			}
13253
13254		});
13255
13256	},
13257
13258	/**
13259	 * Override the base drawDataLabels method by pie specific functionality
13260	 */
13261	drawDataLabels: function () {
13262		var series = this,
13263			data = series.data,
13264			point,
13265			chart = series.chart,
13266			options = series.options.dataLabels,
13267			connectorPadding = pick(options.connectorPadding, 10),
13268			connectorWidth = pick(options.connectorWidth, 1),
13269			connector,
13270			connectorPath,
13271			softConnector = pick(options.softConnector, true),
13272			distanceOption = options.distance,
13273			seriesCenter = series.center,
13274			radius = seriesCenter[2] / 2,
13275			centerY = seriesCenter[1],
13276			outside = distanceOption > 0,
13277			dataLabel,
13278			labelPos,
13279			labelHeight,
13280			halves = [// divide the points into right and left halves for anti collision
13281				[], // right
13282				[]  // left
13283			],
13284			x,
13285			y,
13286			visibility,
13287			rankArr,
13288			sort,
13289			i = 2,
13290			j;
13291
13292		// get out if not enabled
13293		if (!options.enabled) {
13294			return;
13295		}
13296
13297		// run parent method
13298		Series.prototype.drawDataLabels.apply(series);
13299
13300		// arrange points for detection collision
13301		each(data, function (point) {
13302			if (point.dataLabel) { // it may have been cancelled in the base method (#407)
13303				halves[
13304					point.labelPos[7] < mathPI / 2 ? 0 : 1
13305				].push(point);
13306			}
13307		});
13308		halves[1].reverse();
13309
13310		// define the sorting algorithm
13311		sort = function (a, b) {
13312			return b.y - a.y;
13313		};
13314
13315		// assume equal label heights
13316		labelHeight = halves[0][0] && halves[0][0].dataLabel && halves[0][0].dataLabel.getBBox().height;
13317
13318		/* Loop over the points in each quartile, starting from the top and bottom
13319		 * of the pie to detect overlapping labels.
13320		 */
13321		while (i--) {
13322
13323			var slots = [],
13324				slotsLength,
13325				usedSlots = [],
13326				points = halves[i],
13327				pos,
13328				length = points.length,
13329				slotIndex;
13330
13331
13332			// build the slots
13333			for (pos = centerY - radius - distanceOption; pos <= centerY + radius + distanceOption; pos += labelHeight) {
13334				slots.push(pos);
13335				// visualize the slot
13336				/*
13337				var slotX = series.getX(pos, i) + chart.plotLeft - (i ? 100 : 0),
13338					slotY = pos + chart.plotTop;
13339				if (!isNaN(slotX)) {
13340					chart.renderer.rect(slotX, slotY - 7, 100, labelHeight)
13341						.attr({
13342							'stroke-width': 1,
13343							stroke: 'silver'
13344						})
13345						.add();
13346					chart.renderer.text('Slot '+ (slots.length - 1), slotX, slotY + 4)
13347						.attr({
13348							fill: 'silver'
13349						}).add();
13350				}
13351				// */
13352			}
13353			slotsLength = slots.length;
13354
13355			// if there are more values than available slots, remove lowest values
13356			if (length > slotsLength) {
13357				// create an array for sorting and ranking the points within each quarter
13358				rankArr = [].concat(points);
13359				rankArr.sort(sort);
13360				j = length;
13361				while (j--) {
13362					rankArr[j].rank = j;
13363				}
13364				j = length;
13365				while (j--) {
13366					if (points[j].rank >= slotsLength) {
13367						points.splice(j, 1);
13368					}
13369				}
13370				length = points.length;
13371			}
13372
13373			// The label goes to the nearest open slot, but not closer to the edge than
13374			// the label's index.
13375			for (j = 0; j < length; j++) {
13376
13377				point = points[j];
13378				labelPos = point.labelPos;
13379
13380				var closest = 9999,
13381					distance,
13382					slotI;
13383
13384				// find the closest slot index
13385				for (slotI = 0; slotI < slotsLength; slotI++) {
13386					distance = mathAbs(slots[slotI] - labelPos[1]);
13387					if (distance < closest) {
13388						closest = distance;
13389						slotIndex = slotI;
13390					}
13391				}
13392
13393				// if that slot index is closer to the edges of the slots, move it
13394				// to the closest appropriate slot
13395				if (slotIndex < j && slots[j] !== null) { // cluster at the top
13396					slotIndex = j;
13397				} else if (slotsLength  < length - j + slotIndex && slots[j] !== null) { // cluster at the bottom
13398					slotIndex = slotsLength - length + j;
13399					while (slots[slotIndex] === null) { // make sure it is not taken
13400						slotIndex++;
13401					}
13402				} else {
13403					// Slot is taken, find next free slot below. In the next run, the next slice will find the
13404					// slot above these, because it is the closest one
13405					while (slots[slotIndex] === null) { // make sure it is not taken
13406						slotIndex++;
13407					}
13408				}
13409
13410				usedSlots.push({ i: slotIndex, y: slots[slotIndex] });
13411				slots[slotIndex] = null; // mark as taken
13412			}
13413			// sort them in order to fill in from the top
13414			usedSlots.sort(sort);
13415
13416
13417			// now the used slots are sorted, fill them up sequentially
13418			for (j = 0; j < length; j++) {
13419
13420				point = points[j];
13421				labelPos = point.labelPos;
13422				dataLabel = point.dataLabel;
13423				var slot = usedSlots.pop(),
13424					naturalY = labelPos[1];
13425
13426				visibility = point.visible === false ? HIDDEN : VISIBLE;
13427				slotIndex = slot.i;
13428
13429				// if the slot next to currrent slot is free, the y value is allowed
13430				// to fall back to the natural position
13431				y = slot.y;
13432				if ((naturalY > y && slots[slotIndex + 1] !== null) ||
13433						(naturalY < y &&  slots[slotIndex - 1] !== null)) {
13434					y = naturalY;
13435				}
13436
13437				// get the x - use the natural x position for first and last slot, to prevent the top
13438				// and botton slice connectors from touching each other on either side
13439				x = series.getX(slotIndex === 0 || slotIndex === slots.length - 1 ? naturalY : y, i);
13440
13441				// move or place the data label
13442				dataLabel
13443					.attr({
13444						visibility: visibility,
13445						align: labelPos[6]
13446					})[dataLabel.moved ? 'animate' : 'attr']({
13447						x: x + options.x +
13448							({ left: connectorPadding, right: -connectorPadding }[labelPos[6]] || 0),
13449						y: y + options.y
13450					});
13451				dataLabel.moved = true;
13452
13453				// draw the connector
13454				if (outside && connectorWidth) {
13455					connector = point.connector;
13456
13457					connectorPath = softConnector ? [
13458						M,
13459						x + (labelPos[6] === 'left' ? 5 : -5), y, // end of the string at the label
13460						'C',
13461						x, y, // first break, next to the label
13462						2 * labelPos[2] - labelPos[4], 2 * labelPos[3] - labelPos[5],
13463						labelPos[2], labelPos[3], // second break
13464						L,
13465						labelPos[4], labelPos[5] // base
13466					] : [
13467						M,
13468						x + (labelPos[6] === 'left' ? 5 : -5), y, // end of the string at the label
13469						L,
13470						labelPos[2], labelPos[3], // second break
13471						L,
13472						labelPos[4], labelPos[5] // base
13473					];
13474
13475					if (connector) {
13476						connector.animate({ d: connectorPath });
13477						connector.attr('visibility', visibility);
13478
13479					} else {
13480						point.connector = connector = series.chart.renderer.path(connectorPath).attr({
13481							'stroke-width': connectorWidth,
13482							stroke: options.connectorColor || point.color || '#606060',
13483							visibility: visibility,
13484							zIndex: 3
13485						})
13486						.translate(chart.plotLeft, chart.plotTop)
13487						.add();
13488					}
13489				}
13490			}
13491		}
13492	},
13493
13494	/**
13495	 * Draw point specific tracker objects. Inherit directly from column series.
13496	 */
13497	drawTracker: ColumnSeries.prototype.drawTracker,
13498
13499	/**
13500	 * Pies don't have point marker symbols
13501	 */
13502	getSymbol: function () {}
13503
13504});
13505seriesTypes.pie = PieSeries;
13506
13507
13508// global variables
13509extend(Highcharts, {
13510	Chart: Chart,
13511	dateFormat: dateFormat,
13512	pathAnim: pathAnim,
13513	getOptions: getOptions,
13514	hasBidiBug: hasBidiBug,
13515	numberFormat: numberFormat,
13516	Point: Point,
13517	Color: Color,
13518	Renderer: Renderer,
13519	SVGRenderer: SVGRenderer,
13520	VMLRenderer: VMLRenderer,
13521	CanVGRenderer: CanVGRenderer,
13522	seriesTypes: seriesTypes,
13523	setOptions: setOptions,
13524	Series: Series,
13525
13526	// Expose utility funcitons for modules
13527	addEvent: addEvent,
13528	removeEvent: removeEvent,
13529	createElement: createElement,
13530	discardElement: discardElement,
13531	css: css,
13532	each: each,
13533	extend: extend,
13534	map: map,
13535	merge: merge,
13536	pick: pick,
13537	splat: splat,
13538	extendClass: extendClass,
13539	placeBox: placeBox,
13540	product: 'Highcharts',
13541	version: '2.2.1'
13542});
13543}());

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.