懒羊羊
2023-08-30 1ac2bc1590406d9babec036e154d8d08f34a6aa1
提交 | 用户 | 时间
1ac2bc 1 // ==ClosureCompiler==
2 // @compilation_level SIMPLE_OPTIMIZATIONS
3
4 /**
5  * @license Highcharts JS v3.0.6 (2013-10-04)
6  *
7  * (c) 2009-2013 Torstein Hønsi
8  *
9  * License: www.highcharts.com/license
10  */
11
12 // JSLint options:
13 /*global Highcharts, document, window, navigator, setInterval, clearInterval, clearTimeout, setTimeout, location, jQuery, $, console, each, grep */
14
15 (function () {
16 // encapsulated variables
17 var UNDEFINED,
18     doc = document,
19     win = window,
20     math = Math,
21     mathRound = math.round,
22     mathFloor = math.floor,
23     mathCeil = math.ceil,
24     mathMax = math.max,
25     mathMin = math.min,
26     mathAbs = math.abs,
27     mathCos = math.cos,
28     mathSin = math.sin,
29     mathPI = math.PI,
30     deg2rad = mathPI * 2 / 360,
31
32
33     // some variables
34     userAgent = navigator.userAgent,
35     isOpera = win.opera,
36     isIE = /msie/i.test(userAgent) && !isOpera,
37     docMode8 = doc.documentMode === 8,
38     isWebKit = /AppleWebKit/.test(userAgent),
39     isFirefox = /Firefox/.test(userAgent),
40     isTouchDevice = /(Mobile|Android|Windows Phone)/.test(userAgent),
41     SVG_NS = 'http://www.w3.org/2000/svg',
42     hasSVG = !!doc.createElementNS && !!doc.createElementNS(SVG_NS, 'svg').createSVGRect,
43     hasBidiBug = isFirefox && parseInt(userAgent.split('Firefox/')[1], 10) < 4, // issue #38
44     useCanVG = !hasSVG && !isIE && !!doc.createElement('canvas').getContext,
45     Renderer,
46     hasTouch = doc.documentElement.ontouchstart !== UNDEFINED,
47     symbolSizes = {},
48     idCounter = 0,
49     garbageBin,
50     defaultOptions,
51     dateFormat, // function
52     globalAnimation,
53     pathAnim,
54     timeUnits,
55     noop = function () {},
56     charts = [],
57     PRODUCT = 'Highcharts',
58     VERSION = '3.0.6',
59
60     // some constants for frequently used strings
61     DIV = 'div',
62     ABSOLUTE = 'absolute',
63     RELATIVE = 'relative',
64     HIDDEN = 'hidden',
65     PREFIX = 'highcharts-',
66     VISIBLE = 'visible',
67     PX = 'px',
68     NONE = 'none',
69     M = 'M',
70     L = 'L',
71     /*
72      * Empirical lowest possible opacities for TRACKER_FILL
73      * IE6: 0.002
74      * IE7: 0.002
75      * IE8: 0.002
76      * IE9: 0.00000000001 (unlimited)
77      * IE10: 0.0001 (exporting only)
78      * FF: 0.00000000001 (unlimited)
79      * Chrome: 0.000001
80      * Safari: 0.000001
81      * Opera: 0.00000000001 (unlimited)
82      */
83     TRACKER_FILL = 'rgba(192,192,192,' + (hasSVG ? 0.0001 : 0.002) + ')', // invisible but clickable
84     //TRACKER_FILL = 'rgba(192,192,192,0.5)',
85     NORMAL_STATE = '',
86     HOVER_STATE = 'hover',
87     SELECT_STATE = 'select',
88     MILLISECOND = 'millisecond',
89     SECOND = 'second',
90     MINUTE = 'minute',
91     HOUR = 'hour',
92     DAY = 'day',
93     WEEK = 'week',
94     MONTH = 'month',
95     YEAR = 'year',
96
97     // constants for attributes
98     LINEAR_GRADIENT = 'linearGradient',
99     STOPS = 'stops',
100     STROKE_WIDTH = 'stroke-width',
101
102     // time methods, changed based on whether or not UTC is used
103     makeTime,
104     getMinutes,
105     getHours,
106     getDay,
107     getDate,
108     getMonth,
109     getFullYear,
110     setMinutes,
111     setHours,
112     setDate,
113     setMonth,
114     setFullYear,
115
116
117     // lookup over the types and the associated classes
118     seriesTypes = {};
119
120 // The Highcharts namespace
121 win.Highcharts = win.Highcharts ? error(16, true) : {};
122
123 /**
124  * Extend an object with the members of another
125  * @param {Object} a The object to be extended
126  * @param {Object} b The object to add to the first one
127  */
128 function extend(a, b) {
129     var n;
130     if (!a) {
131         a = {};
132     }
133     for (n in b) {
134         a[n] = b[n];
135     }
136     return a;
137 }
138     
139 /**
140  * Deep merge two or more objects and return a third object.
141  * Previously this function redirected to jQuery.extend(true), but this had two limitations.
142  * First, it deep merged arrays, which lead to workarounds in Highcharts. Second,
143  * it copied properties from extended prototypes. 
144  */
145 function merge() {
146     var i,
147         len = arguments.length,
148         ret = {},
149         doCopy = function (copy, original) {
150             var value, key;
151
152             // An object is replacing a primitive
153             if (typeof copy !== 'object') {
154                 copy = {};
155             }
156
157             for (key in original) {
158                 if (original.hasOwnProperty(key)) {
159                     value = original[key];
160
161                     // Copy the contents of objects, but not arrays or DOM nodes
162                     if (value && typeof value === 'object' && Object.prototype.toString.call(value) !== '[object Array]'
163                             && typeof value.nodeType !== 'number') {
164                         copy[key] = doCopy(copy[key] || {}, value);
165                 
166                     // Primitives and arrays are copied over directly
167                     } else {
168                         copy[key] = original[key];
169                     }
170                 }
171             }
172             return copy;
173         };
174
175     // For each argument, extend the return
176     for (i = 0; i < len; i++) {
177         ret = doCopy(ret, arguments[i]);
178     }
179
180     return ret;
181 }
182
183 /**
184  * Take an array and turn into a hash with even number arguments as keys and odd numbers as
185  * values. Allows creating constants for commonly used style properties, attributes etc.
186  * Avoid it in performance critical situations like looping
187  */
188 function hash() {
189     var i = 0,
190         args = arguments,
191         length = args.length,
192         obj = {};
193     for (; i < length; i++) {
194         obj[args[i++]] = args[i];
195     }
196     return obj;
197 }
198
199 /**
200  * Shortcut for parseInt
201  * @param {Object} s
202  * @param {Number} mag Magnitude
203  */
204 function pInt(s, mag) {
205     return parseInt(s, mag || 10);
206 }
207
208 /**
209  * Check for string
210  * @param {Object} s
211  */
212 function isString(s) {
213     return typeof s === 'string';
214 }
215
216 /**
217  * Check for object
218  * @param {Object} obj
219  */
220 function isObject(obj) {
221     return typeof obj === 'object';
222 }
223
224 /**
225  * Check for array
226  * @param {Object} obj
227  */
228 function isArray(obj) {
229     return Object.prototype.toString.call(obj) === '[object Array]';
230 }
231
232 /**
233  * Check for number
234  * @param {Object} n
235  */
236 function isNumber(n) {
237     return typeof n === 'number';
238 }
239
240 function log2lin(num) {
241     return math.log(num) / math.LN10;
242 }
243 function lin2log(num) {
244     return math.pow(10, num);
245 }
246
247 /**
248  * Remove last occurence of an item from an array
249  * @param {Array} arr
250  * @param {Mixed} item
251  */
252 function erase(arr, item) {
253     var i = arr.length;
254     while (i--) {
255         if (arr[i] === item) {
256             arr.splice(i, 1);
257             break;
258         }
259     }
260     //return arr;
261 }
262
263 /**
264  * Returns true if the object is not null or undefined. Like MooTools' $.defined.
265  * @param {Object} obj
266  */
267 function defined(obj) {
268     return obj !== UNDEFINED && obj !== null;
269 }
270
271 /**
272  * Set or get an attribute or an object of attributes. Can't use jQuery attr because
273  * it attempts to set expando properties on the SVG element, which is not allowed.
274  *
275  * @param {Object} elem The DOM element to receive the attribute(s)
276  * @param {String|Object} prop The property or an abject of key-value pairs
277  * @param {String} value The value if a single property is set
278  */
279 function attr(elem, prop, value) {
280     var key,
281         setAttribute = 'setAttribute',
282         ret;
283
284     // if the prop is a string
285     if (isString(prop)) {
286         // set the value
287         if (defined(value)) {
288
289             elem[setAttribute](prop, value);
290
291         // get the value
292         } else if (elem && elem.getAttribute) { // elem not defined when printing pie demo...
293             ret = elem.getAttribute(prop);
294         }
295
296     // else if prop is defined, it is a hash of key/value pairs
297     } else if (defined(prop) && isObject(prop)) {
298         for (key in prop) {
299             elem[setAttribute](key, prop[key]);
300         }
301     }
302     return ret;
303 }
304 /**
305  * Check if an element is an array, and if not, make it into an array. Like
306  * MooTools' $.splat.
307  */
308 function splat(obj) {
309     return isArray(obj) ? obj : [obj];
310 }
311
312
313 /**
314  * Return the first value that is defined. Like MooTools' $.pick.
315  */
316 function pick() {
317     var args = arguments,
318         i,
319         arg,
320         length = args.length;
321     for (i = 0; i < length; i++) {
322         arg = args[i];
323         if (typeof arg !== 'undefined' && arg !== null) {
324             return arg;
325         }
326     }
327 }
328
329 /**
330  * Set CSS on a given element
331  * @param {Object} el
332  * @param {Object} styles Style object with camel case property names
333  */
334 function css(el, styles) {
335     if (isIE) {
336         if (styles && styles.opacity !== UNDEFINED) {
337             styles.filter = 'alpha(opacity=' + (styles.opacity * 100) + ')';
338         }
339     }
340     extend(el.style, styles);
341 }
342
343 /**
344  * Utility function to create element with attributes and styles
345  * @param {Object} tag
346  * @param {Object} attribs
347  * @param {Object} styles
348  * @param {Object} parent
349  * @param {Object} nopad
350  */
351 function createElement(tag, attribs, styles, parent, nopad) {
352     var el = doc.createElement(tag);
353     if (attribs) {
354         extend(el, attribs);
355     }
356     if (nopad) {
357         css(el, {padding: 0, border: NONE, margin: 0});
358     }
359     if (styles) {
360         css(el, styles);
361     }
362     if (parent) {
363         parent.appendChild(el);
364     }
365     return el;
366 }
367
368 /**
369  * Extend a prototyped class by new members
370  * @param {Object} parent
371  * @param {Object} members
372  */
373 function extendClass(parent, members) {
374     var object = function () {};
375     object.prototype = new parent();
376     extend(object.prototype, members);
377     return object;
378 }
379
380 /**
381  * Format a number and return a string based on input settings
382  * @param {Number} number The input number to format
383  * @param {Number} decimals The amount of decimals
384  * @param {String} decPoint The decimal point, defaults to the one given in the lang options
385  * @param {String} thousandsSep The thousands separator, defaults to the one given in the lang options
386  */
387 function numberFormat(number, decimals, decPoint, thousandsSep) {
388     var lang = defaultOptions.lang,
389         // http://kevin.vanzonneveld.net/techblog/article/javascript_equivalent_for_phps_number_format/
390         n = +number || 0,
391         c = decimals === -1 ?
392             (n.toString().split('.')[1] || '').length : // preserve decimals
393             (isNaN(decimals = mathAbs(decimals)) ? 2 : decimals),
394         d = decPoint === undefined ? lang.decimalPoint : decPoint,
395         t = thousandsSep === undefined ? lang.thousandsSep : thousandsSep,
396         s = n < 0 ? "-" : "",
397         i = String(pInt(n = mathAbs(n).toFixed(c))),
398         j = i.length > 3 ? i.length % 3 : 0;
399
400     return s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) +
401         (c ? d + mathAbs(n - i).toFixed(c).slice(2) : "");
402 }
403
404 /**
405  * Pad a string to a given length by adding 0 to the beginning
406  * @param {Number} number
407  * @param {Number} length
408  */
409 function pad(number, length) {
410     // Create an array of the remaining length +1 and join it with 0's
411     return new Array((length || 2) + 1 - String(number).length).join(0) + number;
412 }
413
414 /**
415  * Wrap a method with extended functionality, preserving the original function
416  * @param {Object} obj The context object that the method belongs to 
417  * @param {String} method The name of the method to extend
418  * @param {Function} func A wrapper function callback. This function is called with the same arguments
419  * as the original function, except that the original function is unshifted and passed as the first 
420  * argument. 
421  */
422 function wrap(obj, method, func) {
423     var proceed = obj[method];
424     obj[method] = function () {
425         var args = Array.prototype.slice.call(arguments);
426         args.unshift(proceed);
427         return func.apply(this, args);
428     };
429 }
430
431 /**
432  * Based on http://www.php.net/manual/en/function.strftime.php
433  * @param {String} format
434  * @param {Number} timestamp
435  * @param {Boolean} capitalize
436  */
437 dateFormat = function (format, timestamp, capitalize) {
438     if (!defined(timestamp) || isNaN(timestamp)) {
439         return 'Invalid date';
440     }
441     format = pick(format, '%Y-%m-%d %H:%M:%S');
442
443     var date = new Date(timestamp),
444         key, // used in for constuct below
445         // get the basic time values
446         hours = date[getHours](),
447         day = date[getDay](),
448         dayOfMonth = date[getDate](),
449         month = date[getMonth](),
450         fullYear = date[getFullYear](),
451         lang = defaultOptions.lang,
452         langWeekdays = lang.weekdays,
453
454         // List all format keys. Custom formats can be added from the outside. 
455         replacements = extend({
456
457             // Day
458             'a': langWeekdays[day].substr(0, 3), // Short weekday, like 'Mon'
459             'A': langWeekdays[day], // Long weekday, like 'Monday'
460             'd': pad(dayOfMonth), // Two digit day of the month, 01 to 31
461             'e': dayOfMonth, // Day of the month, 1 through 31
462
463             // Week (none implemented)
464             //'W': weekNumber(),
465
466             // Month
467             'b': lang.shortMonths[month], // Short month, like 'Jan'
468             'B': lang.months[month], // Long month, like 'January'
469             'm': pad(month + 1), // Two digit month number, 01 through 12
470
471             // Year
472             'y': fullYear.toString().substr(2, 2), // Two digits year, like 09 for 2009
473             'Y': fullYear, // Four digits year, like 2009
474
475             // Time
476             'H': pad(hours), // Two digits hours in 24h format, 00 through 23
477             'I': pad((hours % 12) || 12), // Two digits hours in 12h format, 00 through 11
478             'l': (hours % 12) || 12, // Hours in 12h format, 1 through 12
479             'M': pad(date[getMinutes]()), // Two digits minutes, 00 through 59
480             'p': hours < 12 ? 'AM' : 'PM', // Upper case AM or PM
481             'P': hours < 12 ? 'am' : 'pm', // Lower case AM or PM
482             'S': pad(date.getSeconds()), // Two digits seconds, 00 through  59
483             'L': pad(mathRound(timestamp % 1000), 3) // Milliseconds (naming from Ruby)
484         }, Highcharts.dateFormats);
485
486
487     // do the replaces
488     for (key in replacements) {
489         while (format.indexOf('%' + key) !== -1) { // regex would do it in one line, but this is faster
490             format = format.replace('%' + key, typeof replacements[key] === 'function' ? replacements[key](timestamp) : replacements[key]);
491         }
492     }
493
494     // Optionally capitalize the string and return
495     return capitalize ? format.substr(0, 1).toUpperCase() + format.substr(1) : format;
496 };
497
498 /** 
499  * Format a single variable. Similar to sprintf, without the % prefix.
500  */
501 function formatSingle(format, val) {
502     var floatRegex = /f$/,
503         decRegex = /\.([0-9])/,
504         lang = defaultOptions.lang,
505         decimals;
506
507     if (floatRegex.test(format)) { // float
508         decimals = format.match(decRegex);
509         decimals = decimals ? decimals[1] : -1;
510         val = numberFormat(
511             val,
512             decimals,
513             lang.decimalPoint,
514             format.indexOf(',') > -1 ? lang.thousandsSep : ''
515         );
516     } else {
517         val = dateFormat(format, val);
518     }
519     return val;
520 }
521
522 /**
523  * Format a string according to a subset of the rules of Python's String.format method.
524  */
525 function format(str, ctx) {
526     var splitter = '{',
527         isInside = false,
528         segment,
529         valueAndFormat,
530         path,
531         i,
532         len,
533         ret = [],
534         val,
535         index;
536     
537     while ((index = str.indexOf(splitter)) !== -1) {
538         
539         segment = str.slice(0, index);
540         if (isInside) { // we're on the closing bracket looking back
541             
542             valueAndFormat = segment.split(':');
543             path = valueAndFormat.shift().split('.'); // get first and leave format
544             len = path.length;
545             val = ctx;
546
547             // Assign deeper paths
548             for (i = 0; i < len; i++) {
549                 val = val[path[i]];
550             }
551
552             // Format the replacement
553             if (valueAndFormat.length) {
554                 val = formatSingle(valueAndFormat.join(':'), val);
555             }
556
557             // Push the result and advance the cursor
558             ret.push(val);
559             
560         } else {
561             ret.push(segment);
562             
563         }
564         str = str.slice(index + 1); // the rest
565         isInside = !isInside; // toggle
566         splitter = isInside ? '}' : '{'; // now look for next matching bracket
567     }
568     ret.push(str);
569     return ret.join('');
570 }
571
572 /**
573  * Get the magnitude of a number
574  */
575 function getMagnitude(num) {
576     return math.pow(10, mathFloor(math.log(num) / math.LN10));
577 }
578
579 /**
580  * Take an interval and normalize it to multiples of 1, 2, 2.5 and 5
581  * @param {Number} interval
582  * @param {Array} multiples
583  * @param {Number} magnitude
584  * @param {Object} options
585  */
586 function normalizeTickInterval(interval, multiples, magnitude, options) {
587     var normalized, i;
588
589     // round to a tenfold of 1, 2, 2.5 or 5
590     magnitude = pick(magnitude, 1);
591     normalized = interval / magnitude;
592
593     // multiples for a linear scale
594     if (!multiples) {
595         multiples = [1, 2, 2.5, 5, 10];
596
597         // the allowDecimals option
598         if (options && options.allowDecimals === false) {
599             if (magnitude === 1) {
600                 multiples = [1, 2, 5, 10];
601             } else if (magnitude <= 0.1) {
602                 multiples = [1 / magnitude];
603             }
604         }
605     }
606
607     // normalize the interval to the nearest multiple
608     for (i = 0; i < multiples.length; i++) {
609         interval = multiples[i];
610         if (normalized <= (multiples[i] + (multiples[i + 1] || multiples[i])) / 2) {
611             break;
612         }
613     }
614
615     // multiply back to the correct magnitude
616     interval *= magnitude;
617
618     return interval;
619 }
620
621 /**
622  * Get a normalized tick interval for dates. Returns a configuration object with
623  * unit range (interval), count and name. Used to prepare data for getTimeTicks. 
624  * Previously this logic was part of getTimeTicks, but as getTimeTicks now runs
625  * of segments in stock charts, the normalizing logic was extracted in order to 
626  * prevent it for running over again for each segment having the same interval. 
627  * #662, #697.
628  */
629 function normalizeTimeTickInterval(tickInterval, unitsOption) {
630     var units = unitsOption || [[
631                 MILLISECOND, // unit name
632                 [1, 2, 5, 10, 20, 25, 50, 100, 200, 500] // allowed multiples
633             ], [
634                 SECOND,
635                 [1, 2, 5, 10, 15, 30]
636             ], [
637                 MINUTE,
638                 [1, 2, 5, 10, 15, 30]
639             ], [
640                 HOUR,
641                 [1, 2, 3, 4, 6, 8, 12]
642             ], [
643                 DAY,
644                 [1, 2]
645             ], [
646                 WEEK,
647                 [1, 2]
648             ], [
649                 MONTH,
650                 [1, 2, 3, 4, 6]
651             ], [
652                 YEAR,
653                 null
654             ]],
655         unit = units[units.length - 1], // default unit is years
656         interval = timeUnits[unit[0]],
657         multiples = unit[1],
658         count,
659         i;
660         
661     // loop through the units to find the one that best fits the tickInterval
662     for (i = 0; i < units.length; i++) {
663         unit = units[i];
664         interval = timeUnits[unit[0]];
665         multiples = unit[1];
666
667
668         if (units[i + 1]) {
669             // lessThan is in the middle between the highest multiple and the next unit.
670             var lessThan = (interval * multiples[multiples.length - 1] +
671                         timeUnits[units[i + 1][0]]) / 2;
672
673             // break and keep the current unit
674             if (tickInterval <= lessThan) {
675                 break;
676             }
677         }
678     }
679
680     // prevent 2.5 years intervals, though 25, 250 etc. are allowed
681     if (interval === timeUnits[YEAR] && tickInterval < 5 * interval) {
682         multiples = [1, 2, 5];
683     }
684
685     // get the count
686     count = normalizeTickInterval(
687         tickInterval / interval, 
688         multiples,
689         unit[0] === YEAR ? getMagnitude(tickInterval / interval) : 1 // #1913
690     );
691     
692     return {
693         unitRange: interval,
694         count: count,
695         unitName: unit[0]
696     };
697 }
698
699 /**
700  * Set the tick positions to a time unit that makes sense, for example
701  * on the first of each month or on every Monday. Return an array
702  * with the time positions. Used in datetime axes as well as for grouping
703  * data on a datetime axis.
704  *
705  * @param {Object} normalizedInterval The interval in axis values (ms) and the count
706  * @param {Number} min The minimum in axis values
707  * @param {Number} max The maximum in axis values
708  * @param {Number} startOfWeek
709  */
710 function getTimeTicks(normalizedInterval, min, max, startOfWeek) {
711     var tickPositions = [],
712         i,
713         higherRanks = {},
714         useUTC = defaultOptions.global.useUTC,
715         minYear, // used in months and years as a basis for Date.UTC()
716         minDate = new Date(min),
717         interval = normalizedInterval.unitRange,
718         count = normalizedInterval.count;
719
720     if (defined(min)) { // #1300
721         if (interval >= timeUnits[SECOND]) { // second
722             minDate.setMilliseconds(0);
723             minDate.setSeconds(interval >= timeUnits[MINUTE] ? 0 :
724                 count * mathFloor(minDate.getSeconds() / count));
725         }
726     
727         if (interval >= timeUnits[MINUTE]) { // minute
728             minDate[setMinutes](interval >= timeUnits[HOUR] ? 0 :
729                 count * mathFloor(minDate[getMinutes]() / count));
730         }
731     
732         if (interval >= timeUnits[HOUR]) { // hour
733             minDate[setHours](interval >= timeUnits[DAY] ? 0 :
734                 count * mathFloor(minDate[getHours]() / count));
735         }
736     
737         if (interval >= timeUnits[DAY]) { // day
738             minDate[setDate](interval >= timeUnits[MONTH] ? 1 :
739                 count * mathFloor(minDate[getDate]() / count));
740         }
741     
742         if (interval >= timeUnits[MONTH]) { // month
743             minDate[setMonth](interval >= timeUnits[YEAR] ? 0 :
744                 count * mathFloor(minDate[getMonth]() / count));
745             minYear = minDate[getFullYear]();
746         }
747     
748         if (interval >= timeUnits[YEAR]) { // year
749             minYear -= minYear % count;
750             minDate[setFullYear](minYear);
751         }
752     
753         // week is a special case that runs outside the hierarchy
754         if (interval === timeUnits[WEEK]) {
755             // get start of current week, independent of count
756             minDate[setDate](minDate[getDate]() - minDate[getDay]() +
757                 pick(startOfWeek, 1));
758         }
759     
760     
761         // get tick positions
762         i = 1;
763         minYear = minDate[getFullYear]();
764         var time = minDate.getTime(),
765             minMonth = minDate[getMonth](),
766             minDateDate = minDate[getDate](),
767             timezoneOffset = useUTC ? 
768                 0 : 
769                 (24 * 3600 * 1000 + minDate.getTimezoneOffset() * 60 * 1000) % (24 * 3600 * 1000); // #950
770     
771         // iterate and add tick positions at appropriate values
772         while (time < max) {
773             tickPositions.push(time);
774     
775             // if the interval is years, use Date.UTC to increase years
776             if (interval === timeUnits[YEAR]) {
777                 time = makeTime(minYear + i * count, 0);
778     
779             // if the interval is months, use Date.UTC to increase months
780             } else if (interval === timeUnits[MONTH]) {
781                 time = makeTime(minYear, minMonth + i * count);
782     
783             // if we're using global time, the interval is not fixed as it jumps
784             // one hour at the DST crossover
785             } else if (!useUTC && (interval === timeUnits[DAY] || interval === timeUnits[WEEK])) {
786                 time = makeTime(minYear, minMonth, minDateDate +
787                     i * count * (interval === timeUnits[DAY] ? 1 : 7));
788     
789             // else, the interval is fixed and we use simple addition
790             } else {
791                 time += interval * count;
792             }
793     
794             i++;
795         }
796     
797         // push the last time
798         tickPositions.push(time);
799
800
801         // mark new days if the time is dividible by day (#1649, #1760)
802         each(grep(tickPositions, function (time) {
803             return interval <= timeUnits[HOUR] && time % timeUnits[DAY] === timezoneOffset;
804         }), function (time) {
805             higherRanks[time] = DAY;
806         });
807     }
808
809
810     // record information on the chosen unit - for dynamic label formatter
811     tickPositions.info = extend(normalizedInterval, {
812         higherRanks: higherRanks,
813         totalRange: interval * count
814     });
815
816     return tickPositions;
817 }
818
819 /**
820  * Helper class that contains variuos counters that are local to the chart.
821  */
822 function ChartCounters() {
823     this.color = 0;
824     this.symbol = 0;
825 }
826
827 ChartCounters.prototype =  {
828     /**
829      * Wraps the color counter if it reaches the specified length.
830      */
831     wrapColor: function (length) {
832         if (this.color >= length) {
833             this.color = 0;
834         }
835     },
836
837     /**
838      * Wraps the symbol counter if it reaches the specified length.
839      */
840     wrapSymbol: function (length) {
841         if (this.symbol >= length) {
842             this.symbol = 0;
843         }
844     }
845 };
846
847
848 /**
849  * Utility method that sorts an object array and keeping the order of equal items.
850  * ECMA script standard does not specify the behaviour when items are equal.
851  */
852 function stableSort(arr, sortFunction) {
853     var length = arr.length,
854         sortValue,
855         i;
856
857     // Add index to each item
858     for (i = 0; i < length; i++) {
859         arr[i].ss_i = i; // stable sort index
860     }
861
862     arr.sort(function (a, b) {
863         sortValue = sortFunction(a, b);
864         return sortValue === 0 ? a.ss_i - b.ss_i : sortValue;
865     });
866
867     // Remove index from items
868     for (i = 0; i < length; i++) {
869         delete arr[i].ss_i; // stable sort index
870     }
871 }
872
873 /**
874  * Non-recursive method to find the lowest member of an array. Math.min raises a maximum
875  * call stack size exceeded error in Chrome when trying to apply more than 150.000 points. This
876  * method is slightly slower, but safe.
877  */
878 function arrayMin(data) {
879     var i = data.length,
880         min = data[0];
881
882     while (i--) {
883         if (data[i] < min) {
884             min = data[i];
885         }
886     }
887     return min;
888 }
889
890 /**
891  * Non-recursive method to find the lowest member of an array. Math.min raises a maximum
892  * call stack size exceeded error in Chrome when trying to apply more than 150.000 points. This
893  * method is slightly slower, but safe.
894  */
895 function arrayMax(data) {
896     var i = data.length,
897         max = data[0];
898
899     while (i--) {
900         if (data[i] > max) {
901             max = data[i];
902         }
903     }
904     return max;
905 }
906
907 /**
908  * Utility method that destroys any SVGElement or VMLElement that are properties on the given object.
909  * It loops all properties and invokes destroy if there is a destroy method. The property is
910  * then delete'ed.
911  * @param {Object} The object to destroy properties on
912  * @param {Object} Exception, do not destroy this property, only delete it.
913  */
914 function destroyObjectProperties(obj, except) {
915     var n;
916     for (n in obj) {
917         // If the object is non-null and destroy is defined
918         if (obj[n] && obj[n] !== except && obj[n].destroy) {
919             // Invoke the destroy
920             obj[n].destroy();
921         }
922
923         // Delete the property from the object.
924         delete obj[n];
925     }
926 }
927
928
929 /**
930  * Discard an element by moving it to the bin and delete
931  * @param {Object} The HTML node to discard
932  */
933 function discardElement(element) {
934     // create a garbage bin element, not part of the DOM
935     if (!garbageBin) {
936         garbageBin = createElement(DIV);
937     }
938
939     // move the node and empty bin
940     if (element) {
941         garbageBin.appendChild(element);
942     }
943     garbageBin.innerHTML = '';
944 }
945
946 /**
947  * Provide error messages for debugging, with links to online explanation 
948  */
949 function error(code, stop) {
950     var msg = 'Highcharts error #' + code + ': www.highcharts.com/errors/' + code;
951     if (stop) {
952         throw msg;
953     } else if (win.console) {
954         console.log(msg);
955     }
956 }
957
958 /**
959  * Fix JS round off float errors
960  * @param {Number} num
961  */
962 function correctFloat(num) {
963     return parseFloat(
964         num.toPrecision(14)
965     );
966 }
967
968 /**
969  * Set the global animation to either a given value, or fall back to the
970  * given chart's animation option
971  * @param {Object} animation
972  * @param {Object} chart
973  */
974 function setAnimation(animation, chart) {
975     globalAnimation = pick(animation, chart.animation);
976 }
977
978 /**
979  * The time unit lookup
980  */
981 /*jslint white: true*/
982 timeUnits = hash(
983     MILLISECOND, 1,
984     SECOND, 1000,
985     MINUTE, 60000,
986     HOUR, 3600000,
987     DAY, 24 * 3600000,
988     WEEK, 7 * 24 * 3600000,
989     MONTH, 31 * 24 * 3600000,
990     YEAR, 31556952000
991 );
992 /*jslint white: false*/
993 /**
994  * Path interpolation algorithm used across adapters
995  */
996 pathAnim = {
997     /**
998      * Prepare start and end values so that the path can be animated one to one
999      */
1000     init: function (elem, fromD, toD) {
1001         fromD = fromD || '';
1002         var shift = elem.shift,
1003             bezier = fromD.indexOf('C') > -1,
1004             numParams = bezier ? 7 : 3,
1005             endLength,
1006             slice,
1007             i,
1008             start = fromD.split(' '),
1009             end = [].concat(toD), // copy
1010             startBaseLine,
1011             endBaseLine,
1012             sixify = function (arr) { // in splines make move points have six parameters like bezier curves
1013                 i = arr.length;
1014                 while (i--) {
1015                     if (arr[i] === M) {
1016                         arr.splice(i + 1, 0, arr[i + 1], arr[i + 2], arr[i + 1], arr[i + 2]);
1017                     }
1018                 }
1019             };
1020
1021         if (bezier) {
1022             sixify(start);
1023             sixify(end);
1024         }
1025
1026         // pull out the base lines before padding
1027         if (elem.isArea) {
1028             startBaseLine = start.splice(start.length - 6, 6);
1029             endBaseLine = end.splice(end.length - 6, 6);
1030         }
1031
1032         // if shifting points, prepend a dummy point to the end path
1033         if (shift <= end.length / numParams && start.length === end.length) {
1034             while (shift--) {
1035                 end = [].concat(end).splice(0, numParams).concat(end);
1036             }
1037         }
1038         elem.shift = 0; // reset for following animations
1039
1040         // copy and append last point until the length matches the end length
1041         if (start.length) {
1042             endLength = end.length;
1043             while (start.length < endLength) {
1044
1045                 //bezier && sixify(start);
1046                 slice = [].concat(start).splice(start.length - numParams, numParams);
1047                 if (bezier) { // disable first control point
1048                     slice[numParams - 6] = slice[numParams - 2];
1049                     slice[numParams - 5] = slice[numParams - 1];
1050                 }
1051                 start = start.concat(slice);
1052             }
1053         }
1054
1055         if (startBaseLine) { // append the base lines for areas
1056             start = start.concat(startBaseLine);
1057             end = end.concat(endBaseLine);
1058         }
1059         return [start, end];
1060     },
1061
1062     /**
1063      * Interpolate each value of the path and return the array
1064      */
1065     step: function (start, end, pos, complete) {
1066         var ret = [],
1067             i = start.length,
1068             startVal;
1069
1070         if (pos === 1) { // land on the final path without adjustment points appended in the ends
1071             ret = complete;
1072
1073         } else if (i === end.length && pos < 1) {
1074             while (i--) {
1075                 startVal = parseFloat(start[i]);
1076                 ret[i] =
1077                     isNaN(startVal) ? // a letter instruction like M or L
1078                         start[i] :
1079                         pos * (parseFloat(end[i] - startVal)) + startVal;
1080
1081             }
1082         } else { // if animation is finished or length not matching, land on right value
1083             ret = end;
1084         }
1085         return ret;
1086     }
1087 };
1088
1089 (function ($) {
1090     /**
1091      * The default HighchartsAdapter for jQuery
1092      */
1093     win.HighchartsAdapter = win.HighchartsAdapter || ($ && {
1094         
1095         /**
1096          * Initialize the adapter by applying some extensions to jQuery
1097          */
1098         init: function (pathAnim) {
1099             
1100             // extend the animate function to allow SVG animations
1101             var Fx = $.fx,
1102                 Step = Fx.step,
1103                 dSetter,
1104                 Tween = $.Tween,
1105                 propHooks = Tween && Tween.propHooks,
1106                 opacityHook = $.cssHooks.opacity;
1107             
1108             /*jslint unparam: true*//* allow unused param x in this function */
1109             $.extend($.easing, {
1110                 easeOutQuad: function (x, t, b, c, d) {
1111                     return -c * (t /= d) * (t - 2) + b;
1112                 }
1113             });
1114             /*jslint unparam: false*/
1115         
1116             // extend some methods to check for elem.attr, which means it is a Highcharts SVG object
1117             $.each(['cur', '_default', 'width', 'height', 'opacity'], function (i, fn) {
1118                 var obj = Step,
1119                     base,
1120                     elem;
1121                     
1122                 // Handle different parent objects
1123                 if (fn === 'cur') {
1124                     obj = Fx.prototype; // 'cur', the getter, relates to Fx.prototype
1125                 
1126                 } else if (fn === '_default' && Tween) { // jQuery 1.8 model
1127                     obj = propHooks[fn];
1128                     fn = 'set';
1129                 }
1130         
1131                 // Overwrite the method
1132                 base = obj[fn];
1133                 if (base) { // step.width and step.height don't exist in jQuery < 1.7
1134         
1135                     // create the extended function replacement
1136                     obj[fn] = function (fx) {
1137         
1138                         // Fx.prototype.cur does not use fx argument
1139                         fx = i ? fx : this;
1140
1141                         // Don't run animations on textual properties like align (#1821)
1142                         if (fx.prop === 'align') {
1143                             return;
1144                         }
1145         
1146                         // shortcut
1147                         elem = fx.elem;
1148         
1149                         // Fx.prototype.cur returns the current value. The other ones are setters
1150                         // and returning a value has no effect.
1151                         return elem.attr ? // is SVG element wrapper
1152                             elem.attr(fx.prop, fn === 'cur' ? UNDEFINED : fx.now) : // apply the SVG wrapper's method
1153                             base.apply(this, arguments); // use jQuery's built-in method
1154                     };
1155                 }
1156             });
1157
1158             // Extend the opacity getter, needed for fading opacity with IE9 and jQuery 1.10+
1159             wrap(opacityHook, 'get', function (proceed, elem, computed) {
1160                 return elem.attr ? (elem.opacity || 0) : proceed.call(this, elem, computed);
1161             });
1162             
1163             
1164             // Define the setter function for d (path definitions)
1165             dSetter = function (fx) {
1166                 var elem = fx.elem,
1167                     ends;
1168         
1169                 // Normally start and end should be set in state == 0, but sometimes,
1170                 // for reasons unknown, this doesn't happen. Perhaps state == 0 is skipped
1171                 // in these cases
1172                 if (!fx.started) {
1173                     ends = pathAnim.init(elem, elem.d, elem.toD);
1174                     fx.start = ends[0];
1175                     fx.end = ends[1];
1176                     fx.started = true;
1177                 }
1178         
1179         
1180                 // interpolate each value of the path
1181                 elem.attr('d', pathAnim.step(fx.start, fx.end, fx.pos, elem.toD));
1182             };
1183             
1184             // jQuery 1.8 style
1185             if (Tween) {
1186                 propHooks.d = {
1187                     set: dSetter
1188                 };
1189             // pre 1.8
1190             } else {
1191                 // animate paths
1192                 Step.d = dSetter;
1193             }
1194             
1195             /**
1196              * Utility for iterating over an array. Parameters are reversed compared to jQuery.
1197              * @param {Array} arr
1198              * @param {Function} fn
1199              */
1200             this.each = Array.prototype.forEach ?
1201                 function (arr, fn) { // modern browsers
1202                     return Array.prototype.forEach.call(arr, fn);
1203                     
1204                 } : 
1205                 function (arr, fn) { // legacy
1206                     var i = 0, 
1207                         len = arr.length;
1208                     for (; i < len; i++) {
1209                         if (fn.call(arr[i], arr[i], i, arr) === false) {
1210                             return i;
1211                         }
1212                     }
1213                 };
1214             
1215             /**
1216              * Register Highcharts as a plugin in the respective framework
1217              */
1218             $.fn.highcharts = function () {
1219                 var constr = 'Chart', // default constructor
1220                     args = arguments,
1221                     options,
1222                     ret,
1223                     chart;
1224
1225                 if (isString(args[0])) {
1226                     constr = args[0];
1227                     args = Array.prototype.slice.call(args, 1); 
1228                 }
1229                 options = args[0];
1230
1231                 // Create the chart
1232                 if (options !== UNDEFINED) {
1233                     /*jslint unused:false*/
1234                     options.chart = options.chart || {};
1235                     options.chart.renderTo = this[0];
1236                     chart = new Highcharts[constr](options, args[1]);
1237                     ret = this;
1238                     /*jslint unused:true*/
1239                 }
1240
1241                 // When called without parameters or with the return argument, get a predefined chart
1242                 if (options === UNDEFINED) {
1243                     ret = charts[attr(this[0], 'data-highcharts-chart')];
1244                 }    
1245
1246                 return ret;
1247             };
1248
1249         },
1250
1251         
1252         /**
1253          * Downloads a script and executes a callback when done.
1254          * @param {String} scriptLocation
1255          * @param {Function} callback
1256          */
1257         getScript: $.getScript,
1258         
1259         /**
1260          * Return the index of an item in an array, or -1 if not found
1261          */
1262         inArray: $.inArray,
1263         
1264         /**
1265          * A direct link to jQuery methods. MooTools and Prototype adapters must be implemented for each case of method.
1266          * @param {Object} elem The HTML element
1267          * @param {String} method Which method to run on the wrapped element
1268          */
1269         adapterRun: function (elem, method) {
1270             return $(elem)[method]();
1271         },
1272     
1273         /**
1274          * Filter an array
1275          */
1276         grep: $.grep,
1277     
1278         /**
1279          * Map an array
1280          * @param {Array} arr
1281          * @param {Function} fn
1282          */
1283         map: function (arr, fn) {
1284             //return jQuery.map(arr, fn);
1285             var results = [],
1286                 i = 0,
1287                 len = arr.length;
1288             for (; i < len; i++) {
1289                 results[i] = fn.call(arr[i], arr[i], i, arr);
1290             }
1291             return results;
1292     
1293         },
1294     
1295         /**
1296          * Get the position of an element relative to the top left of the page
1297          */
1298         offset: function (el) {
1299             return $(el).offset();
1300         },
1301     
1302         /**
1303          * Add an event listener
1304          * @param {Object} el A HTML element or custom object
1305          * @param {String} event The event type
1306          * @param {Function} fn The event handler
1307          */
1308         addEvent: function (el, event, fn) {
1309             $(el).bind(event, fn);
1310         },
1311     
1312         /**
1313          * Remove event added with addEvent
1314          * @param {Object} el The object
1315          * @param {String} eventType The event type. Leave blank to remove all events.
1316          * @param {Function} handler The function to remove
1317          */
1318         removeEvent: function (el, eventType, handler) {
1319             // workaround for jQuery issue with unbinding custom events:
1320             // http://forum.jQuery.com/topic/javascript-error-when-unbinding-a-custom-event-using-jQuery-1-4-2
1321             var func = doc.removeEventListener ? 'removeEventListener' : 'detachEvent';
1322             if (doc[func] && el && !el[func]) {
1323                 el[func] = function () {};
1324             }
1325     
1326             $(el).unbind(eventType, handler);
1327         },
1328     
1329         /**
1330          * Fire an event on a custom object
1331          * @param {Object} el
1332          * @param {String} type
1333          * @param {Object} eventArguments
1334          * @param {Function} defaultFunction
1335          */
1336         fireEvent: function (el, type, eventArguments, defaultFunction) {
1337             var event = $.Event(type),
1338                 detachedType = 'detached' + type,
1339                 defaultPrevented;
1340     
1341             // Remove warnings in Chrome when accessing layerX and layerY. Although Highcharts
1342             // never uses these properties, Chrome includes them in the default click event and
1343             // raises the warning when they are copied over in the extend statement below.
1344             //
1345             // To avoid problems in IE (see #1010) where we cannot delete the properties and avoid
1346             // testing if they are there (warning in chrome) the only option is to test if running IE.
1347             if (!isIE && eventArguments) {
1348                 delete eventArguments.layerX;
1349                 delete eventArguments.layerY;
1350             }
1351     
1352             extend(event, eventArguments);
1353     
1354             // Prevent jQuery from triggering the object method that is named the
1355             // same as the event. For example, if the event is 'select', jQuery
1356             // attempts calling el.select and it goes into a loop.
1357             if (el[type]) {
1358                 el[detachedType] = el[type];
1359                 el[type] = null;
1360             }
1361     
1362             // Wrap preventDefault and stopPropagation in try/catch blocks in
1363             // order to prevent JS errors when cancelling events on non-DOM
1364             // objects. #615.
1365             /*jslint unparam: true*/
1366             $.each(['preventDefault', 'stopPropagation'], function (i, fn) {
1367                 var base = event[fn];
1368                 event[fn] = function () {
1369                     try {
1370                         base.call(event);
1371                     } catch (e) {
1372                         if (fn === 'preventDefault') {
1373                             defaultPrevented = true;
1374                         }
1375                     }
1376                 };
1377             });
1378             /*jslint unparam: false*/
1379     
1380             // trigger it
1381             $(el).trigger(event);
1382     
1383             // attach the method
1384             if (el[detachedType]) {
1385                 el[type] = el[detachedType];
1386                 el[detachedType] = null;
1387             }
1388     
1389             if (defaultFunction && !event.isDefaultPrevented() && !defaultPrevented) {
1390                 defaultFunction(event);
1391             }
1392         },
1393         
1394         /**
1395          * Extension method needed for MooTools
1396          */
1397         washMouseEvent: function (e) {
1398             var ret = e.originalEvent || e;
1399             
1400             // computed by jQuery, needed by IE8
1401             if (ret.pageX === UNDEFINED) { // #1236
1402                 ret.pageX = e.pageX;
1403                 ret.pageY = e.pageY;
1404             }
1405             
1406             return ret;
1407         },
1408     
1409         /**
1410          * Animate a HTML element or SVG element wrapper
1411          * @param {Object} el
1412          * @param {Object} params
1413          * @param {Object} options jQuery-like animation options: duration, easing, callback
1414          */
1415         animate: function (el, params, options) {
1416             var $el = $(el);
1417             if (!el.style) {
1418                 el.style = {}; // #1881
1419             }
1420             if (params.d) {
1421                 el.toD = params.d; // keep the array form for paths, used in $.fx.step.d
1422                 params.d = 1; // because in jQuery, animating to an array has a different meaning
1423             }
1424     
1425             $el.stop();
1426             if (params.opacity !== UNDEFINED && el.attr) {
1427                 params.opacity += 'px'; // force jQuery to use same logic as width and height (#2161)
1428             }
1429             $el.animate(params, options);
1430     
1431         },
1432         /**
1433          * Stop running animation
1434          */
1435         stop: function (el) {
1436             $(el).stop();
1437         }
1438     });
1439 }(win.jQuery));
1440
1441
1442 // check for a custom HighchartsAdapter defined prior to this file
1443 var globalAdapter = win.HighchartsAdapter,
1444     adapter = globalAdapter || {};
1445     
1446 // Initialize the adapter
1447 if (globalAdapter) {
1448     globalAdapter.init.call(globalAdapter, pathAnim);
1449 }
1450
1451
1452 // Utility functions. If the HighchartsAdapter is not defined, adapter is an empty object
1453 // and all the utility functions will be null. In that case they are populated by the
1454 // default adapters below.
1455 var adapterRun = adapter.adapterRun,
1456     getScript = adapter.getScript,
1457     inArray = adapter.inArray,
1458     each = adapter.each,
1459     grep = adapter.grep,
1460     offset = adapter.offset,
1461     map = adapter.map,
1462     addEvent = adapter.addEvent,
1463     removeEvent = adapter.removeEvent,
1464     fireEvent = adapter.fireEvent,
1465     washMouseEvent = adapter.washMouseEvent,
1466     animate = adapter.animate,
1467     stop = adapter.stop;
1468
1469
1470
1471 /* ****************************************************************************
1472  * Handle the options                                                         *
1473  *****************************************************************************/
1474 var
1475
1476 defaultLabelOptions = {
1477     enabled: true,
1478     // rotation: 0,
1479     // align: 'center',
1480     x: 0,
1481     y: 15,
1482     /*formatter: function () {
1483         return this.value;
1484     },*/
1485     style: {
1486         color: '#666',
1487         cursor: 'default',
1488         fontSize: '11px',
1489         lineHeight: '14px'
1490     }
1491 };
1492
1493 defaultOptions = {
1494     colors: ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970',
1495         '#f28f43', '#77a1e5', '#c42525', '#a6c96a'],
1496     symbols: ['circle', 'diamond', 'square', 'triangle', 'triangle-down'],
1497     lang: {
1498         loading: 'Loading...',
1499         months: ['January', 'February', 'March', 'April', 'May', 'June', 'July',
1500                 'August', 'September', 'October', 'November', 'December'],
1501         shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
1502         weekdays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
1503         decimalPoint: '.',
1504         numericSymbols: ['k', 'M', 'G', 'T', 'P', 'E'], // SI prefixes used in axis labels
1505         resetZoom: 'Reset zoom',
1506         resetZoomTitle: 'Reset zoom level 1:1',
1507         thousandsSep: ','
1508     },
1509     global: {
1510         useUTC: true,
1511         canvasToolsURL: 'http://code.highcharts.com/3.0.6/modules/canvas-tools.js',
1512         VMLRadialGradientURL: 'http://code.highcharts.com/3.0.6/gfx/vml-radial-gradient.png'
1513     },
1514     chart: {
1515         //animation: true,
1516         //alignTicks: false,
1517         //reflow: true,
1518         //className: null,
1519         //events: { load, selection },
1520         //margin: [null],
1521         //marginTop: null,
1522         //marginRight: null,
1523         //marginBottom: null,
1524         //marginLeft: null,
1525         borderColor: '#4572A7',
1526         //borderWidth: 0,
1527         borderRadius: 5,
1528         defaultSeriesType: 'line',
1529         ignoreHiddenSeries: true,
1530         //inverted: false,
1531         //shadow: false,
1532         spacing: [10, 10, 15, 10],
1533         //spacingTop: 10,
1534         //spacingRight: 10,
1535         //spacingBottom: 15,
1536         //spacingLeft: 10,
1537         style: {
1538             fontFamily: '"Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, Helvetica, sans-serif', // default font
1539             fontSize: '12px'
1540         },
1541         backgroundColor: '#FFFFFF',
1542         //plotBackgroundColor: null,
1543         plotBorderColor: '#C0C0C0',
1544         //plotBorderWidth: 0,
1545         //plotShadow: false,
1546         //zoomType: ''
1547         resetZoomButton: {
1548             theme: {
1549                 zIndex: 20
1550             },
1551             position: {
1552                 align: 'right',
1553                 x: -10,
1554                 //verticalAlign: 'top',
1555                 y: 10
1556             }
1557             // relativeTo: 'plot'
1558         }
1559     },
1560     title: {
1561         text: 'Chart title',
1562         align: 'center',
1563         // floating: false,
1564         margin: 15,
1565         // x: 0,
1566         // verticalAlign: 'top',
1567         // y: null,
1568         style: {
1569             color: '#274b6d',//#3E576F',
1570             fontSize: '16px'
1571         }
1572
1573     },
1574     subtitle: {
1575         text: '',
1576         align: 'center',
1577         // floating: false
1578         // x: 0,
1579         // verticalAlign: 'top',
1580         // y: null,
1581         style: {
1582             color: '#4d759e'
1583         }
1584     },
1585
1586     plotOptions: {
1587         line: { // base series options
1588             allowPointSelect: false,
1589             showCheckbox: false,
1590             animation: {
1591                 duration: 1000
1592             },
1593             //connectNulls: false,
1594             //cursor: 'default',
1595             //clip: true,
1596             //dashStyle: null,
1597             //enableMouseTracking: true,
1598             events: {},
1599             //legendIndex: 0,
1600             lineWidth: 2,
1601             //shadow: false,
1602             // stacking: null,
1603             marker: {
1604                 enabled: true,
1605                 //symbol: null,
1606                 lineWidth: 0,
1607                 radius: 4,
1608                 lineColor: '#FFFFFF',
1609                 //fillColor: null,
1610                 states: { // states for a single point
1611                     hover: {
1612                         enabled: true
1613                         //radius: base + 2
1614                     },
1615                     select: {
1616                         fillColor: '#FFFFFF',
1617                         lineColor: '#000000',
1618                         lineWidth: 2
1619                     }
1620                 }
1621             },
1622             point: {
1623                 events: {}
1624             },
1625             dataLabels: merge(defaultLabelOptions, {
1626                 align: 'center',
1627                 enabled: false,
1628                 formatter: function () {
1629                     return this.y === null ? '' : numberFormat(this.y, -1);
1630                 },
1631                 verticalAlign: 'bottom', // above singular point
1632                 y: 0
1633                 // backgroundColor: undefined,
1634                 // borderColor: undefined,
1635                 // borderRadius: undefined,
1636                 // borderWidth: undefined,
1637                 // padding: 3,
1638                 // shadow: false
1639             }),
1640             cropThreshold: 300, // draw points outside the plot area when the number of points is less than this
1641             pointRange: 0,
1642             //pointStart: 0,
1643             //pointInterval: 1,
1644             showInLegend: true,
1645             states: { // states for the entire series
1646                 hover: {
1647                     //enabled: false,
1648                     //lineWidth: base + 1,
1649                     marker: {
1650                         // lineWidth: base + 1,
1651                         // radius: base + 1
1652                     }
1653                 },
1654                 select: {
1655                     marker: {}
1656                 }
1657             },
1658             stickyTracking: true
1659             //tooltip: {
1660                 //pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.y}</b>'
1661                 //valueDecimals: null,
1662                 //xDateFormat: '%A, %b %e, %Y',
1663                 //valuePrefix: '',
1664                 //ySuffix: ''                
1665             //}
1666             // turboThreshold: 1000
1667             // zIndex: null
1668         }
1669     },
1670     labels: {
1671         //items: [],
1672         style: {
1673             //font: defaultFont,
1674             position: ABSOLUTE,
1675             color: '#3E576F'
1676         }
1677     },
1678     legend: {
1679         enabled: true,
1680         align: 'center',
1681         //floating: false,
1682         layout: 'horizontal',
1683         labelFormatter: function () {
1684             return this.name;
1685         },
1686         borderWidth: 1,
1687         borderColor: '#909090',
1688         borderRadius: 5,
1689         navigation: {
1690             // animation: true,
1691             activeColor: '#274b6d',
1692             // arrowSize: 12
1693             inactiveColor: '#CCC'
1694             // style: {} // text styles
1695         },
1696         // margin: 10,
1697         // reversed: false,
1698         shadow: false,
1699         // backgroundColor: null,
1700         /*style: {
1701             padding: '5px'
1702         },*/
1703         itemStyle: {
1704             cursor: 'pointer',
1705             color: '#274b6d',
1706             fontSize: '12px'
1707         },
1708         itemHoverStyle: {
1709             //cursor: 'pointer', removed as of #601
1710             color: '#000'
1711         },
1712         itemHiddenStyle: {
1713             color: '#CCC'
1714         },
1715         itemCheckboxStyle: {
1716             position: ABSOLUTE,
1717             width: '13px', // for IE precision
1718             height: '13px'
1719         },
1720         // itemWidth: undefined,
1721         symbolWidth: 16,
1722         symbolPadding: 5,
1723         verticalAlign: 'bottom',
1724         // width: undefined,
1725         x: 0,
1726         y: 0,
1727         title: {
1728             //text: null,
1729             style: {
1730                 fontWeight: 'bold'
1731             }
1732         }            
1733     },
1734
1735     loading: {
1736         // hideDuration: 100,
1737         labelStyle: {
1738             fontWeight: 'bold',
1739             position: RELATIVE,
1740             top: '1em'
1741         },
1742         // showDuration: 0,
1743         style: {
1744             position: ABSOLUTE,
1745             backgroundColor: 'white',
1746             opacity: 0.5,
1747             textAlign: 'center'
1748         }
1749     },
1750
1751     tooltip: {
1752         enabled: true,
1753         animation: hasSVG,
1754         //crosshairs: null,
1755         backgroundColor: 'rgba(255, 255, 255, .85)',
1756         borderWidth: 1,
1757         borderRadius: 3,
1758         dateTimeLabelFormats: { 
1759             millisecond: '%A, %b %e, %H:%M:%S.%L',
1760             second: '%A, %b %e, %H:%M:%S',
1761             minute: '%A, %b %e, %H:%M',
1762             hour: '%A, %b %e, %H:%M',
1763             day: '%A, %b %e, %Y',
1764             week: 'Week from %A, %b %e, %Y',
1765             month: '%B %Y',
1766             year: '%Y'
1767         },
1768         //formatter: defaultFormatter,
1769         headerFormat: '<span style="font-size: 10px">{point.key}</span><br/>',
1770         pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.y}</b><br/>',
1771         shadow: true,
1772         //shared: false,
1773         snap: isTouchDevice ? 25 : 10,
1774         style: {
1775             color: '#333333',
1776             cursor: 'default',
1777             fontSize: '12px',
1778             padding: '8px',
1779             whiteSpace: 'nowrap'
1780         }
1781         //xDateFormat: '%A, %b %e, %Y',
1782         //valueDecimals: null,
1783         //valuePrefix: '',
1784         //valueSuffix: ''
1785     },
1786
1787     credits: {
1788         enabled: true,
1789         text: 'Highcharts.com',
1790         href: 'http://www.highcharts.com',
1791         position: {
1792             align: 'right',
1793             x: -10,
1794             verticalAlign: 'bottom',
1795             y: -5
1796         },
1797         style: {
1798             cursor: 'pointer',
1799             color: '#909090',
1800             fontSize: '9px'
1801         }
1802     }
1803 };
1804
1805
1806
1807
1808 // Series defaults
1809 var defaultPlotOptions = defaultOptions.plotOptions,
1810     defaultSeriesOptions = defaultPlotOptions.line;
1811
1812 // set the default time methods
1813 setTimeMethods();
1814
1815
1816
1817 /**
1818  * Set the time methods globally based on the useUTC option. Time method can be either
1819  * local time or UTC (default).
1820  */
1821 function setTimeMethods() {
1822     var useUTC = defaultOptions.global.useUTC,
1823         GET = useUTC ? 'getUTC' : 'get',
1824         SET = useUTC ? 'setUTC' : 'set';
1825
1826     makeTime = useUTC ? Date.UTC : function (year, month, date, hours, minutes, seconds) {
1827         return new Date(
1828             year,
1829             month,
1830             pick(date, 1),
1831             pick(hours, 0),
1832             pick(minutes, 0),
1833             pick(seconds, 0)
1834         ).getTime();
1835     };
1836     getMinutes =  GET + 'Minutes';
1837     getHours =    GET + 'Hours';
1838     getDay =      GET + 'Day';
1839     getDate =     GET + 'Date';
1840     getMonth =    GET + 'Month';
1841     getFullYear = GET + 'FullYear';
1842     setMinutes =  SET + 'Minutes';
1843     setHours =    SET + 'Hours';
1844     setDate =     SET + 'Date';
1845     setMonth =    SET + 'Month';
1846     setFullYear = SET + 'FullYear';
1847
1848 }
1849
1850 /**
1851  * Merge the default options with custom options and return the new options structure
1852  * @param {Object} options The new custom options
1853  */
1854 function setOptions(options) {
1855     
1856     // Pull out axis options and apply them to the respective default axis options 
1857     /*defaultXAxisOptions = merge(defaultXAxisOptions, options.xAxis);
1858     defaultYAxisOptions = merge(defaultYAxisOptions, options.yAxis);
1859     options.xAxis = options.yAxis = UNDEFINED;*/
1860     
1861     // Merge in the default options
1862     defaultOptions = merge(defaultOptions, options);
1863     
1864     // Apply UTC
1865     setTimeMethods();
1866
1867     return defaultOptions;
1868 }
1869
1870 /**
1871  * Get the updated default options. Merely exposing defaultOptions for outside modules
1872  * isn't enough because the setOptions method creates a new object.
1873  */
1874 function getOptions() {
1875     return defaultOptions;
1876 }
1877
1878
1879 /**
1880  * Handle color operations. The object methods are chainable.
1881  * @param {String} input The input color in either rbga or hex format
1882  */
1883 var Color = function (input) {
1884     // declare variables
1885     var rgba = [], result, stops;
1886
1887     /**
1888      * Parse the input color to rgba array
1889      * @param {String} input
1890      */
1891     function init(input) {
1892
1893         // Gradients
1894         if (input && input.stops) {
1895             stops = map(input.stops, function (stop) {
1896                 return Color(stop[1]);
1897             });
1898
1899         // Solid colors
1900         } else {
1901             // rgba
1902             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);
1903             if (result) {
1904                 rgba = [pInt(result[1]), pInt(result[2]), pInt(result[3]), parseFloat(result[4], 10)];
1905             } else { 
1906                 // hex
1907                 result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(input);
1908                 if (result) {
1909                     rgba = [pInt(result[1], 16), pInt(result[2], 16), pInt(result[3], 16), 1];
1910                 } else {
1911                     // rgb
1912                     result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(input);
1913                     if (result) {
1914                         rgba = [pInt(result[1]), pInt(result[2]), pInt(result[3]), 1];
1915                     }
1916                 }
1917             }
1918         }        
1919
1920     }
1921     /**
1922      * Return the color a specified format
1923      * @param {String} format
1924      */
1925     function get(format) {
1926         var ret;
1927
1928         if (stops) {
1929             ret = merge(input);
1930             ret.stops = [].concat(ret.stops);
1931             each(stops, function (stop, i) {
1932                 ret.stops[i] = [ret.stops[i][0], stop.get(format)];
1933             });
1934
1935         // it's NaN if gradient colors on a column chart
1936         } else if (rgba && !isNaN(rgba[0])) {
1937             if (format === 'rgb') {
1938                 ret = 'rgb(' + rgba[0] + ',' + rgba[1] + ',' + rgba[2] + ')';
1939             } else if (format === 'a') {
1940                 ret = rgba[3];
1941             } else {
1942                 ret = 'rgba(' + rgba.join(',') + ')';
1943             }
1944         } else {
1945             ret = input;
1946         }
1947         return ret;
1948     }
1949
1950     /**
1951      * Brighten the color
1952      * @param {Number} alpha
1953      */
1954     function brighten(alpha) {
1955         if (stops) {
1956             each(stops, function (stop) {
1957                 stop.brighten(alpha);
1958             });
1959         
1960         } else if (isNumber(alpha) && alpha !== 0) {
1961             var i;
1962             for (i = 0; i < 3; i++) {
1963                 rgba[i] += pInt(alpha * 255);
1964
1965                 if (rgba[i] < 0) {
1966                     rgba[i] = 0;
1967                 }
1968                 if (rgba[i] > 255) {
1969                     rgba[i] = 255;
1970                 }
1971             }
1972         }
1973         return this;
1974     }
1975     /**
1976      * Set the color's opacity to a given alpha value
1977      * @param {Number} alpha
1978      */
1979     function setOpacity(alpha) {
1980         rgba[3] = alpha;
1981         return this;
1982     }
1983
1984     // initialize: parse the input
1985     init(input);
1986
1987     // public methods
1988     return {
1989         get: get,
1990         brighten: brighten,
1991         rgba: rgba,
1992         setOpacity: setOpacity
1993     };
1994 };
1995
1996
1997 /**
1998  * A wrapper object for SVG elements
1999  */
2000 function SVGElement() {}
2001
2002 SVGElement.prototype = {
2003     /**
2004      * Initialize the SVG renderer
2005      * @param {Object} renderer
2006      * @param {String} nodeName
2007      */
2008     init: function (renderer, nodeName) {
2009         var wrapper = this;
2010         wrapper.element = nodeName === 'span' ?
2011             createElement(nodeName) :
2012             doc.createElementNS(SVG_NS, nodeName);
2013         wrapper.renderer = renderer;
2014         /**
2015          * A collection of attribute setters. These methods, if defined, are called right before a certain
2016          * attribute is set on an element wrapper. Returning false prevents the default attribute
2017          * setter to run. Returning a value causes the default setter to set that value. Used in
2018          * Renderer.label.
2019          */
2020         wrapper.attrSetters = {};
2021     },
2022     /**
2023      * Default base for animation
2024      */
2025     opacity: 1,
2026     /**
2027      * Animate a given attribute
2028      * @param {Object} params
2029      * @param {Number} options The same options as in jQuery animation
2030      * @param {Function} complete Function to perform at the end of animation
2031      */
2032     animate: function (params, options, complete) {
2033         var animOptions = pick(options, globalAnimation, true);
2034         stop(this); // stop regardless of animation actually running, or reverting to .attr (#607)
2035         if (animOptions) {
2036             animOptions = merge(animOptions);
2037             if (complete) { // allows using a callback with the global animation without overwriting it
2038                 animOptions.complete = complete;
2039             }
2040             animate(this, params, animOptions);
2041         } else {
2042             this.attr(params);
2043             if (complete) {
2044                 complete();
2045             }
2046         }
2047     },
2048     /**
2049      * Set or get a given attribute
2050      * @param {Object|String} hash
2051      * @param {Mixed|Undefined} val
2052      */
2053     attr: function (hash, val) {
2054         var wrapper = this,
2055             key,
2056             value,
2057             result,
2058             i,
2059             child,
2060             element = wrapper.element,
2061             nodeName = element.nodeName.toLowerCase(), // Android2 requires lower for "text"
2062             renderer = wrapper.renderer,
2063             skipAttr,
2064             titleNode,
2065             attrSetters = wrapper.attrSetters,
2066             shadows = wrapper.shadows,
2067             hasSetSymbolSize,
2068             doTransform,
2069             ret = wrapper;
2070
2071         // single key-value pair
2072         if (isString(hash) && defined(val)) {
2073             key = hash;
2074             hash = {};
2075             hash[key] = val;
2076         }
2077
2078         // used as a getter: first argument is a string, second is undefined
2079         if (isString(hash)) {
2080             key = hash;
2081             if (nodeName === 'circle') {
2082                 key = { x: 'cx', y: 'cy' }[key] || key;
2083             } else if (key === 'strokeWidth') {
2084                 key = 'stroke-width';
2085             }
2086             ret = attr(element, key) || wrapper[key] || 0;
2087             if (key !== 'd' && key !== 'visibility' && key !== 'fill') { // 'd' is string in animation step
2088                 ret = parseFloat(ret);
2089             }
2090
2091         // setter
2092         } else {
2093
2094             for (key in hash) {
2095                 skipAttr = false; // reset
2096                 value = hash[key];
2097
2098                 // check for a specific attribute setter
2099                 result = attrSetters[key] && attrSetters[key].call(wrapper, value, key);
2100
2101                 if (result !== false) {
2102                     if (result !== UNDEFINED) {
2103                         value = result; // the attribute setter has returned a new value to set
2104                     }
2105
2106
2107                     // paths
2108                     if (key === 'd') {
2109                         if (value && value.join) { // join path
2110                             value = value.join(' ');
2111                         }
2112                         if (/(NaN| {2}|^$)/.test(value)) {
2113                             value = 'M 0 0';
2114                         }
2115                         //wrapper.d = value; // shortcut for animations
2116
2117                     // update child tspans x values
2118                     } else if (key === 'x' && nodeName === 'text') {
2119                         for (i = 0; i < element.childNodes.length; i++) {
2120                             child = element.childNodes[i];
2121                             // if the x values are equal, the tspan represents a linebreak
2122                             if (attr(child, 'x') === attr(element, 'x')) {
2123                                 //child.setAttribute('x', value);
2124                                 attr(child, 'x', value);
2125                             }
2126                         }
2127
2128                     } else if (wrapper.rotation && (key === 'x' || key === 'y')) {
2129                         doTransform = true;
2130
2131                     // apply gradients
2132                     } else if (key === 'fill') {
2133                         value = renderer.color(value, element, key);
2134
2135                     // circle x and y
2136                     } else if (nodeName === 'circle' && (key === 'x' || key === 'y')) {
2137                         key = { x: 'cx', y: 'cy' }[key] || key;
2138
2139                     // rectangle border radius
2140                     } else if (nodeName === 'rect' && key === 'r') {
2141                         attr(element, {
2142                             rx: value,
2143                             ry: value
2144                         });
2145                         skipAttr = true;
2146
2147                     // translation and text rotation
2148                     } else if (key === 'translateX' || key === 'translateY' || key === 'rotation' ||
2149                             key === 'verticalAlign' || key === 'scaleX' || key === 'scaleY') {
2150                         doTransform = true;
2151                         skipAttr = true;
2152
2153                     // apply opacity as subnode (required by legacy WebKit and Batik)
2154                     } else if (key === 'stroke') {
2155                         value = renderer.color(value, element, key);
2156
2157                     // emulate VML's dashstyle implementation
2158                     } else if (key === 'dashstyle') {
2159                         key = 'stroke-dasharray';
2160                         value = value && value.toLowerCase();
2161                         if (value === 'solid') {
2162                             value = NONE;
2163                         } else if (value) {
2164                             value = value
2165                                 .replace('shortdashdotdot', '3,1,1,1,1,1,')
2166                                 .replace('shortdashdot', '3,1,1,1')
2167                                 .replace('shortdot', '1,1,')
2168                                 .replace('shortdash', '3,1,')
2169                                 .replace('longdash', '8,3,')
2170                                 .replace(/dot/g, '1,3,')
2171                                 .replace('dash', '4,3,')
2172                                 .replace(/,$/, '')
2173                                 .split(','); // ending comma
2174
2175                             i = value.length;
2176                             while (i--) {
2177                                 value[i] = pInt(value[i]) * pick(hash['stroke-width'], wrapper['stroke-width']);
2178                             }
2179                             value = value.join(',');
2180                         }
2181
2182                     // IE9/MooTools combo: MooTools returns objects instead of numbers and IE9 Beta 2
2183                     // is unable to cast them. Test again with final IE9.
2184                     } else if (key === 'width') {
2185                         value = pInt(value);
2186
2187                     // Text alignment
2188                     } else if (key === 'align') {
2189                         key = 'text-anchor';
2190                         value = { left: 'start', center: 'middle', right: 'end' }[value];
2191
2192                     // Title requires a subnode, #431
2193                     } else if (key === 'title') {
2194                         titleNode = element.getElementsByTagName('title')[0];
2195                         if (!titleNode) {
2196                             titleNode = doc.createElementNS(SVG_NS, 'title');
2197                             element.appendChild(titleNode);
2198                         }
2199                         titleNode.textContent = value;
2200                     }
2201
2202                     // jQuery animate changes case
2203                     if (key === 'strokeWidth') {
2204                         key = 'stroke-width';
2205                     }
2206
2207                     // In Chrome/Win < 6 as well as Batik, the stroke attribute can't be set when the stroke-
2208                     // width is 0. #1369
2209                     if (key === 'stroke-width' || key === 'stroke') {
2210                         wrapper[key] = value;
2211                         // Only apply the stroke attribute if the stroke width is defined and larger than 0
2212                         if (wrapper.stroke && wrapper['stroke-width']) {
2213                             attr(element, 'stroke', wrapper.stroke);
2214                             attr(element, 'stroke-width', wrapper['stroke-width']);
2215                             wrapper.hasStroke = true;
2216                         } else if (key === 'stroke-width' && value === 0 && wrapper.hasStroke) {
2217                             element.removeAttribute('stroke');
2218                             wrapper.hasStroke = false;
2219                         }
2220                         skipAttr = true;
2221                     }
2222
2223                     // symbols
2224                     if (wrapper.symbolName && /^(x|y|width|height|r|start|end|innerR|anchorX|anchorY)/.test(key)) {
2225
2226
2227                         if (!hasSetSymbolSize) {
2228                             wrapper.symbolAttr(hash);
2229                             hasSetSymbolSize = true;
2230                         }
2231                         skipAttr = true;
2232                     }
2233
2234                     // let the shadow follow the main element
2235                     if (shadows && /^(width|height|visibility|x|y|d|transform|cx|cy|r)$/.test(key)) {
2236                         i = shadows.length;
2237                         while (i--) {
2238                             attr(
2239                                 shadows[i],
2240                                 key,
2241                                 key === 'height' ?
2242                                     mathMax(value - (shadows[i].cutHeight || 0), 0) :
2243                                     value
2244                             );
2245                         }
2246                     }
2247
2248                     // validate heights
2249                     if ((key === 'width' || key === 'height') && nodeName === 'rect' && value < 0) {
2250                         value = 0;
2251                     }
2252
2253                     // Record for animation and quick access without polling the DOM
2254                     wrapper[key] = value;
2255
2256
2257                     if (key === 'text') {
2258                         // Delete bBox memo when the text changes
2259                         if (value !== wrapper.textStr) {
2260                             delete wrapper.bBox;
2261                         }
2262                         wrapper.textStr = value;
2263                         if (wrapper.added) {
2264                             renderer.buildText(wrapper);
2265                         }
2266                     } else if (!skipAttr) {
2267                         attr(element, key, value);
2268                     }
2269
2270                 }
2271
2272             }
2273
2274             // Update transform. Do this outside the loop to prevent redundant updating for batch setting
2275             // of attributes.
2276             if (doTransform) {
2277                 wrapper.updateTransform();
2278             }
2279
2280         }
2281
2282         return ret;
2283     },
2284
2285
2286     /**
2287      * Add a class name to an element
2288      */
2289     addClass: function (className) {
2290         var element = this.element,
2291             currentClassName = attr(element, 'class') || '';
2292
2293         if (currentClassName.indexOf(className) === -1) {
2294             attr(element, 'class', currentClassName + ' ' + className);
2295         }
2296         return this;
2297     },
2298     /* hasClass and removeClass are not (yet) needed
2299     hasClass: function (className) {
2300         return attr(this.element, 'class').indexOf(className) !== -1;
2301     },
2302     removeClass: function (className) {
2303         attr(this.element, 'class', attr(this.element, 'class').replace(className, ''));
2304         return this;
2305     },
2306     */
2307
2308     /**
2309      * If one of the symbol size affecting parameters are changed,
2310      * check all the others only once for each call to an element's
2311      * .attr() method
2312      * @param {Object} hash
2313      */
2314     symbolAttr: function (hash) {
2315         var wrapper = this;
2316
2317         each(['x', 'y', 'r', 'start', 'end', 'width', 'height', 'innerR', 'anchorX', 'anchorY'], function (key) {
2318             wrapper[key] = pick(hash[key], wrapper[key]);
2319         });
2320
2321         wrapper.attr({
2322             d: wrapper.renderer.symbols[wrapper.symbolName](
2323                 wrapper.x,
2324                 wrapper.y,
2325                 wrapper.width,
2326                 wrapper.height,
2327                 wrapper
2328             )
2329         });
2330     },
2331
2332     /**
2333      * Apply a clipping path to this object
2334      * @param {String} id
2335      */
2336     clip: function (clipRect) {
2337         return this.attr('clip-path', clipRect ? 'url(' + this.renderer.url + '#' + clipRect.id + ')' : NONE);
2338     },
2339
2340     /**
2341      * Calculate the coordinates needed for drawing a rectangle crisply and return the
2342      * calculated attributes
2343      * @param {Number} strokeWidth
2344      * @param {Number} x
2345      * @param {Number} y
2346      * @param {Number} width
2347      * @param {Number} height
2348      */
2349     crisp: function (strokeWidth, x, y, width, height) {
2350
2351         var wrapper = this,
2352             key,
2353             attribs = {},
2354             values = {},
2355             normalizer;
2356
2357         strokeWidth = strokeWidth || wrapper.strokeWidth || (wrapper.attr && wrapper.attr('stroke-width')) || 0;
2358         normalizer = mathRound(strokeWidth) % 2 / 2; // mathRound because strokeWidth can sometimes have roundoff errors
2359
2360         // normalize for crisp edges
2361         values.x = mathFloor(x || wrapper.x || 0) + normalizer;
2362         values.y = mathFloor(y || wrapper.y || 0) + normalizer;
2363         values.width = mathFloor((width || wrapper.width || 0) - 2 * normalizer);
2364         values.height = mathFloor((height || wrapper.height || 0) - 2 * normalizer);
2365         values.strokeWidth = strokeWidth;
2366
2367         for (key in values) {
2368             if (wrapper[key] !== values[key]) { // only set attribute if changed
2369                 wrapper[key] = attribs[key] = values[key];
2370             }
2371         }
2372
2373         return attribs;
2374     },
2375
2376     /**
2377      * Set styles for the element
2378      * @param {Object} styles
2379      */
2380     css: function (styles) {
2381         /*jslint unparam: true*//* allow unused param a in the regexp function below */
2382         var elemWrapper = this,
2383             elem = elemWrapper.element,
2384             textWidth = styles && styles.width && elem.nodeName.toLowerCase() === 'text',
2385             n,
2386             serializedCss = '',
2387             hyphenate = function (a, b) { return '-' + b.toLowerCase(); };
2388         /*jslint unparam: false*/
2389
2390         // convert legacy
2391         if (styles && styles.color) {
2392             styles.fill = styles.color;
2393         }
2394
2395         // Merge the new styles with the old ones
2396         styles = extend(
2397             elemWrapper.styles,
2398             styles
2399         );
2400
2401         // store object
2402         elemWrapper.styles = styles;
2403
2404
2405         // Don't handle line wrap on canvas
2406         if (useCanVG && textWidth) {
2407             delete styles.width;
2408         }
2409
2410         // serialize and set style attribute
2411         if (isIE && !hasSVG) { // legacy IE doesn't support setting style attribute
2412             if (textWidth) {
2413                 delete styles.width;
2414             }
2415             css(elemWrapper.element, styles);
2416         } else {
2417             for (n in styles) {
2418                 serializedCss += n.replace(/([A-Z])/g, hyphenate) + ':' + styles[n] + ';';
2419             }
2420             attr(elem, 'style', serializedCss); // #1881
2421         }
2422
2423
2424         // re-build text
2425         if (textWidth && elemWrapper.added) {
2426             elemWrapper.renderer.buildText(elemWrapper);
2427         }
2428
2429         return elemWrapper;
2430     },
2431
2432     /**
2433      * Add an event listener
2434      * @param {String} eventType
2435      * @param {Function} handler
2436      */
2437     on: function (eventType, handler) {
2438         var svgElement = this,
2439             element = svgElement.element;
2440         
2441         // touch
2442         if (hasTouch && eventType === 'click') {
2443             element.ontouchstart = function (e) {            
2444                 svgElement.touchEventFired = Date.now();                
2445                 e.preventDefault();
2446                 handler.call(element, e);
2447             };
2448             element.onclick = function (e) {                                                
2449                 if (userAgent.indexOf('Android') === -1 || Date.now() - (svgElement.touchEventFired || 0) > 1100) { // #2269
2450                     handler.call(element, e);
2451                 }
2452             };            
2453         } else {
2454             // simplest possible event model for internal use
2455             element['on' + eventType] = handler;
2456         }
2457         return this;
2458     },
2459
2460     /**
2461      * Set the coordinates needed to draw a consistent radial gradient across
2462      * pie slices regardless of positioning inside the chart. The format is
2463      * [centerX, centerY, diameter] in pixels.
2464      */
2465     setRadialReference: function (coordinates) {
2466         this.element.radialReference = coordinates;
2467         return this;
2468     },
2469
2470     /**
2471      * Move an object and its children by x and y values
2472      * @param {Number} x
2473      * @param {Number} y
2474      */
2475     translate: function (x, y) {
2476         return this.attr({
2477             translateX: x,
2478             translateY: y
2479         });
2480     },
2481
2482     /**
2483      * Invert a group, rotate and flip
2484      */
2485     invert: function () {
2486         var wrapper = this;
2487         wrapper.inverted = true;
2488         wrapper.updateTransform();
2489         return wrapper;
2490     },
2491
2492     /**
2493      * Apply CSS to HTML elements. This is used in text within SVG rendering and
2494      * by the VML renderer
2495      */
2496     htmlCss: function (styles) {
2497         var wrapper = this,
2498             element = wrapper.element,
2499             textWidth = styles && element.tagName === 'SPAN' && styles.width;
2500
2501         if (textWidth) {
2502             delete styles.width;
2503             wrapper.textWidth = textWidth;
2504             wrapper.updateTransform();
2505         }
2506
2507         wrapper.styles = extend(wrapper.styles, styles);
2508         css(wrapper.element, styles);
2509
2510         return wrapper;
2511     },
2512
2513
2514
2515     /**
2516      * VML and useHTML method for calculating the bounding box based on offsets
2517      * @param {Boolean} refresh Whether to force a fresh value from the DOM or to
2518      * use the cached value
2519      *
2520      * @return {Object} A hash containing values for x, y, width and height
2521      */
2522
2523     htmlGetBBox: function () {
2524         var wrapper = this,
2525             element = wrapper.element,
2526             bBox = wrapper.bBox;
2527
2528         // faking getBBox in exported SVG in legacy IE
2529         if (!bBox) {
2530             // faking getBBox in exported SVG in legacy IE (is this a duplicate of the fix for #1079?)
2531             if (element.nodeName === 'text') {
2532                 element.style.position = ABSOLUTE;
2533             }
2534
2535             bBox = wrapper.bBox = {
2536                 x: element.offsetLeft,
2537                 y: element.offsetTop,
2538                 width: element.offsetWidth,
2539                 height: element.offsetHeight
2540             };
2541         }
2542
2543         return bBox;
2544     },
2545
2546     /**
2547      * VML override private method to update elements based on internal
2548      * properties based on SVG transform
2549      */
2550     htmlUpdateTransform: function () {
2551         // aligning non added elements is expensive
2552         if (!this.added) {
2553             this.alignOnAdd = true;
2554             return;
2555         }
2556
2557         var wrapper = this,
2558             renderer = wrapper.renderer,
2559             elem = wrapper.element,
2560             translateX = wrapper.translateX || 0,
2561             translateY = wrapper.translateY || 0,
2562             x = wrapper.x || 0,
2563             y = wrapper.y || 0,
2564             align = wrapper.textAlign || 'left',
2565             alignCorrection = { left: 0, center: 0.5, right: 1 }[align],
2566             nonLeft = align && align !== 'left',
2567             shadows = wrapper.shadows;
2568
2569         // apply translate
2570         css(elem, {
2571             marginLeft: translateX,
2572             marginTop: translateY
2573         });
2574         if (shadows) { // used in labels/tooltip
2575             each(shadows, function (shadow) {
2576                 css(shadow, {
2577                     marginLeft: translateX + 1,
2578                     marginTop: translateY + 1
2579                 });
2580             });
2581         }
2582
2583         // apply inversion
2584         if (wrapper.inverted) { // wrapper is a group
2585             each(elem.childNodes, function (child) {
2586                 renderer.invertChild(child, elem);
2587             });
2588         }
2589
2590         if (elem.tagName === 'SPAN') {
2591
2592             var width, height,
2593                 rotation = wrapper.rotation,
2594                 baseline,
2595                 radians = 0,
2596                 costheta = 1,
2597                 sintheta = 0,
2598                 quad,
2599                 textWidth = pInt(wrapper.textWidth),
2600                 xCorr = wrapper.xCorr || 0,
2601                 yCorr = wrapper.yCorr || 0,
2602                 currentTextTransform = [rotation, align, elem.innerHTML, wrapper.textWidth].join(',');
2603
2604             if (currentTextTransform !== wrapper.cTT) { // do the calculations and DOM access only if properties changed
2605
2606                 if (defined(rotation)) {
2607
2608                     radians = rotation * deg2rad; // deg to rad
2609                     costheta = mathCos(radians);
2610                     sintheta = mathSin(radians);
2611
2612                     wrapper.setSpanRotation(rotation, sintheta, costheta);
2613
2614                 }
2615
2616                 width = pick(wrapper.elemWidth, elem.offsetWidth);
2617                 height = pick(wrapper.elemHeight, elem.offsetHeight);
2618
2619                 // update textWidth
2620                 if (width > textWidth && /[ \-]/.test(elem.textContent || elem.innerText)) { // #983, #1254
2621                     css(elem, {
2622                         width: textWidth + PX,
2623                         display: 'block',
2624                         whiteSpace: 'normal'
2625                     });
2626                     width = textWidth;
2627                 }
2628
2629                 // correct x and y
2630                 baseline = renderer.fontMetrics(elem.style.fontSize).b;
2631                 xCorr = costheta < 0 && -width;
2632                 yCorr = sintheta < 0 && -height;
2633
2634                 // correct for baseline and corners spilling out after rotation
2635                 quad = costheta * sintheta < 0;
2636                 xCorr += sintheta * baseline * (quad ? 1 - alignCorrection : alignCorrection);
2637                 yCorr -= costheta * baseline * (rotation ? (quad ? alignCorrection : 1 - alignCorrection) : 1);
2638
2639                 // correct for the length/height of the text
2640                 if (nonLeft) {
2641                     xCorr -= width * alignCorrection * (costheta < 0 ? -1 : 1);
2642                     if (rotation) {
2643                         yCorr -= height * alignCorrection * (sintheta < 0 ? -1 : 1);
2644                     }
2645                     css(elem, {
2646                         textAlign: align
2647                     });
2648                 }
2649
2650                 // record correction
2651                 wrapper.xCorr = xCorr;
2652                 wrapper.yCorr = yCorr;
2653             }
2654
2655             // apply position with correction
2656             css(elem, {
2657                 left: (x + xCorr) + PX,
2658                 top: (y + yCorr) + PX
2659             });
2660
2661             // force reflow in webkit to apply the left and top on useHTML element (#1249)
2662             if (isWebKit) {
2663                 height = elem.offsetHeight; // assigned to height for JSLint purpose
2664             }
2665
2666             // record current text transform
2667             wrapper.cTT = currentTextTransform;
2668         }
2669     },
2670
2671     /**
2672      * Set the rotation of an individual HTML span
2673      */
2674     setSpanRotation: function (rotation) {
2675         var rotationStyle = {},
2676             cssTransformKey = isIE ? '-ms-transform' : isWebKit ? '-webkit-transform' : isFirefox ? 'MozTransform' : isOpera ? '-o-transform' : '';
2677
2678         rotationStyle[cssTransformKey] = rotationStyle.transform = 'rotate(' + rotation + 'deg)';
2679         css(this.element, rotationStyle);
2680     },
2681
2682     /**
2683      * Private method to update the transform attribute based on internal
2684      * properties
2685      */
2686     updateTransform: function () {
2687         var wrapper = this,
2688             translateX = wrapper.translateX || 0,
2689             translateY = wrapper.translateY || 0,
2690             scaleX = wrapper.scaleX,
2691             scaleY = wrapper.scaleY,
2692             inverted = wrapper.inverted,
2693             rotation = wrapper.rotation,
2694             transform;
2695
2696         // flipping affects translate as adjustment for flipping around the group's axis
2697         if (inverted) {
2698             translateX += wrapper.attr('width');
2699             translateY += wrapper.attr('height');
2700         }
2701
2702         // Apply translate. Nearly all transformed elements have translation, so instead
2703         // of checking for translate = 0, do it always (#1767, #1846).
2704         transform = ['translate(' + translateX + ',' + translateY + ')'];
2705
2706         // apply rotation
2707         if (inverted) {
2708             transform.push('rotate(90) scale(-1,1)');
2709         } else if (rotation) { // text rotation
2710             transform.push('rotate(' + rotation + ' ' + (wrapper.x || 0) + ' ' + (wrapper.y || 0) + ')');
2711         }
2712
2713         // apply scale
2714         if (defined(scaleX) || defined(scaleY)) {
2715             transform.push('scale(' + pick(scaleX, 1) + ' ' + pick(scaleY, 1) + ')');
2716         }
2717
2718         if (transform.length) {
2719             attr(wrapper.element, 'transform', transform.join(' '));
2720         }
2721     },
2722     /**
2723      * Bring the element to the front
2724      */
2725     toFront: function () {
2726         var element = this.element;
2727         element.parentNode.appendChild(element);
2728         return this;
2729     },
2730
2731
2732     /**
2733      * Break down alignment options like align, verticalAlign, x and y
2734      * to x and y relative to the chart.
2735      *
2736      * @param {Object} alignOptions
2737      * @param {Boolean} alignByTranslate
2738      * @param {String[Object} box The box to align to, needs a width and height. When the
2739      *        box is a string, it refers to an object in the Renderer. For example, when
2740      *        box is 'spacingBox', it refers to Renderer.spacingBox which holds width, height
2741      *        x and y properties.
2742      *
2743      */
2744     align: function (alignOptions, alignByTranslate, box) {
2745         var align,
2746             vAlign,
2747             x,
2748             y,
2749             attribs = {},
2750             alignTo,
2751             renderer = this.renderer,
2752             alignedObjects = renderer.alignedObjects;
2753
2754         // First call on instanciate
2755         if (alignOptions) {
2756             this.alignOptions = alignOptions;
2757             this.alignByTranslate = alignByTranslate;
2758             if (!box || isString(box)) { // boxes other than renderer handle this internally
2759                 this.alignTo = alignTo = box || 'renderer';
2760                 erase(alignedObjects, this); // prevent duplicates, like legendGroup after resize
2761                 alignedObjects.push(this);
2762                 box = null; // reassign it below
2763             }
2764
2765         // When called on resize, no arguments are supplied
2766         } else {
2767             alignOptions = this.alignOptions;
2768             alignByTranslate = this.alignByTranslate;
2769             alignTo = this.alignTo;
2770         }
2771
2772         box = pick(box, renderer[alignTo], renderer);
2773
2774         // Assign variables
2775         align = alignOptions.align;
2776         vAlign = alignOptions.verticalAlign;
2777         x = (box.x || 0) + (alignOptions.x || 0); // default: left align
2778         y = (box.y || 0) + (alignOptions.y || 0); // default: top align
2779
2780         // Align
2781         if (align === 'right' || align === 'center') {
2782             x += (box.width - (alignOptions.width || 0)) /
2783                     { right: 1, center: 2 }[align];
2784         }
2785         attribs[alignByTranslate ? 'translateX' : 'x'] = mathRound(x);
2786
2787
2788         // Vertical align
2789         if (vAlign === 'bottom' || vAlign === 'middle') {
2790             y += (box.height - (alignOptions.height || 0)) /
2791                     ({ bottom: 1, middle: 2 }[vAlign] || 1);
2792
2793         }
2794         attribs[alignByTranslate ? 'translateY' : 'y'] = mathRound(y);
2795
2796         // Animate only if already placed
2797         this[this.placed ? 'animate' : 'attr'](attribs);
2798         this.placed = true;
2799         this.alignAttr = attribs;
2800
2801         return this;
2802     },
2803
2804     /**
2805      * Get the bounding box (width, height, x and y) for the element
2806      */
2807     getBBox: function () {
2808         var wrapper = this,
2809             bBox = wrapper.bBox,
2810             renderer = wrapper.renderer,
2811             width,
2812             height,
2813             rotation = wrapper.rotation,
2814             element = wrapper.element,
2815             styles = wrapper.styles,
2816             rad = rotation * deg2rad;
2817
2818         if (!bBox) {
2819             // SVG elements
2820             if (element.namespaceURI === SVG_NS || renderer.forExport) {
2821                 try { // Fails in Firefox if the container has display: none.
2822
2823                     bBox = element.getBBox ?
2824                         // SVG: use extend because IE9 is not allowed to change width and height in case
2825                         // of rotation (below)
2826                         extend({}, element.getBBox()) :
2827                         // Canvas renderer and legacy IE in export mode
2828                         {
2829                             width: element.offsetWidth,
2830                             height: element.offsetHeight
2831                         };
2832                 } catch (e) {}
2833
2834                 // If the bBox is not set, the try-catch block above failed. The other condition
2835                 // is for Opera that returns a width of -Infinity on hidden elements.
2836                 if (!bBox || bBox.width < 0) {
2837                     bBox = { width: 0, height: 0 };
2838                 }
2839
2840
2841             // VML Renderer or useHTML within SVG
2842             } else {
2843
2844                 bBox = wrapper.htmlGetBBox();
2845
2846             }
2847
2848             // True SVG elements as well as HTML elements in modern browsers using the .useHTML option
2849             // need to compensated for rotation
2850             if (renderer.isSVG) {
2851                 width = bBox.width;
2852                 height = bBox.height;
2853
2854                 // Workaround for wrong bounding box in IE9 and IE10 (#1101, #1505, #1669)
2855                 if (isIE && styles && styles.fontSize === '11px' && height.toPrecision(3) === '22.7') {
2856                     bBox.height = height = 14;
2857                 }
2858
2859                 // Adjust for rotated text
2860                 if (rotation) {
2861                     bBox.width = mathAbs(height * mathSin(rad)) + mathAbs(width * mathCos(rad));
2862                     bBox.height = mathAbs(height * mathCos(rad)) + mathAbs(width * mathSin(rad));
2863                 }
2864             }
2865
2866             wrapper.bBox = bBox;
2867         }
2868         return bBox;
2869     },
2870
2871     /**
2872      * Show the element
2873      */
2874     show: function () {
2875         return this.attr({ visibility: VISIBLE });
2876     },
2877
2878     /**
2879      * Hide the element
2880      */
2881     hide: function () {
2882         return this.attr({ visibility: HIDDEN });
2883     },
2884
2885     fadeOut: function (duration) {
2886         var elemWrapper = this;
2887         elemWrapper.animate({
2888             opacity: 0
2889         }, {
2890             duration: duration || 150,
2891             complete: function () {
2892                 elemWrapper.hide();
2893             }
2894         });
2895     },
2896
2897     /**
2898      * Add the element
2899      * @param {Object|Undefined} parent Can be an element, an element wrapper or undefined
2900      *    to append the element to the renderer.box.
2901      */
2902     add: function (parent) {
2903
2904         var renderer = this.renderer,
2905             parentWrapper = parent || renderer,
2906             parentNode = parentWrapper.element || renderer.box,
2907             childNodes = parentNode.childNodes,
2908             element = this.element,
2909             zIndex = attr(element, 'zIndex'),
2910             otherElement,
2911             otherZIndex,
2912             i,
2913             inserted;
2914
2915         if (parent) {
2916             this.parentGroup = parent;
2917         }
2918
2919         // mark as inverted
2920         this.parentInverted = parent && parent.inverted;
2921
2922         // build formatted text
2923         if (this.textStr !== undefined) {
2924             renderer.buildText(this);
2925         }
2926
2927         // mark the container as having z indexed children
2928         if (zIndex) {
2929             parentWrapper.handleZ = true;
2930             zIndex = pInt(zIndex);
2931         }
2932
2933         // insert according to this and other elements' zIndex
2934         if (parentWrapper.handleZ) { // this element or any of its siblings has a z index
2935             for (i = 0; i < childNodes.length; i++) {
2936                 otherElement = childNodes[i];
2937                 otherZIndex = attr(otherElement, 'zIndex');
2938                 if (otherElement !== element && (
2939                         // insert before the first element with a higher zIndex
2940                         pInt(otherZIndex) > zIndex ||
2941                         // if no zIndex given, insert before the first element with a zIndex
2942                         (!defined(zIndex) && defined(otherZIndex))
2943
2944                         )) {
2945                     parentNode.insertBefore(element, otherElement);
2946                     inserted = true;
2947                     break;
2948                 }
2949             }
2950         }
2951
2952         // default: append at the end
2953         if (!inserted) {
2954             parentNode.appendChild(element);
2955         }
2956
2957         // mark as added
2958         this.added = true;
2959
2960         // fire an event for internal hooks
2961         fireEvent(this, 'add');
2962
2963         return this;
2964     },
2965
2966     /**
2967      * Removes a child either by removeChild or move to garbageBin.
2968      * Issue 490; in VML removeChild results in Orphaned nodes according to sIEve, discardElement does not.
2969      */
2970     safeRemoveChild: function (element) {
2971         var parentNode = element.parentNode;
2972         if (parentNode) {
2973             parentNode.removeChild(element);
2974         }
2975     },
2976
2977     /**
2978      * Destroy the element and element wrapper
2979      */
2980     destroy: function () {
2981         var wrapper = this,
2982             element = wrapper.element || {},
2983             shadows = wrapper.shadows,
2984             parentToClean = wrapper.renderer.isSVG && element.nodeName === 'SPAN' && element.parentNode,
2985             grandParent,
2986             key,
2987             i;
2988
2989         // remove events
2990         element.onclick = element.onmouseout = element.onmouseover = element.onmousemove = element.point = null;
2991         stop(wrapper); // stop running animations
2992
2993         if (wrapper.clipPath) {
2994             wrapper.clipPath = wrapper.clipPath.destroy();
2995         }
2996
2997         // Destroy stops in case this is a gradient object
2998         if (wrapper.stops) {
2999             for (i = 0; i < wrapper.stops.length; i++) {
3000                 wrapper.stops[i] = wrapper.stops[i].destroy();
3001             }
3002             wrapper.stops = null;
3003         }
3004
3005         // remove element
3006         wrapper.safeRemoveChild(element);
3007
3008         // destroy shadows
3009         if (shadows) {
3010             each(shadows, function (shadow) {
3011                 wrapper.safeRemoveChild(shadow);
3012             });
3013         }
3014
3015         // In case of useHTML, clean up empty containers emulating SVG groups (#1960).
3016         while (parentToClean && parentToClean.childNodes.length === 0) {
3017             grandParent = parentToClean.parentNode;
3018             wrapper.safeRemoveChild(parentToClean);
3019             parentToClean = grandParent;
3020         }
3021
3022         // remove from alignObjects
3023         if (wrapper.alignTo) {
3024             erase(wrapper.renderer.alignedObjects, wrapper);
3025         }
3026
3027         for (key in wrapper) {
3028             delete wrapper[key];
3029         }
3030
3031         return null;
3032     },
3033
3034     /**
3035      * Add a shadow to the element. Must be done after the element is added to the DOM
3036      * @param {Boolean|Object} shadowOptions
3037      */
3038     shadow: function (shadowOptions, group, cutOff) {
3039         var shadows = [],
3040             i,
3041             shadow,
3042             element = this.element,
3043             strokeWidth,
3044             shadowWidth,
3045             shadowElementOpacity,
3046
3047             // compensate for inverted plot area
3048             transform;
3049
3050
3051         if (shadowOptions) {
3052             shadowWidth = pick(shadowOptions.width, 3);
3053             shadowElementOpacity = (shadowOptions.opacity || 0.15) / shadowWidth;
3054             transform = this.parentInverted ?
3055                 '(-1,-1)' :
3056                 '(' + pick(shadowOptions.offsetX, 1) + ', ' + pick(shadowOptions.offsetY, 1) + ')';
3057             for (i = 1; i <= shadowWidth; i++) {
3058                 shadow = element.cloneNode(0);
3059                 strokeWidth = (shadowWidth * 2) + 1 - (2 * i);
3060                 attr(shadow, {
3061                     'isShadow': 'true',
3062                     'stroke': shadowOptions.color || 'black',
3063                     'stroke-opacity': shadowElementOpacity * i,
3064                     'stroke-width': strokeWidth,
3065                     'transform': 'translate' + transform,
3066                     'fill': NONE
3067                 });
3068                 if (cutOff) {
3069                     attr(shadow, 'height', mathMax(attr(shadow, 'height') - strokeWidth, 0));
3070                     shadow.cutHeight = strokeWidth;
3071                 }
3072
3073                 if (group) {
3074                     group.element.appendChild(shadow);
3075                 } else {
3076                     element.parentNode.insertBefore(shadow, element);
3077                 }
3078
3079                 shadows.push(shadow);
3080             }
3081
3082             this.shadows = shadows;
3083         }
3084         return this;
3085
3086     }
3087 };
3088
3089
3090 /**
3091  * The default SVG renderer
3092  */
3093 var SVGRenderer = function () {
3094     this.init.apply(this, arguments);
3095 };
3096 SVGRenderer.prototype = {
3097     Element: SVGElement,
3098
3099     /**
3100      * Initialize the SVGRenderer
3101      * @param {Object} container
3102      * @param {Number} width
3103      * @param {Number} height
3104      * @param {Boolean} forExport
3105      */
3106     init: function (container, width, height, forExport) {
3107         var renderer = this,
3108             loc = location,
3109             boxWrapper,
3110             element,
3111             desc;
3112
3113         boxWrapper = renderer.createElement('svg')
3114             .attr({
3115                 version: '1.1'
3116             });
3117         element = boxWrapper.element;
3118         container.appendChild(element);
3119
3120         // For browsers other than IE, add the namespace attribute (#1978)
3121         if (container.innerHTML.indexOf('xmlns') === -1) {
3122             attr(element, 'xmlns', SVG_NS);
3123         }
3124
3125         // object properties
3126         renderer.isSVG = true;
3127         renderer.box = element;
3128         renderer.boxWrapper = boxWrapper;
3129         renderer.alignedObjects = [];
3130
3131         // Page url used for internal references. #24, #672, #1070
3132         renderer.url = (isFirefox || isWebKit) && doc.getElementsByTagName('base').length ?
3133             loc.href
3134                 .replace(/#.*?$/, '') // remove the hash
3135                 .replace(/([\('\)])/g, '\\$1') // escape parantheses and quotes
3136                 .replace(/ /g, '%20') : // replace spaces (needed for Safari only)
3137             '';
3138
3139         // Add description
3140         desc = this.createElement('desc').add();
3141         desc.element.appendChild(doc.createTextNode('Created with ' + PRODUCT + ' ' + VERSION));
3142
3143
3144         renderer.defs = this.createElement('defs').add();
3145         renderer.forExport = forExport;
3146         renderer.gradients = {}; // Object where gradient SvgElements are stored
3147
3148         renderer.setSize(width, height, false);
3149
3150
3151
3152         // Issue 110 workaround:
3153         // In Firefox, if a div is positioned by percentage, its pixel position may land
3154         // between pixels. The container itself doesn't display this, but an SVG element
3155         // inside this container will be drawn at subpixel precision. In order to draw
3156         // sharp lines, this must be compensated for. This doesn't seem to work inside
3157         // iframes though (like in jsFiddle).
3158         var subPixelFix, rect;
3159         if (isFirefox && container.getBoundingClientRect) {
3160             renderer.subPixelFix = subPixelFix = function () {
3161                 css(container, { left: 0, top: 0 });
3162                 rect = container.getBoundingClientRect();
3163                 css(container, {
3164                     left: (mathCeil(rect.left) - rect.left) + PX,
3165                     top: (mathCeil(rect.top) - rect.top) + PX
3166                 });
3167             };
3168
3169             // run the fix now
3170             subPixelFix();
3171
3172             // run it on resize
3173             addEvent(win, 'resize', subPixelFix);
3174         }
3175     },
3176
3177     /**
3178      * Detect whether the renderer is hidden. This happens when one of the parent elements
3179      * has display: none. #608.
3180      */
3181     isHidden: function () {
3182         return !this.boxWrapper.getBBox().width;
3183     },
3184
3185     /**
3186      * Destroys the renderer and its allocated members.
3187      */
3188     destroy: function () {
3189         var renderer = this,
3190             rendererDefs = renderer.defs;
3191         renderer.box = null;
3192         renderer.boxWrapper = renderer.boxWrapper.destroy();
3193
3194         // Call destroy on all gradient elements
3195         destroyObjectProperties(renderer.gradients || {});
3196         renderer.gradients = null;
3197
3198         // Defs are null in VMLRenderer
3199         // Otherwise, destroy them here.
3200         if (rendererDefs) {
3201             renderer.defs = rendererDefs.destroy();
3202         }
3203
3204         // Remove sub pixel fix handler
3205         // We need to check that there is a handler, otherwise all functions that are registered for event 'resize' are removed
3206         // See issue #982
3207         if (renderer.subPixelFix) {
3208             removeEvent(win, 'resize', renderer.subPixelFix);
3209         }
3210
3211         renderer.alignedObjects = null;
3212
3213         return null;
3214     },
3215
3216     /**
3217      * Create a wrapper for an SVG element
3218      * @param {Object} nodeName
3219      */
3220     createElement: function (nodeName) {
3221         var wrapper = new this.Element();
3222         wrapper.init(this, nodeName);
3223         return wrapper;
3224     },
3225
3226     /**
3227      * Dummy function for use in canvas renderer
3228      */
3229     draw: function () {},
3230
3231     /**
3232      * Parse a simple HTML string into SVG tspans
3233      *
3234      * @param {Object} textNode The parent text SVG node
3235      */
3236     buildText: function (wrapper) {
3237         var textNode = wrapper.element,
3238             renderer = this,
3239             forExport = renderer.forExport,
3240             lines = pick(wrapper.textStr, '').toString()
3241                 .replace(/<(b|strong)>/g, '<span style="font-weight:bold">')
3242                 .replace(/<(i|em)>/g, '<span style="font-style:italic">')
3243                 .replace(/<a/g, '<span')
3244                 .replace(/<\/(b|strong|i|em|a)>/g, '</span>')
3245                 .split(/<br.*?>/g),
3246             childNodes = textNode.childNodes,
3247             styleRegex = /style="([^"]+)"/,
3248             hrefRegex = /href="(http[^"]+)"/,
3249             parentX = attr(textNode, 'x'),
3250             textStyles = wrapper.styles,
3251             width = textStyles && textStyles.width && pInt(textStyles.width),
3252             textLineHeight = textStyles && textStyles.lineHeight,
3253             i = childNodes.length;
3254
3255         /// remove old text
3256         while (i--) {
3257             textNode.removeChild(childNodes[i]);
3258         }
3259
3260         if (width && !wrapper.added) {
3261             this.box.appendChild(textNode); // attach it to the DOM to read offset width
3262         }
3263
3264         // remove empty line at end
3265         if (lines[lines.length - 1] === '') {
3266             lines.pop();
3267         }
3268
3269         // build the lines
3270         each(lines, function (line, lineNo) {
3271             var spans, spanNo = 0;
3272
3273             line = line.replace(/<span/g, '|||<span').replace(/<\/span>/g, '</span>|||');
3274             spans = line.split('|||');
3275
3276             each(spans, function (span) {
3277                 if (span !== '' || spans.length === 1) {
3278                     var attributes = {},
3279                         tspan = doc.createElementNS(SVG_NS, 'tspan'),
3280                         spanStyle; // #390
3281                     if (styleRegex.test(span)) {
3282                         spanStyle = span.match(styleRegex)[1].replace(/(;| |^)color([ :])/, '$1fill$2');
3283                         attr(tspan, 'style', spanStyle);
3284                     }
3285                     if (hrefRegex.test(span) && !forExport) { // Not for export - #1529
3286                         attr(tspan, 'onclick', 'location.href=\"' + span.match(hrefRegex)[1] + '\"');
3287                         css(tspan, { cursor: 'pointer' });
3288                     }
3289
3290                     span = (span.replace(/<(.|\n)*?>/g, '') || ' ')
3291                         .replace(/&lt;/g, '<')
3292                         .replace(/&gt;/g, '>');
3293
3294                     // Nested tags aren't supported, and cause crash in Safari (#1596)
3295                     if (span !== ' ') {
3296
3297                         // add the text node
3298                         tspan.appendChild(doc.createTextNode(span));
3299
3300                         if (!spanNo) { // first span in a line, align it to the left
3301                             attributes.x = parentX;
3302                         } else {
3303                             attributes.dx = 0; // #16
3304                         }
3305
3306                         // add attributes
3307                         attr(tspan, attributes);
3308
3309                         // first span on subsequent line, add the line height
3310                         if (!spanNo && lineNo) {
3311
3312                             // allow getting the right offset height in exporting in IE
3313                             if (!hasSVG && forExport) {
3314                                 css(tspan, { display: 'block' });
3315                             }
3316
3317                             // Set the line height based on the font size of either
3318                             // the text element or the tspan element
3319                             attr(
3320                                 tspan,
3321                                 'dy',
3322                                 textLineHeight || renderer.fontMetrics(
3323                                     /px$/.test(tspan.style.fontSize) ?
3324                                         tspan.style.fontSize :
3325                                         textStyles.fontSize
3326                                 ).h,
3327                                 // Safari 6.0.2 - too optimized for its own good (#1539)
3328                                 // TODO: revisit this with future versions of Safari
3329                                 isWebKit && tspan.offsetHeight
3330                             );
3331                         }
3332
3333                         // Append it
3334                         textNode.appendChild(tspan);
3335
3336                         spanNo++;
3337
3338                         // check width and apply soft breaks
3339                         if (width) {
3340                             var words = span.replace(/([^\^])-/g, '$1- ').split(' '), // #1273
3341                                 tooLong,
3342                                 actualWidth,
3343                                 clipHeight = wrapper._clipHeight,
3344                                 rest = [],
3345                                 dy = pInt(textLineHeight || 16),
3346                                 softLineNo = 1,
3347                                 bBox;
3348
3349                             while (words.length || rest.length) {
3350                                 delete wrapper.bBox; // delete cache
3351                                 bBox = wrapper.getBBox();
3352                                 actualWidth = bBox.width;
3353                                 tooLong = actualWidth > width;
3354                                 if (!tooLong || words.length === 1) { // new line needed
3355                                     words = rest;
3356                                     rest = [];
3357                                     if (words.length) {
3358                                         softLineNo++;
3359
3360                                         if (clipHeight && softLineNo * dy > clipHeight) {
3361                                             words = ['...'];
3362                                             wrapper.attr('title', wrapper.textStr);
3363                                         } else {
3364
3365                                             tspan = doc.createElementNS(SVG_NS, 'tspan');
3366                                             attr(tspan, {
3367                                                 dy: dy,
3368                                                 x: parentX
3369                                             });
3370                                             if (spanStyle) { // #390
3371                                                 attr(tspan, 'style', spanStyle);
3372                                             }
3373                                             textNode.appendChild(tspan);
3374
3375                                             if (actualWidth > width) { // a single word is pressing it out
3376                                                 width = actualWidth;
3377                                             }
3378                                         }
3379                                     }
3380                                 } else { // append to existing line tspan
3381                                     tspan.removeChild(tspan.firstChild);
3382                                     rest.unshift(words.pop());
3383                                 }
3384                                 if (words.length) {
3385                                     tspan.appendChild(doc.createTextNode(words.join(' ').replace(/- /g, '-')));
3386                                 }
3387                             }
3388                         }
3389                     }
3390                 }
3391             });
3392         });
3393     },
3394
3395     /**
3396      * Create a button with preset states
3397      * @param {String} text
3398      * @param {Number} x
3399      * @param {Number} y
3400      * @param {Function} callback
3401      * @param {Object} normalState
3402      * @param {Object} hoverState
3403      * @param {Object} pressedState
3404      */
3405     button: function (text, x, y, callback, normalState, hoverState, pressedState, disabledState) {
3406         var label = this.label(text, x, y, null, null, null, null, null, 'button'),
3407             curState = 0,
3408             stateOptions,
3409             stateStyle,
3410             normalStyle,
3411             hoverStyle,
3412             pressedStyle,
3413             disabledStyle,
3414             STYLE = 'style',
3415             verticalGradient = { x1: 0, y1: 0, x2: 0, y2: 1 };
3416
3417         // Normal state - prepare the attributes
3418         normalState = merge({
3419             'stroke-width': 1,
3420             stroke: '#CCCCCC',
3421             fill: {
3422                 linearGradient: verticalGradient,
3423                 stops: [
3424                     [0, '#FEFEFE'],
3425                     [1, '#F6F6F6']
3426                 ]
3427             },
3428             r: 2,
3429             padding: 5,
3430             style: {
3431                 color: 'black'
3432             }
3433         }, normalState);
3434         normalStyle = normalState[STYLE];
3435         delete normalState[STYLE];
3436
3437         // Hover state
3438         hoverState = merge(normalState, {
3439             stroke: '#68A',
3440             fill: {
3441                 linearGradient: verticalGradient,
3442                 stops: [
3443                     [0, '#FFF'],
3444                     [1, '#ACF']
3445                 ]
3446             }
3447         }, hoverState);
3448         hoverStyle = hoverState[STYLE];
3449         delete hoverState[STYLE];
3450
3451         // Pressed state
3452         pressedState = merge(normalState, {
3453             stroke: '#68A',
3454             fill: {
3455                 linearGradient: verticalGradient,
3456                 stops: [
3457                     [0, '#9BD'],
3458                     [1, '#CDF']
3459                 ]
3460             }
3461         }, pressedState);
3462         pressedStyle = pressedState[STYLE];
3463         delete pressedState[STYLE];
3464
3465         // Disabled state
3466         disabledState = merge(normalState, {
3467             style: {
3468                 color: '#CCC'
3469             }
3470         }, disabledState);
3471         disabledStyle = disabledState[STYLE];
3472         delete disabledState[STYLE];
3473
3474         // Add the events. IE9 and IE10 need mouseover and mouseout to funciton (#667).
3475         addEvent(label.element, isIE ? 'mouseover' : 'mouseenter', function () {
3476             if (curState !== 3) {
3477                 label.attr(hoverState)
3478                     .css(hoverStyle);
3479             }
3480         });
3481         addEvent(label.element, isIE ? 'mouseout' : 'mouseleave', function () {
3482             if (curState !== 3) {
3483                 stateOptions = [normalState, hoverState, pressedState][curState];
3484                 stateStyle = [normalStyle, hoverStyle, pressedStyle][curState];
3485                 label.attr(stateOptions)
3486                     .css(stateStyle);
3487             }
3488         });
3489
3490         label.setState = function (state) {
3491             label.state = curState = state;
3492             if (!state) {
3493                 label.attr(normalState)
3494                     .css(normalStyle);
3495             } else if (state === 2) {
3496                 label.attr(pressedState)
3497                     .css(pressedStyle);
3498             } else if (state === 3) {
3499                 label.attr(disabledState)
3500                     .css(disabledStyle);
3501             }
3502         };
3503
3504         return label
3505             .on('click', function () {
3506                 if (curState !== 3) {
3507                     callback.call(label);
3508                 }
3509             })
3510             .attr(normalState)
3511             .css(extend({ cursor: 'default' }, normalStyle));
3512     },
3513
3514     /**
3515      * Make a straight line crisper by not spilling out to neighbour pixels
3516      * @param {Array} points
3517      * @param {Number} width
3518      */
3519     crispLine: function (points, width) {
3520         // points format: [M, 0, 0, L, 100, 0]
3521         // normalize to a crisp line
3522         if (points[1] === points[4]) {
3523             // Substract due to #1129. Now bottom and left axis gridlines behave the same.
3524             points[1] = points[4] = mathRound(points[1]) - (width % 2 / 2);
3525         }
3526         if (points[2] === points[5]) {
3527             points[2] = points[5] = mathRound(points[2]) + (width % 2 / 2);
3528         }
3529         return points;
3530     },
3531
3532
3533     /**
3534      * Draw a path
3535      * @param {Array} path An SVG path in array form
3536      */
3537     path: function (path) {
3538         var attr = {
3539             fill: NONE
3540         };
3541         if (isArray(path)) {
3542             attr.d = path;
3543         } else if (isObject(path)) { // attributes
3544             extend(attr, path);
3545         }
3546         return this.createElement('path').attr(attr);
3547     },
3548
3549     /**
3550      * Draw and return an SVG circle
3551      * @param {Number} x The x position
3552      * @param {Number} y The y position
3553      * @param {Number} r The radius
3554      */
3555     circle: function (x, y, r) {
3556         var attr = isObject(x) ?
3557             x :
3558             {
3559                 x: x,
3560                 y: y,
3561                 r: r
3562             };
3563
3564         return this.createElement('circle').attr(attr);
3565     },
3566
3567     /**
3568      * Draw and return an arc
3569      * @param {Number} x X position
3570      * @param {Number} y Y position
3571      * @param {Number} r Radius
3572      * @param {Number} innerR Inner radius like used in donut charts
3573      * @param {Number} start Starting angle
3574      * @param {Number} end Ending angle
3575      */
3576     arc: function (x, y, r, innerR, start, end) {
3577         var arc;
3578
3579         if (isObject(x)) {
3580             y = x.y;
3581             r = x.r;
3582             innerR = x.innerR;
3583             start = x.start;
3584             end = x.end;
3585             x = x.x;
3586         }
3587
3588         // Arcs are defined as symbols for the ability to set
3589         // attributes in attr and animate
3590         arc = this.symbol('arc', x || 0, y || 0, r || 0, r || 0, {
3591             innerR: innerR || 0,
3592             start: start || 0,
3593             end: end || 0
3594         });
3595         arc.r = r; // #959
3596         return arc;
3597     },
3598
3599     /**
3600      * Draw and return a rectangle
3601      * @param {Number} x Left position
3602      * @param {Number} y Top position
3603      * @param {Number} width
3604      * @param {Number} height
3605      * @param {Number} r Border corner radius
3606      * @param {Number} strokeWidth A stroke width can be supplied to allow crisp drawing
3607      */
3608     rect: function (x, y, width, height, r, strokeWidth) {
3609
3610         r = isObject(x) ? x.r : r;
3611
3612         var wrapper = this.createElement('rect').attr({
3613                 rx: r,
3614                 ry: r,
3615                 fill: NONE
3616             });
3617         return wrapper.attr(
3618                 isObject(x) ?
3619                     x :
3620                     // do not crispify when an object is passed in (as in column charts)
3621                     wrapper.crisp(strokeWidth, x, y, mathMax(width, 0), mathMax(height, 0))
3622             );
3623     },
3624
3625     /**
3626      * Resize the box and re-align all aligned elements
3627      * @param {Object} width
3628      * @param {Object} height
3629      * @param {Boolean} animate
3630      *
3631      */
3632     setSize: function (width, height, animate) {
3633         var renderer = this,
3634             alignedObjects = renderer.alignedObjects,
3635             i = alignedObjects.length;
3636
3637         renderer.width = width;
3638         renderer.height = height;
3639
3640         renderer.boxWrapper[pick(animate, true) ? 'animate' : 'attr']({
3641             width: width,
3642             height: height
3643         });
3644
3645         while (i--) {
3646             alignedObjects[i].align();
3647         }
3648     },
3649
3650     /**
3651      * Create a group
3652      * @param {String} name The group will be given a class name of 'highcharts-{name}'.
3653      *     This can be used for styling and scripting.
3654      */
3655     g: function (name) {
3656         var elem = this.createElement('g');
3657         return defined(name) ? elem.attr({ 'class': PREFIX + name }) : elem;
3658     },
3659
3660     /**
3661      * Display an image
3662      * @param {String} src
3663      * @param {Number} x
3664      * @param {Number} y
3665      * @param {Number} width
3666      * @param {Number} height
3667      */
3668     image: function (src, x, y, width, height) {
3669         var attribs = {
3670                 preserveAspectRatio: NONE
3671             },
3672             elemWrapper;
3673
3674         // optional properties
3675         if (arguments.length > 1) {
3676             extend(attribs, {
3677                 x: x,
3678                 y: y,
3679                 width: width,
3680                 height: height
3681             });
3682         }
3683
3684         elemWrapper = this.createElement('image').attr(attribs);
3685
3686         // set the href in the xlink namespace
3687         if (elemWrapper.element.setAttributeNS) {
3688             elemWrapper.element.setAttributeNS('http://www.w3.org/1999/xlink',
3689                 'href', src);
3690         } else {
3691             // could be exporting in IE
3692             // using href throws "not supported" in ie7 and under, requries regex shim to fix later
3693             elemWrapper.element.setAttribute('hc-svg-href', src);
3694     }
3695
3696         return elemWrapper;
3697     },
3698
3699     /**
3700      * Draw a symbol out of pre-defined shape paths from the namespace 'symbol' object.
3701      *
3702      * @param {Object} symbol
3703      * @param {Object} x
3704      * @param {Object} y
3705      * @param {Object} radius
3706      * @param {Object} options
3707      */
3708     symbol: function (symbol, x, y, width, height, options) {
3709
3710         var obj,
3711
3712             // get the symbol definition function
3713             symbolFn = this.symbols[symbol],
3714
3715             // check if there's a path defined for this symbol
3716             path = symbolFn && symbolFn(
3717                 mathRound(x),
3718                 mathRound(y),
3719                 width,
3720                 height,
3721                 options
3722             ),
3723
3724             imageElement,
3725             imageRegex = /^url\((.*?)\)$/,
3726             imageSrc,
3727             imageSize,
3728             centerImage;
3729
3730         if (path) {
3731
3732             obj = this.path(path);
3733             // expando properties for use in animate and attr
3734             extend(obj, {
3735                 symbolName: symbol,
3736                 x: x,
3737                 y: y,
3738                 width: width,
3739                 height: height
3740             });
3741             if (options) {
3742                 extend(obj, options);
3743             }
3744
3745
3746         // image symbols
3747         } else if (imageRegex.test(symbol)) {
3748
3749             // On image load, set the size and position
3750             centerImage = function (img, size) {
3751                 if (img.element) { // it may be destroyed in the meantime (#1390)
3752                     img.attr({
3753                         width: size[0],
3754                         height: size[1]
3755                     });
3756
3757                     if (!img.alignByTranslate) { // #185
3758                         img.translate(
3759                             mathRound((width - size[0]) / 2), // #1378
3760                             mathRound((height - size[1]) / 2)
3761                         );
3762                     }
3763                 }
3764             };
3765
3766             imageSrc = symbol.match(imageRegex)[1];
3767             imageSize = symbolSizes[imageSrc];
3768
3769             // Ireate the image synchronously, add attribs async
3770             obj = this.image(imageSrc)
3771                 .attr({
3772                     x: x,
3773                     y: y
3774                 });
3775             obj.isImg = true;
3776
3777             if (imageSize) {
3778                 centerImage(obj, imageSize);
3779             } else {
3780                 // Initialize image to be 0 size so export will still function if there's no cached sizes.
3781                 //
3782                 obj.attr({ width: 0, height: 0 });
3783
3784                 // Create a dummy JavaScript image to get the width and height. Due to a bug in IE < 8,
3785                 // the created element must be assigned to a variable in order to load (#292).
3786                 imageElement = createElement('img', {
3787                     onload: function () {
3788                         centerImage(obj, symbolSizes[imageSrc] = [this.width, this.height]);
3789                     },
3790                     src: imageSrc
3791                 });
3792             }
3793         }
3794
3795         return obj;
3796     },
3797
3798     /**
3799      * An extendable collection of functions for defining symbol paths.
3800      */
3801     symbols: {
3802         'circle': function (x, y, w, h) {
3803             var cpw = 0.166 * w;
3804             return [
3805                 M, x + w / 2, y,
3806                 'C', x + w + cpw, y, x + w + cpw, y + h, x + w / 2, y + h,
3807                 'C', x - cpw, y + h, x - cpw, y, x + w / 2, y,
3808                 'Z'
3809             ];
3810         },
3811
3812         'square': function (x, y, w, h) {
3813             return [
3814                 M, x, y,
3815                 L, x + w, y,
3816                 x + w, y + h,
3817                 x, y + h,
3818                 'Z'
3819             ];
3820         },
3821
3822         'triangle': function (x, y, w, h) {
3823             return [
3824                 M, x + w / 2, y,
3825                 L, x + w, y + h,
3826                 x, y + h,
3827                 'Z'
3828             ];
3829         },
3830
3831         'triangle-down': function (x, y, w, h) {
3832             return [
3833                 M, x, y,
3834                 L, x + w, y,
3835                 x + w / 2, y + h,
3836                 'Z'
3837             ];
3838         },
3839         'diamond': function (x, y, w, h) {
3840             return [
3841                 M, x + w / 2, y,
3842                 L, x + w, y + h / 2,
3843                 x + w / 2, y + h,
3844                 x, y + h / 2,
3845                 'Z'
3846             ];
3847         },
3848         'arc': function (x, y, w, h, options) {
3849             var start = options.start,
3850                 radius = options.r || w || h,
3851                 end = options.end - 0.001, // to prevent cos and sin of start and end from becoming equal on 360 arcs (related: #1561)
3852                 innerRadius = options.innerR,
3853                 open = options.open,
3854                 cosStart = mathCos(start),
3855                 sinStart = mathSin(start),
3856                 cosEnd = mathCos(end),
3857                 sinEnd = mathSin(end),
3858                 longArc = options.end - start < mathPI ? 0 : 1;
3859
3860             return [
3861                 M,
3862                 x + radius * cosStart,
3863                 y + radius * sinStart,
3864                 'A', // arcTo
3865                 radius, // x radius
3866                 radius, // y radius
3867                 0, // slanting
3868                 longArc, // long or short arc
3869                 1, // clockwise
3870                 x + radius * cosEnd,
3871                 y + radius * sinEnd,
3872                 open ? M : L,
3873                 x + innerRadius * cosEnd,
3874                 y + innerRadius * sinEnd,
3875                 'A', // arcTo
3876                 innerRadius, // x radius
3877                 innerRadius, // y radius
3878                 0, // slanting
3879                 longArc, // long or short arc
3880                 0, // clockwise
3881                 x + innerRadius * cosStart,
3882                 y + innerRadius * sinStart,
3883
3884                 open ? '' : 'Z' // close
3885             ];
3886         }
3887     },
3888
3889     /**
3890      * Define a clipping rectangle
3891      * @param {String} id
3892      * @param {Number} x
3893      * @param {Number} y
3894      * @param {Number} width
3895      * @param {Number} height
3896      */
3897     clipRect: function (x, y, width, height) {
3898         var wrapper,
3899             id = PREFIX + idCounter++,
3900
3901             clipPath = this.createElement('clipPath').attr({
3902                 id: id
3903             }).add(this.defs);
3904
3905         wrapper = this.rect(x, y, width, height, 0).add(clipPath);
3906         wrapper.id = id;
3907         wrapper.clipPath = clipPath;
3908
3909         return wrapper;
3910     },
3911
3912
3913     /**
3914      * Take a color and return it if it's a string, make it a gradient if it's a
3915      * gradient configuration object. Prior to Highstock, an array was used to define
3916      * a linear gradient with pixel positions relative to the SVG. In newer versions
3917      * we change the coordinates to apply relative to the shape, using coordinates
3918      * 0-1 within the shape. To preserve backwards compatibility, linearGradient
3919      * in this definition is an object of x1, y1, x2 and y2.
3920      *
3921      * @param {Object} color The color or config object
3922      */
3923     color: function (color, elem, prop) {
3924         var renderer = this,
3925             colorObject,
3926             regexRgba = /^rgba/,
3927             gradName,
3928             gradAttr,
3929             gradients,
3930             gradientObject,
3931             stops,
3932             stopColor,
3933             stopOpacity,
3934             radialReference,
3935             n,
3936             id,
3937             key = [];
3938
3939         // Apply linear or radial gradients
3940         if (color && color.linearGradient) {
3941             gradName = 'linearGradient';
3942         } else if (color && color.radialGradient) {
3943             gradName = 'radialGradient';
3944         }
3945
3946         if (gradName) {
3947             gradAttr = color[gradName];
3948             gradients = renderer.gradients;
3949             stops = color.stops;
3950             radialReference = elem.radialReference;
3951
3952             // Keep < 2.2 kompatibility
3953             if (isArray(gradAttr)) {
3954                 color[gradName] = gradAttr = {
3955                     x1: gradAttr[0],
3956                     y1: gradAttr[1],
3957                     x2: gradAttr[2],
3958                     y2: gradAttr[3],
3959                     gradientUnits: 'userSpaceOnUse'
3960                 };
3961             }
3962
3963             // Correct the radial gradient for the radial reference system
3964             if (gradName === 'radialGradient' && radialReference && !defined(gradAttr.gradientUnits)) {
3965                 gradAttr = merge(gradAttr, {
3966                     cx: (radialReference[0] - radialReference[2] / 2) + gradAttr.cx * radialReference[2],
3967                     cy: (radialReference[1] - radialReference[2] / 2) + gradAttr.cy * radialReference[2],
3968                     r: gradAttr.r * radialReference[2],
3969                     gradientUnits: 'userSpaceOnUse'
3970                 });
3971             }
3972
3973             // Build the unique key to detect whether we need to create a new element (#1282)
3974             for (n in gradAttr) {
3975                 if (n !== 'id') {
3976                     key.push(n, gradAttr[n]);
3977                 }
3978             }
3979             for (n in stops) {
3980                 key.push(stops[n]);
3981             }
3982             key = key.join(',');
3983
3984             // Check if a gradient object with the same config object is created within this renderer
3985             if (gradients[key]) {
3986                 id = gradients[key].id;
3987
3988             } else {
3989
3990                 // Set the id and create the element
3991                 gradAttr.id = id = PREFIX + idCounter++;
3992                 gradients[key] = gradientObject = renderer.createElement(gradName)
3993                     .attr(gradAttr)
3994                     .add(renderer.defs);
3995
3996
3997                 // The gradient needs to keep a list of stops to be able to destroy them
3998                 gradientObject.stops = [];
3999                 each(stops, function (stop) {
4000                     var stopObject;
4001                     if (regexRgba.test(stop[1])) {
4002                         colorObject = Color(stop[1]);
4003                         stopColor = colorObject.get('rgb');
4004                         stopOpacity = colorObject.get('a');
4005                     } else {
4006                         stopColor = stop[1];
4007                         stopOpacity = 1;
4008                     }
4009                     stopObject = renderer.createElement('stop').attr({
4010                         offset: stop[0],
4011                         'stop-color': stopColor,
4012                         'stop-opacity': stopOpacity
4013                     }).add(gradientObject);
4014
4015                     // Add the stop element to the gradient
4016                     gradientObject.stops.push(stopObject);
4017                 });
4018             }
4019
4020             // Return the reference to the gradient object
4021             return 'url(' + renderer.url + '#' + id + ')';
4022
4023         // Webkit and Batik can't show rgba.
4024         } else if (regexRgba.test(color)) {
4025             colorObject = Color(color);
4026             attr(elem, prop + '-opacity', colorObject.get('a'));
4027
4028             return colorObject.get('rgb');
4029
4030
4031         } else {
4032             // Remove the opacity attribute added above. Does not throw if the attribute is not there.
4033             elem.removeAttribute(prop + '-opacity');
4034
4035             return color;
4036         }
4037
4038     },
4039
4040
4041     /**
4042      * Add text to the SVG object
4043      * @param {String} str
4044      * @param {Number} x Left position
4045      * @param {Number} y Top position
4046      * @param {Boolean} useHTML Use HTML to render the text
4047      */
4048     text: function (str, x, y, useHTML) {
4049
4050         // declare variables
4051         var renderer = this,
4052             defaultChartStyle = defaultOptions.chart.style,
4053             fakeSVG = useCanVG || (!hasSVG && renderer.forExport),
4054             wrapper;
4055
4056         if (useHTML && !renderer.forExport) {
4057             return renderer.html(str, x, y);
4058         }
4059
4060         x = mathRound(pick(x, 0));
4061         y = mathRound(pick(y, 0));
4062
4063         wrapper = renderer.createElement('text')
4064             .attr({
4065                 x: x,
4066                 y: y,
4067                 text: str
4068             })
4069             .css({
4070                 fontFamily: defaultChartStyle.fontFamily,
4071                 fontSize: defaultChartStyle.fontSize
4072             });
4073
4074         // Prevent wrapping from creating false offsetWidths in export in legacy IE (#1079, #1063)
4075         if (fakeSVG) {
4076             wrapper.css({
4077                 position: ABSOLUTE
4078             });
4079         }
4080
4081         wrapper.x = x;
4082         wrapper.y = y;
4083         return wrapper;
4084     },
4085
4086
4087     /**
4088      * Create HTML text node. This is used by the VML renderer as well as the SVG
4089      * renderer through the useHTML option.
4090      *
4091      * @param {String} str
4092      * @param {Number} x
4093      * @param {Number} y
4094      */
4095     html: function (str, x, y) {
4096         var defaultChartStyle = defaultOptions.chart.style,
4097             wrapper = this.createElement('span'),
4098             attrSetters = wrapper.attrSetters,
4099             element = wrapper.element,
4100             renderer = wrapper.renderer;
4101
4102         // Text setter
4103         attrSetters.text = function (value) {
4104             if (value !== element.innerHTML) {
4105                 delete this.bBox;
4106             }
4107             element.innerHTML = value;
4108             return false;
4109         };
4110
4111         // Various setters which rely on update transform
4112         attrSetters.x = attrSetters.y = attrSetters.align = function (value, key) {
4113             if (key === 'align') {
4114                 key = 'textAlign'; // Do not overwrite the SVGElement.align method. Same as VML.
4115             }
4116             wrapper[key] = value;
4117             wrapper.htmlUpdateTransform();
4118             return false;
4119         };
4120
4121         // Set the default attributes
4122         wrapper.attr({
4123                 text: str,
4124                 x: mathRound(x),
4125                 y: mathRound(y)
4126             })
4127             .css({
4128                 position: ABSOLUTE,
4129                 whiteSpace: 'nowrap',
4130                 fontFamily: defaultChartStyle.fontFamily,
4131                 fontSize: defaultChartStyle.fontSize
4132             });
4133
4134         // Use the HTML specific .css method
4135         wrapper.css = wrapper.htmlCss;
4136
4137         // This is specific for HTML within SVG
4138         if (renderer.isSVG) {
4139             wrapper.add = function (svgGroupWrapper) {
4140
4141                 var htmlGroup,
4142                     container = renderer.box.parentNode,
4143                     parentGroup,
4144                     parents = [];
4145
4146                 // Create a mock group to hold the HTML elements
4147                 if (svgGroupWrapper) {
4148                     htmlGroup = svgGroupWrapper.div;
4149                     if (!htmlGroup) {
4150
4151                         // Read the parent chain into an array and read from top down
4152                         parentGroup = svgGroupWrapper;
4153                         while (parentGroup) {
4154
4155                             parents.push(parentGroup);
4156
4157                             // Move up to the next parent group
4158                             parentGroup = parentGroup.parentGroup;
4159                         }
4160
4161                         // Ensure dynamically updating position when any parent is translated
4162                         each(parents.reverse(), function (parentGroup) {
4163                             var htmlGroupStyle;
4164
4165                             // Create a HTML div and append it to the parent div to emulate
4166                             // the SVG group structure
4167                             htmlGroup = parentGroup.div = parentGroup.div || createElement(DIV, {
4168                                 className: attr(parentGroup.element, 'class')
4169                             }, {
4170                                 position: ABSOLUTE,
4171                                 left: (parentGroup.translateX || 0) + PX,
4172                                 top: (parentGroup.translateY || 0) + PX
4173                             }, htmlGroup || container); // the top group is appended to container
4174
4175                             // Shortcut
4176                             htmlGroupStyle = htmlGroup.style;
4177
4178                             // Set listeners to update the HTML div's position whenever the SVG group
4179                             // position is changed
4180                             extend(parentGroup.attrSetters, {
4181                                 translateX: function (value) {
4182                                     htmlGroupStyle.left = value + PX;
4183                                 },
4184                                 translateY: function (value) {
4185                                     htmlGroupStyle.top = value + PX;
4186                                 },
4187                                 visibility: function (value, key) {
4188                                     htmlGroupStyle[key] = value;
4189                                 }
4190                             });
4191                         });
4192
4193                     }
4194                 } else {
4195                     htmlGroup = container;
4196                 }
4197
4198                 htmlGroup.appendChild(element);
4199
4200                 // Shared with VML:
4201                 wrapper.added = true;
4202                 if (wrapper.alignOnAdd) {
4203                     wrapper.htmlUpdateTransform();
4204                 }
4205
4206                 return wrapper;
4207             };
4208         }
4209         return wrapper;
4210     },
4211
4212     /**
4213      * Utility to return the baseline offset and total line height from the font size
4214      */
4215     fontMetrics: function (fontSize) {
4216         fontSize = pInt(fontSize || 11);
4217
4218         // Empirical values found by comparing font size and bounding box height.
4219         // Applies to the default font family. http://jsfiddle.net/highcharts/7xvn7/
4220         var lineHeight = fontSize < 24 ? fontSize + 4 : mathRound(fontSize * 1.2),
4221             baseline = mathRound(lineHeight * 0.8);
4222
4223         return {
4224             h: lineHeight,
4225             b: baseline
4226         };
4227     },
4228
4229     /**
4230      * Add a label, a text item that can hold a colored or gradient background
4231      * as well as a border and shadow.
4232      * @param {string} str
4233      * @param {Number} x
4234      * @param {Number} y
4235      * @param {String} shape
4236      * @param {Number} anchorX In case the shape has a pointer, like a flag, this is the
4237      *    coordinates it should be pinned to
4238      * @param {Number} anchorY
4239      * @param {Boolean} baseline Whether to position the label relative to the text baseline,
4240      *    like renderer.text, or to the upper border of the rectangle.
4241      * @param {String} className Class name for the group
4242      */
4243     label: function (str, x, y, shape, anchorX, anchorY, useHTML, baseline, className) {
4244
4245         var renderer = this,
4246             wrapper = renderer.g(className),
4247             text = renderer.text('', 0, 0, useHTML)
4248                 .attr({
4249                     zIndex: 1
4250                 }),
4251                 //.add(wrapper),
4252             box,
4253             bBox,
4254             alignFactor = 0,
4255             padding = 3,
4256             paddingLeft = 0,
4257             width,
4258             height,
4259             wrapperX,
4260             wrapperY,
4261             crispAdjust = 0,
4262             deferredAttr = {},
4263             baselineOffset,
4264             attrSetters = wrapper.attrSetters,
4265             needsBox;
4266
4267         /**
4268          * This function runs after the label is added to the DOM (when the bounding box is
4269          * available), and after the text of the label is updated to detect the new bounding
4270          * box and reflect it in the border box.
4271          */
4272         function updateBoxSize() {
4273             var boxX,
4274                 boxY,
4275                 style = text.element.style;
4276
4277             bBox = (width === undefined || height === undefined || wrapper.styles.textAlign) &&
4278                 text.getBBox();
4279             wrapper.width = (width || bBox.width || 0) + 2 * padding + paddingLeft;
4280             wrapper.height = (height || bBox.height || 0) + 2 * padding;
4281
4282             // update the label-scoped y offset
4283             baselineOffset = padding + renderer.fontMetrics(style && style.fontSize).b;
4284
4285             if (needsBox) {
4286
4287                 // create the border box if it is not already present
4288                 if (!box) {
4289                     boxX = mathRound(-alignFactor * padding);
4290                     boxY = baseline ? -baselineOffset : 0;
4291
4292                     wrapper.box = box = shape ?
4293                         renderer.symbol(shape, boxX, boxY, wrapper.width, wrapper.height) :
4294                         renderer.rect(boxX, boxY, wrapper.width, wrapper.height, 0, deferredAttr[STROKE_WIDTH]);
4295                     box.add(wrapper);
4296                 }
4297
4298                 // apply the box attributes
4299                 if (!box.isImg) { // #1630
4300                     box.attr(merge({
4301                         width: wrapper.width,
4302                         height: wrapper.height
4303                     }, deferredAttr));
4304                 }
4305                 deferredAttr = null;
4306             }
4307         }
4308
4309         /**
4310          * This function runs after setting text or padding, but only if padding is changed
4311          */
4312         function updateTextPadding() {
4313             var styles = wrapper.styles,
4314                 textAlign = styles && styles.textAlign,
4315                 x = paddingLeft + padding * (1 - alignFactor),
4316                 y;
4317
4318             // determin y based on the baseline
4319             y = baseline ? 0 : baselineOffset;
4320
4321             // compensate for alignment
4322             if (defined(width) && (textAlign === 'center' || textAlign === 'right')) {
4323                 x += { center: 0.5, right: 1 }[textAlign] * (width - bBox.width);
4324             }
4325
4326             // update if anything changed
4327             if (x !== text.x || y !== text.y) {
4328                 text.attr({
4329                     x: x,
4330                     y: y
4331                 });
4332             }
4333
4334             // record current values
4335             text.x = x;
4336             text.y = y;
4337         }
4338
4339         /**
4340          * Set a box attribute, or defer it if the box is not yet created
4341          * @param {Object} key
4342          * @param {Object} value
4343          */
4344         function boxAttr(key, value) {
4345             if (box) {
4346                 box.attr(key, value);
4347             } else {
4348                 deferredAttr[key] = value;
4349             }
4350         }
4351
4352         function getSizeAfterAdd() {
4353             text.add(wrapper);
4354             wrapper.attr({
4355                 text: str, // alignment is available now
4356                 x: x,
4357                 y: y
4358             });
4359
4360             if (box && defined(anchorX)) {
4361                 wrapper.attr({
4362                     anchorX: anchorX,
4363                     anchorY: anchorY
4364                 });
4365             }
4366         }
4367
4368         /**
4369          * After the text element is added, get the desired size of the border box
4370          * and add it before the text in the DOM.
4371          */
4372         addEvent(wrapper, 'add', getSizeAfterAdd);
4373
4374         /*
4375          * Add specific attribute setters.
4376          */
4377
4378         // only change local variables
4379         attrSetters.width = function (value) {
4380             width = value;
4381             return false;
4382         };
4383         attrSetters.height = function (value) {
4384             height = value;
4385             return false;
4386         };
4387         attrSetters.padding =  function (value) {
4388             if (defined(value) && value !== padding) {
4389                 padding = value;
4390                 updateTextPadding();
4391             }
4392             return false;
4393         };
4394         attrSetters.paddingLeft =  function (value) {
4395             if (defined(value) && value !== paddingLeft) {
4396                 paddingLeft = value;
4397                 updateTextPadding();
4398             }
4399             return false;
4400         };
4401
4402
4403         // change local variable and set attribue as well
4404         attrSetters.align = function (value) {
4405             alignFactor = { left: 0, center: 0.5, right: 1 }[value];
4406             return false; // prevent setting text-anchor on the group
4407         };
4408
4409         // apply these to the box and the text alike
4410         attrSetters.text = function (value, key) {
4411             text.attr(key, value);
4412             updateBoxSize();
4413             updateTextPadding();
4414             return false;
4415         };
4416
4417         // apply these to the box but not to the text
4418         attrSetters[STROKE_WIDTH] = function (value, key) {
4419             needsBox = true;
4420             crispAdjust = value % 2 / 2;
4421             boxAttr(key, value);
4422             return false;
4423         };
4424         attrSetters.stroke = attrSetters.fill = attrSetters.r = function (value, key) {
4425             if (key === 'fill') {
4426                 needsBox = true;
4427             }
4428             boxAttr(key, value);
4429             return false;
4430         };
4431         attrSetters.anchorX = function (value, key) {
4432             anchorX = value;
4433             boxAttr(key, value + crispAdjust - wrapperX);
4434             return false;
4435         };
4436         attrSetters.anchorY = function (value, key) {
4437             anchorY = value;
4438             boxAttr(key, value - wrapperY);
4439             return false;
4440         };
4441
4442         // rename attributes
4443         attrSetters.x = function (value) {
4444             wrapper.x = value; // for animation getter
4445             value -= alignFactor * ((width || bBox.width) + padding);
4446             wrapperX = mathRound(value);
4447
4448             wrapper.attr('translateX', wrapperX);
4449             return false;
4450         };
4451         attrSetters.y = function (value) {
4452             wrapperY = wrapper.y = mathRound(value);
4453             wrapper.attr('translateY', wrapperY);
4454             return false;
4455         };
4456
4457         // Redirect certain methods to either the box or the text
4458         var baseCss = wrapper.css;
4459         return extend(wrapper, {
4460             /**
4461              * Pick up some properties and apply them to the text instead of the wrapper
4462              */
4463             css: function (styles) {
4464                 if (styles) {
4465                     var textStyles = {};
4466                     styles = merge(styles); // create a copy to avoid altering the original object (#537)
4467                     each(['fontSize', 'fontWeight', 'fontFamily', 'color', 'lineHeight', 'width', 'textDecoration', 'textShadow'], function (prop) {
4468                         if (styles[prop] !== UNDEFINED) {
4469                             textStyles[prop] = styles[prop];
4470                             delete styles[prop];
4471                         }
4472                     });
4473                     text.css(textStyles);
4474                 }
4475                 return baseCss.call(wrapper, styles);
4476             },
4477             /**
4478              * Return the bounding box of the box, not the group
4479              */
4480             getBBox: function () {
4481                 return {
4482                     width: bBox.width + 2 * padding,
4483                     height: bBox.height + 2 * padding,
4484                     x: bBox.x - padding,
4485                     y: bBox.y - padding
4486                 };
4487             },
4488             /**
4489              * Apply the shadow to the box
4490              */
4491             shadow: function (b) {
4492                 if (box) {
4493                     box.shadow(b);
4494                 }
4495                 return wrapper;
4496             },
4497             /**
4498              * Destroy and release memory.
4499              */
4500             destroy: function () {
4501                 removeEvent(wrapper, 'add', getSizeAfterAdd);
4502
4503                 // Added by button implementation
4504                 removeEvent(wrapper.element, 'mouseenter');
4505                 removeEvent(wrapper.element, 'mouseleave');
4506
4507                 if (text) {
4508                     text = text.destroy();
4509                 }
4510                 if (box) {
4511                     box = box.destroy();
4512                 }
4513                 // Call base implementation to destroy the rest
4514                 SVGElement.prototype.destroy.call(wrapper);
4515
4516                 // Release local pointers (#1298)
4517                 wrapper = renderer = updateBoxSize = updateTextPadding = boxAttr = getSizeAfterAdd = null;
4518             }
4519         });
4520     }
4521 }; // end SVGRenderer
4522
4523
4524 // general renderer
4525 Renderer = SVGRenderer;
4526
4527
4528 /* ****************************************************************************
4529  *                                                                            *
4530  * START OF INTERNET EXPLORER <= 8 SPECIFIC CODE                              *
4531  *                                                                            *
4532  * For applications and websites that don't need IE support, like platform    *
4533  * targeted mobile apps and web apps, this code can be removed.               *
4534  *                                                                            *
4535  *****************************************************************************/
4536
4537 /**
4538  * @constructor
4539  */
4540 var VMLRenderer, VMLElement;
4541 if (!hasSVG && !useCanVG) {
4542
4543 /**
4544  * The VML element wrapper.
4545  */
4546 Highcharts.VMLElement = VMLElement = {
4547
4548     /**
4549      * Initialize a new VML element wrapper. It builds the markup as a string
4550      * to minimize DOM traffic.
4551      * @param {Object} renderer
4552      * @param {Object} nodeName
4553      */
4554     init: function (renderer, nodeName) {
4555         var wrapper = this,
4556             markup =  ['<', nodeName, ' filled="f" stroked="f"'],
4557             style = ['position: ', ABSOLUTE, ';'],
4558             isDiv = nodeName === DIV;
4559
4560         // divs and shapes need size
4561         if (nodeName === 'shape' || isDiv) {
4562             style.push('left:0;top:0;width:1px;height:1px;');
4563         }
4564         style.push('visibility: ', isDiv ? HIDDEN : VISIBLE);
4565
4566         markup.push(' style="', style.join(''), '"/>');
4567
4568         // create element with default attributes and style
4569         if (nodeName) {
4570             markup = isDiv || nodeName === 'span' || nodeName === 'img' ?
4571                 markup.join('')
4572                 : renderer.prepVML(markup);
4573             wrapper.element = createElement(markup);
4574         }
4575
4576         wrapper.renderer = renderer;
4577         wrapper.attrSetters = {};
4578     },
4579
4580     /**
4581      * Add the node to the given parent
4582      * @param {Object} parent
4583      */
4584     add: function (parent) {
4585         var wrapper = this,
4586             renderer = wrapper.renderer,
4587             element = wrapper.element,
4588             box = renderer.box,
4589             inverted = parent && parent.inverted,
4590
4591             // get the parent node
4592             parentNode = parent ?
4593                 parent.element || parent :
4594                 box;
4595
4596
4597         // if the parent group is inverted, apply inversion on all children
4598         if (inverted) { // only on groups
4599             renderer.invertChild(element, parentNode);
4600         }
4601
4602         // append it
4603         parentNode.appendChild(element);
4604
4605         // align text after adding to be able to read offset
4606         wrapper.added = true;
4607         if (wrapper.alignOnAdd && !wrapper.deferUpdateTransform) {
4608             wrapper.updateTransform();
4609         }
4610
4611         // fire an event for internal hooks
4612         fireEvent(wrapper, 'add');
4613
4614         return wrapper;
4615     },
4616
4617     /**
4618      * VML always uses htmlUpdateTransform
4619      */
4620     updateTransform: SVGElement.prototype.htmlUpdateTransform,
4621
4622     /**
4623      * Set the rotation of a span with oldIE's filter
4624      */
4625     setSpanRotation: function (rotation, sintheta, costheta) {
4626         // Adjust for alignment and rotation. Rotation of useHTML content is not yet implemented
4627         // but it can probably be implemented for Firefox 3.5+ on user request. FF3.5+
4628         // has support for CSS3 transform. The getBBox method also needs to be updated
4629         // to compensate for the rotation, like it currently does for SVG.
4630         // Test case: http://highcharts.com/tests/?file=text-rotation
4631         css(this.element, {
4632             filter: rotation ? ['progid:DXImageTransform.Microsoft.Matrix(M11=', costheta,
4633                 ', M12=', -sintheta, ', M21=', sintheta, ', M22=', costheta,
4634                 ', sizingMethod=\'auto expand\')'].join('') : NONE
4635         });
4636     },
4637
4638     /**
4639      * Converts a subset of an SVG path definition to its VML counterpart. Takes an array
4640      * as the parameter and returns a string.
4641      */
4642     pathToVML: function (value) {
4643         // convert paths
4644         var i = value.length,
4645             path = [],
4646             clockwise;
4647
4648         while (i--) {
4649
4650             // Multiply by 10 to allow subpixel precision.
4651             // Substracting half a pixel seems to make the coordinates
4652             // align with SVG, but this hasn't been tested thoroughly
4653             if (isNumber(value[i])) {
4654                 path[i] = mathRound(value[i] * 10) - 5;
4655             } else if (value[i] === 'Z') { // close the path
4656                 path[i] = 'x';
4657             } else {
4658                 path[i] = value[i];
4659
4660                 // When the start X and end X coordinates of an arc are too close,
4661                 // they are rounded to the same value above. In this case, substract 1 from the end X
4662                 // position. #760, #1371.
4663                 if (value.isArc && (value[i] === 'wa' || value[i] === 'at')) {
4664                     clockwise = value[i] === 'wa' ? 1 : -1; // #1642
4665                     if (path[i + 5] === path[i + 7]) {
4666                         path[i + 7] -= clockwise;
4667                     }
4668                     // Start and end Y (#1410)
4669                     if (path[i + 6] === path[i + 8]) {
4670                         path[i + 8] -= clockwise;
4671                     }
4672                 }
4673             }
4674         }
4675         // Loop up again to handle path shortcuts (#2132)
4676         /*while (i++ < path.length) {
4677             if (path[i] === 'H') { // horizontal line to
4678                 path[i] = 'L';
4679                 path.splice(i + 2, 0, path[i - 1]);
4680             } else if (path[i] === 'V') { // vertical line to
4681                 path[i] = 'L';
4682                 path.splice(i + 1, 0, path[i - 2]);
4683             }
4684         }*/
4685         return path.join(' ') || 'x';
4686     },
4687
4688     /**
4689      * Get or set attributes
4690      */
4691     attr: function (hash, val) {
4692         var wrapper = this,
4693             key,
4694             value,
4695             i,
4696             result,
4697             element = wrapper.element || {},
4698             elemStyle = element.style,
4699             nodeName = element.nodeName,
4700             renderer = wrapper.renderer,
4701             symbolName = wrapper.symbolName,
4702             hasSetSymbolSize,
4703             shadows = wrapper.shadows,
4704             skipAttr,
4705             attrSetters = wrapper.attrSetters,
4706             ret = wrapper;
4707
4708         // single key-value pair
4709         if (isString(hash) && defined(val)) {
4710             key = hash;
4711             hash = {};
4712             hash[key] = val;
4713         }
4714
4715         // used as a getter, val is undefined
4716         if (isString(hash)) {
4717             key = hash;
4718             if (key === 'strokeWidth' || key === 'stroke-width') {
4719                 ret = wrapper.strokeweight;
4720             } else {
4721                 ret = wrapper[key];
4722             }
4723
4724         // setter
4725         } else {
4726             for (key in hash) {
4727                 value = hash[key];
4728                 skipAttr = false;
4729
4730                 // check for a specific attribute setter
4731                 result = attrSetters[key] && attrSetters[key].call(wrapper, value, key);
4732
4733                 if (result !== false && value !== null) { // #620
4734
4735                     if (result !== UNDEFINED) {
4736                         value = result; // the attribute setter has returned a new value to set
4737                     }
4738
4739
4740                     // prepare paths
4741                     // symbols
4742                     if (symbolName && /^(x|y|r|start|end|width|height|innerR|anchorX|anchorY)/.test(key)) {
4743                         // if one of the symbol size affecting parameters are changed,
4744                         // check all the others only once for each call to an element's
4745                         // .attr() method
4746                         if (!hasSetSymbolSize) {
4747                             wrapper.symbolAttr(hash);
4748
4749                             hasSetSymbolSize = true;
4750                         }
4751                         skipAttr = true;
4752
4753                     } else if (key === 'd') {
4754                         value = value || [];
4755                         wrapper.d = value.join(' '); // used in getter for animation
4756
4757                         element.path = value = wrapper.pathToVML(value);
4758
4759                         // update shadows
4760                         if (shadows) {
4761                             i = shadows.length;
4762                             while (i--) {
4763                                 shadows[i].path = shadows[i].cutOff ? this.cutOffPath(value, shadows[i].cutOff) : value;
4764                             }
4765                         }
4766                         skipAttr = true;
4767
4768                     // handle visibility
4769                     } else if (key === 'visibility') {
4770
4771                         // let the shadow follow the main element
4772                         if (shadows) {
4773                             i = shadows.length;
4774                             while (i--) {
4775                                 shadows[i].style[key] = value;
4776                             }
4777                         }
4778
4779                         // Instead of toggling the visibility CSS property, move the div out of the viewport.
4780                         // This works around #61 and #586
4781                         if (nodeName === 'DIV') {
4782                             value = value === HIDDEN ? '-999em' : 0;
4783
4784                             // In order to redraw, IE7 needs the div to be visible when tucked away
4785                             // outside the viewport. So the visibility is actually opposite of
4786                             // the expected value. This applies to the tooltip only.
4787                             if (!docMode8) {
4788                                 elemStyle[key] = value ? VISIBLE : HIDDEN;
4789                             }
4790                             key = 'top';
4791                         }
4792                         elemStyle[key] = value;
4793                         skipAttr = true;
4794
4795                     // directly mapped to css
4796                     } else if (key === 'zIndex') {
4797
4798                         if (value) {
4799                             elemStyle[key] = value;
4800                         }
4801                         skipAttr = true;
4802
4803                     // x, y, width, height
4804                     } else if (inArray(key, ['x', 'y', 'width', 'height']) !== -1) {
4805
4806                         wrapper[key] = value; // used in getter
4807
4808                         if (key === 'x' || key === 'y') {
4809                             key = { x: 'left', y: 'top' }[key];
4810                         } else {
4811                             value = mathMax(0, value); // don't set width or height below zero (#311)
4812                         }
4813
4814                         // clipping rectangle special
4815                         if (wrapper.updateClipping) {
4816                             wrapper[key] = value; // the key is now 'left' or 'top' for 'x' and 'y'
4817                             wrapper.updateClipping();
4818                         } else {
4819                             // normal
4820                             elemStyle[key] = value;
4821                         }
4822
4823                         skipAttr = true;
4824
4825                     // class name
4826                     } else if (key === 'class' && nodeName === 'DIV') {
4827                         // IE8 Standards mode has problems retrieving the className
4828                         element.className = value;
4829
4830                     // stroke
4831                     } else if (key === 'stroke') {
4832
4833                         value = renderer.color(value, element, key);
4834
4835                         key = 'strokecolor';
4836
4837                     // stroke width
4838                     } else if (key === 'stroke-width' || key === 'strokeWidth') {
4839                         element.stroked = value ? true : false;
4840                         key = 'strokeweight';
4841                         wrapper[key] = value; // used in getter, issue #113
4842                         if (isNumber(value)) {
4843                             value += PX;
4844                         }
4845
4846                     // dashStyle
4847                     } else if (key === 'dashstyle') {
4848                         var strokeElem = element.getElementsByTagName('stroke')[0] ||
4849                             createElement(renderer.prepVML(['<stroke/>']), null, null, element);
4850                         strokeElem[key] = value || 'solid';
4851                         wrapper.dashstyle = value; /* because changing stroke-width will change the dash length
4852                             and cause an epileptic effect */
4853                         skipAttr = true;
4854
4855                     // fill
4856                     } else if (key === 'fill') {
4857
4858                         if (nodeName === 'SPAN') { // text color
4859                             elemStyle.color = value;
4860                         } else if (nodeName !== 'IMG') { // #1336
4861                             element.filled = value !== NONE ? true : false;
4862
4863                             value = renderer.color(value, element, key, wrapper);
4864
4865                             key = 'fillcolor';
4866                         }
4867
4868                     // opacity: don't bother - animation is too slow and filters introduce artifacts
4869                     } else if (key === 'opacity') {
4870                         /*css(element, {
4871                             opacity: value
4872                         });*/
4873                         skipAttr = true;
4874
4875                     // rotation on VML elements
4876                     } else if (nodeName === 'shape' && key === 'rotation') {
4877
4878                         wrapper[key] = element.style[key] = value; // style is for #1873
4879
4880                         // Correction for the 1x1 size of the shape container. Used in gauge needles.
4881                         element.style.left = -mathRound(mathSin(value * deg2rad) + 1) + PX;
4882                         element.style.top = mathRound(mathCos(value * deg2rad)) + PX;
4883
4884                     // translation for animation
4885                     } else if (key === 'translateX' || key === 'translateY' || key === 'rotation') {
4886                         wrapper[key] = value;
4887                         wrapper.updateTransform();
4888
4889                         skipAttr = true;
4890
4891                     // text for rotated and non-rotated elements
4892                     } else if (key === 'text') {
4893                         this.bBox = null;
4894                         element.innerHTML = value;
4895                         skipAttr = true;
4896                     }
4897
4898
4899                     if (!skipAttr) {
4900                         if (docMode8) { // IE8 setAttribute bug
4901                             element[key] = value;
4902                         } else {
4903                             attr(element, key, value);
4904                         }
4905                     }
4906
4907                 }
4908             }
4909         }
4910         return ret;
4911     },
4912
4913     /**
4914      * Set the element's clipping to a predefined rectangle
4915      *
4916      * @param {String} id The id of the clip rectangle
4917      */
4918     clip: function (clipRect) {
4919         var wrapper = this,
4920             clipMembers,
4921             cssRet;
4922
4923         if (clipRect) {
4924             clipMembers = clipRect.members;
4925             erase(clipMembers, wrapper); // Ensure unique list of elements (#1258)
4926             clipMembers.push(wrapper);
4927             wrapper.destroyClip = function () {
4928                 erase(clipMembers, wrapper);
4929             };
4930             cssRet = clipRect.getCSS(wrapper);
4931
4932         } else {
4933             if (wrapper.destroyClip) {
4934                 wrapper.destroyClip();
4935             }
4936             cssRet = { clip: docMode8 ? 'inherit' : 'rect(auto)' }; // #1214
4937         }
4938
4939         return wrapper.css(cssRet);
4940
4941     },
4942
4943     /**
4944      * Set styles for the element
4945      * @param {Object} styles
4946      */
4947     css: SVGElement.prototype.htmlCss,
4948
4949     /**
4950      * Removes a child either by removeChild or move to garbageBin.
4951      * Issue 490; in VML removeChild results in Orphaned nodes according to sIEve, discardElement does not.
4952      */
4953     safeRemoveChild: function (element) {
4954         // discardElement will detach the node from its parent before attaching it
4955         // to the garbage bin. Therefore it is important that the node is attached and have parent.
4956         if (element.parentNode) {
4957             discardElement(element);
4958         }
4959     },
4960
4961     /**
4962      * Extend element.destroy by removing it from the clip members array
4963      */
4964     destroy: function () {
4965         if (this.destroyClip) {
4966             this.destroyClip();
4967         }
4968
4969         return SVGElement.prototype.destroy.apply(this);
4970     },
4971
4972     /**
4973      * Add an event listener. VML override for normalizing event parameters.
4974      * @param {String} eventType
4975      * @param {Function} handler
4976      */
4977     on: function (eventType, handler) {
4978         // simplest possible event model for internal use
4979         this.element['on' + eventType] = function () {
4980             var evt = win.event;
4981             evt.target = evt.srcElement;
4982             handler(evt);
4983         };
4984         return this;
4985     },
4986
4987     /**
4988      * In stacked columns, cut off the shadows so that they don't overlap
4989      */
4990     cutOffPath: function (path, length) {
4991
4992         var len;
4993
4994         path = path.split(/[ ,]/);
4995         len = path.length;
4996
4997         if (len === 9 || len === 11) {
4998             path[len - 4] = path[len - 2] = pInt(path[len - 2]) - 10 * length;
4999         }
5000         return path.join(' ');
5001     },
5002
5003     /**
5004      * Apply a drop shadow by copying elements and giving them different strokes
5005      * @param {Boolean|Object} shadowOptions
5006      */
5007     shadow: function (shadowOptions, group, cutOff) {
5008         var shadows = [],
5009             i,
5010             element = this.element,
5011             renderer = this.renderer,
5012             shadow,
5013             elemStyle = element.style,
5014             markup,
5015             path = element.path,
5016             strokeWidth,
5017             modifiedPath,
5018             shadowWidth,
5019             shadowElementOpacity;
5020
5021         // some times empty paths are not strings
5022         if (path && typeof path.value !== 'string') {
5023             path = 'x';
5024         }
5025         modifiedPath = path;
5026
5027         if (shadowOptions) {
5028             shadowWidth = pick(shadowOptions.width, 3);
5029             shadowElementOpacity = (shadowOptions.opacity || 0.15) / shadowWidth;
5030             for (i = 1; i <= 3; i++) {
5031
5032                 strokeWidth = (shadowWidth * 2) + 1 - (2 * i);
5033
5034                 // Cut off shadows for stacked column items
5035                 if (cutOff) {
5036                     modifiedPath = this.cutOffPath(path.value, strokeWidth + 0.5);
5037                 }
5038
5039                 markup = ['<shape isShadow="true" strokeweight="', strokeWidth,
5040                     '" filled="false" path="', modifiedPath,
5041                     '" coordsize="10 10" style="', element.style.cssText, '" />'];
5042
5043                 shadow = createElement(renderer.prepVML(markup),
5044                     null, {
5045                         left: pInt(elemStyle.left) + pick(shadowOptions.offsetX, 1),
5046                         top: pInt(elemStyle.top) + pick(shadowOptions.offsetY, 1)
5047                     }
5048                 );
5049                 if (cutOff) {
5050                     shadow.cutOff = strokeWidth + 1;
5051                 }
5052
5053                 // apply the opacity
5054                 markup = ['<stroke color="', shadowOptions.color || 'black', '" opacity="', shadowElementOpacity * i, '"/>'];
5055                 createElement(renderer.prepVML(markup), null, null, shadow);
5056
5057
5058                 // insert it
5059                 if (group) {
5060                     group.element.appendChild(shadow);
5061                 } else {
5062                     element.parentNode.insertBefore(shadow, element);
5063                 }
5064
5065                 // record it
5066                 shadows.push(shadow);
5067
5068             }
5069
5070             this.shadows = shadows;
5071         }
5072         return this;
5073
5074     }
5075 };
5076 VMLElement = extendClass(SVGElement, VMLElement);
5077
5078 /**
5079  * The VML renderer
5080  */
5081 var VMLRendererExtension = { // inherit SVGRenderer
5082
5083     Element: VMLElement,
5084     isIE8: userAgent.indexOf('MSIE 8.0') > -1,
5085
5086
5087     /**
5088      * Initialize the VMLRenderer
5089      * @param {Object} container
5090      * @param {Number} width
5091      * @param {Number} height
5092      */
5093     init: function (container, width, height) {
5094         var renderer = this,
5095             boxWrapper,
5096             box;
5097
5098         renderer.alignedObjects = [];
5099
5100         boxWrapper = renderer.createElement(DIV);
5101         box = boxWrapper.element;
5102         box.style.position = RELATIVE; // for freeform drawing using renderer directly
5103         container.appendChild(boxWrapper.element);
5104
5105
5106         // generate the containing box
5107         renderer.isVML = true;
5108         renderer.box = box;
5109         renderer.boxWrapper = boxWrapper;
5110
5111
5112         renderer.setSize(width, height, false);
5113
5114         // The only way to make IE6 and IE7 print is to use a global namespace. However,
5115         // with IE8 the only way to make the dynamic shapes visible in screen and print mode
5116         // seems to be to add the xmlns attribute and the behaviour style inline.
5117         if (!doc.namespaces.hcv) {
5118
5119             doc.namespaces.add('hcv', 'urn:schemas-microsoft-com:vml');
5120
5121             // Setup default CSS (#2153)
5122             (doc.styleSheets.length ? doc.styleSheets[0] : doc.createStyleSheet()).cssText +=
5123                 'hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke' +
5124                 '{ behavior:url(#default#VML); display: inline-block; } ';
5125
5126         }
5127     },
5128
5129
5130     /**
5131      * Detect whether the renderer is hidden. This happens when one of the parent elements
5132      * has display: none
5133      */
5134     isHidden: function () {
5135         return !this.box.offsetWidth;
5136     },
5137
5138     /**
5139      * Define a clipping rectangle. In VML it is accomplished by storing the values
5140      * for setting the CSS style to all associated members.
5141      *
5142      * @param {Number} x
5143      * @param {Number} y
5144      * @param {Number} width
5145      * @param {Number} height
5146      */
5147     clipRect: function (x, y, width, height) {
5148
5149         // create a dummy element
5150         var clipRect = this.createElement(),
5151             isObj = isObject(x);
5152
5153         // mimic a rectangle with its style object for automatic updating in attr
5154         return extend(clipRect, {
5155             members: [],
5156             left: (isObj ? x.x : x) + 1,
5157             top: (isObj ? x.y : y) + 1,
5158             width: (isObj ? x.width : width) - 1,
5159             height: (isObj ? x.height : height) - 1,
5160             getCSS: function (wrapper) {
5161                 var element = wrapper.element,
5162                     nodeName = element.nodeName,
5163                     isShape = nodeName === 'shape',
5164                     inverted = wrapper.inverted,
5165                     rect = this,
5166                     top = rect.top - (isShape ? element.offsetTop : 0),
5167                     left = rect.left,
5168                     right = left + rect.width,
5169                     bottom = top + rect.height,
5170                     ret = {
5171                         clip: 'rect(' +
5172                             mathRound(inverted ? left : top) + 'px,' +
5173                             mathRound(inverted ? bottom : right) + 'px,' +
5174                             mathRound(inverted ? right : bottom) + 'px,' +
5175                             mathRound(inverted ? top : left) + 'px)'
5176                     };
5177
5178                 // issue 74 workaround
5179                 if (!inverted && docMode8 && nodeName === 'DIV') {
5180                     extend(ret, {
5181                         width: right + PX,
5182                         height: bottom + PX
5183                     });
5184                 }
5185                 return ret;
5186             },
5187
5188             // used in attr and animation to update the clipping of all members
5189             updateClipping: function () {
5190                 each(clipRect.members, function (member) {
5191                     member.css(clipRect.getCSS(member));
5192                 });
5193             }
5194         });
5195
5196     },
5197
5198
5199     /**
5200      * Take a color and return it if it's a string, make it a gradient if it's a
5201      * gradient configuration object, and apply opacity.
5202      *
5203      * @param {Object} color The color or config object
5204      */
5205     color: function (color, elem, prop, wrapper) {
5206         var renderer = this,
5207             colorObject,
5208             regexRgba = /^rgba/,
5209             markup,
5210             fillType,
5211             ret = NONE;
5212
5213         // Check for linear or radial gradient
5214         if (color && color.linearGradient) {
5215             fillType = 'gradient';
5216         } else if (color && color.radialGradient) {
5217             fillType = 'pattern';
5218         }
5219
5220
5221         if (fillType) {
5222
5223             var stopColor,
5224                 stopOpacity,
5225                 gradient = color.linearGradient || color.radialGradient,
5226                 x1,
5227                 y1,
5228                 x2,
5229                 y2,
5230                 opacity1,
5231                 opacity2,
5232                 color1,
5233                 color2,
5234                 fillAttr = '',
5235                 stops = color.stops,
5236                 firstStop,
5237                 lastStop,
5238                 colors = [],
5239                 addFillNode = function () {
5240                     // Add the fill subnode. When colors attribute is used, the meanings of opacity and o:opacity2
5241                     // are reversed.
5242                     markup = ['<fill colors="' + colors.join(',') + '" opacity="', opacity2, '" o:opacity2="', opacity1,
5243                         '" type="', fillType, '" ', fillAttr, 'focus="100%" method="any" />'];
5244                     createElement(renderer.prepVML(markup), null, null, elem);
5245                 };
5246
5247             // Extend from 0 to 1
5248             firstStop = stops[0];
5249             lastStop = stops[stops.length - 1];
5250             if (firstStop[0] > 0) {
5251                 stops.unshift([
5252                     0,
5253                     firstStop[1]
5254                 ]);
5255             }
5256             if (lastStop[0] < 1) {
5257                 stops.push([
5258                     1,
5259                     lastStop[1]
5260                 ]);
5261             }
5262
5263             // Compute the stops
5264             each(stops, function (stop, i) {
5265                 if (regexRgba.test(stop[1])) {
5266                     colorObject = Color(stop[1]);
5267                     stopColor = colorObject.get('rgb');
5268                     stopOpacity = colorObject.get('a');
5269                 } else {
5270                     stopColor = stop[1];
5271                     stopOpacity = 1;
5272                 }
5273
5274                 // Build the color attribute
5275                 colors.push((stop[0] * 100) + '% ' + stopColor);
5276
5277                 // Only start and end opacities are allowed, so we use the first and the last
5278                 if (!i) {
5279                     opacity1 = stopOpacity;
5280                     color2 = stopColor;
5281                 } else {
5282                     opacity2 = stopOpacity;
5283                     color1 = stopColor;
5284                 }
5285             });
5286
5287             // Apply the gradient to fills only.
5288             if (prop === 'fill') {
5289
5290                 // Handle linear gradient angle
5291                 if (fillType === 'gradient') {
5292                     x1 = gradient.x1 || gradient[0] || 0;
5293                     y1 = gradient.y1 || gradient[1] || 0;
5294                     x2 = gradient.x2 || gradient[2] || 0;
5295                     y2 = gradient.y2 || gradient[3] || 0;
5296                     fillAttr = 'angle="' + (90  - math.atan(
5297                         (y2 - y1) / // y vector
5298                         (x2 - x1) // x vector
5299                         ) * 180 / mathPI) + '"';
5300
5301                     addFillNode();
5302
5303                 // Radial (circular) gradient
5304                 } else {
5305
5306                     var r = gradient.r,
5307                         sizex = r * 2,
5308                         sizey = r * 2,
5309                         cx = gradient.cx,
5310                         cy = gradient.cy,
5311                         radialReference = elem.radialReference,
5312                         bBox,
5313                         applyRadialGradient = function () {
5314                             if (radialReference) {
5315                                 bBox = wrapper.getBBox();
5316                                 cx += (radialReference[0] - bBox.x) / bBox.width - 0.5;
5317                                 cy += (radialReference[1] - bBox.y) / bBox.height - 0.5;
5318                                 sizex *= radialReference[2] / bBox.width;
5319                                 sizey *= radialReference[2] / bBox.height;
5320                             }
5321                             fillAttr = 'src="' + defaultOptions.global.VMLRadialGradientURL + '" ' +
5322                                 'size="' + sizex + ',' + sizey + '" ' +
5323                                 'origin="0.5,0.5" ' +
5324                                 'position="' + cx + ',' + cy + '" ' +
5325                                 'color2="' + color2 + '" ';
5326
5327                             addFillNode();
5328                         };
5329
5330                     // Apply radial gradient
5331                     if (wrapper.added) {
5332                         applyRadialGradient();
5333                     } else {
5334                         // We need to know the bounding box to get the size and position right
5335                         addEvent(wrapper, 'add', applyRadialGradient);
5336                     }
5337
5338                     // The fill element's color attribute is broken in IE8 standards mode, so we
5339                     // need to set the parent shape's fillcolor attribute instead.
5340                     ret = color1;
5341                 }
5342
5343             // Gradients are not supported for VML stroke, return the first color. #722.
5344             } else {
5345                 ret = stopColor;
5346             }
5347
5348         // if the color is an rgba color, split it and add a fill node
5349         // to hold the opacity component
5350         } else if (regexRgba.test(color) && elem.tagName !== 'IMG') {
5351
5352             colorObject = Color(color);
5353
5354             markup = ['<', prop, ' opacity="', colorObject.get('a'), '"/>'];
5355             createElement(this.prepVML(markup), null, null, elem);
5356
5357             ret = colorObject.get('rgb');
5358
5359
5360         } else {
5361             var propNodes = elem.getElementsByTagName(prop); // 'stroke' or 'fill' node
5362             if (propNodes.length) {
5363                 propNodes[0].opacity = 1;
5364                 propNodes[0].type = 'solid';
5365             }
5366             ret = color;
5367         }
5368
5369         return ret;
5370     },
5371
5372     /**
5373      * Take a VML string and prepare it for either IE8 or IE6/IE7.
5374      * @param {Array} markup A string array of the VML markup to prepare
5375      */
5376     prepVML: function (markup) {
5377         var vmlStyle = 'display:inline-block;behavior:url(#default#VML);',
5378             isIE8 = this.isIE8;
5379
5380         markup = markup.join('');
5381
5382         if (isIE8) { // add xmlns and style inline
5383             markup = markup.replace('/>', ' xmlns="urn:schemas-microsoft-com:vml" />');
5384             if (markup.indexOf('style="') === -1) {
5385                 markup = markup.replace('/>', ' style="' + vmlStyle + '" />');
5386             } else {
5387                 markup = markup.replace('style="', 'style="' + vmlStyle);
5388             }
5389
5390         } else { // add namespace
5391             markup = markup.replace('<', '<hcv:');
5392         }
5393
5394         return markup;
5395     },
5396
5397     /**
5398      * Create rotated and aligned text
5399      * @param {String} str
5400      * @param {Number} x
5401      * @param {Number} y
5402      */
5403     text: SVGRenderer.prototype.html,
5404
5405     /**
5406      * Create and return a path element
5407      * @param {Array} path
5408      */
5409     path: function (path) {
5410         var attr = {
5411             // subpixel precision down to 0.1 (width and height = 1px)
5412             coordsize: '10 10'
5413         };
5414         if (isArray(path)) {
5415             attr.d = path;
5416         } else if (isObject(path)) { // attributes
5417             extend(attr, path);
5418         }
5419         // create the shape
5420         return this.createElement('shape').attr(attr);
5421     },
5422
5423     /**
5424      * Create and return a circle element. In VML circles are implemented as
5425      * shapes, which is faster than v:oval
5426      * @param {Number} x
5427      * @param {Number} y
5428      * @param {Number} r
5429      */
5430     circle: function (x, y, r) {
5431         var circle = this.symbol('circle');
5432         if (isObject(x)) {
5433             r = x.r;
5434             y = x.y;
5435             x = x.x;
5436         }
5437         circle.isCircle = true; // Causes x and y to mean center (#1682)
5438         circle.r = r;
5439         return circle.attr({ x: x, y: y });
5440     },
5441
5442     /**
5443      * Create a group using an outer div and an inner v:group to allow rotating
5444      * and flipping. A simple v:group would have problems with positioning
5445      * child HTML elements and CSS clip.
5446      *
5447      * @param {String} name The name of the group
5448      */
5449     g: function (name) {
5450         var wrapper,
5451             attribs;
5452
5453         // set the class name
5454         if (name) {
5455             attribs = { 'className': PREFIX + name, 'class': PREFIX + name };
5456         }
5457
5458         // the div to hold HTML and clipping
5459         wrapper = this.createElement(DIV).attr(attribs);
5460
5461         return wrapper;
5462     },
5463
5464     /**
5465      * VML override to create a regular HTML image
5466      * @param {String} src
5467      * @param {Number} x
5468      * @param {Number} y
5469      * @param {Number} width
5470      * @param {Number} height
5471      */
5472     image: function (src, x, y, width, height) {
5473         var obj = this.createElement('img')
5474             .attr({ src: src });
5475
5476         if (arguments.length > 1) {
5477             obj.attr({
5478                 x: x,
5479                 y: y,
5480                 width: width,
5481                 height: height
5482             });
5483         }
5484         return obj;
5485     },
5486
5487     /**
5488      * VML uses a shape for rect to overcome bugs and rotation problems
5489      */
5490     rect: function (x, y, width, height, r, strokeWidth) {
5491
5492         var wrapper = this.symbol('rect');
5493         wrapper.r = isObject(x) ? x.r : r;
5494
5495         //return wrapper.attr(wrapper.crisp(strokeWidth, x, y, mathMax(width, 0), mathMax(height, 0)));
5496         return wrapper.attr(
5497                 isObject(x) ?
5498                     x :
5499                     // do not crispify when an object is passed in (as in column charts)
5500                     wrapper.crisp(strokeWidth, x, y, mathMax(width, 0), mathMax(height, 0))
5501             );
5502     },
5503
5504     /**
5505      * In the VML renderer, each child of an inverted div (group) is inverted
5506      * @param {Object} element
5507      * @param {Object} parentNode
5508      */
5509     invertChild: function (element, parentNode) {
5510         var parentStyle = parentNode.style;
5511         css(element, {
5512             flip: 'x',
5513             left: pInt(parentStyle.width) - 1,
5514             top: pInt(parentStyle.height) - 1,
5515             rotation: -90
5516         });
5517     },
5518
5519     /**
5520      * Symbol definitions that override the parent SVG renderer's symbols
5521      *
5522      */
5523     symbols: {
5524         // VML specific arc function
5525         arc: function (x, y, w, h, options) {
5526             var start = options.start,
5527                 end = options.end,
5528                 radius = options.r || w || h,
5529                 innerRadius = options.innerR,
5530                 cosStart = mathCos(start),
5531                 sinStart = mathSin(start),
5532                 cosEnd = mathCos(end),
5533                 sinEnd = mathSin(end),
5534                 ret;
5535
5536             if (end - start === 0) { // no angle, don't show it.
5537                 return ['x'];
5538             }
5539
5540             ret = [
5541                 'wa', // clockwise arc to
5542                 x - radius, // left
5543                 y - radius, // top
5544                 x + radius, // right
5545                 y + radius, // bottom
5546                 x + radius * cosStart, // start x
5547                 y + radius * sinStart, // start y
5548                 x + radius * cosEnd, // end x
5549                 y + radius * sinEnd  // end y
5550             ];
5551
5552             if (options.open && !innerRadius) {
5553                 ret.push(
5554                     'e',
5555                     M,
5556                     x,// - innerRadius,
5557                     y// - innerRadius
5558                 );
5559             }
5560
5561             ret.push(
5562                 'at', // anti clockwise arc to
5563                 x - innerRadius, // left
5564                 y - innerRadius, // top
5565                 x + innerRadius, // right
5566                 y + innerRadius, // bottom
5567                 x + innerRadius * cosEnd, // start x
5568                 y + innerRadius * sinEnd, // start y
5569                 x + innerRadius * cosStart, // end x
5570                 y + innerRadius * sinStart, // end y
5571                 'x', // finish path
5572                 'e' // close
5573             );
5574
5575             ret.isArc = true;
5576             return ret;
5577
5578         },
5579         // Add circle symbol path. This performs significantly faster than v:oval.
5580         circle: function (x, y, w, h, wrapper) {
5581
5582             if (wrapper) {
5583                 w = h = 2 * wrapper.r;
5584             }
5585
5586             // Center correction, #1682
5587             if (wrapper && wrapper.isCircle) {
5588                 x -= w / 2;
5589                 y -= h / 2;
5590             }
5591
5592             // Return the path
5593             return [
5594                 'wa', // clockwisearcto
5595                 x, // left
5596                 y, // top
5597                 x + w, // right
5598                 y + h, // bottom
5599                 x + w, // start x
5600                 y + h / 2,     // start y
5601                 x + w, // end x
5602                 y + h / 2,     // end y
5603                 //'x', // finish path
5604                 'e' // close
5605             ];
5606         },
5607         /**
5608          * Add rectangle symbol path which eases rotation and omits arcsize problems
5609          * compared to the built-in VML roundrect shape
5610          *
5611          * @param {Number} left Left position
5612          * @param {Number} top Top position
5613          * @param {Number} r Border radius
5614          * @param {Object} options Width and height
5615          */
5616
5617         rect: function (left, top, width, height, options) {
5618
5619             var right = left + width,
5620                 bottom = top + height,
5621                 ret,
5622                 r;
5623
5624             // No radius, return the more lightweight square
5625             if (!defined(options) || !options.r) {
5626                 ret = SVGRenderer.prototype.symbols.square.apply(0, arguments);
5627
5628             // Has radius add arcs for the corners
5629             } else {
5630
5631                 r = mathMin(options.r, width, height);
5632                 ret = [
5633                     M,
5634                     left + r, top,
5635
5636                     L,
5637                     right - r, top,
5638                     'wa',
5639                     right - 2 * r, top,
5640                     right, top + 2 * r,
5641                     right - r, top,
5642                     right, top + r,
5643
5644                     L,
5645                     right, bottom - r,
5646                     'wa',
5647                     right - 2 * r, bottom - 2 * r,
5648                     right, bottom,
5649                     right, bottom - r,
5650                     right - r, bottom,
5651
5652                     L,
5653                     left + r, bottom,
5654                     'wa',
5655                     left, bottom - 2 * r,
5656                     left + 2 * r, bottom,
5657                     left + r, bottom,
5658                     left, bottom - r,
5659
5660                     L,
5661                     left, top + r,
5662                     'wa',
5663                     left, top,
5664                     left + 2 * r, top + 2 * r,
5665                     left, top + r,
5666                     left + r, top,
5667
5668
5669                     'x',
5670                     'e'
5671                 ];
5672             }
5673             return ret;
5674         }
5675     }
5676 };
5677 Highcharts.VMLRenderer = VMLRenderer = function () {
5678     this.init.apply(this, arguments);
5679 };
5680 VMLRenderer.prototype = merge(SVGRenderer.prototype, VMLRendererExtension);
5681
5682     // general renderer
5683     Renderer = VMLRenderer;
5684 }
5685
5686 /* ****************************************************************************
5687  *                                                                            *
5688  * END OF INTERNET EXPLORER <= 8 SPECIFIC CODE                                *
5689  *                                                                            *
5690  *****************************************************************************/
5691 /* ****************************************************************************
5692  *                                                                            *
5693  * START OF ANDROID < 3 SPECIFIC CODE. THIS CAN BE REMOVED IF YOU'RE NOT      *
5694  * TARGETING THAT SYSTEM.                                                     *
5695  *                                                                            *
5696  *****************************************************************************/
5697 var CanVGRenderer,
5698     CanVGController;
5699
5700 if (useCanVG) {
5701     /**
5702      * The CanVGRenderer is empty from start to keep the source footprint small.
5703      * When requested, the CanVGController downloads the rest of the source packaged
5704      * together with the canvg library.
5705      */
5706     Highcharts.CanVGRenderer = CanVGRenderer = function () {
5707         // Override the global SVG namespace to fake SVG/HTML that accepts CSS
5708         SVG_NS = 'http://www.w3.org/1999/xhtml';
5709     };
5710
5711     /**
5712      * Start with an empty symbols object. This is needed when exporting is used (exporting.src.js will add a few symbols), but 
5713      * the implementation from SvgRenderer will not be merged in until first render.
5714      */
5715     CanVGRenderer.prototype.symbols = {};
5716
5717     /**
5718      * Handles on demand download of canvg rendering support.
5719      */
5720     CanVGController = (function () {
5721         // List of renderering calls
5722         var deferredRenderCalls = [];
5723
5724         /**
5725          * When downloaded, we are ready to draw deferred charts.
5726          */
5727         function drawDeferred() {
5728             var callLength = deferredRenderCalls.length,
5729                 callIndex;
5730
5731             // Draw all pending render calls
5732             for (callIndex = 0; callIndex < callLength; callIndex++) {
5733                 deferredRenderCalls[callIndex]();
5734             }
5735             // Clear the list
5736             deferredRenderCalls = [];
5737         }
5738
5739         return {
5740             push: function (func, scriptLocation) {
5741                 // Only get the script once
5742                 if (deferredRenderCalls.length === 0) {
5743                     getScript(scriptLocation, drawDeferred);
5744                 }
5745                 // Register render call
5746                 deferredRenderCalls.push(func);
5747             }
5748         };
5749     }());
5750
5751     Renderer = CanVGRenderer;
5752 } // end CanVGRenderer
5753
5754 /* ****************************************************************************
5755  *                                                                            *
5756  * END OF ANDROID < 3 SPECIFIC CODE                                           *
5757  *                                                                            *
5758  *****************************************************************************/
5759
5760 /**
5761  * The Tick class
5762  */
5763 function Tick(axis, pos, type, noLabel) {
5764     this.axis = axis;
5765     this.pos = pos;
5766     this.type = type || '';
5767     this.isNew = true;
5768
5769     if (!type && !noLabel) {
5770         this.addLabel();
5771     }
5772 }
5773
5774 Tick.prototype = {
5775     /**
5776      * Write the tick label
5777      */
5778     addLabel: function () {
5779         var tick = this,
5780             axis = tick.axis,
5781             options = axis.options,
5782             chart = axis.chart,
5783             horiz = axis.horiz,
5784             categories = axis.categories,
5785             names = axis.series[0] && axis.series[0].names,
5786             pos = tick.pos,
5787             labelOptions = options.labels,
5788             str,
5789             tickPositions = axis.tickPositions,
5790             width = (horiz && categories &&
5791                 !labelOptions.step && !labelOptions.staggerLines &&
5792                 !labelOptions.rotation &&
5793                 chart.plotWidth / tickPositions.length) ||
5794                 (!horiz && (chart.margin[3] || chart.chartWidth * 0.33)), // #1580, #1931
5795             isFirst = pos === tickPositions[0],
5796             isLast = pos === tickPositions[tickPositions.length - 1],
5797             css,
5798             attr,
5799             value = categories ?
5800                 pick(categories[pos], names && names[pos], pos) : 
5801                 pos,
5802             label = tick.label,
5803             tickPositionInfo = tickPositions.info,
5804             dateTimeLabelFormat;
5805
5806         // Set the datetime label format. If a higher rank is set for this position, use that. If not,
5807         // use the general format.
5808         if (axis.isDatetimeAxis && tickPositionInfo) {
5809             dateTimeLabelFormat = options.dateTimeLabelFormats[tickPositionInfo.higherRanks[pos] || tickPositionInfo.unitName];
5810         }
5811
5812         // set properties for access in render method
5813         tick.isFirst = isFirst;
5814         tick.isLast = isLast;
5815
5816         // get the string
5817         str = axis.labelFormatter.call({
5818             axis: axis,
5819             chart: chart,
5820             isFirst: isFirst,
5821             isLast: isLast,
5822             dateTimeLabelFormat: dateTimeLabelFormat,
5823             value: axis.isLog ? correctFloat(lin2log(value)) : value
5824         });
5825
5826         // prepare CSS
5827         css = width && { width: mathMax(1, mathRound(width - 2 * (labelOptions.padding || 10))) + PX };
5828         css = extend(css, labelOptions.style);
5829
5830         // first call
5831         if (!defined(label)) {
5832             attr = {
5833                 align: axis.labelAlign
5834             };
5835             if (isNumber(labelOptions.rotation)) {
5836                 attr.rotation = labelOptions.rotation;
5837             }
5838             if (width && labelOptions.ellipsis) {
5839                 attr._clipHeight = axis.len / tickPositions.length;
5840             }
5841
5842             tick.label =
5843                 defined(str) && labelOptions.enabled ?
5844                     chart.renderer.text(
5845                             str,
5846                             0,
5847                             0,
5848                             labelOptions.useHTML
5849                         )
5850                         .attr(attr)
5851                         // without position absolute, IE export sometimes is wrong
5852                         .css(css)
5853                         .add(axis.labelGroup) :
5854                     null;
5855
5856         // update
5857         } else if (label) {
5858             label.attr({
5859                     text: str
5860                 })
5861                 .css(css);
5862         }
5863     },
5864
5865     /**
5866      * Get the offset height or width of the label
5867      */
5868     getLabelSize: function () {
5869         var label = this.label,
5870             axis = this.axis;
5871         return label ?
5872             ((this.labelBBox = label.getBBox()))[axis.horiz ? 'height' : 'width'] :
5873             0;
5874     },
5875
5876     /**
5877      * Find how far the labels extend to the right and left of the tick's x position. Used for anti-collision
5878      * detection with overflow logic.
5879      */
5880     getLabelSides: function () {
5881         var bBox = this.labelBBox, // assume getLabelSize has run at this point
5882             axis = this.axis,
5883             options = axis.options,
5884             labelOptions = options.labels,
5885             width = bBox.width,
5886             leftSide = width * { left: 0, center: 0.5, right: 1 }[axis.labelAlign] - labelOptions.x;
5887
5888         return [-leftSide, width - leftSide];
5889     },
5890
5891     /**
5892      * Handle the label overflow by adjusting the labels to the left and right edge, or
5893      * hide them if they collide into the neighbour label.
5894      */
5895     handleOverflow: function (index, xy) {
5896         var show = true,
5897             axis = this.axis,
5898             chart = axis.chart,
5899             isFirst = this.isFirst,
5900             isLast = this.isLast,
5901             x = xy.x,
5902             reversed = axis.reversed,
5903             tickPositions = axis.tickPositions;
5904
5905         if (isFirst || isLast) {
5906
5907             var sides = this.getLabelSides(),
5908                 leftSide = sides[0],
5909                 rightSide = sides[1],
5910                 plotLeft = chart.plotLeft,
5911                 plotRight = plotLeft + axis.len,
5912                 neighbour = axis.ticks[tickPositions[index + (isFirst ? 1 : -1)]],
5913                 neighbourEdge = neighbour && neighbour.label.xy && neighbour.label.xy.x + neighbour.getLabelSides()[isFirst ? 0 : 1];
5914
5915             if ((isFirst && !reversed) || (isLast && reversed)) {
5916                 // Is the label spilling out to the left of the plot area?
5917                 if (x + leftSide < plotLeft) {
5918
5919                     // Align it to plot left
5920                     x = plotLeft - leftSide;
5921
5922                     // Hide it if it now overlaps the neighbour label
5923                     if (neighbour && x + rightSide > neighbourEdge) {
5924                         show = false;
5925                     }
5926                 }
5927
5928             } else {
5929                 // Is the label spilling out to the right of the plot area?
5930                 if (x + rightSide > plotRight) {
5931
5932                     // Align it to plot right
5933                     x = plotRight - rightSide;
5934
5935                     // Hide it if it now overlaps the neighbour label
5936                     if (neighbour && x + leftSide < neighbourEdge) {
5937                         show = false;
5938                     }
5939
5940                 }
5941             }
5942
5943             // Set the modified x position of the label
5944             xy.x = x;
5945         }
5946         return show;
5947     },
5948
5949     /**
5950      * Get the x and y position for ticks and labels
5951      */
5952     getPosition: function (horiz, pos, tickmarkOffset, old) {
5953         var axis = this.axis,
5954             chart = axis.chart,
5955             cHeight = (old && chart.oldChartHeight) || chart.chartHeight;
5956         
5957         return {
5958             x: horiz ?
5959                 axis.translate(pos + tickmarkOffset, null, null, old) + axis.transB :
5960                 axis.left + axis.offset + (axis.opposite ? ((old && chart.oldChartWidth) || chart.chartWidth) - axis.right - axis.left : 0),
5961
5962             y: horiz ?
5963                 cHeight - axis.bottom + axis.offset - (axis.opposite ? axis.height : 0) :
5964                 cHeight - axis.translate(pos + tickmarkOffset, null, null, old) - axis.transB
5965         };
5966         
5967     },
5968     
5969     /**
5970      * Get the x, y position of the tick label
5971      */
5972     getLabelPosition: function (x, y, label, horiz, labelOptions, tickmarkOffset, index, step) {
5973         var axis = this.axis,
5974             transA = axis.transA,
5975             reversed = axis.reversed,
5976             staggerLines = axis.staggerLines,
5977             baseline = axis.chart.renderer.fontMetrics(labelOptions.style.fontSize).b,
5978             rotation = labelOptions.rotation;
5979             
5980         x = x + labelOptions.x - (tickmarkOffset && horiz ?
5981             tickmarkOffset * transA * (reversed ? -1 : 1) : 0);
5982         y = y + labelOptions.y - (tickmarkOffset && !horiz ?
5983             tickmarkOffset * transA * (reversed ? 1 : -1) : 0);
5984
5985         // Correct for rotation (#1764)
5986         if (rotation && axis.side === 2) {
5987             y -= baseline - baseline * mathCos(rotation * deg2rad);
5988         }
5989         
5990         // Vertically centered
5991         if (!defined(labelOptions.y) && !rotation) { // #1951
5992             y += baseline - label.getBBox().height / 2;
5993         }
5994         
5995         // Correct for staggered labels
5996         if (staggerLines) {
5997             y += (index / (step || 1) % staggerLines) * (axis.labelOffset / staggerLines);
5998         }
5999         
6000         return {
6001             x: x,
6002             y: y
6003         };
6004     },
6005     
6006     /**
6007      * Extendible method to return the path of the marker
6008      */
6009     getMarkPath: function (x, y, tickLength, tickWidth, horiz, renderer) {
6010         return renderer.crispLine([
6011                 M,
6012                 x,
6013                 y,
6014                 L,
6015                 x + (horiz ? 0 : -tickLength),
6016                 y + (horiz ? tickLength : 0)
6017             ], tickWidth);
6018     },
6019
6020     /**
6021      * Put everything in place
6022      *
6023      * @param index {Number}
6024      * @param old {Boolean} Use old coordinates to prepare an animation into new position
6025      */
6026     render: function (index, old, opacity) {
6027         var tick = this,
6028             axis = tick.axis,
6029             options = axis.options,
6030             chart = axis.chart,
6031             renderer = chart.renderer,
6032             horiz = axis.horiz,
6033             type = tick.type,
6034             label = tick.label,
6035             pos = tick.pos,
6036             labelOptions = options.labels,
6037             gridLine = tick.gridLine,
6038             gridPrefix = type ? type + 'Grid' : 'grid',
6039             tickPrefix = type ? type + 'Tick' : 'tick',
6040             gridLineWidth = options[gridPrefix + 'LineWidth'],
6041             gridLineColor = options[gridPrefix + 'LineColor'],
6042             dashStyle = options[gridPrefix + 'LineDashStyle'],
6043             tickLength = options[tickPrefix + 'Length'],
6044             tickWidth = options[tickPrefix + 'Width'] || 0,
6045             tickColor = options[tickPrefix + 'Color'],
6046             tickPosition = options[tickPrefix + 'Position'],
6047             gridLinePath,
6048             mark = tick.mark,
6049             markPath,
6050             step = labelOptions.step,
6051             attribs,
6052             show = true,
6053             tickmarkOffset = axis.tickmarkOffset,
6054             xy = tick.getPosition(horiz, pos, tickmarkOffset, old),
6055             x = xy.x,
6056             y = xy.y,
6057             reverseCrisp = ((horiz && x === axis.pos + axis.len) || (!horiz && y === axis.pos)) ? -1 : 1, // #1480, #1687
6058             staggerLines = axis.staggerLines;
6059
6060         this.isActive = true;
6061         
6062         // create the grid line
6063         if (gridLineWidth) {
6064             gridLinePath = axis.getPlotLinePath(pos + tickmarkOffset, gridLineWidth * reverseCrisp, old, true);
6065
6066             if (gridLine === UNDEFINED) {
6067                 attribs = {
6068                     stroke: gridLineColor,
6069                     'stroke-width': gridLineWidth
6070                 };
6071                 if (dashStyle) {
6072                     attribs.dashstyle = dashStyle;
6073                 }
6074                 if (!type) {
6075                     attribs.zIndex = 1;
6076                 }
6077                 if (old) {
6078                     attribs.opacity = 0;
6079                 }
6080                 tick.gridLine = gridLine =
6081                     gridLineWidth ?
6082                         renderer.path(gridLinePath)
6083                             .attr(attribs).add(axis.gridGroup) :
6084                         null;
6085             }
6086
6087             // If the parameter 'old' is set, the current call will be followed
6088             // by another call, therefore do not do any animations this time
6089             if (!old && gridLine && gridLinePath) {
6090                 gridLine[tick.isNew ? 'attr' : 'animate']({
6091                     d: gridLinePath,
6092                     opacity: opacity
6093                 });
6094             }
6095         }
6096
6097         // create the tick mark
6098         if (tickWidth && tickLength) {
6099
6100             // negate the length
6101             if (tickPosition === 'inside') {
6102                 tickLength = -tickLength;
6103             }
6104             if (axis.opposite) {
6105                 tickLength = -tickLength;
6106             }
6107
6108             markPath = tick.getMarkPath(x, y, tickLength, tickWidth * reverseCrisp, horiz, renderer);
6109
6110             if (mark) { // updating
6111                 mark.animate({
6112                     d: markPath,
6113                     opacity: opacity
6114                 });
6115             } else { // first time
6116                 tick.mark = renderer.path(
6117                     markPath
6118                 ).attr({
6119                     stroke: tickColor,
6120                     'stroke-width': tickWidth,
6121                     opacity: opacity
6122                 }).add(axis.axisGroup);
6123             }
6124         }
6125
6126         // the label is created on init - now move it into place
6127         if (label && !isNaN(x)) {
6128             label.xy = xy = tick.getLabelPosition(x, y, label, horiz, labelOptions, tickmarkOffset, index, step);
6129
6130             // Apply show first and show last. If the tick is both first and last, it is 
6131             // a single centered tick, in which case we show the label anyway (#2100).
6132             if ((tick.isFirst && !tick.isLast && !pick(options.showFirstLabel, 1)) ||
6133                     (tick.isLast && !tick.isFirst && !pick(options.showLastLabel, 1))) {
6134                 show = false;
6135
6136             // Handle label overflow and show or hide accordingly
6137             } else if (!staggerLines && horiz && labelOptions.overflow === 'justify' && !tick.handleOverflow(index, xy)) {
6138                 show = false;
6139             }
6140
6141             // apply step
6142             if (step && index % step) {
6143                 // show those indices dividable by step
6144                 show = false;
6145             }
6146
6147             // Set the new position, and show or hide
6148             if (show && !isNaN(xy.y)) {
6149                 xy.opacity = opacity;
6150                 label[tick.isNew ? 'attr' : 'animate'](xy);
6151                 tick.isNew = false;
6152             } else {
6153                 label.attr('y', -9999); // #1338
6154             }
6155         }
6156     },
6157
6158     /**
6159      * Destructor for the tick prototype
6160      */
6161     destroy: function () {
6162         destroyObjectProperties(this, this.axis);
6163     }
6164 };
6165
6166 /**
6167  * The object wrapper for plot lines and plot bands
6168  * @param {Object} options
6169  */
6170 function PlotLineOrBand(axis, options) {
6171     this.axis = axis;
6172
6173     if (options) {
6174         this.options = options;
6175         this.id = options.id;
6176     }
6177 }
6178
6179 PlotLineOrBand.prototype = {
6180     
6181     /**
6182      * Render the plot line or plot band. If it is already existing,
6183      * move it.
6184      */
6185     render: function () {
6186         var plotLine = this,
6187             axis = plotLine.axis,
6188             horiz = axis.horiz,
6189             halfPointRange = (axis.pointRange || 0) / 2,
6190             options = plotLine.options,
6191             optionsLabel = options.label,
6192             label = plotLine.label,
6193             width = options.width,
6194             to = options.to,
6195             from = options.from,
6196             isBand = defined(from) && defined(to),
6197             value = options.value,
6198             dashStyle = options.dashStyle,
6199             svgElem = plotLine.svgElem,
6200             path = [],
6201             addEvent,
6202             eventType,
6203             xs,
6204             ys,
6205             x,
6206             y,
6207             color = options.color,
6208             zIndex = options.zIndex,
6209             events = options.events,
6210             attribs,
6211             renderer = axis.chart.renderer;
6212
6213         // logarithmic conversion
6214         if (axis.isLog) {
6215             from = log2lin(from);
6216             to = log2lin(to);
6217             value = log2lin(value);
6218         }
6219
6220         // plot line
6221         if (width) {
6222             path = axis.getPlotLinePath(value, width);
6223             attribs = {
6224                 stroke: color,
6225                 'stroke-width': width
6226             };
6227             if (dashStyle) {
6228                 attribs.dashstyle = dashStyle;
6229             }
6230         } else if (isBand) { // plot band
6231             
6232             // keep within plot area
6233             from = mathMax(from, axis.min - halfPointRange);
6234             to = mathMin(to, axis.max + halfPointRange);
6235             
6236             path = axis.getPlotBandPath(from, to, options);
6237             attribs = {
6238                 fill: color
6239             };
6240             if (options.borderWidth) {
6241                 attribs.stroke = options.borderColor;
6242                 attribs['stroke-width'] = options.borderWidth;
6243             }
6244         } else {
6245             return;
6246         }
6247         // zIndex
6248         if (defined(zIndex)) {
6249             attribs.zIndex = zIndex;
6250         }
6251
6252         // common for lines and bands
6253         if (svgElem) {
6254             if (path) {
6255                 svgElem.animate({
6256                     d: path
6257                 }, null, svgElem.onGetPath);
6258             } else {
6259                 svgElem.hide();
6260                 svgElem.onGetPath = function () {
6261                     svgElem.show();
6262                 };
6263             }
6264         } else if (path && path.length) {
6265             plotLine.svgElem = svgElem = renderer.path(path)
6266                 .attr(attribs).add();
6267
6268             // events
6269             if (events) {
6270                 addEvent = function (eventType) {
6271                     svgElem.on(eventType, function (e) {
6272                         events[eventType].apply(plotLine, [e]);
6273                     });
6274                 };
6275                 for (eventType in events) {
6276                     addEvent(eventType);
6277                 }
6278             }
6279         }
6280
6281         // the plot band/line label
6282         if (optionsLabel && defined(optionsLabel.text) && path && path.length && axis.width > 0 && axis.height > 0) {
6283             // apply defaults
6284             optionsLabel = merge({
6285                 align: horiz && isBand && 'center',
6286                 x: horiz ? !isBand && 4 : 10,
6287                 verticalAlign : !horiz && isBand && 'middle',
6288                 y: horiz ? isBand ? 16 : 10 : isBand ? 6 : -4,
6289                 rotation: horiz && !isBand && 90
6290             }, optionsLabel);
6291
6292             // add the SVG element
6293             if (!label) {
6294                 plotLine.label = label = renderer.text(
6295                         optionsLabel.text,
6296                         0,
6297                         0,
6298                         optionsLabel.useHTML
6299                     )
6300                     .attr({
6301                         align: optionsLabel.textAlign || optionsLabel.align,
6302                         rotation: optionsLabel.rotation,
6303                         zIndex: zIndex
6304                     })
6305                     .css(optionsLabel.style)
6306                     .add();
6307             }
6308
6309             // get the bounding box and align the label
6310             xs = [path[1], path[4], pick(path[6], path[1])];
6311             ys = [path[2], path[5], pick(path[7], path[2])];
6312             x = arrayMin(xs);
6313             y = arrayMin(ys);
6314
6315             label.align(optionsLabel, false, {
6316                 x: x,
6317                 y: y,
6318                 width: arrayMax(xs) - x,
6319                 height: arrayMax(ys) - y
6320             });
6321             label.show();
6322
6323         } else if (label) { // move out of sight
6324             label.hide();
6325         }
6326
6327         // chainable
6328         return plotLine;
6329     },
6330
6331     /**
6332      * Remove the plot line or band
6333      */
6334     destroy: function () {
6335         // remove it from the lookup
6336         erase(this.axis.plotLinesAndBands, this);
6337         
6338         delete this.axis;
6339         destroyObjectProperties(this);
6340     }
6341 };
6342 /**
6343  * The class for stack items
6344  */
6345 function StackItem(axis, options, isNegative, x, stackOption, stacking) {
6346     
6347     var inverted = axis.chart.inverted;
6348
6349     this.axis = axis;
6350
6351     // Tells if the stack is negative
6352     this.isNegative = isNegative;
6353
6354     // Save the options to be able to style the label
6355     this.options = options;
6356
6357     // Save the x value to be able to position the label later
6358     this.x = x;
6359
6360     // Initialize total value
6361     this.total = null;
6362
6363     // This will keep each points' extremes stored by series.index
6364     this.points = {};
6365
6366     // Save the stack option on the series configuration object, and whether to treat it as percent
6367     this.stack = stackOption;
6368     this.percent = stacking === 'percent';
6369
6370     // The align options and text align varies on whether the stack is negative and
6371     // if the chart is inverted or not.
6372     // First test the user supplied value, then use the dynamic.
6373     this.alignOptions = {
6374         align: options.align || (inverted ? (isNegative ? 'left' : 'right') : 'center'),
6375         verticalAlign: options.verticalAlign || (inverted ? 'middle' : (isNegative ? 'bottom' : 'top')),
6376         y: pick(options.y, inverted ? 4 : (isNegative ? 14 : -6)),
6377         x: pick(options.x, inverted ? (isNegative ? -6 : 6) : 0)
6378     };
6379
6380     this.textAlign = options.textAlign || (inverted ? (isNegative ? 'right' : 'left') : 'center');
6381 }
6382
6383 StackItem.prototype = {
6384     destroy: function () {
6385         destroyObjectProperties(this, this.axis);
6386     },
6387
6388     /**
6389      * Renders the stack total label and adds it to the stack label group.
6390      */
6391     render: function (group) {
6392         var options = this.options,
6393             formatOption = options.format,
6394             str = formatOption ?
6395                 format(formatOption, this) : 
6396                 options.formatter.call(this);  // format the text in the label
6397
6398         // Change the text to reflect the new total and set visibility to hidden in case the serie is hidden
6399         if (this.label) {
6400             this.label.attr({text: str, visibility: HIDDEN});
6401         // Create new label
6402         } else {
6403             this.label =
6404                 this.axis.chart.renderer.text(str, 0, 0, options.useHTML)        // dummy positions, actual position updated with setOffset method in columnseries
6405                     .css(options.style)                // apply style
6406                     .attr({
6407                         align: this.textAlign,                // fix the text-anchor
6408                         rotation: options.rotation,    // rotation
6409                         visibility: HIDDEN                    // hidden until setOffset is called
6410                     })                
6411                     .add(group);                            // add to the labels-group
6412         }
6413     },
6414
6415     /**
6416      * Sets the offset that the stack has from the x value and repositions the label.
6417      */
6418     setOffset: function (xOffset, xWidth) {
6419         var stackItem = this,
6420             axis = stackItem.axis,
6421             chart = axis.chart,
6422             inverted = chart.inverted,
6423             neg = this.isNegative,                            // special treatment is needed for negative stacks
6424             y = axis.translate(this.percent ? 100 : this.total, 0, 0, 0, 1), // stack value translated mapped to chart coordinates
6425             yZero = axis.translate(0),                        // stack origin
6426             h = mathAbs(y - yZero),                            // stack height
6427             x = chart.xAxis[0].translate(this.x) + xOffset,    // stack x position
6428             plotHeight = chart.plotHeight,
6429             stackBox = {    // this is the box for the complete stack
6430                 x: inverted ? (neg ? y : y - h) : x,
6431                 y: inverted ? plotHeight - x - xWidth : (neg ? (plotHeight - y - h) : plotHeight - y),
6432                 width: inverted ? h : xWidth,
6433                 height: inverted ? xWidth : h
6434             },
6435             label = this.label,
6436             alignAttr;
6437         
6438         if (label) {
6439             label.align(this.alignOptions, null, stackBox);    // align the label to the box
6440                 
6441             // Set visibility (#678)
6442             alignAttr = label.alignAttr;
6443             label.attr({ 
6444                 visibility: this.options.crop === false || chart.isInsidePlot(alignAttr.x, alignAttr.y) ? 
6445                     (hasSVG ? 'inherit' : VISIBLE) : 
6446                     HIDDEN
6447             });
6448         }
6449     }
6450 };
6451 /**
6452  * Create a new axis object
6453  * @param {Object} chart
6454  * @param {Object} options
6455  */
6456 function Axis() {
6457     this.init.apply(this, arguments);
6458 }
6459
6460 Axis.prototype = {
6461     
6462     /**
6463      * Default options for the X axis - the Y axis has extended defaults 
6464      */
6465     defaultOptions: {
6466         // allowDecimals: null,
6467         // alternateGridColor: null,
6468         // categories: [],
6469         dateTimeLabelFormats: {
6470             millisecond: '%H:%M:%S.%L',
6471             second: '%H:%M:%S',
6472             minute: '%H:%M',
6473             hour: '%H:%M',
6474             day: '%e. %b',
6475             week: '%e. %b',
6476             month: '%b \'%y',
6477             year: '%Y'
6478         },
6479         endOnTick: false,
6480         gridLineColor: '#C0C0C0',
6481         // gridLineDashStyle: 'solid',
6482         // gridLineWidth: 0,
6483         // reversed: false,
6484     
6485         labels: defaultLabelOptions,
6486             // { step: null },
6487         lineColor: '#C0D0E0',
6488         lineWidth: 1,
6489         //linkedTo: null,
6490         //max: undefined,
6491         //min: undefined,
6492         minPadding: 0.01,
6493         maxPadding: 0.01,
6494         //minRange: null,
6495         minorGridLineColor: '#E0E0E0',
6496         // minorGridLineDashStyle: null,
6497         minorGridLineWidth: 1,
6498         minorTickColor: '#A0A0A0',
6499         //minorTickInterval: null,
6500         minorTickLength: 2,
6501         minorTickPosition: 'outside', // inside or outside
6502         //minorTickWidth: 0,
6503         //opposite: false,
6504         //offset: 0,
6505         //plotBands: [{
6506         //    events: {},
6507         //    zIndex: 1,
6508         //    labels: { align, x, verticalAlign, y, style, rotation, textAlign }
6509         //}],
6510         //plotLines: [{
6511         //    events: {}
6512         //  dashStyle: {}
6513         //    zIndex:
6514         //    labels: { align, x, verticalAlign, y, style, rotation, textAlign }
6515         //}],
6516         //reversed: false,
6517         // showFirstLabel: true,
6518         // showLastLabel: true,
6519         startOfWeek: 1,
6520         startOnTick: false,
6521         tickColor: '#C0D0E0',
6522         //tickInterval: null,
6523         tickLength: 5,
6524         tickmarkPlacement: 'between', // on or between
6525         tickPixelInterval: 100,
6526         tickPosition: 'outside',
6527         tickWidth: 1,
6528         title: {
6529             //text: null,
6530             align: 'middle', // low, middle or high
6531             //margin: 0 for horizontal, 10 for vertical axes,
6532             //rotation: 0,
6533             //side: 'outside',
6534             style: {
6535                 color: '#4d759e',
6536                 //font: defaultFont.replace('normal', 'bold')
6537                 fontWeight: 'bold'
6538             }
6539             //x: 0,
6540             //y: 0
6541         },
6542         type: 'linear' // linear, logarithmic or datetime
6543     },
6544     
6545     /**
6546      * This options set extends the defaultOptions for Y axes
6547      */
6548     defaultYAxisOptions: {
6549         endOnTick: true,
6550         gridLineWidth: 1,
6551         tickPixelInterval: 72,
6552         showLastLabel: true,
6553         labels: {
6554             x: -8,
6555             y: 3
6556         },
6557         lineWidth: 0,
6558         maxPadding: 0.05,
6559         minPadding: 0.05,
6560         startOnTick: true,
6561         tickWidth: 0,
6562         title: {
6563             rotation: 270,
6564             text: 'Values'
6565         },
6566         stackLabels: {
6567             enabled: false,
6568             //align: dynamic,
6569             //y: dynamic,
6570             //x: dynamic,
6571             //verticalAlign: dynamic,
6572             //textAlign: dynamic,
6573             //rotation: 0,
6574             formatter: function () {
6575                 return numberFormat(this.total, -1);
6576             },
6577             style: defaultLabelOptions.style
6578         }
6579     },
6580     
6581     /**
6582      * These options extend the defaultOptions for left axes
6583      */
6584     defaultLeftAxisOptions: {
6585         labels: {
6586             x: -8,
6587             y: null
6588         },
6589         title: {
6590             rotation: 270
6591         }
6592     },
6593     
6594     /**
6595      * These options extend the defaultOptions for right axes
6596      */
6597     defaultRightAxisOptions: {
6598         labels: {
6599             x: 8,
6600             y: null
6601         },
6602         title: {
6603             rotation: 90
6604         }
6605     },
6606     
6607     /**
6608      * These options extend the defaultOptions for bottom axes
6609      */
6610     defaultBottomAxisOptions: {
6611         labels: {
6612             x: 0,
6613             y: 14
6614             // overflow: undefined,
6615             // staggerLines: null
6616         },
6617         title: {
6618             rotation: 0
6619         }
6620     },
6621     /**
6622      * These options extend the defaultOptions for left axes
6623      */
6624     defaultTopAxisOptions: {
6625         labels: {
6626             x: 0,
6627             y: -5
6628             // overflow: undefined
6629             // staggerLines: null
6630         },
6631         title: {
6632             rotation: 0
6633         }
6634     },
6635     
6636     /**
6637      * Initialize the axis
6638      */
6639     init: function (chart, userOptions) {
6640             
6641         
6642         var isXAxis = userOptions.isX,
6643             axis = this;
6644     
6645         // Flag, is the axis horizontal
6646         axis.horiz = chart.inverted ? !isXAxis : isXAxis;
6647         
6648         // Flag, isXAxis
6649         axis.isXAxis = isXAxis;
6650         axis.xOrY = isXAxis ? 'x' : 'y';
6651     
6652     
6653         axis.opposite = userOptions.opposite; // needed in setOptions
6654         axis.side = axis.horiz ?
6655                 (axis.opposite ? 0 : 2) : // top : bottom
6656                 (axis.opposite ? 1 : 3);  // right : left
6657     
6658         axis.setOptions(userOptions);
6659         
6660     
6661         var options = this.options,
6662             type = options.type,
6663             isDatetimeAxis = type === 'datetime';
6664     
6665         axis.labelFormatter = options.labels.formatter || axis.defaultLabelFormatter; // can be overwritten by dynamic format
6666     
6667     
6668         // Flag, stagger lines or not
6669         axis.userOptions = userOptions;
6670     
6671         //axis.axisTitleMargin = UNDEFINED,// = options.title.margin,
6672         axis.minPixelPadding = 0;
6673         //axis.ignoreMinPadding = UNDEFINED; // can be set to true by a column or bar series
6674         //axis.ignoreMaxPadding = UNDEFINED;
6675     
6676         axis.chart = chart;
6677         axis.reversed = options.reversed;
6678         axis.zoomEnabled = options.zoomEnabled !== false;
6679     
6680         // Initial categories
6681         axis.categories = options.categories || type === 'category';
6682     
6683         // Elements
6684         //axis.axisGroup = UNDEFINED;
6685         //axis.gridGroup = UNDEFINED;
6686         //axis.axisTitle = UNDEFINED;
6687         //axis.axisLine = UNDEFINED;
6688     
6689         // Shorthand types
6690         axis.isLog = type === 'logarithmic';
6691         axis.isDatetimeAxis = isDatetimeAxis;
6692     
6693         // Flag, if axis is linked to another axis
6694         axis.isLinked = defined(options.linkedTo);
6695         // Linked axis.
6696         //axis.linkedParent = UNDEFINED;    
6697         
6698         // Tick positions
6699         //axis.tickPositions = UNDEFINED; // array containing predefined positions
6700         // Tick intervals
6701         //axis.tickInterval = UNDEFINED;
6702         //axis.minorTickInterval = UNDEFINED;
6703         
6704         axis.tickmarkOffset = (axis.categories && options.tickmarkPlacement === 'between') ? 0.5 : 0;
6705     
6706         // Major ticks
6707         axis.ticks = {};
6708         // Minor ticks
6709         axis.minorTicks = {};
6710         //axis.tickAmount = UNDEFINED;
6711     
6712         // List of plotLines/Bands
6713         axis.plotLinesAndBands = [];
6714     
6715         // Alternate bands
6716         axis.alternateBands = {};
6717     
6718         // Axis metrics
6719         //axis.left = UNDEFINED;
6720         //axis.top = UNDEFINED;
6721         //axis.width = UNDEFINED;
6722         //axis.height = UNDEFINED;
6723         //axis.bottom = UNDEFINED;
6724         //axis.right = UNDEFINED;
6725         //axis.transA = UNDEFINED;
6726         //axis.transB = UNDEFINED;
6727         //axis.oldTransA = UNDEFINED;
6728         axis.len = 0;
6729         //axis.oldMin = UNDEFINED;
6730         //axis.oldMax = UNDEFINED;
6731         //axis.oldUserMin = UNDEFINED;
6732         //axis.oldUserMax = UNDEFINED;
6733         //axis.oldAxisLength = UNDEFINED;
6734         axis.minRange = axis.userMinRange = options.minRange || options.maxZoom;
6735         axis.range = options.range;
6736         axis.offset = options.offset || 0;
6737     
6738     
6739         // Dictionary for stacks
6740         axis.stacks = {};
6741         axis.oldStacks = {};
6742
6743         // Dictionary for stacks max values
6744         axis.stackExtremes = {};
6745
6746         // Min and max in the data
6747         //axis.dataMin = UNDEFINED,
6748         //axis.dataMax = UNDEFINED,
6749     
6750         // The axis range
6751         axis.max = null;
6752         axis.min = null;
6753     
6754         // User set min and max
6755         //axis.userMin = UNDEFINED,
6756         //axis.userMax = UNDEFINED,
6757
6758         // Run Axis
6759         
6760         var eventType,
6761             events = axis.options.events;
6762
6763         // Register
6764         if (inArray(axis, chart.axes) === -1) { // don't add it again on Axis.update()
6765             chart.axes.push(axis);
6766             chart[isXAxis ? 'xAxis' : 'yAxis'].push(axis);
6767         }
6768
6769         axis.series = axis.series || []; // populated by Series
6770
6771         // inverted charts have reversed xAxes as default
6772         if (chart.inverted && isXAxis && axis.reversed === UNDEFINED) {
6773             axis.reversed = true;
6774         }
6775
6776         axis.removePlotBand = axis.removePlotBandOrLine;
6777         axis.removePlotLine = axis.removePlotBandOrLine;
6778
6779
6780         // register event listeners
6781         for (eventType in events) {
6782             addEvent(axis, eventType, events[eventType]);
6783         }
6784
6785         // extend logarithmic axis
6786         if (axis.isLog) {
6787             axis.val2lin = log2lin;
6788             axis.lin2val = lin2log;
6789         }
6790     },
6791     
6792     /**
6793      * Merge and set options
6794      */
6795     setOptions: function (userOptions) {
6796         this.options = merge(
6797             this.defaultOptions,
6798             this.isXAxis ? {} : this.defaultYAxisOptions,
6799             [this.defaultTopAxisOptions, this.defaultRightAxisOptions,
6800                 this.defaultBottomAxisOptions, this.defaultLeftAxisOptions][this.side],
6801             merge(
6802                 defaultOptions[this.isXAxis ? 'xAxis' : 'yAxis'], // if set in setOptions (#1053)
6803                 userOptions
6804             )
6805         );
6806     },
6807
6808     /**
6809      * Update the axis with a new options structure
6810      */
6811     update: function (newOptions, redraw) {
6812         var chart = this.chart;
6813
6814         newOptions = chart.options[this.xOrY + 'Axis'][this.options.index] = merge(this.userOptions, newOptions);
6815
6816         this.destroy(true);
6817         this._addedPlotLB = this.userMin = this.userMax = UNDEFINED; // #1611, #2306
6818
6819         this.init(chart, extend(newOptions, { events: UNDEFINED }));
6820
6821         chart.isDirtyBox = true;
6822         if (pick(redraw, true)) {
6823             chart.redraw();
6824         }
6825     },    
6826     
6827     /**
6828      * Remove the axis from the chart
6829      */
6830     remove: function (redraw) {
6831         var chart = this.chart,
6832             key = this.xOrY + 'Axis'; // xAxis or yAxis
6833
6834         // Remove associated series
6835         each(this.series, function (series) {
6836             series.remove(false);
6837         });
6838
6839         // Remove the axis
6840         erase(chart.axes, this);
6841         erase(chart[key], this);
6842         chart.options[key].splice(this.options.index, 1);
6843         each(chart[key], function (axis, i) { // Re-index, #1706
6844             axis.options.index = i;
6845         });
6846         this.destroy();
6847         chart.isDirtyBox = true;
6848
6849         if (pick(redraw, true)) {
6850             chart.redraw();
6851         }
6852     },
6853     
6854     /** 
6855      * The default label formatter. The context is a special config object for the label.
6856      */
6857     defaultLabelFormatter: function () {
6858         var axis = this.axis,
6859             value = this.value,
6860             categories = axis.categories, 
6861             dateTimeLabelFormat = this.dateTimeLabelFormat,
6862             numericSymbols = defaultOptions.lang.numericSymbols,
6863             i = numericSymbols && numericSymbols.length,
6864             multi,
6865             ret,
6866             formatOption = axis.options.labels.format,
6867             
6868             // make sure the same symbol is added for all labels on a linear axis
6869             numericSymbolDetector = axis.isLog ? value : axis.tickInterval;
6870
6871         if (formatOption) {
6872             ret = format(formatOption, this);
6873         
6874         } else if (categories) {
6875             ret = value;
6876         
6877         } else if (dateTimeLabelFormat) { // datetime axis
6878             ret = dateFormat(dateTimeLabelFormat, value);
6879         
6880         } else if (i && numericSymbolDetector >= 1000) {
6881             // Decide whether we should add a numeric symbol like k (thousands) or M (millions).
6882             // If we are to enable this in tooltip or other places as well, we can move this
6883             // logic to the numberFormatter and enable it by a parameter.
6884             while (i-- && ret === UNDEFINED) {
6885                 multi = Math.pow(1000, i + 1);
6886                 if (numericSymbolDetector >= multi && numericSymbols[i] !== null) {
6887                     ret = numberFormat(value / multi, -1) + numericSymbols[i];
6888                 }
6889             }
6890         }
6891         
6892         if (ret === UNDEFINED) {
6893             if (value >= 1000) { // add thousands separators
6894                 ret = numberFormat(value, 0);
6895
6896             } else { // small numbers
6897                 ret = numberFormat(value, -1);
6898             }
6899         }
6900         
6901         return ret;
6902     },
6903
6904     /**
6905      * Get the minimum and maximum for the series of each axis
6906      */
6907     getSeriesExtremes: function () {
6908         var axis = this,
6909             chart = axis.chart;
6910
6911         axis.hasVisibleSeries = false;
6912
6913         // reset dataMin and dataMax in case we're redrawing
6914         axis.dataMin = axis.dataMax = null;
6915
6916         // reset cached stacking extremes
6917         axis.stackExtremes = {};
6918
6919         axis.buildStacks();
6920
6921         // loop through this axis' series
6922         each(axis.series, function (series) {
6923
6924             if (series.visible || !chart.options.chart.ignoreHiddenSeries) {
6925
6926                 var seriesOptions = series.options,
6927                     xData,
6928                     threshold = seriesOptions.threshold,
6929                     seriesDataMin,
6930                     seriesDataMax;
6931
6932                 axis.hasVisibleSeries = true;
6933
6934                 // Validate threshold in logarithmic axes
6935                 if (axis.isLog && threshold <= 0) {
6936                     threshold = null;
6937                 }
6938
6939                 // Get dataMin and dataMax for X axes
6940                 if (axis.isXAxis) {
6941                     xData = series.xData;
6942                     if (xData.length) {
6943                         axis.dataMin = mathMin(pick(axis.dataMin, xData[0]), arrayMin(xData));
6944                         axis.dataMax = mathMax(pick(axis.dataMax, xData[0]), arrayMax(xData));
6945                     }
6946
6947                 // Get dataMin and dataMax for Y axes, as well as handle stacking and processed data
6948                 } else {
6949
6950                     // Get this particular series extremes
6951                     series.getExtremes();
6952                     seriesDataMax = series.dataMax;
6953                     seriesDataMin = series.dataMin;
6954
6955                     // Get the dataMin and dataMax so far. If percentage is used, the min and max are
6956                     // always 0 and 100. If seriesDataMin and seriesDataMax is null, then series
6957                     // doesn't have active y data, we continue with nulls
6958                     if (defined(seriesDataMin) && defined(seriesDataMax)) {
6959                         axis.dataMin = mathMin(pick(axis.dataMin, seriesDataMin), seriesDataMin);
6960                         axis.dataMax = mathMax(pick(axis.dataMax, seriesDataMax), seriesDataMax);
6961                     }
6962
6963                     // Adjust to threshold
6964                     if (defined(threshold)) {
6965                         if (axis.dataMin >= threshold) {
6966                             axis.dataMin = threshold;
6967                             axis.ignoreMinPadding = true;
6968                         } else if (axis.dataMax < threshold) {
6969                             axis.dataMax = threshold;
6970                             axis.ignoreMaxPadding = true;
6971                         }
6972                     }
6973                 }
6974             }
6975         });
6976     },
6977
6978     /**
6979      * Translate from axis value to pixel position on the chart, or back
6980      *
6981      */
6982     translate: function (val, backwards, cvsCoord, old, handleLog, pointPlacement) {
6983         var axis = this,
6984             axisLength = axis.len,
6985             sign = 1,
6986             cvsOffset = 0,
6987             localA = old ? axis.oldTransA : axis.transA,
6988             localMin = old ? axis.oldMin : axis.min,
6989             returnValue,
6990             minPixelPadding = axis.minPixelPadding,
6991             postTranslate = (axis.options.ordinal || (axis.isLog && handleLog)) && axis.lin2val;
6992
6993         if (!localA) {
6994             localA = axis.transA;
6995         }
6996
6997         // In vertical axes, the canvas coordinates start from 0 at the top like in 
6998         // SVG. 
6999         if (cvsCoord) {
7000             sign *= -1; // canvas coordinates inverts the value
7001             cvsOffset = axisLength;
7002         }
7003
7004         // Handle reversed axis
7005         if (axis.reversed) { 
7006             sign *= -1;
7007             cvsOffset -= sign * axisLength;
7008         }
7009
7010         // From pixels to value
7011         if (backwards) { // reverse translation
7012             
7013             val = val * sign + cvsOffset;
7014             val -= minPixelPadding;
7015             returnValue = val / localA + localMin; // from chart pixel to value
7016             if (postTranslate) { // log and ordinal axes
7017                 returnValue = axis.lin2val(returnValue);
7018             }
7019
7020         // From value to pixels
7021         } else {
7022             if (postTranslate) { // log and ordinal axes
7023                 val = axis.val2lin(val);
7024             }
7025             if (pointPlacement === 'between') {
7026                 pointPlacement = 0.5;
7027             }
7028             returnValue = sign * (val - localMin) * localA + cvsOffset + (sign * minPixelPadding) +
7029                 (isNumber(pointPlacement) ? localA * pointPlacement * axis.pointRange : 0);
7030         }
7031
7032         return returnValue;
7033     },
7034
7035     /**
7036      * Utility method to translate an axis value to pixel position. 
7037      * @param {Number} value A value in terms of axis units
7038      * @param {Boolean} paneCoordinates Whether to return the pixel coordinate relative to the chart
7039      *        or just the axis/pane itself.
7040      */
7041     toPixels: function (value, paneCoordinates) {
7042         return this.translate(value, false, !this.horiz, null, true) + (paneCoordinates ? 0 : this.pos);
7043     },
7044
7045     /*
7046      * Utility method to translate a pixel position in to an axis value
7047      * @param {Number} pixel The pixel value coordinate
7048      * @param {Boolean} paneCoordiantes Whether the input pixel is relative to the chart or just the
7049      *        axis/pane itself.
7050      */
7051     toValue: function (pixel, paneCoordinates) {
7052         return this.translate(pixel - (paneCoordinates ? 0 : this.pos), true, !this.horiz, null, true);
7053     },
7054
7055     /**
7056      * Create the path for a plot line that goes from the given value on
7057      * this axis, across the plot to the opposite side
7058      * @param {Number} value
7059      * @param {Number} lineWidth Used for calculation crisp line
7060      * @param {Number] old Use old coordinates (for resizing and rescaling)
7061      */
7062     getPlotLinePath: function (value, lineWidth, old, force) {
7063         var axis = this,
7064             chart = axis.chart,
7065             axisLeft = axis.left,
7066             axisTop = axis.top,
7067             x1,
7068             y1,
7069             x2,
7070             y2,
7071             translatedValue = axis.translate(value, null, null, old),
7072             cHeight = (old && chart.oldChartHeight) || chart.chartHeight,
7073             cWidth = (old && chart.oldChartWidth) || chart.chartWidth,
7074             skip,
7075             transB = axis.transB;
7076
7077         x1 = x2 = mathRound(translatedValue + transB);
7078         y1 = y2 = mathRound(cHeight - translatedValue - transB);
7079
7080         if (isNaN(translatedValue)) { // no min or max
7081             skip = true;
7082
7083         } else if (axis.horiz) {
7084             y1 = axisTop;
7085             y2 = cHeight - axis.bottom;
7086             if (x1 < axisLeft || x1 > axisLeft + axis.width) {
7087                 skip = true;
7088             }
7089         } else {
7090             x1 = axisLeft;
7091             x2 = cWidth - axis.right;
7092
7093             if (y1 < axisTop || y1 > axisTop + axis.height) {
7094                 skip = true;
7095             }
7096         }
7097         return skip && !force ?
7098             null :
7099             chart.renderer.crispLine([M, x1, y1, L, x2, y2], lineWidth || 0);
7100     },
7101     
7102     /**
7103      * Create the path for a plot band
7104      */
7105     getPlotBandPath: function (from, to) {
7106
7107         var toPath = this.getPlotLinePath(to),
7108             path = this.getPlotLinePath(from);
7109             
7110         if (path && toPath) {
7111             path.push(
7112                 toPath[4],
7113                 toPath[5],
7114                 toPath[1],
7115                 toPath[2]
7116             );
7117         } else { // outside the axis area
7118             path = null;
7119         }
7120         
7121         return path;
7122     },
7123     
7124     /**
7125      * Set the tick positions of a linear axis to round values like whole tens or every five.
7126      */
7127     getLinearTickPositions: function (tickInterval, min, max) {
7128         var pos,
7129             lastPos,
7130             roundedMin = correctFloat(mathFloor(min / tickInterval) * tickInterval),
7131             roundedMax = correctFloat(mathCeil(max / tickInterval) * tickInterval),
7132             tickPositions = [];
7133
7134         // Populate the intermediate values
7135         pos = roundedMin;
7136         while (pos <= roundedMax) {
7137
7138             // Place the tick on the rounded value
7139             tickPositions.push(pos);
7140
7141             // Always add the raw tickInterval, not the corrected one.
7142             pos = correctFloat(pos + tickInterval);
7143
7144             // If the interval is not big enough in the current min - max range to actually increase
7145             // the loop variable, we need to break out to prevent endless loop. Issue #619
7146             if (pos === lastPos) {
7147                 break;
7148             }
7149
7150             // Record the last value
7151             lastPos = pos;
7152         }
7153         return tickPositions;
7154     },
7155     
7156     /**
7157      * Set the tick positions of a logarithmic axis
7158      */
7159     getLogTickPositions: function (interval, min, max, minor) {
7160         var axis = this,
7161             options = axis.options,
7162             axisLength = axis.len,
7163             // Since we use this method for both major and minor ticks,
7164             // use a local variable and return the result
7165             positions = []; 
7166         
7167         // Reset
7168         if (!minor) {
7169             axis._minorAutoInterval = null;
7170         }
7171         
7172         // First case: All ticks fall on whole logarithms: 1, 10, 100 etc.
7173         if (interval >= 0.5) {
7174             interval = mathRound(interval);
7175             positions = axis.getLinearTickPositions(interval, min, max);
7176             
7177         // Second case: We need intermediary ticks. For example 
7178         // 1, 2, 4, 6, 8, 10, 20, 40 etc. 
7179         } else if (interval >= 0.08) {
7180             var roundedMin = mathFloor(min),
7181                 intermediate,
7182                 i,
7183                 j,
7184                 len,
7185                 pos,
7186                 lastPos,
7187                 break2;
7188                 
7189             if (interval > 0.3) {
7190                 intermediate = [1, 2, 4];
7191             } else if (interval > 0.15) { // 0.2 equals five minor ticks per 1, 10, 100 etc
7192                 intermediate = [1, 2, 4, 6, 8];
7193             } else { // 0.1 equals ten minor ticks per 1, 10, 100 etc
7194                 intermediate = [1, 2, 3, 4, 5, 6, 7, 8, 9];
7195             }
7196             
7197             for (i = roundedMin; i < max + 1 && !break2; i++) {
7198                 len = intermediate.length;
7199                 for (j = 0; j < len && !break2; j++) {
7200                     pos = log2lin(lin2log(i) * intermediate[j]);
7201                     
7202                     if (pos > min && (!minor || lastPos <= max)) { // #1670
7203                         positions.push(lastPos);
7204                     }
7205                     
7206                     if (lastPos > max) {
7207                         break2 = true;
7208                     }
7209                     lastPos = pos;
7210                 }
7211             }
7212             
7213         // Third case: We are so deep in between whole logarithmic values that
7214         // we might as well handle the tick positions like a linear axis. For
7215         // example 1.01, 1.02, 1.03, 1.04.
7216         } else {
7217             var realMin = lin2log(min),
7218                 realMax = lin2log(max),
7219                 tickIntervalOption = options[minor ? 'minorTickInterval' : 'tickInterval'],
7220                 filteredTickIntervalOption = tickIntervalOption === 'auto' ? null : tickIntervalOption,
7221                 tickPixelIntervalOption = options.tickPixelInterval / (minor ? 5 : 1),
7222                 totalPixelLength = minor ? axisLength / axis.tickPositions.length : axisLength;
7223             
7224             interval = pick(
7225                 filteredTickIntervalOption,
7226                 axis._minorAutoInterval,
7227                 (realMax - realMin) * tickPixelIntervalOption / (totalPixelLength || 1)
7228             );
7229             
7230             interval = normalizeTickInterval(
7231                 interval, 
7232                 null, 
7233                 getMagnitude(interval)
7234             );
7235             
7236             positions = map(axis.getLinearTickPositions(
7237                 interval, 
7238                 realMin,
7239                 realMax    
7240             ), log2lin);
7241             
7242             if (!minor) {
7243                 axis._minorAutoInterval = interval / 5;
7244             }
7245         }
7246         
7247         // Set the axis-level tickInterval variable 
7248         if (!minor) {
7249             axis.tickInterval = interval;
7250         }
7251         return positions;
7252     },
7253
7254     /**
7255      * Return the minor tick positions. For logarithmic axes, reuse the same logic
7256      * as for major ticks.
7257      */
7258     getMinorTickPositions: function () {
7259         var axis = this,
7260             options = axis.options,
7261             tickPositions = axis.tickPositions,
7262             minorTickInterval = axis.minorTickInterval,
7263             minorTickPositions = [],
7264             pos,
7265             i,
7266             len;
7267         
7268         if (axis.isLog) {
7269             len = tickPositions.length;
7270             for (i = 1; i < len; i++) {
7271                 minorTickPositions = minorTickPositions.concat(
7272                     axis.getLogTickPositions(minorTickInterval, tickPositions[i - 1], tickPositions[i], true)
7273                 );    
7274             }
7275         } else if (axis.isDatetimeAxis && options.minorTickInterval === 'auto') { // #1314
7276             minorTickPositions = minorTickPositions.concat(
7277                 getTimeTicks(
7278                     normalizeTimeTickInterval(minorTickInterval),
7279                     axis.min,
7280                     axis.max,
7281                     options.startOfWeek
7282                 )
7283             );
7284             if (minorTickPositions[0] < axis.min) {
7285                 minorTickPositions.shift();
7286             }
7287         } else {            
7288             for (pos = axis.min + (tickPositions[0] - axis.min) % minorTickInterval; pos <= axis.max; pos += minorTickInterval) {
7289                 minorTickPositions.push(pos);
7290             }
7291         }
7292         return minorTickPositions;
7293     },
7294
7295     /**
7296      * Adjust the min and max for the minimum range. Keep in mind that the series data is 
7297      * not yet processed, so we don't have information on data cropping and grouping, or 
7298      * updated axis.pointRange or series.pointRange. The data can't be processed until
7299      * we have finally established min and max.
7300      */
7301     adjustForMinRange: function () {
7302         var axis = this,
7303             options = axis.options,
7304             min = axis.min,
7305             max = axis.max,
7306             zoomOffset,
7307             spaceAvailable = axis.dataMax - axis.dataMin >= axis.minRange,
7308             closestDataRange,
7309             i,
7310             distance,
7311             xData,
7312             loopLength,
7313             minArgs,
7314             maxArgs;
7315
7316         // Set the automatic minimum range based on the closest point distance
7317         if (axis.isXAxis && axis.minRange === UNDEFINED && !axis.isLog) {
7318
7319             if (defined(options.min) || defined(options.max)) {
7320                 axis.minRange = null; // don't do this again
7321
7322             } else {
7323
7324                 // Find the closest distance between raw data points, as opposed to
7325                 // closestPointRange that applies to processed points (cropped and grouped)
7326                 each(axis.series, function (series) {
7327                     xData = series.xData;
7328                     loopLength = series.xIncrement ? 1 : xData.length - 1;
7329                     for (i = loopLength; i > 0; i--) {
7330                         distance = xData[i] - xData[i - 1];
7331                         if (closestDataRange === UNDEFINED || distance < closestDataRange) {
7332                             closestDataRange = distance;
7333                         }
7334                     }
7335                 });
7336                 axis.minRange = mathMin(closestDataRange * 5, axis.dataMax - axis.dataMin);
7337             }
7338         }
7339
7340         // if minRange is exceeded, adjust
7341         if (max - min < axis.minRange) {
7342             var minRange = axis.minRange;
7343             zoomOffset = (minRange - max + min) / 2;
7344
7345             // if min and max options have been set, don't go beyond it
7346             minArgs = [min - zoomOffset, pick(options.min, min - zoomOffset)];
7347             if (spaceAvailable) { // if space is available, stay within the data range
7348                 minArgs[2] = axis.dataMin;
7349             }
7350             min = arrayMax(minArgs);
7351
7352             maxArgs = [min + minRange, pick(options.max, min + minRange)];
7353             if (spaceAvailable) { // if space is availabe, stay within the data range
7354                 maxArgs[2] = axis.dataMax;
7355             }
7356
7357             max = arrayMin(maxArgs);
7358
7359             // now if the max is adjusted, adjust the min back
7360             if (max - min < minRange) {
7361                 minArgs[0] = max - minRange;
7362                 minArgs[1] = pick(options.min, max - minRange);
7363                 min = arrayMax(minArgs);
7364             }
7365         }
7366         
7367         // Record modified extremes
7368         axis.min = min;
7369         axis.max = max;
7370     },
7371
7372     /**
7373      * Update translation information
7374      */
7375     setAxisTranslation: function (saveOld) {
7376         var axis = this,
7377             range = axis.max - axis.min,
7378             pointRange = 0,
7379             closestPointRange,
7380             minPointOffset = 0,
7381             pointRangePadding = 0,
7382             linkedParent = axis.linkedParent,
7383             ordinalCorrection,
7384             transA = axis.transA;
7385
7386         // adjust translation for padding
7387         if (axis.isXAxis) {
7388             if (linkedParent) {
7389                 minPointOffset = linkedParent.minPointOffset;
7390                 pointRangePadding = linkedParent.pointRangePadding;
7391                 
7392             } else {
7393                 each(axis.series, function (series) {
7394                     var seriesPointRange = series.pointRange,
7395                         pointPlacement = series.options.pointPlacement,
7396                         seriesClosestPointRange = series.closestPointRange;
7397
7398                     if (seriesPointRange > range) { // #1446
7399                         seriesPointRange = 0;
7400                     }
7401                     pointRange = mathMax(pointRange, seriesPointRange);
7402                     
7403                     // minPointOffset is the value padding to the left of the axis in order to make
7404                     // room for points with a pointRange, typically columns. When the pointPlacement option
7405                     // is 'between' or 'on', this padding does not apply.
7406                     minPointOffset = mathMax(
7407                         minPointOffset, 
7408                         isString(pointPlacement) ? 0 : seriesPointRange / 2
7409                     );
7410                     
7411                     // Determine the total padding needed to the length of the axis to make room for the 
7412                     // pointRange. If the series' pointPlacement is 'on', no padding is added.
7413                     pointRangePadding = mathMax(
7414                         pointRangePadding,
7415                         pointPlacement === 'on' ? 0 : seriesPointRange
7416                     );
7417
7418                     // Set the closestPointRange
7419                     if (!series.noSharedTooltip && defined(seriesClosestPointRange)) {
7420                         closestPointRange = defined(closestPointRange) ?
7421                             mathMin(closestPointRange, seriesClosestPointRange) :
7422                             seriesClosestPointRange;
7423                     }
7424                 });
7425             }
7426             
7427             // Record minPointOffset and pointRangePadding
7428             ordinalCorrection = axis.ordinalSlope && closestPointRange ? axis.ordinalSlope / closestPointRange : 1; // #988, #1853
7429             axis.minPointOffset = minPointOffset = minPointOffset * ordinalCorrection;
7430             axis.pointRangePadding = pointRangePadding = pointRangePadding * ordinalCorrection;
7431
7432             // pointRange means the width reserved for each point, like in a column chart
7433             axis.pointRange = mathMin(pointRange, range);
7434
7435             // closestPointRange means the closest distance between points. In columns
7436             // it is mostly equal to pointRange, but in lines pointRange is 0 while closestPointRange
7437             // is some other value
7438             axis.closestPointRange = closestPointRange;
7439         }
7440
7441         // Secondary values
7442         if (saveOld) {
7443             axis.oldTransA = transA;
7444         }
7445         axis.translationSlope = axis.transA = transA = axis.len / ((range + pointRangePadding) || 1);
7446         axis.transB = axis.horiz ? axis.left : axis.bottom; // translation addend
7447         axis.minPixelPadding = transA * minPointOffset;
7448     },
7449
7450     /**
7451      * Set the tick positions to round values and optionally extend the extremes
7452      * to the nearest tick
7453      */
7454     setTickPositions: function (secondPass) {
7455         var axis = this,
7456             chart = axis.chart,
7457             options = axis.options,
7458             isLog = axis.isLog,
7459             isDatetimeAxis = axis.isDatetimeAxis,
7460             isXAxis = axis.isXAxis,
7461             isLinked = axis.isLinked,
7462             tickPositioner = axis.options.tickPositioner,
7463             maxPadding = options.maxPadding,
7464             minPadding = options.minPadding,
7465             length,
7466             linkedParentExtremes,
7467             tickIntervalOption = options.tickInterval,
7468             minTickIntervalOption = options.minTickInterval,
7469             tickPixelIntervalOption = options.tickPixelInterval,
7470             tickPositions,
7471             keepTwoTicksOnly,
7472             categories = axis.categories;
7473
7474         // linked axis gets the extremes from the parent axis
7475         if (isLinked) {
7476             axis.linkedParent = chart[isXAxis ? 'xAxis' : 'yAxis'][options.linkedTo];
7477             linkedParentExtremes = axis.linkedParent.getExtremes();
7478             axis.min = pick(linkedParentExtremes.min, linkedParentExtremes.dataMin);
7479             axis.max = pick(linkedParentExtremes.max, linkedParentExtremes.dataMax);
7480             if (options.type !== axis.linkedParent.options.type) {
7481                 error(11, 1); // Can't link axes of different type
7482             }
7483         } else { // initial min and max from the extreme data values
7484             axis.min = pick(axis.userMin, options.min, axis.dataMin);
7485             axis.max = pick(axis.userMax, options.max, axis.dataMax);
7486         }
7487
7488         if (isLog) {
7489             if (!secondPass && mathMin(axis.min, pick(axis.dataMin, axis.min)) <= 0) { // #978
7490                 error(10, 1); // Can't plot negative values on log axis
7491             }
7492             axis.min = correctFloat(log2lin(axis.min)); // correctFloat cures #934
7493             axis.max = correctFloat(log2lin(axis.max));
7494         }
7495
7496         // handle zoomed range
7497         if (axis.range) {
7498             axis.userMin = axis.min = mathMax(axis.min, axis.max - axis.range); // #618
7499             axis.userMax = axis.max;
7500             if (secondPass) {
7501                 axis.range = null;  // don't use it when running setExtremes
7502             }
7503         }
7504         
7505         // Hook for adjusting this.min and this.max. Used by bubble series.
7506         if (axis.beforePadding) {
7507             axis.beforePadding();
7508         }
7509
7510         // adjust min and max for the minimum range
7511         axis.adjustForMinRange();
7512         
7513         // Pad the values to get clear of the chart's edges. To avoid tickInterval taking the padding
7514         // into account, we do this after computing tick interval (#1337).
7515         if (!categories && !axis.usePercentage && !isLinked && defined(axis.min) && defined(axis.max)) {
7516             length = axis.max - axis.min;
7517             if (length) {
7518                 if (!defined(options.min) && !defined(axis.userMin) && minPadding && (axis.dataMin < 0 || !axis.ignoreMinPadding)) {
7519                     axis.min -= length * minPadding;
7520                 }
7521                 if (!defined(options.max) && !defined(axis.userMax)  && maxPadding && (axis.dataMax > 0 || !axis.ignoreMaxPadding)) {
7522                     axis.max += length * maxPadding;
7523                 }
7524             }
7525         }
7526
7527         // get tickInterval
7528         if (axis.min === axis.max || axis.min === undefined || axis.max === undefined) {
7529             axis.tickInterval = 1;
7530         } else if (isLinked && !tickIntervalOption &&
7531                 tickPixelIntervalOption === axis.linkedParent.options.tickPixelInterval) {
7532             axis.tickInterval = axis.linkedParent.tickInterval;
7533         } else {
7534             axis.tickInterval = pick(
7535                 tickIntervalOption,
7536                 categories ? // for categoried axis, 1 is default, for linear axis use tickPix
7537                     1 :
7538                     // don't let it be more than the data range
7539                     (axis.max - axis.min) * tickPixelIntervalOption / mathMax(axis.len, tickPixelIntervalOption)
7540             );
7541             // For squished axes, set only two ticks
7542             if (!defined(tickIntervalOption) && axis.len < tickPixelIntervalOption && !this.isRadial) {
7543                 keepTwoTicksOnly = true;
7544                 axis.tickInterval /= 4; // tick extremes closer to the real values
7545             }
7546         }
7547
7548         // Now we're finished detecting min and max, crop and group series data. This
7549         // is in turn needed in order to find tick positions in ordinal axes. 
7550         if (isXAxis && !secondPass) {
7551             each(axis.series, function (series) {
7552                 series.processData(axis.min !== axis.oldMin || axis.max !== axis.oldMax);
7553             });
7554         }
7555
7556         // set the translation factor used in translate function
7557         axis.setAxisTranslation(true);
7558
7559         // hook for ordinal axes and radial axes
7560         if (axis.beforeSetTickPositions) {
7561             axis.beforeSetTickPositions();
7562         }
7563         
7564         // hook for extensions, used in Highstock ordinal axes
7565         if (axis.postProcessTickInterval) {
7566             axis.tickInterval = axis.postProcessTickInterval(axis.tickInterval);
7567         }
7568
7569         // In column-like charts, don't cramp in more ticks than there are points (#1943)
7570         if (axis.pointRange) {
7571             axis.tickInterval = mathMax(axis.pointRange, axis.tickInterval);
7572         }
7573         
7574         // Before normalizing the tick interval, handle minimum tick interval. This applies only if tickInterval is not defined.
7575         if (!tickIntervalOption && axis.tickInterval < minTickIntervalOption) {
7576             axis.tickInterval = minTickIntervalOption;
7577         }
7578
7579         // for linear axes, get magnitude and normalize the interval
7580         if (!isDatetimeAxis && !isLog) { // linear
7581             if (!tickIntervalOption) {
7582                 axis.tickInterval = normalizeTickInterval(axis.tickInterval, null, getMagnitude(axis.tickInterval), options);
7583             }
7584         }
7585
7586         // get minorTickInterval
7587         axis.minorTickInterval = options.minorTickInterval === 'auto' && axis.tickInterval ?
7588                 axis.tickInterval / 5 : options.minorTickInterval;
7589
7590         // find the tick positions
7591         axis.tickPositions = tickPositions = options.tickPositions ?
7592             [].concat(options.tickPositions) : // Work on a copy (#1565)
7593             (tickPositioner && tickPositioner.apply(axis, [axis.min, axis.max]));
7594         if (!tickPositions) {
7595             
7596             // Too many ticks
7597             if (!axis.ordinalPositions && (axis.max - axis.min) / axis.tickInterval > mathMax(2 * axis.len, 200)) {
7598                 error(19, true);
7599             }
7600             
7601             if (isDatetimeAxis) {
7602                 tickPositions = (axis.getNonLinearTimeTicks || getTimeTicks)(
7603                     normalizeTimeTickInterval(axis.tickInterval, options.units),
7604                     axis.min,
7605                     axis.max,
7606                     options.startOfWeek,
7607                     axis.ordinalPositions,
7608                     axis.closestPointRange,
7609                     true
7610                 );
7611             } else if (isLog) {
7612                 tickPositions = axis.getLogTickPositions(axis.tickInterval, axis.min, axis.max);
7613             } else {
7614                 tickPositions = axis.getLinearTickPositions(axis.tickInterval, axis.min, axis.max);
7615             }
7616             if (keepTwoTicksOnly) {
7617                 tickPositions.splice(1, tickPositions.length - 2);
7618             }
7619
7620             axis.tickPositions = tickPositions;
7621         }
7622
7623         if (!isLinked) {
7624
7625             // reset min/max or remove extremes based on start/end on tick
7626             var roundedMin = tickPositions[0],
7627                 roundedMax = tickPositions[tickPositions.length - 1],
7628                 minPointOffset = axis.minPointOffset || 0,
7629                 singlePad;
7630
7631             if (options.startOnTick) {
7632                 axis.min = roundedMin;
7633             } else if (axis.min - minPointOffset > roundedMin) {
7634                 tickPositions.shift();
7635             }
7636
7637             if (options.endOnTick) {
7638                 axis.max = roundedMax;
7639             } else if (axis.max + minPointOffset < roundedMax) {
7640                 tickPositions.pop();
7641             }
7642             
7643             // When there is only one point, or all points have the same value on this axis, then min
7644             // and max are equal and tickPositions.length is 1. In this case, add some padding
7645             // in order to center the point, but leave it with one tick. #1337.
7646             if (tickPositions.length === 1) {
7647                 singlePad = 0.001; // The lowest possible number to avoid extra padding on columns
7648                 axis.min -= singlePad;
7649                 axis.max += singlePad;
7650             }
7651         }
7652     },
7653     
7654     /**
7655      * Set the max ticks of either the x and y axis collection
7656      */
7657     setMaxTicks: function () {
7658         
7659         var chart = this.chart,
7660             maxTicks = chart.maxTicks || {},
7661             tickPositions = this.tickPositions,
7662             key = this._maxTicksKey = [this.xOrY, this.pos, this.len].join('-');
7663         
7664         if (!this.isLinked && !this.isDatetimeAxis && tickPositions && tickPositions.length > (maxTicks[key] || 0) && this.options.alignTicks !== false) {
7665             maxTicks[key] = tickPositions.length;
7666         }
7667         chart.maxTicks = maxTicks;
7668     },
7669
7670     /**
7671      * When using multiple axes, adjust the number of ticks to match the highest
7672      * number of ticks in that group
7673      */
7674     adjustTickAmount: function () {
7675         var axis = this,
7676             chart = axis.chart,
7677             key = axis._maxTicksKey,
7678             tickPositions = axis.tickPositions,
7679             maxTicks = chart.maxTicks;
7680
7681         if (maxTicks && maxTicks[key] && !axis.isDatetimeAxis && !axis.categories && !axis.isLinked && axis.options.alignTicks !== false) { // only apply to linear scale
7682             var oldTickAmount = axis.tickAmount,
7683                 calculatedTickAmount = tickPositions.length,
7684                 tickAmount;
7685
7686             // set the axis-level tickAmount to use below
7687             axis.tickAmount = tickAmount = maxTicks[key];
7688
7689             if (calculatedTickAmount < tickAmount) {
7690                 while (tickPositions.length < tickAmount) {
7691                     tickPositions.push(correctFloat(
7692                         tickPositions[tickPositions.length - 1] + axis.tickInterval
7693                     ));
7694                 }
7695                 axis.transA *= (calculatedTickAmount - 1) / (tickAmount - 1);
7696                 axis.max = tickPositions[tickPositions.length - 1];
7697
7698             }
7699             if (defined(oldTickAmount) && tickAmount !== oldTickAmount) {
7700                 axis.isDirty = true;
7701             }
7702         }
7703     },
7704
7705     /**
7706      * Set the scale based on data min and max, user set min and max or options
7707      *
7708      */
7709     setScale: function () {
7710         var axis = this,
7711             stacks = axis.stacks,
7712             type,
7713             i,
7714             isDirtyData,
7715             isDirtyAxisLength;
7716
7717         axis.oldMin = axis.min;
7718         axis.oldMax = axis.max;
7719         axis.oldAxisLength = axis.len;
7720
7721         // set the new axisLength
7722         axis.setAxisSize();
7723         //axisLength = horiz ? axisWidth : axisHeight;
7724         isDirtyAxisLength = axis.len !== axis.oldAxisLength;
7725
7726         // is there new data?
7727         each(axis.series, function (series) {
7728             if (series.isDirtyData || series.isDirty ||
7729                     series.xAxis.isDirty) { // when x axis is dirty, we need new data extremes for y as well
7730                 isDirtyData = true;
7731             }
7732         });
7733
7734         // do we really need to go through all this?
7735         if (isDirtyAxisLength || isDirtyData || axis.isLinked || axis.forceRedraw ||
7736             axis.userMin !== axis.oldUserMin || axis.userMax !== axis.oldUserMax) {
7737             
7738             // reset stacks
7739             if (!axis.isXAxis) {
7740                 for (type in stacks) {
7741                     delete stacks[type];
7742                 }
7743             }
7744
7745             axis.forceRedraw = false;
7746
7747             // get data extremes if needed
7748             axis.getSeriesExtremes();
7749
7750             // get fixed positions based on tickInterval
7751             axis.setTickPositions();
7752
7753             // record old values to decide whether a rescale is necessary later on (#540)
7754             axis.oldUserMin = axis.userMin;
7755             axis.oldUserMax = axis.userMax;
7756
7757             // Mark as dirty if it is not already set to dirty and extremes have changed. #595.
7758             if (!axis.isDirty) {
7759                 axis.isDirty = isDirtyAxisLength || axis.min !== axis.oldMin || axis.max !== axis.oldMax;
7760             }
7761         } else if (!axis.isXAxis) {
7762             if (axis.oldStacks) {
7763                 stacks = axis.stacks = axis.oldStacks;
7764             }
7765
7766             // reset stacks
7767             for (type in stacks) {
7768                 for (i in stacks[type]) {
7769                     stacks[type][i].cum = stacks[type][i].total;
7770                 }
7771             }
7772         }
7773         
7774         // Set the maximum tick amount
7775         axis.setMaxTicks();
7776     },
7777
7778     /**
7779      * Set the extremes and optionally redraw
7780      * @param {Number} newMin
7781      * @param {Number} newMax
7782      * @param {Boolean} redraw
7783      * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
7784      *    configuration
7785      * @param {Object} eventArguments 
7786      *
7787      */
7788     setExtremes: function (newMin, newMax, redraw, animation, eventArguments) {
7789         var axis = this,
7790             chart = axis.chart;
7791
7792         redraw = pick(redraw, true); // defaults to true
7793
7794         // Extend the arguments with min and max
7795         eventArguments = extend(eventArguments, {
7796             min: newMin,
7797             max: newMax
7798         });
7799
7800         // Fire the event
7801         fireEvent(axis, 'setExtremes', eventArguments, function () { // the default event handler
7802
7803             axis.userMin = newMin;
7804             axis.userMax = newMax;
7805             axis.eventArgs = eventArguments;
7806
7807             // Mark for running afterSetExtremes
7808             axis.isDirtyExtremes = true;
7809
7810             // redraw
7811             if (redraw) {
7812                 chart.redraw(animation);
7813             }
7814         });
7815     },
7816     
7817     /**
7818      * Overridable method for zooming chart. Pulled out in a separate method to allow overriding
7819      * in stock charts.
7820      */
7821     zoom: function (newMin, newMax) {
7822
7823         // Prevent pinch zooming out of range. Check for defined is for #1946.
7824         if (!this.allowZoomOutside) {
7825             if (defined(this.dataMin) && newMin <= this.dataMin) {
7826                 newMin = UNDEFINED;
7827             }
7828             if (defined(this.dataMax) && newMax >= this.dataMax) {
7829                 newMax = UNDEFINED;
7830             }
7831         }
7832
7833         // In full view, displaying the reset zoom button is not required
7834         this.displayBtn = newMin !== UNDEFINED || newMax !== UNDEFINED;
7835         
7836         // Do it
7837         this.setExtremes(
7838             newMin,
7839             newMax,
7840             false, 
7841             UNDEFINED, 
7842             { trigger: 'zoom' }
7843         );
7844         return true;
7845     },
7846     
7847     /**
7848      * Update the axis metrics
7849      */
7850     setAxisSize: function () {
7851         var chart = this.chart,
7852             options = this.options,
7853             offsetLeft = options.offsetLeft || 0,
7854             offsetRight = options.offsetRight || 0,
7855             horiz = this.horiz,
7856             width,
7857             height,
7858             top,
7859             left;
7860
7861         // Expose basic values to use in Series object and navigator
7862         this.left = left = pick(options.left, chart.plotLeft + offsetLeft);
7863         this.top = top = pick(options.top, chart.plotTop);
7864         this.width = width = pick(options.width, chart.plotWidth - offsetLeft + offsetRight);
7865         this.height = height = pick(options.height, chart.plotHeight);
7866         this.bottom = chart.chartHeight - height - top;
7867         this.right = chart.chartWidth - width - left;
7868
7869         // Direction agnostic properties
7870         this.len = mathMax(horiz ? width : height, 0); // mathMax fixes #905
7871         this.pos = horiz ? left : top; // distance from SVG origin
7872     },
7873
7874     /**
7875      * Get the actual axis extremes
7876      */
7877     getExtremes: function () {
7878         var axis = this,
7879             isLog = axis.isLog;
7880
7881         return {
7882             min: isLog ? correctFloat(lin2log(axis.min)) : axis.min,
7883             max: isLog ? correctFloat(lin2log(axis.max)) : axis.max,
7884             dataMin: axis.dataMin,
7885             dataMax: axis.dataMax,
7886             userMin: axis.userMin,
7887             userMax: axis.userMax
7888         };
7889     },
7890
7891     /**
7892      * Get the zero plane either based on zero or on the min or max value.
7893      * Used in bar and area plots
7894      */
7895     getThreshold: function (threshold) {
7896         var axis = this,
7897             isLog = axis.isLog;
7898
7899         var realMin = isLog ? lin2log(axis.min) : axis.min,
7900             realMax = isLog ? lin2log(axis.max) : axis.max;
7901         
7902         if (realMin > threshold || threshold === null) {
7903             threshold = realMin;
7904         } else if (realMax < threshold) {
7905             threshold = realMax;
7906         }
7907
7908         return axis.translate(threshold, 0, 1, 0, 1);
7909     },
7910
7911     addPlotBand: function (options) {
7912         this.addPlotBandOrLine(options, 'plotBands');
7913     },
7914     
7915     addPlotLine: function (options) {
7916         this.addPlotBandOrLine(options, 'plotLines');
7917     },
7918
7919     /**
7920      * Add a plot band or plot line after render time
7921      *
7922      * @param options {Object} The plotBand or plotLine configuration object
7923      */
7924     addPlotBandOrLine: function (options, coll) {
7925         var obj = new PlotLineOrBand(this, options).render(),
7926             userOptions = this.userOptions;
7927
7928         if (obj) { // #2189
7929             // Add it to the user options for exporting and Axis.update
7930             if (coll) {
7931                 userOptions[coll] = userOptions[coll] || [];
7932                 userOptions[coll].push(options); 
7933             }
7934             this.plotLinesAndBands.push(obj); 
7935         }
7936         
7937         return obj;
7938     },
7939
7940     /**
7941      * Compute auto alignment for the axis label based on which side the axis is on 
7942      * and the given rotation for the label
7943      */
7944     autoLabelAlign: function (rotation) {
7945         var ret, 
7946             angle = (pick(rotation, 0) - (this.side * 90) + 720) % 360;
7947
7948         if (angle > 15 && angle < 165) {
7949             ret = 'right';
7950         } else if (angle > 195 && angle < 345) {
7951             ret = 'left';
7952         } else {
7953             ret = 'center';
7954         }
7955         return ret;
7956     },
7957
7958     /**
7959      * Render the tick labels to a preliminary position to get their sizes
7960      */
7961     getOffset: function () {
7962         var axis = this,
7963             chart = axis.chart,
7964             renderer = chart.renderer,
7965             options = axis.options,
7966             tickPositions = axis.tickPositions,
7967             ticks = axis.ticks,
7968             horiz = axis.horiz,
7969             side = axis.side,
7970             invertedSide = chart.inverted ? [1, 0, 3, 2][side] : side,
7971             hasData,
7972             showAxis,
7973             titleOffset = 0,
7974             titleOffsetOption,
7975             titleMargin = 0,
7976             axisTitleOptions = options.title,
7977             labelOptions = options.labels,
7978             labelOffset = 0, // reset
7979             axisOffset = chart.axisOffset,
7980             clipOffset = chart.clipOffset,
7981             directionFactor = [-1, 1, 1, -1][side],
7982             n,
7983             i,
7984             autoStaggerLines = 1,
7985             maxStaggerLines = pick(labelOptions.maxStaggerLines, 5),
7986             sortedPositions,
7987             lastRight,
7988             overlap,
7989             pos,
7990             bBox,
7991             x,
7992             w,
7993             lineNo;
7994             
7995         // For reuse in Axis.render
7996         axis.hasData = hasData = (axis.hasVisibleSeries || (defined(axis.min) && defined(axis.max) && !!tickPositions));
7997         axis.showAxis = showAxis = hasData || pick(options.showEmpty, true);
7998
7999         // Set/reset staggerLines
8000         axis.staggerLines = axis.horiz && labelOptions.staggerLines;
8001         
8002         // Create the axisGroup and gridGroup elements on first iteration
8003         if (!axis.axisGroup) {
8004             axis.gridGroup = renderer.g('grid')
8005                 .attr({ zIndex: options.gridZIndex || 1 })
8006                 .add();
8007             axis.axisGroup = renderer.g('axis')
8008                 .attr({ zIndex: options.zIndex || 2 })
8009                 .add();
8010             axis.labelGroup = renderer.g('axis-labels')
8011                 .attr({ zIndex: labelOptions.zIndex || 7 })
8012                 .add();
8013         }
8014
8015         if (hasData || axis.isLinked) {
8016             
8017             // Set the explicit or automatic label alignment
8018             axis.labelAlign = pick(labelOptions.align || axis.autoLabelAlign(labelOptions.rotation));
8019
8020             each(tickPositions, function (pos) {
8021                 if (!ticks[pos]) {
8022                     ticks[pos] = new Tick(axis, pos);
8023                 } else {
8024                     ticks[pos].addLabel(); // update labels depending on tick interval
8025                 }
8026             });
8027
8028             // Handle automatic stagger lines
8029             if (axis.horiz && !axis.staggerLines && maxStaggerLines && !labelOptions.rotation) {
8030                 sortedPositions = axis.reversed ? [].concat(tickPositions).reverse() : tickPositions;
8031                 while (autoStaggerLines < maxStaggerLines) {
8032                     lastRight = [];
8033                     overlap = false;
8034                     
8035                     for (i = 0; i < sortedPositions.length; i++) {
8036                         pos = sortedPositions[i];
8037                         bBox = ticks[pos].label && ticks[pos].label.getBBox();
8038                         w = bBox ? bBox.width : 0;
8039                         lineNo = i % autoStaggerLines;
8040                         
8041                         if (w) {
8042                             x = axis.translate(pos); // don't handle log
8043                             if (lastRight[lineNo] !== UNDEFINED && x < lastRight[lineNo]) {
8044                                 overlap = true;
8045                             }
8046                             lastRight[lineNo] = x + w;
8047                         }
8048                     }
8049                     if (overlap) {
8050                         autoStaggerLines++;
8051                     } else {
8052                         break;
8053                     }
8054                 }
8055
8056                 if (autoStaggerLines > 1) {
8057                     axis.staggerLines = autoStaggerLines;
8058                 }
8059             }
8060
8061
8062             each(tickPositions, function (pos) {
8063                 // left side must be align: right and right side must have align: left for labels
8064                 if (side === 0 || side === 2 || { 1: 'left', 3: 'right' }[side] === axis.labelAlign) {
8065
8066                     // get the highest offset
8067                     labelOffset = mathMax(
8068                         ticks[pos].getLabelSize(),
8069                         labelOffset
8070                     );
8071                 }
8072
8073             });
8074             if (axis.staggerLines) {
8075                 labelOffset *= axis.staggerLines;
8076                 axis.labelOffset = labelOffset;
8077             }
8078             
8079
8080         } else { // doesn't have data
8081             for (n in ticks) {
8082                 ticks[n].destroy();
8083                 delete ticks[n];
8084             }
8085         }
8086
8087         if (axisTitleOptions && axisTitleOptions.text && axisTitleOptions.enabled !== false) { 
8088             if (!axis.axisTitle) {
8089                 axis.axisTitle = renderer.text(
8090                     axisTitleOptions.text,
8091                     0,
8092                     0,
8093                     axisTitleOptions.useHTML
8094                 )
8095                 .attr({
8096                     zIndex: 7,
8097                     rotation: axisTitleOptions.rotation || 0,
8098                     align:
8099                         axisTitleOptions.textAlign ||
8100                         { low: 'left', middle: 'center', high: 'right' }[axisTitleOptions.align]
8101                 })
8102                 .css(axisTitleOptions.style)
8103                 .add(axis.axisGroup);
8104                 axis.axisTitle.isNew = true;
8105             }
8106
8107             if (showAxis) {
8108                 titleOffset = axis.axisTitle.getBBox()[horiz ? 'height' : 'width'];
8109                 titleMargin = pick(axisTitleOptions.margin, horiz ? 5 : 10);
8110                 titleOffsetOption = axisTitleOptions.offset;
8111             }
8112
8113             // hide or show the title depending on whether showEmpty is set
8114             axis.axisTitle[showAxis ? 'show' : 'hide']();
8115         }
8116         
8117         // handle automatic or user set offset
8118         axis.offset = directionFactor * pick(options.offset, axisOffset[side]);
8119         
8120         axis.axisTitleMargin =
8121             pick(titleOffsetOption,
8122                 labelOffset + titleMargin +
8123                 (side !== 2 && labelOffset && directionFactor * options.labels[horiz ? 'y' : 'x'])
8124             );
8125
8126         axisOffset[side] = mathMax(
8127             axisOffset[side],
8128             axis.axisTitleMargin + titleOffset + directionFactor * axis.offset
8129         );
8130         clipOffset[invertedSide] = mathMax(clipOffset[invertedSide], mathFloor(options.lineWidth / 2) * 2);
8131     },
8132     
8133     /**
8134      * Get the path for the axis line
8135      */
8136     getLinePath: function (lineWidth) {
8137         var chart = this.chart,
8138             opposite = this.opposite,
8139             offset = this.offset,
8140             horiz = this.horiz,
8141             lineLeft = this.left + (opposite ? this.width : 0) + offset,
8142             lineTop = chart.chartHeight - this.bottom - (opposite ? this.height : 0) + offset;
8143             
8144         if (opposite) {
8145             lineWidth *= -1; // crispify the other way - #1480, #1687
8146         }
8147
8148         return chart.renderer.crispLine([
8149                 M,
8150                 horiz ?
8151                     this.left :
8152                     lineLeft,
8153                 horiz ?
8154                     lineTop :
8155                     this.top,
8156                 L,
8157                 horiz ?
8158                     chart.chartWidth - this.right :
8159                     lineLeft,
8160                 horiz ?
8161                     lineTop :
8162                     chart.chartHeight - this.bottom
8163             ], lineWidth);
8164     },
8165     
8166     /**
8167      * Position the title
8168      */
8169     getTitlePosition: function () {
8170         // compute anchor points for each of the title align options
8171         var horiz = this.horiz,
8172             axisLeft = this.left,
8173             axisTop = this.top,
8174             axisLength = this.len,
8175             axisTitleOptions = this.options.title,            
8176             margin = horiz ? axisLeft : axisTop,
8177             opposite = this.opposite,
8178             offset = this.offset,
8179             fontSize = pInt(axisTitleOptions.style.fontSize || 12),
8180             
8181             // the position in the length direction of the axis
8182             alongAxis = {
8183                 low: margin + (horiz ? 0 : axisLength),
8184                 middle: margin + axisLength / 2,
8185                 high: margin + (horiz ? axisLength : 0)
8186             }[axisTitleOptions.align],
8187     
8188             // the position in the perpendicular direction of the axis
8189             offAxis = (horiz ? axisTop + this.height : axisLeft) +
8190                 (horiz ? 1 : -1) * // horizontal axis reverses the margin
8191                 (opposite ? -1 : 1) * // so does opposite axes
8192                 this.axisTitleMargin +
8193                 (this.side === 2 ? fontSize : 0);
8194
8195         return {
8196             x: horiz ?
8197                 alongAxis :
8198                 offAxis + (opposite ? this.width : 0) + offset +
8199                     (axisTitleOptions.x || 0), // x
8200             y: horiz ?
8201                 offAxis - (opposite ? this.height : 0) + offset :
8202                 alongAxis + (axisTitleOptions.y || 0) // y
8203         };
8204     },
8205     
8206     /**
8207      * Render the axis
8208      */
8209     render: function () {
8210         var axis = this,
8211             chart = axis.chart,
8212             renderer = chart.renderer,
8213             options = axis.options,
8214             isLog = axis.isLog,
8215             isLinked = axis.isLinked,
8216             tickPositions = axis.tickPositions,
8217             axisTitle = axis.axisTitle,
8218             stacks = axis.stacks,
8219             ticks = axis.ticks,
8220             minorTicks = axis.minorTicks,
8221             alternateBands = axis.alternateBands,
8222             stackLabelOptions = options.stackLabels,
8223             alternateGridColor = options.alternateGridColor,
8224             tickmarkOffset = axis.tickmarkOffset,
8225             lineWidth = options.lineWidth,
8226             linePath,
8227             hasRendered = chart.hasRendered,
8228             slideInTicks = hasRendered && defined(axis.oldMin) && !isNaN(axis.oldMin),
8229             hasData = axis.hasData,
8230             showAxis = axis.showAxis,
8231             from,
8232             to;
8233
8234         // Mark all elements inActive before we go over and mark the active ones
8235         each([ticks, minorTicks, alternateBands], function (coll) {
8236             var pos;
8237             for (pos in coll) {
8238                 coll[pos].isActive = false;
8239             }
8240         });
8241
8242         // If the series has data draw the ticks. Else only the line and title
8243         if (hasData || isLinked) {
8244
8245             // minor ticks
8246             if (axis.minorTickInterval && !axis.categories) {
8247                 each(axis.getMinorTickPositions(), function (pos) {
8248                     if (!minorTicks[pos]) {
8249                         minorTicks[pos] = new Tick(axis, pos, 'minor');
8250                     }
8251
8252                     // render new ticks in old position
8253                     if (slideInTicks && minorTicks[pos].isNew) {
8254                         minorTicks[pos].render(null, true);
8255                     }
8256
8257                     minorTicks[pos].render(null, false, 1);
8258                 });
8259             }
8260
8261             // Major ticks. Pull out the first item and render it last so that
8262             // we can get the position of the neighbour label. #808.
8263             if (tickPositions.length) { // #1300
8264                 each(tickPositions.slice(1).concat([tickPositions[0]]), function (pos, i) {
8265     
8266                     // Reorganize the indices
8267                     i = (i === tickPositions.length - 1) ? 0 : i + 1;
8268     
8269                     // linked axes need an extra check to find out if
8270                     if (!isLinked || (pos >= axis.min && pos <= axis.max)) {
8271     
8272                         if (!ticks[pos]) {
8273                             ticks[pos] = new Tick(axis, pos);
8274                         }
8275     
8276                         // render new ticks in old position
8277                         if (slideInTicks && ticks[pos].isNew) {
8278                             ticks[pos].render(i, true);
8279                         }
8280     
8281                         ticks[pos].render(i, false, 1);
8282                     }
8283     
8284                 });
8285                 // In a categorized axis, the tick marks are displayed between labels. So
8286                 // we need to add a tick mark and grid line at the left edge of the X axis.
8287                 if (tickmarkOffset && axis.min === 0) {
8288                     if (!ticks[-1]) {
8289                         ticks[-1] = new Tick(axis, -1, null, true);
8290                     }
8291                     ticks[-1].render(-1);
8292                 }
8293                 
8294             }
8295
8296             // alternate grid color
8297             if (alternateGridColor) {
8298                 each(tickPositions, function (pos, i) {
8299                     if (i % 2 === 0 && pos < axis.max) {
8300                         if (!alternateBands[pos]) {
8301                             alternateBands[pos] = new PlotLineOrBand(axis);
8302                         }
8303                         from = pos + tickmarkOffset; // #949
8304                         to = tickPositions[i + 1] !== UNDEFINED ? tickPositions[i + 1] + tickmarkOffset : axis.max;
8305                         alternateBands[pos].options = {
8306                             from: isLog ? lin2log(from) : from,
8307                             to: isLog ? lin2log(to) : to,
8308                             color: alternateGridColor
8309                         };
8310                         alternateBands[pos].render();
8311                         alternateBands[pos].isActive = true;
8312                     }
8313                 });
8314             }
8315
8316             // custom plot lines and bands
8317             if (!axis._addedPlotLB) { // only first time
8318                 each((options.plotLines || []).concat(options.plotBands || []), function (plotLineOptions) {
8319                     axis.addPlotBandOrLine(plotLineOptions);
8320                 });
8321                 axis._addedPlotLB = true;
8322             }
8323
8324         } // end if hasData
8325
8326         // Remove inactive ticks
8327         each([ticks, minorTicks, alternateBands], function (coll) {
8328             var pos, 
8329                 i,
8330                 forDestruction = [],
8331                 delay = globalAnimation ? globalAnimation.duration || 500 : 0,
8332                 destroyInactiveItems = function () {
8333                     i = forDestruction.length;
8334                     while (i--) {
8335                         // When resizing rapidly, the same items may be destroyed in different timeouts,
8336                         // or the may be reactivated
8337                         if (coll[forDestruction[i]] && !coll[forDestruction[i]].isActive) {
8338                             coll[forDestruction[i]].destroy();
8339                             delete coll[forDestruction[i]];
8340                         }
8341                     }
8342                     
8343                 };
8344
8345             for (pos in coll) {
8346
8347                 if (!coll[pos].isActive) {
8348                     // Render to zero opacity
8349                     coll[pos].render(pos, false, 0);
8350                     coll[pos].isActive = false;
8351                     forDestruction.push(pos);
8352                 }
8353             }
8354
8355             // When the objects are finished fading out, destroy them
8356             if (coll === alternateBands || !chart.hasRendered || !delay) {
8357                 destroyInactiveItems();
8358             } else if (delay) {
8359                 setTimeout(destroyInactiveItems, delay);
8360             }
8361         });
8362
8363         // Static items. As the axis group is cleared on subsequent calls
8364         // to render, these items are added outside the group.
8365         // axis line
8366         if (lineWidth) {
8367             linePath = axis.getLinePath(lineWidth);
8368             if (!axis.axisLine) {
8369                 axis.axisLine = renderer.path(linePath)
8370                     .attr({
8371                         stroke: options.lineColor,
8372                         'stroke-width': lineWidth,
8373                         zIndex: 7
8374                     })
8375                     .add(axis.axisGroup);
8376             } else {
8377                 axis.axisLine.animate({ d: linePath });
8378             }
8379
8380             // show or hide the line depending on options.showEmpty
8381             axis.axisLine[showAxis ? 'show' : 'hide']();
8382         }
8383
8384         if (axisTitle && showAxis) {
8385             
8386             axisTitle[axisTitle.isNew ? 'attr' : 'animate'](
8387                 axis.getTitlePosition()
8388             );
8389             axisTitle.isNew = false;
8390         }
8391
8392         // Stacked totals:
8393         if (stackLabelOptions && stackLabelOptions.enabled) {
8394             var stackKey, oneStack, stackCategory,
8395                 stackTotalGroup = axis.stackTotalGroup;
8396
8397             // Create a separate group for the stack total labels
8398             if (!stackTotalGroup) {
8399                 axis.stackTotalGroup = stackTotalGroup =
8400                     renderer.g('stack-labels')
8401                         .attr({
8402                             visibility: VISIBLE,
8403                             zIndex: 6
8404                         })
8405                         .add();
8406             }
8407
8408             // plotLeft/Top will change when y axis gets wider so we need to translate the
8409             // stackTotalGroup at every render call. See bug #506 and #516
8410             stackTotalGroup.translate(chart.plotLeft, chart.plotTop);
8411
8412             // Render each stack total
8413             for (stackKey in stacks) {
8414                 oneStack = stacks[stackKey];
8415                 for (stackCategory in oneStack) {
8416                     oneStack[stackCategory].render(stackTotalGroup);
8417                 }
8418             }
8419         }
8420         // End stacked totals
8421
8422         axis.isDirty = false;
8423     },
8424
8425     /**
8426      * Remove a plot band or plot line from the chart by id
8427      * @param {Object} id
8428      */
8429     removePlotBandOrLine: function (id) {
8430         var plotLinesAndBands = this.plotLinesAndBands,
8431             options = this.options,
8432             userOptions = this.userOptions,
8433             i = plotLinesAndBands.length;
8434         while (i--) {
8435             if (plotLinesAndBands[i].id === id) {
8436                 plotLinesAndBands[i].destroy();
8437             }
8438         }
8439         each([options.plotLines || [], userOptions.plotLines || [], options.plotBands || [], userOptions.plotBands || []], function (arr) {
8440             i = arr.length;
8441             while (i--) {
8442                 if (arr[i].id === id) {
8443                     erase(arr, arr[i]);
8444                 }
8445             }
8446         });
8447
8448     },
8449
8450     /**
8451      * Update the axis title by options
8452      */
8453     setTitle: function (newTitleOptions, redraw) {
8454         this.update({ title: newTitleOptions }, redraw);
8455     },
8456
8457     /**
8458      * Redraw the axis to reflect changes in the data or axis extremes
8459      */
8460     redraw: function () {
8461         var axis = this,
8462             chart = axis.chart,
8463             pointer = chart.pointer;
8464
8465         // hide tooltip and hover states
8466         if (pointer.reset) {
8467             pointer.reset(true);
8468         }
8469
8470         // render the axis
8471         axis.render();
8472
8473         // move plot lines and bands
8474         each(axis.plotLinesAndBands, function (plotLine) {
8475             plotLine.render();
8476         });
8477
8478         // mark associated series as dirty and ready for redraw
8479         each(axis.series, function (series) {
8480             series.isDirty = true;
8481         });
8482
8483     },
8484
8485     /**
8486      * Build the stacks from top down
8487      */
8488     buildStacks: function () {
8489         var series = this.series,
8490             i = series.length;
8491         if (!this.isXAxis) {
8492             while (i--) {
8493                 series[i].setStackedPoints();
8494             }
8495             // Loop up again to compute percent stack
8496             if (this.usePercentage) {
8497                 for (i = 0; i < series.length; i++) {
8498                     series[i].setPercentStacks();
8499                 }
8500             }
8501         }
8502     },
8503
8504     /**
8505      * Set new axis categories and optionally redraw
8506      * @param {Array} categories
8507      * @param {Boolean} redraw
8508      */
8509     setCategories: function (categories, redraw) {
8510         this.update({ categories: categories }, redraw);
8511     },
8512
8513     /**
8514      * Destroys an Axis instance.
8515      */
8516     destroy: function (keepEvents) {
8517         var axis = this,
8518             stacks = axis.stacks,
8519             stackKey,
8520             plotLinesAndBands = axis.plotLinesAndBands,
8521             i;
8522
8523         // Remove the events
8524         if (!keepEvents) {
8525             removeEvent(axis);
8526         }
8527
8528         // Destroy each stack total
8529         for (stackKey in stacks) {
8530             destroyObjectProperties(stacks[stackKey]);
8531
8532             stacks[stackKey] = null;
8533         }
8534
8535         // Destroy collections
8536         each([axis.ticks, axis.minorTicks, axis.alternateBands], function (coll) {
8537             destroyObjectProperties(coll);
8538         });
8539         i = plotLinesAndBands.length;
8540         while (i--) { // #1975
8541             plotLinesAndBands[i].destroy();
8542         }
8543
8544         // Destroy local variables
8545         each(['stackTotalGroup', 'axisLine', 'axisGroup', 'gridGroup', 'labelGroup', 'axisTitle'], function (prop) {
8546             if (axis[prop]) {
8547                 axis[prop] = axis[prop].destroy();
8548             }
8549         });
8550     }
8551
8552     
8553 }; // end Axis
8554
8555 /**
8556  * The tooltip object
8557  * @param {Object} chart The chart instance
8558  * @param {Object} options Tooltip options
8559  */
8560 function Tooltip() {
8561     this.init.apply(this, arguments);
8562 }
8563
8564 Tooltip.prototype = {
8565
8566     init: function (chart, options) {
8567
8568         var borderWidth = options.borderWidth,
8569             style = options.style,
8570             padding = pInt(style.padding);
8571
8572         // Save the chart and options
8573         this.chart = chart;
8574         this.options = options;
8575
8576         // Keep track of the current series
8577         //this.currentSeries = UNDEFINED;
8578
8579         // List of crosshairs
8580         this.crosshairs = [];
8581
8582         // Current values of x and y when animating
8583         this.now = { x: 0, y: 0 };
8584
8585         // The tooltip is initially hidden
8586         this.isHidden = true;
8587
8588
8589         // create the label
8590         this.label = chart.renderer.label('', 0, 0, options.shape, null, null, options.useHTML, null, 'tooltip')
8591             .attr({
8592                 padding: padding,
8593                 fill: options.backgroundColor,
8594                 'stroke-width': borderWidth,
8595                 r: options.borderRadius,
8596                 zIndex: 8
8597             })
8598             .css(style)
8599             .css({ padding: 0 }) // Remove it from VML, the padding is applied as an attribute instead (#1117)
8600             .add()
8601             .attr({ y: -999 }); // #2301
8602
8603         // When using canVG the shadow shows up as a gray circle
8604         // even if the tooltip is hidden.
8605         if (!useCanVG) {
8606             this.label.shadow(options.shadow);
8607         }
8608
8609         // Public property for getting the shared state.
8610         this.shared = options.shared;
8611     },
8612
8613     /**
8614      * Destroy the tooltip and its elements.
8615      */
8616     destroy: function () {
8617         each(this.crosshairs, function (crosshair) {
8618             if (crosshair) {
8619                 crosshair.destroy();
8620             }
8621         });
8622
8623         // Destroy and clear local variables
8624         if (this.label) {
8625             this.label = this.label.destroy();
8626         }
8627         clearTimeout(this.hideTimer);
8628         clearTimeout(this.tooltipTimeout);
8629     },
8630
8631     /**
8632      * Provide a soft movement for the tooltip
8633      *
8634      * @param {Number} x
8635      * @param {Number} y
8636      * @private
8637      */
8638     move: function (x, y, anchorX, anchorY) {
8639         var tooltip = this,
8640             now = tooltip.now,
8641             animate = tooltip.options.animation !== false && !tooltip.isHidden;
8642
8643         // get intermediate values for animation
8644         extend(now, {
8645             x: animate ? (2 * now.x + x) / 3 : x,
8646             y: animate ? (now.y + y) / 2 : y,
8647             anchorX: animate ? (2 * now.anchorX + anchorX) / 3 : anchorX,
8648             anchorY: animate ? (now.anchorY + anchorY) / 2 : anchorY
8649         });
8650
8651         // move to the intermediate value
8652         tooltip.label.attr(now);
8653
8654         
8655         // run on next tick of the mouse tracker
8656         if (animate && (mathAbs(x - now.x) > 1 || mathAbs(y - now.y) > 1)) {
8657         
8658             // never allow two timeouts
8659             clearTimeout(this.tooltipTimeout);
8660             
8661             // set the fixed interval ticking for the smooth tooltip
8662             this.tooltipTimeout = setTimeout(function () {
8663                 // The interval function may still be running during destroy, so check that the chart is really there before calling.
8664                 if (tooltip) {
8665                     tooltip.move(x, y, anchorX, anchorY);
8666                 }
8667             }, 32);
8668             
8669         }
8670     },
8671
8672     /**
8673      * Hide the tooltip
8674      */
8675     hide: function () {
8676         var tooltip = this,
8677             hoverPoints;
8678         
8679         clearTimeout(this.hideTimer); // disallow duplicate timers (#1728, #1766)
8680         if (!this.isHidden) {
8681             hoverPoints = this.chart.hoverPoints;
8682
8683             this.hideTimer = setTimeout(function () {
8684                 tooltip.label.fadeOut();
8685                 tooltip.isHidden = true;
8686             }, pick(this.options.hideDelay, 500));
8687
8688             // hide previous hoverPoints and set new
8689             if (hoverPoints) {
8690                 each(hoverPoints, function (point) {
8691                     point.setState();
8692                 });
8693             }
8694
8695             this.chart.hoverPoints = null;
8696         }
8697     },
8698
8699     /**
8700      * Hide the crosshairs
8701      */
8702     hideCrosshairs: function () {
8703         each(this.crosshairs, function (crosshair) {
8704             if (crosshair) {
8705                 crosshair.hide();
8706             }
8707         });
8708     },
8709     
8710     /** 
8711      * Extendable method to get the anchor position of the tooltip
8712      * from a point or set of points
8713      */
8714     getAnchor: function (points, mouseEvent) {
8715         var ret,
8716             chart = this.chart,
8717             inverted = chart.inverted,
8718             plotTop = chart.plotTop,
8719             plotX = 0,
8720             plotY = 0,
8721             yAxis;
8722         
8723         points = splat(points);
8724         
8725         // Pie uses a special tooltipPos
8726         ret = points[0].tooltipPos;
8727         
8728         // When tooltip follows mouse, relate the position to the mouse
8729         if (this.followPointer && mouseEvent) {
8730             if (mouseEvent.chartX === UNDEFINED) {
8731                 mouseEvent = chart.pointer.normalize(mouseEvent);
8732             }
8733             ret = [
8734                 mouseEvent.chartX - chart.plotLeft,
8735                 mouseEvent.chartY - plotTop
8736             ];
8737         }
8738         // When shared, use the average position
8739         if (!ret) {
8740             each(points, function (point) {
8741                 yAxis = point.series.yAxis;
8742                 plotX += point.plotX;
8743                 plotY += (point.plotLow ? (point.plotLow + point.plotHigh) / 2 : point.plotY) +
8744                     (!inverted && yAxis ? yAxis.top - plotTop : 0); // #1151
8745             });
8746             
8747             plotX /= points.length;
8748             plotY /= points.length;
8749             
8750             ret = [
8751                 inverted ? chart.plotWidth - plotY : plotX,
8752                 this.shared && !inverted && points.length > 1 && mouseEvent ? 
8753                     mouseEvent.chartY - plotTop : // place shared tooltip next to the mouse (#424)
8754                     inverted ? chart.plotHeight - plotX : plotY
8755             ];
8756         }
8757
8758         return map(ret, mathRound);
8759     },
8760     
8761     /**
8762      * Place the tooltip in a chart without spilling over
8763      * and not covering the point it self.
8764      */
8765     getPosition: function (boxWidth, boxHeight, point) {
8766         
8767         // Set up the variables
8768         var chart = this.chart,
8769             plotLeft = chart.plotLeft,
8770             plotTop = chart.plotTop,
8771             plotWidth = chart.plotWidth,
8772             plotHeight = chart.plotHeight,
8773             distance = pick(this.options.distance, 12),
8774             pointX = point.plotX,
8775             pointY = point.plotY,
8776             x = pointX + plotLeft + (chart.inverted ? distance : -boxWidth - distance),
8777             y = pointY - boxHeight + plotTop + 15, // 15 means the point is 15 pixels up from the bottom of the tooltip
8778             alignedRight;
8779     
8780         // It is too far to the left, adjust it
8781         if (x < 7) {
8782             x = plotLeft + mathMax(pointX, 0) + distance;
8783         }
8784     
8785         // Test to see if the tooltip is too far to the right,
8786         // if it is, move it back to be inside and then up to not cover the point.
8787         if ((x + boxWidth) > (plotLeft + plotWidth)) {
8788             x -= (x + boxWidth) - (plotLeft + plotWidth);
8789             y = pointY - boxHeight + plotTop - distance;
8790             alignedRight = true;
8791         }
8792     
8793         // If it is now above the plot area, align it to the top of the plot area
8794         if (y < plotTop + 5) {
8795             y = plotTop + 5;
8796     
8797             // If the tooltip is still covering the point, move it below instead
8798             if (alignedRight && pointY >= y && pointY <= (y + boxHeight)) {
8799                 y = pointY + plotTop + distance; // below
8800             }
8801         } 
8802     
8803         // Now if the tooltip is below the chart, move it up. It's better to cover the
8804         // point than to disappear outside the chart. #834.
8805         if (y + boxHeight > plotTop + plotHeight) {
8806             y = mathMax(plotTop, plotTop + plotHeight - boxHeight - distance); // below
8807         }
8808     
8809         return {x: x, y: y};
8810     },
8811
8812     /**
8813      * In case no user defined formatter is given, this will be used. Note that the context
8814      * here is an object holding point, series, x, y etc.
8815      */
8816     defaultFormatter: function (tooltip) {
8817         var items = this.points || splat(this),
8818             series = items[0].series,
8819             s;
8820
8821         // build the header
8822         s = [series.tooltipHeaderFormatter(items[0])];
8823
8824         // build the values
8825         each(items, function (item) {
8826             series = item.series;
8827             s.push((series.tooltipFormatter && series.tooltipFormatter(item)) ||
8828                 item.point.tooltipFormatter(series.tooltipOptions.pointFormat));
8829         });
8830
8831         // footer
8832         s.push(tooltip.options.footerFormat || '');
8833
8834         return s.join('');
8835     },
8836
8837     /**
8838      * Refresh the tooltip's text and position.
8839      * @param {Object} point
8840      */
8841     refresh: function (point, mouseEvent) {
8842         var tooltip = this,
8843             chart = tooltip.chart,
8844             label = tooltip.label,
8845             options = tooltip.options,
8846             x,
8847             y,
8848             anchor,
8849             textConfig = {},
8850             text,
8851             pointConfig = [],
8852             formatter = options.formatter || tooltip.defaultFormatter,
8853             hoverPoints = chart.hoverPoints,
8854             borderColor,
8855             crosshairsOptions = options.crosshairs,
8856             shared = tooltip.shared,
8857             currentSeries;
8858             
8859         clearTimeout(this.hideTimer);
8860         
8861         // get the reference point coordinates (pie charts use tooltipPos)
8862         tooltip.followPointer = splat(point)[0].series.tooltipOptions.followPointer;
8863         anchor = tooltip.getAnchor(point, mouseEvent);
8864         x = anchor[0];
8865         y = anchor[1];
8866
8867         // shared tooltip, array is sent over
8868         if (shared && !(point.series && point.series.noSharedTooltip)) {
8869             
8870             // hide previous hoverPoints and set new
8871             
8872             chart.hoverPoints = point;
8873             if (hoverPoints) {
8874                 each(hoverPoints, function (point) {
8875                     point.setState();
8876                 });
8877             }
8878
8879             each(point, function (item) {
8880                 item.setState(HOVER_STATE);
8881
8882                 pointConfig.push(item.getLabelConfig());
8883             });
8884
8885             textConfig = {
8886                 x: point[0].category,
8887                 y: point[0].y
8888             };
8889             textConfig.points = pointConfig;
8890             point = point[0];
8891
8892         // single point tooltip
8893         } else {
8894             textConfig = point.getLabelConfig();
8895         }
8896         text = formatter.call(textConfig, tooltip);
8897
8898         // register the current series
8899         currentSeries = point.series;
8900
8901         // update the inner HTML
8902         if (text === false) {
8903             this.hide();
8904         } else {
8905
8906             // show it
8907             if (tooltip.isHidden) {
8908                 stop(label);
8909                 label.attr('opacity', 1).show();
8910             }
8911
8912             // update text
8913             label.attr({
8914                 text: text
8915             });
8916
8917             // set the stroke color of the box
8918             borderColor = options.borderColor || point.color || currentSeries.color || '#606060';
8919             label.attr({
8920                 stroke: borderColor
8921             });
8922             
8923             tooltip.updatePosition({ plotX: x, plotY: y });
8924         
8925             this.isHidden = false;
8926         }
8927
8928         // crosshairs
8929         if (crosshairsOptions) {
8930             crosshairsOptions = splat(crosshairsOptions); // [x, y]
8931
8932             var path,
8933                 i = crosshairsOptions.length,
8934                 attribs,
8935                 axis,
8936                 val,
8937                 series;
8938
8939             while (i--) {
8940                 series = point.series;
8941                 axis = series[i ? 'yAxis' : 'xAxis'];
8942                 if (crosshairsOptions[i] && axis) {
8943                     val = i ? pick(point.stackY, point.y) : point.x; // #814
8944                     if (axis.isLog) { // #1671
8945                         val = log2lin(val);
8946                     }
8947                     if (i === 1 && series.modifyValue) { // #1205, #2316
8948                         val = series.modifyValue(val);
8949                     }
8950
8951                     path = axis.getPlotLinePath(
8952                         val,
8953                         1
8954                     );
8955
8956                     if (tooltip.crosshairs[i]) {
8957                         tooltip.crosshairs[i].attr({ d: path, visibility: VISIBLE });
8958                     } else {
8959                         attribs = {
8960                             'stroke-width': crosshairsOptions[i].width || 1,
8961                             stroke: crosshairsOptions[i].color || '#C0C0C0',
8962                             zIndex: crosshairsOptions[i].zIndex || 2
8963                         };
8964                         if (crosshairsOptions[i].dashStyle) {
8965                             attribs.dashstyle = crosshairsOptions[i].dashStyle;
8966                         }
8967                         tooltip.crosshairs[i] = chart.renderer.path(path)
8968                             .attr(attribs)
8969                             .add();
8970                     }
8971                 }
8972             }
8973         }
8974         fireEvent(chart, 'tooltipRefresh', {
8975                 text: text,
8976                 x: x + chart.plotLeft,
8977                 y: y + chart.plotTop,
8978                 borderColor: borderColor
8979             });
8980     },
8981     
8982     /**
8983      * Find the new position and perform the move
8984      */
8985     updatePosition: function (point) {
8986         var chart = this.chart,
8987             label = this.label, 
8988             pos = (this.options.positioner || this.getPosition).call(
8989                 this,
8990                 label.width,
8991                 label.height,
8992                 point
8993             );
8994
8995         // do the move
8996         this.move(
8997             mathRound(pos.x), 
8998             mathRound(pos.y), 
8999             point.plotX + chart.plotLeft, 
9000             point.plotY + chart.plotTop
9001         );
9002     }
9003 };
9004 /**
9005  * The mouse tracker object. All methods starting with "on" are primary DOM event handlers. 
9006  * Subsequent methods should be named differently from what they are doing.
9007  * @param {Object} chart The Chart instance
9008  * @param {Object} options The root options object
9009  */
9010 function Pointer(chart, options) {
9011     this.init(chart, options);
9012 }
9013
9014 Pointer.prototype = {
9015     /**
9016      * Initialize Pointer
9017      */
9018     init: function (chart, options) {
9019         
9020         var chartOptions = options.chart,
9021             chartEvents = chartOptions.events,
9022             zoomType = useCanVG ? '' : chartOptions.zoomType,
9023             inverted = chart.inverted,
9024             zoomX,
9025             zoomY;
9026
9027         // Store references
9028         this.options = options;
9029         this.chart = chart;
9030         
9031         // Zoom status
9032         this.zoomX = zoomX = /x/.test(zoomType);
9033         this.zoomY = zoomY = /y/.test(zoomType);
9034         this.zoomHor = (zoomX && !inverted) || (zoomY && inverted);
9035         this.zoomVert = (zoomY && !inverted) || (zoomX && inverted);
9036
9037         // Do we need to handle click on a touch device?
9038         this.runChartClick = chartEvents && !!chartEvents.click;
9039
9040         this.pinchDown = [];
9041         this.lastValidTouch = {};
9042
9043         if (options.tooltip.enabled) {
9044             chart.tooltip = new Tooltip(chart, options.tooltip);
9045         }
9046
9047         this.setDOMEvents();
9048     }, 
9049
9050     /**
9051      * Add crossbrowser support for chartX and chartY
9052      * @param {Object} e The event object in standard browsers
9053      */
9054     normalize: function (e, chartPosition) {
9055         var chartX,
9056             chartY,
9057             ePos;
9058
9059         // common IE normalizing
9060         e = e || win.event;
9061         if (!e.target) {
9062             e.target = e.srcElement;
9063         }
9064
9065         // Framework specific normalizing (#1165)
9066         e = washMouseEvent(e);
9067         
9068         // iOS
9069         ePos = e.touches ? e.touches.item(0) : e;
9070
9071         // Get mouse position
9072         if (!chartPosition) {
9073             this.chartPosition = chartPosition = offset(this.chart.container);
9074         }
9075
9076         // chartX and chartY
9077         if (ePos.pageX === UNDEFINED) { // IE < 9. #886.
9078             chartX = mathMax(e.x, e.clientX - chartPosition.left); // #2005, #2129: the second case is 
9079                 // for IE10 quirks mode within framesets
9080             chartY = e.y;
9081         } else {
9082             chartX = ePos.pageX - chartPosition.left;
9083             chartY = ePos.pageY - chartPosition.top;
9084         }
9085
9086         return extend(e, {
9087             chartX: mathRound(chartX),
9088             chartY: mathRound(chartY)
9089         });
9090     },
9091
9092     /**
9093      * Get the click position in terms of axis values.
9094      *
9095      * @param {Object} e A pointer event
9096      */
9097     getCoordinates: function (e) {
9098         var coordinates = {
9099                 xAxis: [],
9100                 yAxis: []
9101             };
9102
9103         each(this.chart.axes, function (axis) {
9104             coordinates[axis.isXAxis ? 'xAxis' : 'yAxis'].push({
9105                 axis: axis,
9106                 value: axis.toValue(e[axis.horiz ? 'chartX' : 'chartY'])
9107             });
9108         });
9109         return coordinates;
9110     },
9111     
9112     /**
9113      * Return the index in the tooltipPoints array, corresponding to pixel position in 
9114      * the plot area.
9115      */
9116     getIndex: function (e) {
9117         var chart = this.chart;
9118         return chart.inverted ? 
9119             chart.plotHeight + chart.plotTop - e.chartY : 
9120             e.chartX - chart.plotLeft;
9121     },
9122
9123     /**
9124      * With line type charts with a single tracker, get the point closest to the mouse.
9125      * Run Point.onMouseOver and display tooltip for the point or points.
9126      */
9127     runPointActions: function (e) {
9128         var pointer = this,
9129             chart = pointer.chart,
9130             series = chart.series,
9131             tooltip = chart.tooltip,
9132             point,
9133             points,
9134             hoverPoint = chart.hoverPoint,
9135             hoverSeries = chart.hoverSeries,
9136             i,
9137             j,
9138             distance = chart.chartWidth,
9139             index = pointer.getIndex(e),
9140             anchor;
9141
9142         // shared tooltip
9143         if (tooltip && pointer.options.tooltip.shared && !(hoverSeries && hoverSeries.noSharedTooltip)) {
9144             points = [];
9145
9146             // loop over all series and find the ones with points closest to the mouse
9147             i = series.length;
9148             for (j = 0; j < i; j++) {
9149                 if (series[j].visible &&
9150                         series[j].options.enableMouseTracking !== false &&
9151                         !series[j].noSharedTooltip && series[j].tooltipPoints.length) {
9152                     point = series[j].tooltipPoints[index];
9153                     if (point && point.series) { // not a dummy point, #1544
9154                         point._dist = mathAbs(index - point.clientX);
9155                         distance = mathMin(distance, point._dist);
9156                         points.push(point);
9157                     }
9158                 }
9159             }
9160             // remove furthest points
9161             i = points.length;
9162             while (i--) {
9163                 if (points[i]._dist > distance) {
9164                     points.splice(i, 1);
9165                 }
9166             }
9167             // refresh the tooltip if necessary
9168             if (points.length && (points[0].clientX !== pointer.hoverX)) {
9169                 tooltip.refresh(points, e);
9170                 pointer.hoverX = points[0].clientX;
9171             }
9172         }
9173
9174         // separate tooltip and general mouse events
9175         if (hoverSeries && hoverSeries.tracker) { // only use for line-type series with common tracker
9176
9177             // get the point
9178             point = hoverSeries.tooltipPoints[index];
9179
9180             // a new point is hovered, refresh the tooltip
9181             if (point && point !== hoverPoint) {
9182
9183                 // trigger the events
9184                 point.onMouseOver(e);
9185
9186             }
9187             
9188         } else if (tooltip && tooltip.followPointer && !tooltip.isHidden) {
9189             anchor = tooltip.getAnchor([{}], e);
9190             tooltip.updatePosition({ plotX: anchor[0], plotY: anchor[1] });
9191         }
9192     },
9193
9194
9195
9196     /**
9197      * Reset the tracking by hiding the tooltip, the hover series state and the hover point
9198      * 
9199      * @param allowMove {Boolean} Instead of destroying the tooltip altogether, allow moving it if possible
9200      */
9201     reset: function (allowMove) {
9202         var pointer = this,
9203             chart = pointer.chart,
9204             hoverSeries = chart.hoverSeries,
9205             hoverPoint = chart.hoverPoint,
9206             tooltip = chart.tooltip,
9207             tooltipPoints = tooltip && tooltip.shared ? chart.hoverPoints : hoverPoint;
9208             
9209         // Narrow in allowMove
9210         allowMove = allowMove && tooltip && tooltipPoints;
9211             
9212         // Check if the points have moved outside the plot area, #1003
9213         if (allowMove && splat(tooltipPoints)[0].plotX === UNDEFINED) {
9214             allowMove = false;
9215         }    
9216
9217         // Just move the tooltip, #349
9218         if (allowMove) {
9219             tooltip.refresh(tooltipPoints);
9220
9221         // Full reset
9222         } else {
9223
9224             if (hoverPoint) {
9225                 hoverPoint.onMouseOut();
9226             }
9227
9228             if (hoverSeries) {
9229                 hoverSeries.onMouseOut();
9230             }
9231
9232             if (tooltip) {
9233                 tooltip.hide();
9234                 tooltip.hideCrosshairs();
9235             }
9236
9237             pointer.hoverX = null;
9238
9239         }
9240     },
9241
9242     /**
9243      * Scale series groups to a certain scale and translation
9244      */
9245     scaleGroups: function (attribs, clip) {
9246
9247         var chart = this.chart,
9248             seriesAttribs;
9249
9250         // Scale each series
9251         each(chart.series, function (series) {
9252             seriesAttribs = attribs || series.getPlotBox(); // #1701
9253             if (series.xAxis && series.xAxis.zoomEnabled) {
9254                 series.group.attr(seriesAttribs);
9255                 if (series.markerGroup) {
9256                     series.markerGroup.attr(seriesAttribs);
9257                     series.markerGroup.clip(clip ? chart.clipRect : null);
9258                 }
9259                 if (series.dataLabelsGroup) {
9260                     series.dataLabelsGroup.attr(seriesAttribs);
9261                 }
9262             }
9263         });
9264         
9265         // Clip
9266         chart.clipRect.attr(clip || chart.clipBox);
9267     },
9268
9269     /**
9270      * Run translation operations for each direction (horizontal and vertical) independently
9271      */
9272     pinchTranslateDirection: function (horiz, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch) {
9273         var chart = this.chart,
9274             xy = horiz ? 'x' : 'y',
9275             XY = horiz ? 'X' : 'Y',
9276             sChartXY = 'chart' + XY,
9277             wh = horiz ? 'width' : 'height',
9278             plotLeftTop = chart['plot' + (horiz ? 'Left' : 'Top')],
9279             selectionWH,
9280             selectionXY,
9281             clipXY,
9282             scale = 1,
9283             inverted = chart.inverted,
9284             bounds = chart.bounds[horiz ? 'h' : 'v'],
9285             singleTouch = pinchDown.length === 1,
9286             touch0Start = pinchDown[0][sChartXY],
9287             touch0Now = touches[0][sChartXY],
9288             touch1Start = !singleTouch && pinchDown[1][sChartXY],
9289             touch1Now = !singleTouch && touches[1][sChartXY],
9290             outOfBounds,
9291             transformScale,
9292             scaleKey,
9293             setScale = function () {
9294                 if (!singleTouch && mathAbs(touch0Start - touch1Start) > 20) { // Don't zoom if fingers are too close on this axis
9295                     scale = mathAbs(touch0Now - touch1Now) / mathAbs(touch0Start - touch1Start);    
9296                 }
9297                 
9298                 clipXY = ((plotLeftTop - touch0Now) / scale) + touch0Start;
9299                 selectionWH = chart['plot' + (horiz ? 'Width' : 'Height')] / scale;
9300             };
9301
9302         // Set the scale, first pass
9303         setScale();
9304
9305         selectionXY = clipXY; // the clip position (x or y) is altered if out of bounds, the selection position is not
9306
9307         // Out of bounds
9308         if (selectionXY < bounds.min) {
9309             selectionXY = bounds.min;
9310             outOfBounds = true;
9311         } else if (selectionXY + selectionWH > bounds.max) {
9312             selectionXY = bounds.max - selectionWH;
9313             outOfBounds = true;
9314         }
9315         
9316         // Is the chart dragged off its bounds, determined by dataMin and dataMax?
9317         if (outOfBounds) {
9318
9319             // Modify the touchNow position in order to create an elastic drag movement. This indicates
9320             // to the user that the chart is responsive but can't be dragged further.
9321             touch0Now -= 0.8 * (touch0Now - lastValidTouch[xy][0]);
9322             if (!singleTouch) {
9323                 touch1Now -= 0.8 * (touch1Now - lastValidTouch[xy][1]);
9324             }
9325
9326             // Set the scale, second pass to adapt to the modified touchNow positions
9327             setScale();
9328
9329         } else {
9330             lastValidTouch[xy] = [touch0Now, touch1Now];
9331         }
9332
9333         
9334         // Set geometry for clipping, selection and transformation
9335         if (!inverted) { // TODO: implement clipping for inverted charts
9336             clip[xy] = clipXY - plotLeftTop;
9337             clip[wh] = selectionWH;
9338         }
9339         scaleKey = inverted ? (horiz ? 'scaleY' : 'scaleX') : 'scale' + XY;
9340         transformScale = inverted ? 1 / scale : scale;
9341
9342         selectionMarker[wh] = selectionWH;
9343         selectionMarker[xy] = selectionXY;
9344         transform[scaleKey] = scale;
9345         transform['translate' + XY] = (transformScale * plotLeftTop) + (touch0Now - (transformScale * touch0Start));
9346     },
9347     
9348     /**
9349      * Handle touch events with two touches
9350      */
9351     pinch: function (e) {
9352
9353         var self = this,
9354             chart = self.chart,
9355             pinchDown = self.pinchDown,
9356             followTouchMove = chart.tooltip && chart.tooltip.options.followTouchMove,
9357             touches = e.touches,
9358             touchesLength = touches.length,
9359             lastValidTouch = self.lastValidTouch,
9360             zoomHor = self.zoomHor || self.pinchHor,
9361             zoomVert = self.zoomVert || self.pinchVert,
9362             hasZoom = zoomHor || zoomVert,
9363             selectionMarker = self.selectionMarker,
9364             transform = {},
9365             fireClickEvent = touchesLength === 1 && ((self.inClass(e.target, PREFIX + 'tracker') && 
9366                 chart.runTrackerClick) || chart.runChartClick),
9367             clip = {};
9368
9369         // On touch devices, only proceed to trigger click if a handler is defined
9370         if ((hasZoom || followTouchMove) && !fireClickEvent) {
9371             e.preventDefault();
9372         }
9373         
9374         // Normalize each touch
9375         map(touches, function (e) {
9376             return self.normalize(e);
9377         });
9378             
9379         // Register the touch start position
9380         if (e.type === 'touchstart') {
9381             each(touches, function (e, i) {
9382                 pinchDown[i] = { chartX: e.chartX, chartY: e.chartY };
9383             });
9384             lastValidTouch.x = [pinchDown[0].chartX, pinchDown[1] && pinchDown[1].chartX];
9385             lastValidTouch.y = [pinchDown[0].chartY, pinchDown[1] && pinchDown[1].chartY];
9386
9387             // Identify the data bounds in pixels
9388             each(chart.axes, function (axis) {
9389                 if (axis.zoomEnabled) {
9390                     var bounds = chart.bounds[axis.horiz ? 'h' : 'v'],
9391                         minPixelPadding = axis.minPixelPadding,
9392                         min = axis.toPixels(axis.dataMin),
9393                         max = axis.toPixels(axis.dataMax),
9394                         absMin = mathMin(min, max),
9395                         absMax = mathMax(min, max);
9396
9397                     // Store the bounds for use in the touchmove handler
9398                     bounds.min = mathMin(axis.pos, absMin - minPixelPadding);
9399                     bounds.max = mathMax(axis.pos + axis.len, absMax + minPixelPadding);
9400                 }
9401             });
9402         
9403         // Event type is touchmove, handle panning and pinching
9404         } else if (pinchDown.length) { // can be 0 when releasing, if touchend fires first
9405             
9406
9407             // Set the marker
9408             if (!selectionMarker) {
9409                 self.selectionMarker = selectionMarker = extend({
9410                     destroy: noop
9411                 }, chart.plotBox);
9412             }
9413
9414             
9415
9416             if (zoomHor) {
9417                 self.pinchTranslateDirection(true, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch);
9418             }
9419             if (zoomVert) {
9420                 self.pinchTranslateDirection(false, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch);
9421             }
9422
9423             self.hasPinched = hasZoom;
9424
9425             // Scale and translate the groups to provide visual feedback during pinching
9426             self.scaleGroups(transform, clip);
9427             
9428             // Optionally move the tooltip on touchmove
9429             if (!hasZoom && followTouchMove && touchesLength === 1) {
9430                 this.runPointActions(self.normalize(e));
9431             }
9432         }
9433     },
9434
9435     /**
9436      * Start a drag operation
9437      */
9438     dragStart: function (e) {
9439         var chart = this.chart;
9440
9441         // Record the start position
9442         chart.mouseIsDown = e.type;
9443         chart.cancelClick = false;
9444         chart.mouseDownX = this.mouseDownX = e.chartX;
9445         chart.mouseDownY = this.mouseDownY = e.chartY;
9446     },
9447
9448     /**
9449      * Perform a drag operation in response to a mousemove event while the mouse is down
9450      */
9451     drag: function (e) {
9452
9453         var chart = this.chart,
9454             chartOptions = chart.options.chart,
9455             chartX = e.chartX,
9456             chartY = e.chartY,
9457             zoomHor = this.zoomHor,
9458             zoomVert = this.zoomVert,
9459             plotLeft = chart.plotLeft,
9460             plotTop = chart.plotTop,
9461             plotWidth = chart.plotWidth,
9462             plotHeight = chart.plotHeight,
9463             clickedInside,
9464             size,
9465             mouseDownX = this.mouseDownX,
9466             mouseDownY = this.mouseDownY;
9467
9468         // If the mouse is outside the plot area, adjust to cooordinates
9469         // inside to prevent the selection marker from going outside
9470         if (chartX < plotLeft) {
9471             chartX = plotLeft;
9472         } else if (chartX > plotLeft + plotWidth) {
9473             chartX = plotLeft + plotWidth;
9474         }
9475
9476         if (chartY < plotTop) {
9477             chartY = plotTop;
9478         } else if (chartY > plotTop + plotHeight) {
9479             chartY = plotTop + plotHeight;
9480         }
9481         
9482         // determine if the mouse has moved more than 10px
9483         this.hasDragged = Math.sqrt(
9484             Math.pow(mouseDownX - chartX, 2) +
9485             Math.pow(mouseDownY - chartY, 2)
9486         );
9487         if (this.hasDragged > 10) {
9488             clickedInside = chart.isInsidePlot(mouseDownX - plotLeft, mouseDownY - plotTop);
9489
9490             // make a selection
9491             if (chart.hasCartesianSeries && (this.zoomX || this.zoomY) && clickedInside) {
9492                 if (!this.selectionMarker) {
9493                     this.selectionMarker = chart.renderer.rect(
9494                         plotLeft,
9495                         plotTop,
9496                         zoomHor ? 1 : plotWidth,
9497                         zoomVert ? 1 : plotHeight,
9498                         0
9499                     )
9500                     .attr({
9501                         fill: chartOptions.selectionMarkerFill || 'rgba(69,114,167,0.25)',
9502                         zIndex: 7
9503                     })
9504                     .add();
9505                 }
9506             }
9507
9508             // adjust the width of the selection marker
9509             if (this.selectionMarker && zoomHor) {
9510                 size = chartX - mouseDownX;
9511                 this.selectionMarker.attr({
9512                     width: mathAbs(size),
9513                     x: (size > 0 ? 0 : size) + mouseDownX
9514                 });
9515             }
9516             // adjust the height of the selection marker
9517             if (this.selectionMarker && zoomVert) {
9518                 size = chartY - mouseDownY;
9519                 this.selectionMarker.attr({
9520                     height: mathAbs(size),
9521                     y: (size > 0 ? 0 : size) + mouseDownY
9522                 });
9523             }
9524
9525             // panning
9526             if (clickedInside && !this.selectionMarker && chartOptions.panning) {
9527                 chart.pan(e, chartOptions.panning);
9528             }
9529         }
9530     },
9531
9532     /**
9533      * On mouse up or touch end across the entire document, drop the selection.
9534      */
9535     drop: function (e) {
9536         var chart = this.chart,
9537             hasPinched = this.hasPinched;
9538
9539         if (this.selectionMarker) {
9540             var selectionData = {
9541                     xAxis: [],
9542                     yAxis: [],
9543                     originalEvent: e.originalEvent || e
9544                 },
9545                 selectionBox = this.selectionMarker,
9546                 selectionLeft = selectionBox.x,
9547                 selectionTop = selectionBox.y,
9548                 runZoom;
9549             // a selection has been made
9550             if (this.hasDragged || hasPinched) {
9551
9552                 // record each axis' min and max
9553                 each(chart.axes, function (axis) {
9554                     if (axis.zoomEnabled) {
9555                         var horiz = axis.horiz,
9556                             selectionMin = axis.toValue((horiz ? selectionLeft : selectionTop)),
9557                             selectionMax = axis.toValue((horiz ? selectionLeft + selectionBox.width : selectionTop + selectionBox.height));
9558
9559                         if (!isNaN(selectionMin) && !isNaN(selectionMax)) { // #859
9560                             selectionData[axis.xOrY + 'Axis'].push({
9561                                 axis: axis,
9562                                 min: mathMin(selectionMin, selectionMax), // for reversed axes,
9563                                 max: mathMax(selectionMin, selectionMax)
9564                             });
9565                             runZoom = true;
9566                         }
9567                     }
9568                 });
9569                 if (runZoom) {
9570                     fireEvent(chart, 'selection', selectionData, function (args) { 
9571                         chart.zoom(extend(args, hasPinched ? { animation: false } : null)); 
9572                     });
9573                 }
9574
9575             }
9576             this.selectionMarker = this.selectionMarker.destroy();
9577
9578             // Reset scaling preview
9579             if (hasPinched) {
9580                 this.scaleGroups();
9581             }
9582         }
9583
9584         // Reset all
9585         if (chart) { // it may be destroyed on mouse up - #877
9586             css(chart.container, { cursor: chart._cursor });
9587             chart.cancelClick = this.hasDragged > 10; // #370
9588             chart.mouseIsDown = this.hasDragged = this.hasPinched = false;
9589             this.pinchDown = [];
9590         }
9591     },
9592
9593     onContainerMouseDown: function (e) {
9594
9595         e = this.normalize(e);
9596
9597         // issue #295, dragging not always working in Firefox
9598         if (e.preventDefault) {
9599             e.preventDefault();
9600         }
9601         
9602         this.dragStart(e);
9603     },
9604
9605     
9606
9607     onDocumentMouseUp: function (e) {
9608         this.drop(e);
9609     },
9610
9611     /**
9612      * Special handler for mouse move that will hide the tooltip when the mouse leaves the plotarea.
9613      * Issue #149 workaround. The mouseleave event does not always fire. 
9614      */
9615     onDocumentMouseMove: function (e) {
9616         var chart = this.chart,
9617             chartPosition = this.chartPosition,
9618             hoverSeries = chart.hoverSeries;
9619
9620         e = this.normalize(e, chartPosition);
9621
9622         // If we're outside, hide the tooltip
9623         if (chartPosition && hoverSeries && !this.inClass(e.target, 'highcharts-tracker') &&
9624                 !chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) {
9625             this.reset();
9626         }
9627     },
9628
9629     /**
9630      * When mouse leaves the container, hide the tooltip.
9631      */
9632     onContainerMouseLeave: function () {
9633         this.reset();
9634         this.chartPosition = null; // also reset the chart position, used in #149 fix
9635     },
9636
9637     // The mousemove, touchmove and touchstart event handler
9638     onContainerMouseMove: function (e) {
9639
9640         var chart = this.chart;
9641
9642         // normalize
9643         e = this.normalize(e);
9644
9645         // #295
9646         e.returnValue = false;
9647         
9648         
9649         if (chart.mouseIsDown === 'mousedown') {
9650             this.drag(e);
9651         } 
9652         
9653         // Show the tooltip and run mouse over events (#977)
9654         if ((this.inClass(e.target, 'highcharts-tracker') || 
9655                 chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) && !chart.openMenu) {
9656             this.runPointActions(e);
9657         }
9658     },
9659
9660     /**
9661      * Utility to detect whether an element has, or has a parent with, a specific
9662      * class name. Used on detection of tracker objects and on deciding whether
9663      * hovering the tooltip should cause the active series to mouse out.
9664      */
9665     inClass: function (element, className) {
9666         var elemClassName;
9667         while (element) {
9668             elemClassName = attr(element, 'class');
9669             if (elemClassName) {
9670                 if (elemClassName.indexOf(className) !== -1) {
9671                     return true;
9672                 } else if (elemClassName.indexOf(PREFIX + 'container') !== -1) {
9673                     return false;
9674                 }
9675             }
9676             element = element.parentNode;
9677         }        
9678     },
9679
9680     onTrackerMouseOut: function (e) {
9681         var series = this.chart.hoverSeries;
9682         if (series && !series.options.stickyTracking && !this.inClass(e.toElement || e.relatedTarget, PREFIX + 'tooltip')) {
9683             series.onMouseOut();
9684         }
9685     },
9686
9687     onContainerClick: function (e) {
9688         var chart = this.chart,
9689             hoverPoint = chart.hoverPoint, 
9690             plotLeft = chart.plotLeft,
9691             plotTop = chart.plotTop,
9692             inverted = chart.inverted,
9693             chartPosition,
9694             plotX,
9695             plotY;
9696         
9697         e = this.normalize(e);
9698         e.cancelBubble = true; // IE specific
9699
9700         if (!chart.cancelClick) {
9701             
9702             // On tracker click, fire the series and point events. #783, #1583
9703             if (hoverPoint && this.inClass(e.target, PREFIX + 'tracker')) {
9704                 chartPosition = this.chartPosition;
9705                 plotX = hoverPoint.plotX;
9706                 plotY = hoverPoint.plotY;
9707
9708                 // add page position info
9709                 extend(hoverPoint, {
9710                     pageX: chartPosition.left + plotLeft +
9711                         (inverted ? chart.plotWidth - plotY : plotX),
9712                     pageY: chartPosition.top + plotTop +
9713                         (inverted ? chart.plotHeight - plotX : plotY)
9714                 });
9715             
9716                 // the series click event
9717                 fireEvent(hoverPoint.series, 'click', extend(e, {
9718                     point: hoverPoint
9719                 }));
9720
9721                 // the point click event
9722                 if (chart.hoverPoint) { // it may be destroyed (#1844)
9723                     hoverPoint.firePointEvent('click', e);
9724                 }
9725
9726             // When clicking outside a tracker, fire a chart event
9727             } else {
9728                 extend(e, this.getCoordinates(e));
9729
9730                 // fire a click event in the chart
9731                 if (chart.isInsidePlot(e.chartX - plotLeft, e.chartY - plotTop)) {
9732                     fireEvent(chart, 'click', e);
9733                 }
9734             }
9735
9736
9737         }
9738     },
9739
9740     onContainerTouchStart: function (e) {
9741         var chart = this.chart;
9742
9743         if (e.touches.length === 1) {
9744
9745             e = this.normalize(e);
9746
9747             if (chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) {
9748
9749                 // Prevent the click pseudo event from firing unless it is set in the options
9750                 /*if (!chart.runChartClick) {
9751                     e.preventDefault();
9752                 }*/
9753             
9754                 // Run mouse events and display tooltip etc
9755                 this.runPointActions(e);
9756
9757                 this.pinch(e);
9758
9759             } else {
9760                 // Hide the tooltip on touching outside the plot area (#1203)
9761                 this.reset();
9762             }
9763
9764         } else if (e.touches.length === 2) {
9765             this.pinch(e);
9766         }        
9767     },
9768
9769     onContainerTouchMove: function (e) {
9770         if (e.touches.length === 1 || e.touches.length === 2) {
9771             this.pinch(e);
9772         }
9773     },
9774
9775     onDocumentTouchEnd: function (e) {
9776         this.drop(e);
9777     },
9778
9779     /**
9780      * Set the JS DOM events on the container and document. This method should contain
9781      * a one-to-one assignment between methods and their handlers. Any advanced logic should
9782      * be moved to the handler reflecting the event's name.
9783      */
9784     setDOMEvents: function () {
9785
9786         var pointer = this,
9787             container = pointer.chart.container,
9788             events;
9789
9790         this._events = events = [
9791             [container, 'onmousedown', 'onContainerMouseDown'],
9792             [container, 'onmousemove', 'onContainerMouseMove'],
9793             [container, 'onclick', 'onContainerClick'],
9794             [container, 'mouseleave', 'onContainerMouseLeave'],
9795             [doc, 'mousemove', 'onDocumentMouseMove'],
9796             [doc, 'mouseup', 'onDocumentMouseUp']
9797         ];
9798
9799         if (hasTouch) {
9800             events.push(
9801                 [container, 'ontouchstart', 'onContainerTouchStart'],
9802                 [container, 'ontouchmove', 'onContainerTouchMove'],
9803                 [doc, 'touchend', 'onDocumentTouchEnd']
9804             );
9805         }
9806
9807         each(events, function (eventConfig) {
9808
9809             // First, create the callback function that in turn calls the method on Pointer
9810             pointer['_' + eventConfig[2]] = function (e) {
9811                 pointer[eventConfig[2]](e);
9812             };
9813
9814             // Now attach the function, either as a direct property or through addEvent
9815             if (eventConfig[1].indexOf('on') === 0) {
9816                 eventConfig[0][eventConfig[1]] = pointer['_' + eventConfig[2]];
9817             } else {
9818                 addEvent(eventConfig[0], eventConfig[1], pointer['_' + eventConfig[2]]);
9819             }
9820         });
9821
9822         
9823     },
9824
9825     /**
9826      * Destroys the Pointer object and disconnects DOM events.
9827      */
9828     destroy: function () {
9829         var pointer = this;
9830
9831         // Release all DOM events
9832         each(pointer._events, function (eventConfig) {    
9833             if (eventConfig[1].indexOf('on') === 0) {
9834                 eventConfig[0][eventConfig[1]] = null; // delete breaks oldIE
9835             } else {        
9836                 removeEvent(eventConfig[0], eventConfig[1], pointer['_' + eventConfig[2]]);
9837             }
9838         });
9839         delete pointer._events;
9840
9841         // memory and CPU leak
9842         clearInterval(pointer.tooltipTimeout);
9843     }
9844 };
9845 /**
9846  * The overview of the chart's series
9847  */
9848 function Legend(chart, options) {
9849     this.init(chart, options);
9850 }
9851
9852 Legend.prototype = {
9853     
9854     /**
9855      * Initialize the legend
9856      */
9857     init: function (chart, options) {
9858         
9859         var legend = this,
9860             itemStyle = options.itemStyle,
9861             padding = pick(options.padding, 8),
9862             itemMarginTop = options.itemMarginTop || 0;
9863     
9864         this.options = options;
9865
9866         if (!options.enabled) {
9867             return;
9868         }
9869     
9870         legend.baseline = pInt(itemStyle.fontSize) + 3 + itemMarginTop; // used in Series prototype
9871         legend.itemStyle = itemStyle;
9872         legend.itemHiddenStyle = merge(itemStyle, options.itemHiddenStyle);
9873         legend.itemMarginTop = itemMarginTop;
9874         legend.padding = padding;
9875         legend.initialItemX = padding;
9876         legend.initialItemY = padding - 5; // 5 is the number of pixels above the text
9877         legend.maxItemWidth = 0;
9878         legend.chart = chart;
9879         legend.itemHeight = 0;
9880         legend.lastLineHeight = 0;
9881
9882         // Render it
9883         legend.render();
9884
9885         // move checkboxes
9886         addEvent(legend.chart, 'endResize', function () { 
9887             legend.positionCheckboxes();
9888         });
9889
9890     },
9891
9892     /**
9893      * Set the colors for the legend item
9894      * @param {Object} item A Series or Point instance
9895      * @param {Object} visible Dimmed or colored
9896      */
9897     colorizeItem: function (item, visible) {
9898         var legend = this,
9899             options = legend.options,
9900             legendItem = item.legendItem,
9901             legendLine = item.legendLine,
9902             legendSymbol = item.legendSymbol,
9903             hiddenColor = legend.itemHiddenStyle.color,
9904             textColor = visible ? options.itemStyle.color : hiddenColor,
9905             symbolColor = visible ? item.color : hiddenColor,
9906             markerOptions = item.options && item.options.marker,
9907             symbolAttr = {
9908                 stroke: symbolColor,
9909                 fill: symbolColor
9910             },
9911             key,
9912             val;
9913         
9914         if (legendItem) {
9915             legendItem.css({ fill: textColor, color: textColor }); // color for #1553, oldIE
9916         }
9917         if (legendLine) {
9918             legendLine.attr({ stroke: symbolColor });
9919         }
9920         
9921         if (legendSymbol) {
9922             
9923             // Apply marker options
9924             if (markerOptions && legendSymbol.isMarker) { // #585
9925                 markerOptions = item.convertAttribs(markerOptions);
9926                 for (key in markerOptions) {
9927                     val = markerOptions[key];
9928                     if (val !== UNDEFINED) {
9929                         symbolAttr[key] = val;
9930                     }
9931                 }
9932             }
9933
9934             legendSymbol.attr(symbolAttr);
9935         }
9936     },
9937
9938     /**
9939      * Position the legend item
9940      * @param {Object} item A Series or Point instance
9941      */
9942     positionItem: function (item) {
9943         var legend = this,
9944             options = legend.options,
9945             symbolPadding = options.symbolPadding,
9946             ltr = !options.rtl,
9947             legendItemPos = item._legendItemPos,
9948             itemX = legendItemPos[0],
9949             itemY = legendItemPos[1],
9950             checkbox = item.checkbox;
9951
9952         if (item.legendGroup) {
9953             item.legendGroup.translate(
9954                 ltr ? itemX : legend.legendWidth - itemX - 2 * symbolPadding - 4,
9955                 itemY
9956             );
9957         }
9958
9959         if (checkbox) {
9960             checkbox.x = itemX;
9961             checkbox.y = itemY;
9962         }
9963     },
9964
9965     /**
9966      * Destroy a single legend item
9967      * @param {Object} item The series or point
9968      */
9969     destroyItem: function (item) {
9970         var checkbox = item.checkbox;
9971
9972         // destroy SVG elements
9973         each(['legendItem', 'legendLine', 'legendSymbol', 'legendGroup'], function (key) {
9974             if (item[key]) {
9975                 item[key] = item[key].destroy();
9976             }
9977         });
9978
9979         if (checkbox) {
9980             discardElement(item.checkbox);
9981         }
9982     },
9983
9984     /**
9985      * Destroys the legend.
9986      */
9987     destroy: function () {
9988         var legend = this,
9989             legendGroup = legend.group,
9990             box = legend.box;
9991
9992         if (box) {
9993             legend.box = box.destroy();
9994         }
9995
9996         if (legendGroup) {
9997             legend.group = legendGroup.destroy();
9998         }
9999     },
10000
10001     /**
10002      * Position the checkboxes after the width is determined
10003      */
10004     positionCheckboxes: function (scrollOffset) {
10005         var alignAttr = this.group.alignAttr,
10006             translateY,
10007             clipHeight = this.clipHeight || this.legendHeight;
10008
10009         if (alignAttr) {
10010             translateY = alignAttr.translateY;
10011             each(this.allItems, function (item) {
10012                 var checkbox = item.checkbox,
10013                     top;
10014                 
10015                 if (checkbox) {
10016                     top = (translateY + checkbox.y + (scrollOffset || 0) + 3);
10017                     css(checkbox, {
10018                         left: (alignAttr.translateX + item.legendItemWidth + checkbox.x - 20) + PX,
10019                         top: top + PX,
10020                         display: top > translateY - 6 && top < translateY + clipHeight - 6 ? '' : NONE
10021                     });
10022                 }
10023             });
10024         }
10025     },
10026     
10027     /**
10028      * Render the legend title on top of the legend
10029      */
10030     renderTitle: function () {
10031         var options = this.options,
10032             padding = this.padding,
10033             titleOptions = options.title,
10034             titleHeight = 0,
10035             bBox;
10036         
10037         if (titleOptions.text) {
10038             if (!this.title) {
10039                 this.title = this.chart.renderer.label(titleOptions.text, padding - 3, padding - 4, null, null, null, null, null, 'legend-title')
10040                     .attr({ zIndex: 1 })
10041                     .css(titleOptions.style)
10042                     .add(this.group);
10043             }
10044             bBox = this.title.getBBox();
10045             titleHeight = bBox.height;
10046             this.offsetWidth = bBox.width; // #1717
10047             this.contentGroup.attr({ translateY: titleHeight });
10048         }
10049         this.titleHeight = titleHeight;
10050     },
10051
10052     /**
10053      * Render a single specific legend item
10054      * @param {Object} item A series or point
10055      */
10056     renderItem: function (item) {
10057         var legend = this,
10058             chart = legend.chart,
10059             renderer = chart.renderer,
10060             options = legend.options,
10061             horizontal = options.layout === 'horizontal',
10062             symbolWidth = options.symbolWidth,
10063             symbolPadding = options.symbolPadding,
10064             itemStyle = legend.itemStyle,
10065             itemHiddenStyle = legend.itemHiddenStyle,
10066             padding = legend.padding,
10067             itemDistance = horizontal ? pick(options.itemDistance, 8) : 0,
10068             ltr = !options.rtl,
10069             itemHeight,
10070             widthOption = options.width,
10071             itemMarginBottom = options.itemMarginBottom || 0,
10072             itemMarginTop = legend.itemMarginTop,
10073             initialItemX = legend.initialItemX,
10074             bBox,
10075             itemWidth,
10076             li = item.legendItem,
10077             series = item.series || item,
10078             itemOptions = series.options,
10079             showCheckbox = itemOptions.showCheckbox,
10080             useHTML = options.useHTML;
10081
10082         if (!li) { // generate it once, later move it
10083
10084             // Generate the group box
10085             // A group to hold the symbol and text. Text is to be appended in Legend class.
10086             item.legendGroup = renderer.g('legend-item')
10087                 .attr({ zIndex: 1 })
10088                 .add(legend.scrollGroup);
10089
10090             // Draw the legend symbol inside the group box
10091             series.drawLegendSymbol(legend, item);
10092
10093             // Generate the list item text and add it to the group
10094             item.legendItem = li = renderer.text(
10095                     options.labelFormat ? format(options.labelFormat, item) : options.labelFormatter.call(item),
10096                     ltr ? symbolWidth + symbolPadding : -symbolPadding,
10097                     legend.baseline,
10098                     useHTML
10099                 )
10100                 .css(merge(item.visible ? itemStyle : itemHiddenStyle)) // merge to prevent modifying original (#1021)
10101                 .attr({
10102                     align: ltr ? 'left' : 'right',
10103                     zIndex: 2
10104                 })
10105                 .add(item.legendGroup);
10106
10107             // Set the events on the item group, or in case of useHTML, the item itself (#1249)
10108             (useHTML ? li : item.legendGroup).on('mouseover', function () {
10109                     item.setState(HOVER_STATE);
10110                     li.css(legend.options.itemHoverStyle);
10111                 })
10112                 .on('mouseout', function () {
10113                     li.css(item.visible ? itemStyle : itemHiddenStyle);
10114                     item.setState();
10115                 })
10116                 .on('click', function (event) {
10117                     var strLegendItemClick = 'legendItemClick',
10118                         fnLegendItemClick = function () {
10119                             item.setVisible();
10120                         };
10121                         
10122                     // Pass over the click/touch event. #4.
10123                     event = {
10124                         browserEvent: event
10125                     };
10126
10127                     // click the name or symbol
10128                     if (item.firePointEvent) { // point
10129                         item.firePointEvent(strLegendItemClick, event, fnLegendItemClick);
10130                     } else {
10131                         fireEvent(item, strLegendItemClick, event, fnLegendItemClick);
10132                     }
10133                 });
10134
10135             // Colorize the items
10136             legend.colorizeItem(item, item.visible);
10137
10138             // add the HTML checkbox on top
10139             if (itemOptions && showCheckbox) {
10140                 item.checkbox = createElement('input', {
10141                     type: 'checkbox',
10142                     checked: item.selected,
10143                     defaultChecked: item.selected // required by IE7
10144                 }, options.itemCheckboxStyle, chart.container);
10145
10146                 addEvent(item.checkbox, 'click', function (event) {
10147                     var target = event.target;
10148                     fireEvent(item, 'checkboxClick', {
10149                             checked: target.checked
10150                         },
10151                         function () {
10152                             item.select();
10153                         }
10154                     );
10155                 });
10156             }
10157         }
10158
10159         // calculate the positions for the next line
10160         bBox = li.getBBox();
10161
10162         itemWidth = item.legendItemWidth =
10163             options.itemWidth || symbolWidth + symbolPadding + bBox.width + itemDistance +
10164             (showCheckbox ? 20 : 0);
10165         legend.itemHeight = itemHeight = bBox.height;
10166
10167         // if the item exceeds the width, start a new line
10168         if (horizontal && legend.itemX - initialItemX + itemWidth >
10169                 (widthOption || (chart.chartWidth - 2 * padding - initialItemX))) {
10170             legend.itemX = initialItemX;
10171             legend.itemY += itemMarginTop + legend.lastLineHeight + itemMarginBottom;
10172             legend.lastLineHeight = 0; // reset for next line
10173         }
10174
10175         // If the item exceeds the height, start a new column
10176         /*if (!horizontal && legend.itemY + options.y + itemHeight > chart.chartHeight - spacingTop - spacingBottom) {
10177             legend.itemY = legend.initialItemY;
10178             legend.itemX += legend.maxItemWidth;
10179             legend.maxItemWidth = 0;
10180         }*/
10181
10182         // Set the edge positions
10183         legend.maxItemWidth = mathMax(legend.maxItemWidth, itemWidth);
10184         legend.lastItemY = itemMarginTop + legend.itemY + itemMarginBottom;
10185         legend.lastLineHeight = mathMax(itemHeight, legend.lastLineHeight); // #915
10186
10187         // cache the position of the newly generated or reordered items
10188         item._legendItemPos = [legend.itemX, legend.itemY];
10189
10190         // advance
10191         if (horizontal) {
10192             legend.itemX += itemWidth;
10193
10194         } else {
10195             legend.itemY += itemMarginTop + itemHeight + itemMarginBottom;
10196             legend.lastLineHeight = itemHeight;
10197         }
10198
10199         // the width of the widest item
10200         legend.offsetWidth = widthOption || mathMax(
10201             (horizontal ? legend.itemX - initialItemX - itemDistance : itemWidth) + padding,
10202             legend.offsetWidth
10203         );
10204     },
10205
10206     /**
10207      * Render the legend. This method can be called both before and after
10208      * chart.render. If called after, it will only rearrange items instead
10209      * of creating new ones.
10210      */
10211     render: function () {
10212         var legend = this,
10213             chart = legend.chart,
10214             renderer = chart.renderer,
10215             legendGroup = legend.group,
10216             allItems,
10217             display,
10218             legendWidth,
10219             legendHeight,
10220             box = legend.box,
10221             options = legend.options,
10222             padding = legend.padding,
10223             legendBorderWidth = options.borderWidth,
10224             legendBackgroundColor = options.backgroundColor;
10225
10226         legend.itemX = legend.initialItemX;
10227         legend.itemY = legend.initialItemY;
10228         legend.offsetWidth = 0;
10229         legend.lastItemY = 0;
10230
10231         if (!legendGroup) {
10232             legend.group = legendGroup = renderer.g('legend')
10233                 .attr({ zIndex: 7 }) 
10234                 .add();
10235             legend.contentGroup = renderer.g()
10236                 .attr({ zIndex: 1 }) // above background
10237                 .add(legendGroup);
10238             legend.scrollGroup = renderer.g()
10239                 .add(legend.contentGroup);
10240         }
10241         
10242         legend.renderTitle();
10243
10244         // add each series or point
10245         allItems = [];
10246         each(chart.series, function (serie) {
10247             var seriesOptions = serie.options;
10248
10249             if (!seriesOptions.showInLegend || defined(seriesOptions.linkedTo)) {
10250                 return;
10251             }
10252
10253             // use points or series for the legend item depending on legendType
10254             allItems = allItems.concat(
10255                     serie.legendItems ||
10256                     (seriesOptions.legendType === 'point' ?
10257                             serie.data :
10258                             serie)
10259             );
10260         });
10261
10262         // sort by legendIndex
10263         stableSort(allItems, function (a, b) {
10264             return ((a.options && a.options.legendIndex) || 0) - ((b.options && b.options.legendIndex) || 0);
10265         });
10266
10267         // reversed legend
10268         if (options.reversed) {
10269             allItems.reverse();
10270         }
10271
10272         legend.allItems = allItems;
10273         legend.display = display = !!allItems.length;
10274
10275         // render the items
10276         each(allItems, function (item) {
10277             legend.renderItem(item); 
10278         });
10279
10280         // Draw the border
10281         legendWidth = options.width || legend.offsetWidth;
10282         legendHeight = legend.lastItemY + legend.lastLineHeight + legend.titleHeight;
10283         
10284         
10285         legendHeight = legend.handleOverflow(legendHeight);
10286
10287         if (legendBorderWidth || legendBackgroundColor) {
10288             legendWidth += padding;
10289             legendHeight += padding;
10290
10291             if (!box) {
10292                 legend.box = box = renderer.rect(
10293                     0,
10294                     0,
10295                     legendWidth,
10296                     legendHeight,
10297                     options.borderRadius,
10298                     legendBorderWidth || 0
10299                 ).attr({
10300                     stroke: options.borderColor,
10301                     'stroke-width': legendBorderWidth || 0,
10302                     fill: legendBackgroundColor || NONE
10303                 })
10304                 .add(legendGroup)
10305                 .shadow(options.shadow);
10306                 box.isNew = true;
10307
10308             } else if (legendWidth > 0 && legendHeight > 0) {
10309                 box[box.isNew ? 'attr' : 'animate'](
10310                     box.crisp(null, null, null, legendWidth, legendHeight)
10311                 );
10312                 box.isNew = false;
10313             }
10314
10315             // hide the border if no items
10316             box[display ? 'show' : 'hide']();
10317         }
10318         
10319         legend.legendWidth = legendWidth;
10320         legend.legendHeight = legendHeight;
10321
10322         // Now that the legend width and height are established, put the items in the 
10323         // final position
10324         each(allItems, function (item) {
10325             legend.positionItem(item);
10326         });
10327
10328         // 1.x compatibility: positioning based on style
10329         /*var props = ['left', 'right', 'top', 'bottom'],
10330             prop,
10331             i = 4;
10332         while (i--) {
10333             prop = props[i];
10334             if (options.style[prop] && options.style[prop] !== 'auto') {
10335                 options[i < 2 ? 'align' : 'verticalAlign'] = prop;
10336                 options[i < 2 ? 'x' : 'y'] = pInt(options.style[prop]) * (i % 2 ? -1 : 1);
10337             }
10338         }*/
10339
10340         if (display) {
10341             legendGroup.align(extend({
10342                 width: legendWidth,
10343                 height: legendHeight
10344             }, options), true, 'spacingBox');
10345         }
10346
10347         if (!chart.isResizing) {
10348             this.positionCheckboxes();
10349         }
10350     },
10351     
10352     /**
10353      * Set up the overflow handling by adding navigation with up and down arrows below the
10354      * legend.
10355      */
10356     handleOverflow: function (legendHeight) {
10357         var legend = this,
10358             chart = this.chart,
10359             renderer = chart.renderer,
10360             pageCount,
10361             options = this.options,
10362             optionsY = options.y,
10363             alignTop = options.verticalAlign === 'top',
10364             spaceHeight = chart.spacingBox.height + (alignTop ? -optionsY : optionsY) - this.padding,
10365             maxHeight = options.maxHeight,
10366             clipHeight,
10367             clipRect = this.clipRect,
10368             navOptions = options.navigation,
10369             animation = pick(navOptions.animation, true),
10370             arrowSize = navOptions.arrowSize || 12,
10371             nav = this.nav;
10372             
10373         // Adjust the height
10374         if (options.layout === 'horizontal') {
10375             spaceHeight /= 2;
10376         }
10377         if (maxHeight) {
10378             spaceHeight = mathMin(spaceHeight, maxHeight);
10379         }
10380         
10381         // Reset the legend height and adjust the clipping rectangle
10382         if (legendHeight > spaceHeight && !options.useHTML) {
10383
10384             this.clipHeight = clipHeight = spaceHeight - 20 - this.titleHeight;
10385             this.pageCount = pageCount = mathCeil(legendHeight / clipHeight);
10386             this.currentPage = pick(this.currentPage, 1);
10387             this.fullHeight = legendHeight;
10388             
10389             // Only apply clipping if needed. Clipping causes blurred legend in PDF export (#1787)
10390             if (!clipRect) {
10391                 clipRect = legend.clipRect = renderer.clipRect(0, 0, 9999, 0);
10392                 legend.contentGroup.clip(clipRect);
10393             }
10394             clipRect.attr({
10395                 height: clipHeight
10396             });
10397             
10398             // Add navigation elements
10399             if (!nav) {
10400                 this.nav = nav = renderer.g().attr({ zIndex: 1 }).add(this.group);
10401                 this.up = renderer.symbol('triangle', 0, 0, arrowSize, arrowSize)
10402                     .on('click', function () {
10403                         legend.scroll(-1, animation);
10404                     })
10405                     .add(nav);
10406                 this.pager = renderer.text('', 15, 10)
10407                     .css(navOptions.style)
10408                     .add(nav);
10409                 this.down = renderer.symbol('triangle-down', 0, 0, arrowSize, arrowSize)
10410                     .on('click', function () {
10411                         legend.scroll(1, animation);
10412                     })
10413                     .add(nav);
10414             }
10415             
10416             // Set initial position
10417             legend.scroll(0);
10418             
10419             legendHeight = spaceHeight;
10420             
10421         } else if (nav) {
10422             clipRect.attr({
10423                 height: chart.chartHeight
10424             });
10425             nav.hide();
10426             this.scrollGroup.attr({
10427                 translateY: 1
10428             });
10429             this.clipHeight = 0; // #1379
10430         }
10431         
10432         return legendHeight;
10433     },
10434     
10435     /**
10436      * Scroll the legend by a number of pages
10437      * @param {Object} scrollBy
10438      * @param {Object} animation
10439      */
10440     scroll: function (scrollBy, animation) {
10441         var pageCount = this.pageCount,
10442             currentPage = this.currentPage + scrollBy,
10443             clipHeight = this.clipHeight,
10444             navOptions = this.options.navigation,
10445             activeColor = navOptions.activeColor,
10446             inactiveColor = navOptions.inactiveColor,
10447             pager = this.pager,
10448             padding = this.padding,
10449             scrollOffset;
10450         
10451         // When resizing while looking at the last page
10452         if (currentPage > pageCount) {
10453             currentPage = pageCount;
10454         }
10455         
10456         if (currentPage > 0) {
10457             
10458             if (animation !== UNDEFINED) {
10459                 setAnimation(animation, this.chart);
10460             }
10461             
10462             this.nav.attr({
10463                 translateX: padding,
10464                 translateY: clipHeight + 7 + this.titleHeight,
10465                 visibility: VISIBLE
10466             });
10467             this.up.attr({
10468                     fill: currentPage === 1 ? inactiveColor : activeColor
10469                 })
10470                 .css({
10471                     cursor: currentPage === 1 ? 'default' : 'pointer'
10472                 });
10473             pager.attr({
10474                 text: currentPage + '/' + this.pageCount
10475             });
10476             this.down.attr({
10477                     x: 18 + this.pager.getBBox().width, // adjust to text width
10478                     fill: currentPage === pageCount ? inactiveColor : activeColor
10479                 })
10480                 .css({
10481                     cursor: currentPage === pageCount ? 'default' : 'pointer'
10482                 });
10483             
10484             scrollOffset = -mathMin(clipHeight * (currentPage - 1), this.fullHeight - clipHeight + padding) + 1;
10485             this.scrollGroup.animate({
10486                 translateY: scrollOffset
10487             });
10488             pager.attr({
10489                 text: currentPage + '/' + pageCount
10490             });
10491             
10492             
10493             this.currentPage = currentPage;
10494             this.positionCheckboxes(scrollOffset);
10495         }
10496             
10497     }
10498     
10499 };
10500
10501 // Workaround for #2030, horizontal legend items not displaying in IE11 Preview.
10502 // TODO: When IE11 is released, check again for this bug, and remove the fix
10503 // or make a better one.
10504 if (/Trident.*?11\.0/.test(userAgent)) {
10505     wrap(Legend.prototype, 'positionItem', function (proceed, item) {
10506         var legend = this;
10507         setTimeout(function () {
10508             proceed.call(legend, item);
10509         });
10510     });
10511 }
10512
10513 /**
10514  * The chart class
10515  * @param {Object} options
10516  * @param {Function} callback Function to run when the chart has loaded
10517  */
10518 function Chart() {
10519     this.init.apply(this, arguments);
10520 }
10521
10522 Chart.prototype = {
10523
10524     /**
10525      * Initialize the chart
10526      */
10527     init: function (userOptions, callback) {
10528
10529         // Handle regular options
10530         var options,
10531             seriesOptions = userOptions.series; // skip merging data points to increase performance
10532
10533         userOptions.series = null;
10534         options = merge(defaultOptions, userOptions); // do the merge
10535         options.series = userOptions.series = seriesOptions; // set back the series data
10536
10537         var optionsChart = options.chart;
10538         
10539         // Create margin & spacing array
10540         this.margin = this.splashArray('margin', optionsChart);
10541         this.spacing = this.splashArray('spacing', optionsChart);
10542
10543         var chartEvents = optionsChart.events;
10544
10545         //this.runChartClick = chartEvents && !!chartEvents.click;
10546         this.bounds = { h: {}, v: {} }; // Pixel data bounds for touch zoom
10547
10548         this.callback = callback;
10549         this.isResizing = 0;
10550         this.options = options;
10551         //chartTitleOptions = UNDEFINED;
10552         //chartSubtitleOptions = UNDEFINED;
10553
10554         this.axes = [];
10555         this.series = [];
10556         this.hasCartesianSeries = optionsChart.showAxes;
10557         //this.axisOffset = UNDEFINED;
10558         //this.maxTicks = UNDEFINED; // handle the greatest amount of ticks on grouped axes
10559         //this.inverted = UNDEFINED;
10560         //this.loadingShown = UNDEFINED;
10561         //this.container = UNDEFINED;
10562         //this.chartWidth = UNDEFINED;
10563         //this.chartHeight = UNDEFINED;
10564         //this.marginRight = UNDEFINED;
10565         //this.marginBottom = UNDEFINED;
10566         //this.containerWidth = UNDEFINED;
10567         //this.containerHeight = UNDEFINED;
10568         //this.oldChartWidth = UNDEFINED;
10569         //this.oldChartHeight = UNDEFINED;
10570
10571         //this.renderTo = UNDEFINED;
10572         //this.renderToClone = UNDEFINED;
10573
10574         //this.spacingBox = UNDEFINED
10575
10576         //this.legend = UNDEFINED;
10577
10578         // Elements
10579         //this.chartBackground = UNDEFINED;
10580         //this.plotBackground = UNDEFINED;
10581         //this.plotBGImage = UNDEFINED;
10582         //this.plotBorder = UNDEFINED;
10583         //this.loadingDiv = UNDEFINED;
10584         //this.loadingSpan = UNDEFINED;
10585
10586         var chart = this,
10587             eventType;
10588
10589         // Add the chart to the global lookup
10590         chart.index = charts.length;
10591         charts.push(chart);
10592
10593         // Set up auto resize
10594         if (optionsChart.reflow !== false) {
10595             addEvent(chart, 'load', function () {
10596                 chart.initReflow();
10597             });
10598         }
10599
10600         // Chart event handlers
10601         if (chartEvents) {
10602             for (eventType in chartEvents) {
10603                 addEvent(chart, eventType, chartEvents[eventType]);
10604             }
10605         }
10606
10607         chart.xAxis = [];
10608         chart.yAxis = [];
10609
10610         // Expose methods and variables
10611         chart.animation = useCanVG ? false : pick(optionsChart.animation, true);
10612         chart.pointCount = 0;
10613         chart.counters = new ChartCounters();
10614
10615         chart.firstRender();
10616     },
10617
10618     /**
10619      * Initialize an individual series, called internally before render time
10620      */
10621     initSeries: function (options) {
10622         var chart = this,
10623             optionsChart = chart.options.chart,
10624             type = options.type || optionsChart.type || optionsChart.defaultSeriesType,
10625             series,
10626             constr = seriesTypes[type];
10627
10628         // No such series type
10629         if (!constr) {
10630             error(17, true);
10631         }
10632
10633         series = new constr();
10634         series.init(this, options);
10635         return series;
10636     },
10637
10638     /**
10639      * Add a series dynamically after  time
10640      *
10641      * @param {Object} options The config options
10642      * @param {Boolean} redraw Whether to redraw the chart after adding. Defaults to true.
10643      * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
10644      *    configuration
10645      *
10646      * @return {Object} series The newly created series object
10647      */
10648     addSeries: function (options, redraw, animation) {
10649         var series,
10650             chart = this;
10651
10652         if (options) {
10653             redraw = pick(redraw, true); // defaults to true
10654
10655             fireEvent(chart, 'addSeries', { options: options }, function () {
10656                 series = chart.initSeries(options);
10657                 
10658                 chart.isDirtyLegend = true; // the series array is out of sync with the display
10659                 chart.linkSeries();
10660                 if (redraw) {
10661                     chart.redraw(animation);
10662                 }
10663             });
10664         }
10665
10666         return series;
10667     },
10668
10669     /**
10670      * Add an axis to the chart
10671      * @param {Object} options The axis option
10672      * @param {Boolean} isX Whether it is an X axis or a value axis
10673      */
10674     addAxis: function (options, isX, redraw, animation) {
10675         var key = isX ? 'xAxis' : 'yAxis',
10676             chartOptions = this.options,
10677             axis;
10678
10679         /*jslint unused: false*/
10680         axis = new Axis(this, merge(options, {
10681             index: this[key].length,
10682             isX: isX
10683         }));
10684         /*jslint unused: true*/
10685
10686         // Push the new axis options to the chart options
10687         chartOptions[key] = splat(chartOptions[key] || {});
10688         chartOptions[key].push(options);
10689
10690         if (pick(redraw, true)) {
10691             this.redraw(animation);
10692         }
10693     },
10694
10695     /**
10696      * Check whether a given point is within the plot area
10697      *
10698      * @param {Number} plotX Pixel x relative to the plot area
10699      * @param {Number} plotY Pixel y relative to the plot area
10700      * @param {Boolean} inverted Whether the chart is inverted
10701      */
10702     isInsidePlot: function (plotX, plotY, inverted) {
10703         var x = inverted ? plotY : plotX,
10704             y = inverted ? plotX : plotY;
10705             
10706         return x >= 0 &&
10707             x <= this.plotWidth &&
10708             y >= 0 &&
10709             y <= this.plotHeight;
10710     },
10711
10712     /**
10713      * Adjust all axes tick amounts
10714      */
10715     adjustTickAmounts: function () {
10716         if (this.options.chart.alignTicks !== false) {
10717             each(this.axes, function (axis) {
10718                 axis.adjustTickAmount();
10719             });
10720         }
10721         this.maxTicks = null;
10722     },
10723
10724     /**
10725      * Redraw legend, axes or series based on updated data
10726      *
10727      * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
10728      *    configuration
10729      */
10730     redraw: function (animation) {
10731         var chart = this,
10732             axes = chart.axes,
10733             series = chart.series,
10734             pointer = chart.pointer,
10735             legend = chart.legend,
10736             redrawLegend = chart.isDirtyLegend,
10737             hasStackedSeries,
10738             hasDirtyStacks,
10739             isDirtyBox = chart.isDirtyBox, // todo: check if it has actually changed?
10740             seriesLength = series.length,
10741             i = seriesLength,
10742             serie,
10743             renderer = chart.renderer,
10744             isHiddenChart = renderer.isHidden(),
10745             afterRedraw = [];
10746             
10747         setAnimation(animation, chart);
10748         
10749         if (isHiddenChart) {
10750             chart.cloneRenderTo();
10751         }
10752
10753         // Adjust title layout (reflow multiline text)
10754         chart.layOutTitles();
10755
10756         // link stacked series
10757         while (i--) {
10758             serie = series[i];
10759
10760             if (serie.options.stacking) {
10761                 hasStackedSeries = true;
10762                 
10763                 if (serie.isDirty) {
10764                     hasDirtyStacks = true;
10765                     break;
10766                 }
10767             }
10768         }
10769         if (hasDirtyStacks) { // mark others as dirty
10770             i = seriesLength;
10771             while (i--) {
10772                 serie = series[i];
10773                 if (serie.options.stacking) {
10774                     serie.isDirty = true;
10775                 }
10776             }
10777         }
10778
10779         // handle updated data in the series
10780         each(series, function (serie) {
10781             if (serie.isDirty) { // prepare the data so axis can read it
10782                 if (serie.options.legendType === 'point') {
10783                     redrawLegend = true;
10784                 }
10785             }
10786         });
10787
10788         // handle added or removed series
10789         if (redrawLegend && legend.options.enabled) { // series or pie points are added or removed
10790             // draw legend graphics
10791             legend.render();
10792
10793             chart.isDirtyLegend = false;
10794         }
10795
10796         // reset stacks
10797         if (hasStackedSeries) {
10798             chart.getStacks();
10799         }
10800
10801
10802         if (chart.hasCartesianSeries) {
10803             if (!chart.isResizing) {
10804
10805                 // reset maxTicks
10806                 chart.maxTicks = null;
10807
10808                 // set axes scales
10809                 each(axes, function (axis) {
10810                     axis.setScale();
10811                 });
10812             }
10813
10814             chart.adjustTickAmounts();
10815             chart.getMargins();
10816
10817             // If one axis is dirty, all axes must be redrawn (#792, #2169)
10818             each(axes, function (axis) {
10819                 if (axis.isDirty) {
10820                     isDirtyBox = true;
10821                 }
10822             });
10823
10824             // redraw axes
10825             each(axes, function (axis) {
10826                 
10827                 // Fire 'afterSetExtremes' only if extremes are set
10828                 if (axis.isDirtyExtremes) { // #821
10829                     axis.isDirtyExtremes = false;
10830                     afterRedraw.push(function () { // prevent a recursive call to chart.redraw() (#1119)
10831                         fireEvent(axis, 'afterSetExtremes', extend(axis.eventArgs, axis.getExtremes())); // #747, #751
10832                         delete axis.eventArgs;
10833                     });
10834                 }
10835                 
10836                 if (isDirtyBox || hasStackedSeries) {
10837                     axis.redraw();
10838                 }
10839             });
10840
10841
10842         }
10843         // the plot areas size has changed
10844         if (isDirtyBox) {
10845             chart.drawChartBox();
10846         }
10847
10848
10849         // redraw affected series
10850         each(series, function (serie) {
10851             if (serie.isDirty && serie.visible &&
10852                     (!serie.isCartesian || serie.xAxis)) { // issue #153
10853                 serie.redraw();
10854             }
10855         });
10856
10857         // move tooltip or reset
10858         if (pointer && pointer.reset) {
10859             pointer.reset(true);
10860         }
10861
10862         // redraw if canvas
10863         renderer.draw();
10864
10865         // fire the event
10866         fireEvent(chart, 'redraw'); // jQuery breaks this when calling it from addEvent. Overwrites chart.redraw
10867         
10868         if (isHiddenChart) {
10869             chart.cloneRenderTo(true);
10870         }
10871         
10872         // Fire callbacks that are put on hold until after the redraw
10873         each(afterRedraw, function (callback) {
10874             callback.call();
10875         });
10876     },
10877
10878
10879
10880     /**
10881      * Dim the chart and show a loading text or symbol
10882      * @param {String} str An optional text to show in the loading label instead of the default one
10883      */
10884     showLoading: function (str) {
10885         var chart = this,
10886             options = chart.options,
10887             loadingDiv = chart.loadingDiv;
10888
10889         var loadingOptions = options.loading;
10890
10891         // create the layer at the first call
10892         if (!loadingDiv) {
10893             chart.loadingDiv = loadingDiv = createElement(DIV, {
10894                 className: PREFIX + 'loading'
10895             }, extend(loadingOptions.style, {
10896                 zIndex: 10,
10897                 display: NONE
10898             }), chart.container);
10899
10900             chart.loadingSpan = createElement(
10901                 'span',
10902                 null,
10903                 loadingOptions.labelStyle,
10904                 loadingDiv
10905             );
10906
10907         }
10908
10909         // update text
10910         chart.loadingSpan.innerHTML = str || options.lang.loading;
10911
10912         // show it
10913         if (!chart.loadingShown) {
10914             css(loadingDiv, { 
10915                 opacity: 0, 
10916                 display: '',
10917                 left: chart.plotLeft + PX,
10918                 top: chart.plotTop + PX,
10919                 width: chart.plotWidth + PX,
10920                 height: chart.plotHeight + PX
10921             });
10922             animate(loadingDiv, {
10923                 opacity: loadingOptions.style.opacity
10924             }, {
10925                 duration: loadingOptions.showDuration || 0
10926             });
10927             chart.loadingShown = true;
10928         }
10929     },
10930
10931     /**
10932      * Hide the loading layer
10933      */
10934     hideLoading: function () {
10935         var options = this.options,
10936             loadingDiv = this.loadingDiv;
10937
10938         if (loadingDiv) {
10939             animate(loadingDiv, {
10940                 opacity: 0
10941             }, {
10942                 duration: options.loading.hideDuration || 100,
10943                 complete: function () {
10944                     css(loadingDiv, { display: NONE });
10945                 }
10946             });
10947         }
10948         this.loadingShown = false;
10949     },
10950
10951     /**
10952      * Get an axis, series or point object by id.
10953      * @param id {String} The id as given in the configuration options
10954      */
10955     get: function (id) {
10956         var chart = this,
10957             axes = chart.axes,
10958             series = chart.series;
10959
10960         var i,
10961             j,
10962             points;
10963
10964         // search axes
10965         for (i = 0; i < axes.length; i++) {
10966             if (axes[i].options.id === id) {
10967                 return axes[i];
10968             }
10969         }
10970
10971         // search series
10972         for (i = 0; i < series.length; i++) {
10973             if (series[i].options.id === id) {
10974                 return series[i];
10975             }
10976         }
10977
10978         // search points
10979         for (i = 0; i < series.length; i++) {
10980             points = series[i].points || [];
10981             for (j = 0; j < points.length; j++) {
10982                 if (points[j].id === id) {
10983                     return points[j];
10984                 }
10985             }
10986         }
10987         return null;
10988     },
10989
10990     /**
10991      * Create the Axis instances based on the config options
10992      */
10993     getAxes: function () {
10994         var chart = this,
10995             options = this.options,
10996             xAxisOptions = options.xAxis = splat(options.xAxis || {}),
10997             yAxisOptions = options.yAxis = splat(options.yAxis || {}),
10998             optionsArray,
10999             axis;
11000
11001         // make sure the options are arrays and add some members
11002         each(xAxisOptions, function (axis, i) {
11003             axis.index = i;
11004             axis.isX = true;
11005         });
11006
11007         each(yAxisOptions, function (axis, i) {
11008             axis.index = i;
11009         });
11010
11011         // concatenate all axis options into one array
11012         optionsArray = xAxisOptions.concat(yAxisOptions);
11013
11014         each(optionsArray, function (axisOptions) {
11015             axis = new Axis(chart, axisOptions);
11016         });
11017
11018         chart.adjustTickAmounts();
11019     },
11020
11021
11022     /**
11023      * Get the currently selected points from all series
11024      */
11025     getSelectedPoints: function () {
11026         var points = [];
11027         each(this.series, function (serie) {
11028             points = points.concat(grep(serie.points || [], function (point) {
11029                 return point.selected;
11030             }));
11031         });
11032         return points;
11033     },
11034
11035     /**
11036      * Get the currently selected series
11037      */
11038     getSelectedSeries: function () {
11039         return grep(this.series, function (serie) {
11040             return serie.selected;
11041         });
11042     },
11043
11044     /**
11045      * Generate stacks for each series and calculate stacks total values
11046      */
11047     getStacks: function () {
11048         var chart = this;
11049
11050         // reset stacks for each yAxis
11051         each(chart.yAxis, function (axis) {
11052             if (axis.stacks && axis.hasVisibleSeries) {
11053                 axis.oldStacks = axis.stacks;
11054             }
11055         });
11056
11057         each(chart.series, function (series) {
11058             if (series.options.stacking && (series.visible === true || chart.options.chart.ignoreHiddenSeries === false)) {
11059                 series.stackKey = series.type + pick(series.options.stack, '');
11060             }
11061         });
11062     },
11063
11064     /**
11065      * Display the zoom button
11066      */
11067     showResetZoom: function () {
11068         var chart = this,
11069             lang = defaultOptions.lang,
11070             btnOptions = chart.options.chart.resetZoomButton,
11071             theme = btnOptions.theme,
11072             states = theme.states,
11073             alignTo = btnOptions.relativeTo === 'chart' ? null : 'plotBox';
11074             
11075         this.resetZoomButton = chart.renderer.button(lang.resetZoom, null, null, function () { chart.zoomOut(); }, theme, states && states.hover)
11076             .attr({
11077                 align: btnOptions.position.align,
11078                 title: lang.resetZoomTitle
11079             })
11080             .add()
11081             .align(btnOptions.position, false, alignTo);
11082             
11083     },
11084
11085     /**
11086      * Zoom out to 1:1
11087      */
11088     zoomOut: function () {
11089         var chart = this;
11090         fireEvent(chart, 'selection', { resetSelection: true }, function () { 
11091             chart.zoom();
11092         });
11093     },
11094
11095     /**
11096      * Zoom into a given portion of the chart given by axis coordinates
11097      * @param {Object} event
11098      */
11099     zoom: function (event) {
11100         var chart = this,
11101             hasZoomed,
11102             pointer = chart.pointer,
11103             displayButton = false,
11104             resetZoomButton;
11105
11106         // If zoom is called with no arguments, reset the axes
11107         if (!event || event.resetSelection) {
11108             each(chart.axes, function (axis) {
11109                 hasZoomed = axis.zoom();
11110             });
11111         } else { // else, zoom in on all axes
11112             each(event.xAxis.concat(event.yAxis), function (axisData) {
11113                 var axis = axisData.axis,
11114                     isXAxis = axis.isXAxis;
11115
11116                 // don't zoom more than minRange
11117                 if (pointer[isXAxis ? 'zoomX' : 'zoomY'] || pointer[isXAxis ? 'pinchX' : 'pinchY']) {
11118                     hasZoomed = axis.zoom(axisData.min, axisData.max);
11119                     if (axis.displayBtn) {
11120                         displayButton = true;
11121                     }
11122                 }
11123             });
11124         }
11125         
11126         // Show or hide the Reset zoom button
11127         resetZoomButton = chart.resetZoomButton;
11128         if (displayButton && !resetZoomButton) {
11129             chart.showResetZoom();
11130         } else if (!displayButton && isObject(resetZoomButton)) {
11131             chart.resetZoomButton = resetZoomButton.destroy();
11132         }
11133         
11134
11135         // Redraw
11136         if (hasZoomed) {
11137             chart.redraw(
11138                 pick(chart.options.chart.animation, event && event.animation, chart.pointCount < 100) // animation
11139             );
11140         }
11141     },
11142
11143     /**
11144      * Pan the chart by dragging the mouse across the pane. This function is called
11145      * on mouse move, and the distance to pan is computed from chartX compared to
11146      * the first chartX position in the dragging operation.
11147      */
11148     pan: function (e, panning) {
11149
11150         var chart = this,
11151             hoverPoints = chart.hoverPoints,
11152             doRedraw;
11153
11154         // remove active points for shared tooltip
11155         if (hoverPoints) {
11156             each(hoverPoints, function (point) {
11157                 point.setState();
11158             });
11159         }
11160
11161         each(panning === 'xy' ? [1, 0] : [1], function (isX) { // xy is used in maps
11162             var mousePos = e[isX ? 'chartX' : 'chartY'],
11163                 axis = chart[isX ? 'xAxis' : 'yAxis'][0],
11164                 startPos = chart[isX ? 'mouseDownX' : 'mouseDownY'],
11165                 halfPointRange = (axis.pointRange || 0) / 2,
11166                 extremes = axis.getExtremes(),
11167                 newMin = axis.toValue(startPos - mousePos, true) + halfPointRange,
11168                 newMax = axis.toValue(startPos + chart[isX ? 'plotWidth' : 'plotHeight'] - mousePos, true) - halfPointRange;
11169
11170             if (axis.series.length && newMin > mathMin(extremes.dataMin, extremes.min) && newMax < mathMax(extremes.dataMax, extremes.max)) {
11171                 axis.setExtremes(newMin, newMax, false, false, { trigger: 'pan' });
11172                 doRedraw = true;
11173             }
11174
11175             chart[isX ? 'mouseDownX' : 'mouseDownY'] = mousePos; // set new reference for next run
11176         });
11177
11178         if (doRedraw) {
11179             chart.redraw(false);
11180         }
11181         css(chart.container, { cursor: 'move' });
11182     },
11183
11184     /**
11185      * Show the title and subtitle of the chart
11186      *
11187      * @param titleOptions {Object} New title options
11188      * @param subtitleOptions {Object} New subtitle options
11189      *
11190      */
11191     setTitle: function (titleOptions, subtitleOptions) {
11192         var chart = this,
11193             options = chart.options,
11194             chartTitleOptions,
11195             chartSubtitleOptions;
11196
11197         chartTitleOptions = options.title = merge(options.title, titleOptions);
11198         chartSubtitleOptions = options.subtitle = merge(options.subtitle, subtitleOptions);
11199
11200         // add title and subtitle
11201         each([
11202             ['title', titleOptions, chartTitleOptions],
11203             ['subtitle', subtitleOptions, chartSubtitleOptions]
11204         ], function (arr) {
11205             var name = arr[0],
11206                 title = chart[name],
11207                 titleOptions = arr[1],
11208                 chartTitleOptions = arr[2];
11209
11210             if (title && titleOptions) {
11211                 chart[name] = title = title.destroy(); // remove old
11212             }
11213             
11214             if (chartTitleOptions && chartTitleOptions.text && !title) {
11215                 chart[name] = chart.renderer.text(
11216                     chartTitleOptions.text,
11217                     0,
11218                     0,
11219                     chartTitleOptions.useHTML
11220                 )
11221                 .attr({
11222                     align: chartTitleOptions.align,
11223                     'class': PREFIX + name,
11224                     zIndex: chartTitleOptions.zIndex || 4
11225                 })
11226                 .css(chartTitleOptions.style)
11227                 .add();
11228             }    
11229         });
11230         chart.layOutTitles();
11231     },
11232
11233     /**
11234      * Lay out the chart titles and cache the full offset height for use in getMargins
11235      */
11236     layOutTitles: function () {
11237         var titleOffset = 0,
11238             title = this.title,
11239             subtitle = this.subtitle,
11240             options = this.options,
11241             titleOptions = options.title,
11242             subtitleOptions = options.subtitle,
11243             autoWidth = this.spacingBox.width - 44; // 44 makes room for default context button
11244
11245         if (title) {
11246             title
11247                 .css({ width: (titleOptions.width || autoWidth) + PX })
11248                 .align(extend({ y: 15 }, titleOptions), false, 'spacingBox');
11249             
11250             if (!titleOptions.floating && !titleOptions.verticalAlign) {
11251                 titleOffset = title.getBBox().height;
11252
11253                 // Adjust for browser consistency + backwards compat after #776 fix
11254                 if (titleOffset >= 18 && titleOffset <= 25) {
11255                     titleOffset = 15; 
11256                 }
11257             }
11258         }
11259         if (subtitle) {
11260             subtitle
11261                 .css({ width: (subtitleOptions.width || autoWidth) + PX })
11262                 .align(extend({ y: titleOffset + titleOptions.margin }, subtitleOptions), false, 'spacingBox');
11263             
11264             if (!subtitleOptions.floating && !subtitleOptions.verticalAlign) {
11265                 titleOffset = mathCeil(titleOffset + subtitle.getBBox().height);
11266             }
11267         }
11268
11269         this.titleOffset = titleOffset; // used in getMargins
11270     },
11271
11272     /**
11273      * Get chart width and height according to options and container size
11274      */
11275     getChartSize: function () {
11276         var chart = this,
11277             optionsChart = chart.options.chart,
11278             renderTo = chart.renderToClone || chart.renderTo;
11279
11280         // get inner width and height from jQuery (#824)
11281         chart.containerWidth = adapterRun(renderTo, 'width');
11282         chart.containerHeight = adapterRun(renderTo, 'height');
11283         
11284         chart.chartWidth = mathMax(0, optionsChart.width || chart.containerWidth || 600); // #1393, 1460
11285         chart.chartHeight = mathMax(0, pick(optionsChart.height,
11286             // the offsetHeight of an empty container is 0 in standard browsers, but 19 in IE7:
11287             chart.containerHeight > 19 ? chart.containerHeight : 400));
11288     },
11289
11290     /**
11291      * Create a clone of the chart's renderTo div and place it outside the viewport to allow
11292      * size computation on chart.render and chart.redraw
11293      */
11294     cloneRenderTo: function (revert) {
11295         var clone = this.renderToClone,
11296             container = this.container;
11297         
11298         // Destroy the clone and bring the container back to the real renderTo div
11299         if (revert) {
11300             if (clone) {
11301                 this.renderTo.appendChild(container);
11302                 discardElement(clone);
11303                 delete this.renderToClone;
11304             }
11305         
11306         // Set up the clone
11307         } else {
11308             if (container && container.parentNode === this.renderTo) {
11309                 this.renderTo.removeChild(container); // do not clone this
11310             }
11311             this.renderToClone = clone = this.renderTo.cloneNode(0);
11312             css(clone, {
11313                 position: ABSOLUTE,
11314                 top: '-9999px',
11315                 display: 'block' // #833
11316             });
11317             doc.body.appendChild(clone);
11318             if (container) {
11319                 clone.appendChild(container);
11320             }
11321         }
11322     },
11323
11324     /**
11325      * Get the containing element, determine the size and create the inner container
11326      * div to hold the chart
11327      */
11328     getContainer: function () {
11329         var chart = this,
11330             container,
11331             optionsChart = chart.options.chart,
11332             chartWidth,
11333             chartHeight,
11334             renderTo,
11335             indexAttrName = 'data-highcharts-chart',
11336             oldChartIndex,
11337             containerId;
11338
11339         chart.renderTo = renderTo = optionsChart.renderTo;
11340         containerId = PREFIX + idCounter++;
11341
11342         if (isString(renderTo)) {
11343             chart.renderTo = renderTo = doc.getElementById(renderTo);
11344         }
11345         
11346         // Display an error if the renderTo is wrong
11347         if (!renderTo) {
11348             error(13, true);
11349         }
11350         
11351         // If the container already holds a chart, destroy it
11352         oldChartIndex = pInt(attr(renderTo, indexAttrName));
11353         if (!isNaN(oldChartIndex) && charts[oldChartIndex]) {
11354             charts[oldChartIndex].destroy();
11355         }        
11356         
11357         // Make a reference to the chart from the div
11358         attr(renderTo, indexAttrName, chart.index);
11359
11360         // remove previous chart
11361         renderTo.innerHTML = '';
11362
11363         // If the container doesn't have an offsetWidth, it has or is a child of a node
11364         // that has display:none. We need to temporarily move it out to a visible
11365         // state to determine the size, else the legend and tooltips won't render
11366         // properly
11367         if (!renderTo.offsetWidth) {
11368             chart.cloneRenderTo();
11369         }
11370
11371         // get the width and height
11372         chart.getChartSize();
11373         chartWidth = chart.chartWidth;
11374         chartHeight = chart.chartHeight;
11375
11376         // create the inner container
11377         chart.container = container = createElement(DIV, {
11378                 className: PREFIX + 'container' +
11379                     (optionsChart.className ? ' ' + optionsChart.className : ''),
11380                 id: containerId
11381             }, extend({
11382                 position: RELATIVE,
11383                 overflow: HIDDEN, // needed for context menu (avoid scrollbars) and
11384                     // content overflow in IE
11385                 width: chartWidth + PX,
11386                 height: chartHeight + PX,
11387                 textAlign: 'left',
11388                 lineHeight: 'normal', // #427
11389                 zIndex: 0, // #1072
11390                 '-webkit-tap-highlight-color': 'rgba(0,0,0,0)'
11391             }, optionsChart.style),
11392             chart.renderToClone || renderTo
11393         );
11394
11395         // cache the cursor (#1650)
11396         chart._cursor = container.style.cursor;
11397
11398         chart.renderer =
11399             optionsChart.forExport ? // force SVG, used for SVG export
11400                 new SVGRenderer(container, chartWidth, chartHeight, true) :
11401                 new Renderer(container, chartWidth, chartHeight);
11402
11403         if (useCanVG) {
11404             // If we need canvg library, extend and configure the renderer
11405             // to get the tracker for translating mouse events
11406             chart.renderer.create(chart, container, chartWidth, chartHeight);
11407         }
11408     },
11409
11410     /**
11411      * Calculate margins by rendering axis labels in a preliminary position. Title,
11412      * subtitle and legend have already been rendered at this stage, but will be
11413      * moved into their final positions
11414      */
11415     getMargins: function () {
11416         var chart = this,
11417             spacing = chart.spacing,
11418             axisOffset,
11419             legend = chart.legend,
11420             margin = chart.margin,
11421             legendOptions = chart.options.legend,
11422             legendMargin = pick(legendOptions.margin, 10),
11423             legendX = legendOptions.x,
11424             legendY = legendOptions.y,
11425             align = legendOptions.align,
11426             verticalAlign = legendOptions.verticalAlign,
11427             titleOffset = chart.titleOffset;
11428
11429         chart.resetMargins();
11430         axisOffset = chart.axisOffset;
11431
11432         // Adjust for title and subtitle
11433         if (titleOffset && !defined(margin[0])) {
11434             chart.plotTop = mathMax(chart.plotTop, titleOffset + chart.options.title.margin + spacing[0]);
11435         }
11436         
11437         // Adjust for legend
11438         if (legend.display && !legendOptions.floating) {
11439             if (align === 'right') { // horizontal alignment handled first
11440                 if (!defined(margin[1])) {
11441                     chart.marginRight = mathMax(
11442                         chart.marginRight,
11443                         legend.legendWidth - legendX + legendMargin + spacing[1]
11444                     );
11445                 }
11446             } else if (align === 'left') {
11447                 if (!defined(margin[3])) {
11448                     chart.plotLeft = mathMax(
11449                         chart.plotLeft,
11450                         legend.legendWidth + legendX + legendMargin + spacing[3]
11451                     );
11452                 }
11453
11454             } else if (verticalAlign === 'top') {
11455                 if (!defined(margin[0])) {
11456                     chart.plotTop = mathMax(
11457                         chart.plotTop,
11458                         legend.legendHeight + legendY + legendMargin + spacing[0]
11459                     );
11460                 }
11461
11462             } else if (verticalAlign === 'bottom') {
11463                 if (!defined(margin[2])) {
11464                     chart.marginBottom = mathMax(
11465                         chart.marginBottom,
11466                         legend.legendHeight - legendY + legendMargin + spacing[2]
11467                     );
11468                 }
11469             }
11470         }
11471
11472         // adjust for scroller
11473         if (chart.extraBottomMargin) {
11474             chart.marginBottom += chart.extraBottomMargin;
11475         }
11476         if (chart.extraTopMargin) {
11477             chart.plotTop += chart.extraTopMargin;
11478         }
11479
11480         // pre-render axes to get labels offset width
11481         if (chart.hasCartesianSeries) {
11482             each(chart.axes, function (axis) {
11483                 axis.getOffset();
11484             });
11485         }
11486         
11487         if (!defined(margin[3])) {
11488             chart.plotLeft += axisOffset[3];
11489         }
11490         if (!defined(margin[0])) {
11491             chart.plotTop += axisOffset[0];
11492         }
11493         if (!defined(margin[2])) {
11494             chart.marginBottom += axisOffset[2];
11495         }
11496         if (!defined(margin[1])) {
11497             chart.marginRight += axisOffset[1];
11498         }
11499
11500         chart.setChartSize();
11501
11502     },
11503
11504     /**
11505      * Add the event handlers necessary for auto resizing
11506      *
11507      */
11508     initReflow: function () {
11509         var chart = this,
11510             optionsChart = chart.options.chart,
11511             renderTo = chart.renderTo,
11512             reflowTimeout;
11513             
11514         function reflow(e) {
11515             var width = optionsChart.width || adapterRun(renderTo, 'width'),
11516                 height = optionsChart.height || adapterRun(renderTo, 'height'),
11517                 target = e ? e.target : win; // #805 - MooTools doesn't supply e
11518                 
11519             // Width and height checks for display:none. Target is doc in IE8 and Opera,
11520             // win in Firefox, Chrome and IE9.
11521             if (!chart.hasUserSize && width && height && (target === win || target === doc)) {
11522                 
11523                 if (width !== chart.containerWidth || height !== chart.containerHeight) {
11524                     clearTimeout(reflowTimeout);
11525                     chart.reflowTimeout = reflowTimeout = setTimeout(function () {
11526                         if (chart.container) { // It may have been destroyed in the meantime (#1257)
11527                             chart.setSize(width, height, false);
11528                             chart.hasUserSize = null;
11529                         }
11530                     }, 100);
11531                 }
11532                 chart.containerWidth = width;
11533                 chart.containerHeight = height;
11534             }
11535         }
11536         chart.reflow = reflow;
11537         addEvent(win, 'resize', reflow);
11538         addEvent(chart, 'destroy', function () {
11539             removeEvent(win, 'resize', reflow);
11540         });
11541     },
11542
11543     /**
11544      * Resize the chart to a given width and height
11545      * @param {Number} width
11546      * @param {Number} height
11547      * @param {Object|Boolean} animation
11548      */
11549     setSize: function (width, height, animation) {
11550         var chart = this,
11551             chartWidth,
11552             chartHeight,
11553             fireEndResize;
11554
11555         // Handle the isResizing counter
11556         chart.isResizing += 1;
11557         fireEndResize = function () {
11558             if (chart) {
11559                 fireEvent(chart, 'endResize', null, function () {
11560                     chart.isResizing -= 1;
11561                 });
11562             }
11563         };
11564
11565         // set the animation for the current process
11566         setAnimation(animation, chart);
11567
11568         chart.oldChartHeight = chart.chartHeight;
11569         chart.oldChartWidth = chart.chartWidth;
11570         if (defined(width)) {
11571             chart.chartWidth = chartWidth = mathMax(0, mathRound(width));
11572             chart.hasUserSize = !!chartWidth;
11573         }
11574         if (defined(height)) {
11575             chart.chartHeight = chartHeight = mathMax(0, mathRound(height));
11576         }
11577
11578         css(chart.container, {
11579             width: chartWidth + PX,
11580             height: chartHeight + PX
11581         });
11582         chart.setChartSize(true);
11583         chart.renderer.setSize(chartWidth, chartHeight, animation);
11584
11585         // handle axes
11586         chart.maxTicks = null;
11587         each(chart.axes, function (axis) {
11588             axis.isDirty = true;
11589             axis.setScale();
11590         });
11591
11592         // make sure non-cartesian series are also handled
11593         each(chart.series, function (serie) {
11594             serie.isDirty = true;
11595         });
11596
11597         chart.isDirtyLegend = true; // force legend redraw
11598         chart.isDirtyBox = true; // force redraw of plot and chart border
11599
11600         chart.getMargins();
11601
11602         chart.redraw(animation);
11603
11604
11605         chart.oldChartHeight = null;
11606         fireEvent(chart, 'resize');
11607
11608         // fire endResize and set isResizing back
11609         // If animation is disabled, fire without delay
11610         if (globalAnimation === false) {
11611             fireEndResize();
11612         } else { // else set a timeout with the animation duration
11613             setTimeout(fireEndResize, (globalAnimation && globalAnimation.duration) || 500);
11614         }
11615     },
11616
11617     /**
11618      * Set the public chart properties. This is done before and after the pre-render
11619      * to determine margin sizes
11620      */
11621     setChartSize: function (skipAxes) {
11622         var chart = this,
11623             inverted = chart.inverted,
11624             renderer = chart.renderer,
11625             chartWidth = chart.chartWidth,
11626             chartHeight = chart.chartHeight,
11627             optionsChart = chart.options.chart,
11628             spacing = chart.spacing,
11629             clipOffset = chart.clipOffset,
11630             clipX,
11631             clipY,
11632             plotLeft,
11633             plotTop,
11634             plotWidth,
11635             plotHeight,
11636             plotBorderWidth;
11637
11638         chart.plotLeft = plotLeft = mathRound(chart.plotLeft);
11639         chart.plotTop = plotTop = mathRound(chart.plotTop);
11640         chart.plotWidth = plotWidth = mathMax(0, mathRound(chartWidth - plotLeft - chart.marginRight));
11641         chart.plotHeight = plotHeight = mathMax(0, mathRound(chartHeight - plotTop - chart.marginBottom));
11642
11643         chart.plotSizeX = inverted ? plotHeight : plotWidth;
11644         chart.plotSizeY = inverted ? plotWidth : plotHeight;
11645         
11646         chart.plotBorderWidth = optionsChart.plotBorderWidth || 0;
11647
11648         // Set boxes used for alignment
11649         chart.spacingBox = renderer.spacingBox = {
11650             x: spacing[3],
11651             y: spacing[0],
11652             width: chartWidth - spacing[3] - spacing[1],
11653             height: chartHeight - spacing[0] - spacing[2]
11654         };
11655         chart.plotBox = renderer.plotBox = {
11656             x: plotLeft,
11657             y: plotTop,
11658             width: plotWidth,
11659             height: plotHeight
11660         };
11661
11662         plotBorderWidth = 2 * mathFloor(chart.plotBorderWidth / 2);
11663         clipX = mathCeil(mathMax(plotBorderWidth, clipOffset[3]) / 2);
11664         clipY = mathCeil(mathMax(plotBorderWidth, clipOffset[0]) / 2);
11665         chart.clipBox = {
11666             x: clipX, 
11667             y: clipY, 
11668             width: mathFloor(chart.plotSizeX - mathMax(plotBorderWidth, clipOffset[1]) / 2 - clipX), 
11669             height: mathFloor(chart.plotSizeY - mathMax(plotBorderWidth, clipOffset[2]) / 2 - clipY)
11670         };
11671
11672         if (!skipAxes) {
11673             each(chart.axes, function (axis) {
11674                 axis.setAxisSize();
11675                 axis.setAxisTranslation();
11676             });
11677         }
11678     },
11679
11680     /**
11681      * Initial margins before auto size margins are applied
11682      */
11683     resetMargins: function () {
11684         var chart = this,
11685             spacing = chart.spacing,
11686             margin = chart.margin;
11687
11688         chart.plotTop = pick(margin[0], spacing[0]);
11689         chart.marginRight = pick(margin[1], spacing[1]);
11690         chart.marginBottom = pick(margin[2], spacing[2]);
11691         chart.plotLeft = pick(margin[3], spacing[3]);
11692         chart.axisOffset = [0, 0, 0, 0]; // top, right, bottom, left
11693         chart.clipOffset = [0, 0, 0, 0];
11694     },
11695
11696     /**
11697      * Draw the borders and backgrounds for chart and plot area
11698      */
11699     drawChartBox: function () {
11700         var chart = this,
11701             optionsChart = chart.options.chart,
11702             renderer = chart.renderer,
11703             chartWidth = chart.chartWidth,
11704             chartHeight = chart.chartHeight,
11705             chartBackground = chart.chartBackground,
11706             plotBackground = chart.plotBackground,
11707             plotBorder = chart.plotBorder,
11708             plotBGImage = chart.plotBGImage,
11709             chartBorderWidth = optionsChart.borderWidth || 0,
11710             chartBackgroundColor = optionsChart.backgroundColor,
11711             plotBackgroundColor = optionsChart.plotBackgroundColor,
11712             plotBackgroundImage = optionsChart.plotBackgroundImage,
11713             plotBorderWidth = optionsChart.plotBorderWidth || 0,
11714             mgn,
11715             bgAttr,
11716             plotLeft = chart.plotLeft,
11717             plotTop = chart.plotTop,
11718             plotWidth = chart.plotWidth,
11719             plotHeight = chart.plotHeight,
11720             plotBox = chart.plotBox,
11721             clipRect = chart.clipRect,
11722             clipBox = chart.clipBox;
11723
11724         // Chart area
11725         mgn = chartBorderWidth + (optionsChart.shadow ? 8 : 0);
11726
11727         if (chartBorderWidth || chartBackgroundColor) {
11728             if (!chartBackground) {
11729                 
11730                 bgAttr = {
11731                     fill: chartBackgroundColor || NONE
11732                 };
11733                 if (chartBorderWidth) { // #980
11734                     bgAttr.stroke = optionsChart.borderColor;
11735                     bgAttr['stroke-width'] = chartBorderWidth;
11736                 }
11737                 chart.chartBackground = renderer.rect(mgn / 2, mgn / 2, chartWidth - mgn, chartHeight - mgn,
11738                         optionsChart.borderRadius, chartBorderWidth)
11739                     .attr(bgAttr)
11740                     .add()
11741                     .shadow(optionsChart.shadow);
11742
11743             } else { // resize
11744                 chartBackground.animate(
11745                     chartBackground.crisp(null, null, null, chartWidth - mgn, chartHeight - mgn)
11746                 );
11747             }
11748         }
11749
11750
11751         // Plot background
11752         if (plotBackgroundColor) {
11753             if (!plotBackground) {
11754                 chart.plotBackground = renderer.rect(plotLeft, plotTop, plotWidth, plotHeight, 0)
11755                     .attr({
11756                         fill: plotBackgroundColor
11757                     })
11758                     .add()
11759                     .shadow(optionsChart.plotShadow);
11760             } else {
11761                 plotBackground.animate(plotBox);
11762             }
11763         }
11764         if (plotBackgroundImage) {
11765             if (!plotBGImage) {
11766                 chart.plotBGImage = renderer.image(plotBackgroundImage, plotLeft, plotTop, plotWidth, plotHeight)
11767                     .add();
11768             } else {
11769                 plotBGImage.animate(plotBox);
11770             }
11771         }
11772         
11773         // Plot clip
11774         if (!clipRect) {
11775             chart.clipRect = renderer.clipRect(clipBox);
11776         } else {
11777             clipRect.animate({
11778                 width: clipBox.width,
11779                 height: clipBox.height
11780             });
11781         }
11782
11783         // Plot area border
11784         if (plotBorderWidth) {
11785             if (!plotBorder) {
11786                 chart.plotBorder = renderer.rect(plotLeft, plotTop, plotWidth, plotHeight, 0, -plotBorderWidth)
11787                     .attr({
11788                         stroke: optionsChart.plotBorderColor,
11789                         'stroke-width': plotBorderWidth,
11790                         zIndex: 1
11791                     })
11792                     .add();
11793             } else {
11794                 plotBorder.animate(
11795                     plotBorder.crisp(null, plotLeft, plotTop, plotWidth, plotHeight)
11796                 );
11797             }
11798         }
11799
11800         // reset
11801         chart.isDirtyBox = false;
11802     },
11803
11804     /**
11805      * Detect whether a certain chart property is needed based on inspecting its options
11806      * and series. This mainly applies to the chart.invert property, and in extensions to 
11807      * the chart.angular and chart.polar properties.
11808      */
11809     propFromSeries: function () {
11810         var chart = this,
11811             optionsChart = chart.options.chart,
11812             klass,
11813             seriesOptions = chart.options.series,
11814             i,
11815             value;
11816             
11817             
11818         each(['inverted', 'angular', 'polar'], function (key) {
11819             
11820             // The default series type's class
11821             klass = seriesTypes[optionsChart.type || optionsChart.defaultSeriesType];
11822             
11823             // Get the value from available chart-wide properties
11824             value = (
11825                 chart[key] || // 1. it is set before
11826                 optionsChart[key] || // 2. it is set in the options
11827                 (klass && klass.prototype[key]) // 3. it's default series class requires it
11828             );
11829     
11830             // 4. Check if any the chart's series require it
11831             i = seriesOptions && seriesOptions.length;
11832             while (!value && i--) {
11833                 klass = seriesTypes[seriesOptions[i].type];
11834                 if (klass && klass.prototype[key]) {
11835                     value = true;
11836                 }
11837             }
11838     
11839             // Set the chart property
11840             chart[key] = value;    
11841         });
11842         
11843     },
11844
11845     /**
11846      * Link two or more series together. This is done initially from Chart.render,
11847      * and after Chart.addSeries and Series.remove.
11848      */
11849     linkSeries: function () {
11850         var chart = this,
11851             chartSeries = chart.series;
11852
11853         // Reset links
11854         each(chartSeries, function (series) {
11855             series.linkedSeries.length = 0;
11856         });
11857
11858         // Apply new links
11859         each(chartSeries, function (series) {
11860             var linkedTo = series.options.linkedTo;
11861             if (isString(linkedTo)) {
11862                 if (linkedTo === ':previous') {
11863                     linkedTo = chart.series[series.index - 1];
11864                 } else {
11865                     linkedTo = chart.get(linkedTo);
11866                 }
11867                 if (linkedTo) {
11868                     linkedTo.linkedSeries.push(series);
11869                     series.linkedParent = linkedTo;
11870                 }
11871             }
11872         });
11873     },
11874
11875     /**
11876      * Render all graphics for the chart
11877      */
11878     render: function () {
11879         var chart = this,
11880             axes = chart.axes,
11881             renderer = chart.renderer,
11882             options = chart.options;
11883
11884         var labels = options.labels,
11885             credits = options.credits,
11886             creditsHref;
11887
11888         // Title
11889         chart.setTitle();
11890
11891
11892         // Legend
11893         chart.legend = new Legend(chart, options.legend);
11894
11895         chart.getStacks(); // render stacks
11896
11897         // Get margins by pre-rendering axes
11898         // set axes scales
11899         each(axes, function (axis) {
11900             axis.setScale();
11901         });
11902
11903         chart.getMargins();
11904
11905         chart.maxTicks = null; // reset for second pass
11906         each(axes, function (axis) {
11907             axis.setTickPositions(true); // update to reflect the new margins
11908             axis.setMaxTicks();
11909         });
11910         chart.adjustTickAmounts();
11911         chart.getMargins(); // second pass to check for new labels
11912
11913
11914         // Draw the borders and backgrounds
11915         chart.drawChartBox();        
11916
11917
11918         // Axes
11919         if (chart.hasCartesianSeries) {
11920             each(axes, function (axis) {
11921                 axis.render();
11922             });
11923         }
11924
11925         // The series
11926         if (!chart.seriesGroup) {
11927             chart.seriesGroup = renderer.g('series-group')
11928                 .attr({ zIndex: 3 })
11929                 .add();
11930         }
11931         each(chart.series, function (serie) {
11932             serie.translate();
11933             serie.setTooltipPoints();
11934             serie.render();
11935         });
11936
11937         // Labels
11938         if (labels.items) {
11939             each(labels.items, function (label) {
11940                 var style = extend(labels.style, label.style),
11941                     x = pInt(style.left) + chart.plotLeft,
11942                     y = pInt(style.top) + chart.plotTop + 12;
11943
11944                 // delete to prevent rewriting in IE
11945                 delete style.left;
11946                 delete style.top;
11947
11948                 renderer.text(
11949                     label.html,
11950                     x,
11951                     y
11952                 )
11953                 .attr({ zIndex: 2 })
11954                 .css(style)
11955                 .add();
11956
11957             });
11958         }
11959
11960         // Credits
11961         if (credits.enabled && !chart.credits) {
11962             creditsHref = credits.href;
11963             chart.credits = renderer.text(
11964                 credits.text,
11965                 0,
11966                 0
11967             )
11968             .on('click', function () {
11969                 if (creditsHref) {
11970                     location.href = creditsHref;
11971                 }
11972             })
11973             .attr({
11974                 align: credits.position.align,
11975                 zIndex: 8
11976             })
11977             .css(credits.style)
11978             .add()
11979             .align(credits.position);
11980         }
11981
11982         // Set flag
11983         chart.hasRendered = true;
11984
11985     },
11986
11987     /**
11988      * Clean up memory usage
11989      */
11990     destroy: function () {
11991         var chart = this,
11992             axes = chart.axes,
11993             series = chart.series,
11994             container = chart.container,
11995             i,
11996             parentNode = container && container.parentNode;
11997             
11998         // fire the chart.destoy event
11999         fireEvent(chart, 'destroy');
12000         
12001         // Delete the chart from charts lookup array
12002         charts[chart.index] = UNDEFINED;
12003         chart.renderTo.removeAttribute('data-highcharts-chart');
12004
12005         // remove events
12006         removeEvent(chart);
12007
12008         // ==== Destroy collections:
12009         // Destroy axes
12010         i = axes.length;
12011         while (i--) {
12012             axes[i] = axes[i].destroy();
12013         }
12014
12015         // Destroy each series
12016         i = series.length;
12017         while (i--) {
12018             series[i] = series[i].destroy();
12019         }
12020
12021         // ==== Destroy chart properties:
12022         each(['title', 'subtitle', 'chartBackground', 'plotBackground', 'plotBGImage', 
12023                 'plotBorder', 'seriesGroup', 'clipRect', 'credits', 'pointer', 'scroller', 
12024                 'rangeSelector', 'legend', 'resetZoomButton', 'tooltip', 'renderer'], function (name) {
12025             var prop = chart[name];
12026
12027             if (prop && prop.destroy) {
12028                 chart[name] = prop.destroy();
12029             }
12030         });
12031
12032         // remove container and all SVG
12033         if (container) { // can break in IE when destroyed before finished loading
12034             container.innerHTML = '';
12035             removeEvent(container);
12036             if (parentNode) {
12037                 discardElement(container);
12038             }
12039
12040         }
12041
12042         // clean it all up
12043         for (i in chart) {
12044             delete chart[i];
12045         }
12046
12047     },
12048
12049
12050     /**
12051      * VML namespaces can't be added until after complete. Listening
12052      * for Perini's doScroll hack is not enough.
12053      */
12054     isReadyToRender: function () {
12055         var chart = this;
12056
12057         // Note: in spite of JSLint's complaints, win == win.top is required
12058         /*jslint eqeq: true*/
12059         if ((!hasSVG && (win == win.top && doc.readyState !== 'complete')) || (useCanVG && !win.canvg)) {
12060         /*jslint eqeq: false*/
12061             if (useCanVG) {
12062                 // Delay rendering until canvg library is downloaded and ready
12063                 CanVGController.push(function () { chart.firstRender(); }, chart.options.global.canvasToolsURL);
12064             } else {
12065                 doc.attachEvent('onreadystatechange', function () {
12066                     doc.detachEvent('onreadystatechange', chart.firstRender);
12067                     if (doc.readyState === 'complete') {
12068                         chart.firstRender();
12069                     }
12070                 });
12071             }
12072             return false;
12073         }
12074         return true;
12075     },
12076
12077     /**
12078      * Prepare for first rendering after all data are loaded
12079      */
12080     firstRender: function () {
12081         var chart = this,
12082             options = chart.options,
12083             callback = chart.callback;
12084
12085         // Check whether the chart is ready to render
12086         if (!chart.isReadyToRender()) {
12087             return;
12088         }
12089
12090         // Create the container
12091         chart.getContainer();
12092
12093         // Run an early event after the container and renderer are established
12094         fireEvent(chart, 'init');
12095
12096         
12097         chart.resetMargins();
12098         chart.setChartSize();
12099
12100         // Set the common chart properties (mainly invert) from the given series
12101         chart.propFromSeries();
12102
12103         // get axes
12104         chart.getAxes();
12105
12106         // Initialize the series
12107         each(options.series || [], function (serieOptions) {
12108             chart.initSeries(serieOptions);
12109         });
12110
12111         chart.linkSeries();
12112
12113         // Run an event after axes and series are initialized, but before render. At this stage,
12114         // the series data is indexed and cached in the xData and yData arrays, so we can access
12115         // those before rendering. Used in Highstock. 
12116         fireEvent(chart, 'beforeRender'); 
12117
12118         // depends on inverted and on margins being set
12119         chart.pointer = new Pointer(chart, options);
12120
12121         chart.render();
12122
12123         // add canvas
12124         chart.renderer.draw();
12125         // run callbacks
12126         if (callback) {
12127             callback.apply(chart, [chart]);
12128         }
12129         each(chart.callbacks, function (fn) {
12130             fn.apply(chart, [chart]);
12131         });
12132         
12133         
12134         // If the chart was rendered outside the top container, put it back in
12135         chart.cloneRenderTo(true);
12136
12137         fireEvent(chart, 'load');
12138
12139     },
12140
12141     /**
12142     * Creates arrays for spacing and margin from given options.
12143     */
12144     splashArray: function (target, options) {
12145         var oVar = options[target],
12146             tArray = isObject(oVar) ? oVar : [oVar, oVar, oVar, oVar];
12147
12148         return [pick(options[target + 'Top'], tArray[0]),
12149                 pick(options[target + 'Right'], tArray[1]),
12150                 pick(options[target + 'Bottom'], tArray[2]),
12151                 pick(options[target + 'Left'], tArray[3])];
12152     }
12153 }; // end Chart
12154
12155 // Hook for exporting module
12156 Chart.prototype.callbacks = [];
12157 /**
12158  * The Point object and prototype. Inheritable and used as base for PiePoint
12159  */
12160 var Point = function () {};
12161 Point.prototype = {
12162
12163     /**
12164      * Initialize the point
12165      * @param {Object} series The series object containing this point
12166      * @param {Object} options The data in either number, array or object format
12167      */
12168     init: function (series, options, x) {
12169
12170         var point = this,
12171             colors;
12172         point.series = series;
12173         point.applyOptions(options, x);
12174         point.pointAttr = {};
12175
12176         if (series.options.colorByPoint) {
12177             colors = series.options.colors || series.chart.options.colors;
12178             point.color = point.color || colors[series.colorCounter++];
12179             // loop back to zero
12180             if (series.colorCounter === colors.length) {
12181                 series.colorCounter = 0;
12182             }
12183         }
12184
12185         series.chart.pointCount++;
12186         return point;
12187     },
12188     /**
12189      * Apply the options containing the x and y data and possible some extra properties.
12190      * This is called on point init or from point.update.
12191      *
12192      * @param {Object} options
12193      */
12194     applyOptions: function (options, x) {
12195         var point = this,
12196             series = point.series,
12197             pointValKey = series.pointValKey;
12198
12199         options = Point.prototype.optionsToObject.call(this, options);
12200
12201         // copy options directly to point
12202         extend(point, options);
12203         point.options = point.options ? extend(point.options, options) : options;
12204             
12205         // For higher dimension series types. For instance, for ranges, point.y is mapped to point.low.
12206         if (pointValKey) {
12207             point.y = point[pointValKey];
12208         }
12209         
12210         // If no x is set by now, get auto incremented value. All points must have an
12211         // x value, however the y value can be null to create a gap in the series
12212         if (point.x === UNDEFINED && series) {
12213             point.x = x === UNDEFINED ? series.autoIncrement() : x;
12214         }
12215         
12216         return point;
12217     },
12218
12219     /**
12220      * Transform number or array configs into objects
12221      */
12222     optionsToObject: function (options) {
12223         var ret,
12224             series = this.series,
12225             pointArrayMap = series.pointArrayMap || ['y'],
12226             valueCount = pointArrayMap.length,
12227             firstItemType,
12228             i = 0,
12229             j = 0;
12230
12231         if (typeof options === 'number' || options === null) {
12232             ret = { y: options };
12233
12234         } else if (isArray(options)) {
12235             ret = {};
12236             // with leading x value
12237             if (options.length > valueCount) {
12238                 firstItemType = typeof options[0];
12239                 if (firstItemType === 'string') {
12240                     ret.name = options[0];
12241                 } else if (firstItemType === 'number') {
12242                     ret.x = options[0];
12243                 }
12244                 i++;
12245             }
12246             while (j < valueCount) {
12247                 ret[pointArrayMap[j++]] = options[i++];
12248             }            
12249         } else if (typeof options === 'object') {
12250             ret = options;
12251
12252             // This is the fastest way to detect if there are individual point dataLabels that need 
12253             // to be considered in drawDataLabels. These can only occur in object configs.
12254             if (options.dataLabels) {
12255                 series._hasPointLabels = true;
12256             }
12257
12258             // Same approach as above for markers
12259             if (options.marker) {
12260                 series._hasPointMarkers = true;
12261             }
12262         }
12263         return ret;
12264     },
12265
12266     /**
12267      * Destroy a point to clear memory. Its reference still stays in series.data.
12268      */
12269     destroy: function () {
12270         var point = this,
12271             series = point.series,
12272             chart = series.chart,
12273             hoverPoints = chart.hoverPoints,
12274             prop;
12275
12276         chart.pointCount--;
12277
12278         if (hoverPoints) {
12279             point.setState();
12280             erase(hoverPoints, point);
12281             if (!hoverPoints.length) {
12282                 chart.hoverPoints = null;
12283             }
12284
12285         }
12286         if (point === chart.hoverPoint) {
12287             point.onMouseOut();
12288         }
12289         
12290         // remove all events
12291         if (point.graphic || point.dataLabel) { // removeEvent and destroyElements are performance expensive
12292             removeEvent(point);
12293             point.destroyElements();
12294         }
12295
12296         if (point.legendItem) { // pies have legend items
12297             chart.legend.destroyItem(point);
12298         }
12299
12300         for (prop in point) {
12301             point[prop] = null;
12302         }
12303
12304
12305     },
12306
12307     /**
12308      * Destroy SVG elements associated with the point
12309      */
12310     destroyElements: function () {
12311         var point = this,
12312             props = ['graphic', 'dataLabel', 'dataLabelUpper', 'group', 'connector', 'shadowGroup'],
12313             prop,
12314             i = 6;
12315         while (i--) {
12316             prop = props[i];
12317             if (point[prop]) {
12318                 point[prop] = point[prop].destroy();
12319             }
12320         }
12321     },
12322
12323     /**
12324      * Return the configuration hash needed for the data label and tooltip formatters
12325      */
12326     getLabelConfig: function () {
12327         var point = this;
12328         return {
12329             x: point.category,
12330             y: point.y,
12331             key: point.name || point.category,
12332             series: point.series,
12333             point: point,
12334             percentage: point.percentage,
12335             total: point.total || point.stackTotal
12336         };
12337     },
12338
12339     /**
12340      * Toggle the selection status of a point
12341      * @param {Boolean} selected Whether to select or unselect the point.
12342      * @param {Boolean} accumulate Whether to add to the previous selection. By default,
12343      *     this happens if the control key (Cmd on Mac) was pressed during clicking.
12344      */
12345     select: function (selected, accumulate) {
12346         var point = this,
12347             series = point.series,
12348             chart = series.chart;
12349
12350         selected = pick(selected, !point.selected);
12351
12352         // fire the event with the defalut handler
12353         point.firePointEvent(selected ? 'select' : 'unselect', { accumulate: accumulate }, function () {
12354             point.selected = point.options.selected = selected;
12355             series.options.data[inArray(point, series.data)] = point.options;
12356             
12357             point.setState(selected && SELECT_STATE);
12358
12359             // unselect all other points unless Ctrl or Cmd + click
12360             if (!accumulate) {
12361                 each(chart.getSelectedPoints(), function (loopPoint) {
12362                     if (loopPoint.selected && loopPoint !== point) {
12363                         loopPoint.selected = loopPoint.options.selected = false;
12364                         series.options.data[inArray(loopPoint, series.data)] = loopPoint.options;
12365                         loopPoint.setState(NORMAL_STATE);
12366                         loopPoint.firePointEvent('unselect');
12367                     }
12368                 });
12369             }
12370         });
12371     },
12372
12373     /**
12374      * Runs on mouse over the point
12375      */
12376     onMouseOver: function (e) {
12377         var point = this,
12378             series = point.series,
12379             chart = series.chart,
12380             tooltip = chart.tooltip,
12381             hoverPoint = chart.hoverPoint;
12382
12383         // set normal state to previous series
12384         if (hoverPoint && hoverPoint !== point) {
12385             hoverPoint.onMouseOut();
12386         }
12387
12388         // trigger the event
12389         point.firePointEvent('mouseOver');
12390
12391         // update the tooltip
12392         if (tooltip && (!tooltip.shared || series.noSharedTooltip)) {
12393             tooltip.refresh(point, e);
12394         }
12395
12396         // hover this
12397         point.setState(HOVER_STATE);
12398         chart.hoverPoint = point;
12399     },
12400     
12401     /**
12402      * Runs on mouse out from the point
12403      */
12404     onMouseOut: function () {
12405         var chart = this.series.chart,
12406             hoverPoints = chart.hoverPoints;
12407         
12408         if (!hoverPoints || inArray(this, hoverPoints) === -1) { // #887
12409             this.firePointEvent('mouseOut');
12410     
12411             this.setState();
12412             chart.hoverPoint = null;
12413         }
12414     },
12415
12416     /**
12417      * Extendable method for formatting each point's tooltip line
12418      *
12419      * @return {String} A string to be concatenated in to the common tooltip text
12420      */
12421     tooltipFormatter: function (pointFormat) {
12422         
12423         // Insert options for valueDecimals, valuePrefix, and valueSuffix
12424         var series = this.series,
12425             seriesTooltipOptions = series.tooltipOptions,
12426             valueDecimals = pick(seriesTooltipOptions.valueDecimals, ''),
12427             valuePrefix = seriesTooltipOptions.valuePrefix || '',
12428             valueSuffix = seriesTooltipOptions.valueSuffix || '';
12429             
12430         // Loop over the point array map and replace unformatted values with sprintf formatting markup
12431         each(series.pointArrayMap || ['y'], function (key) {
12432             key = '{point.' + key; // without the closing bracket
12433             if (valuePrefix || valueSuffix) {
12434                 pointFormat = pointFormat.replace(key + '}', valuePrefix + key + '}' + valueSuffix);
12435             }
12436             pointFormat = pointFormat.replace(key + '}', key + ':,.' + valueDecimals + 'f}');
12437         });
12438         
12439         return format(pointFormat, {
12440             point: this,
12441             series: this.series
12442         });
12443     },
12444
12445     /**
12446      * Update the point with new options (typically x/y data) and optionally redraw the series.
12447      *
12448      * @param {Object} options Point options as defined in the series.data array
12449      * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
12450      * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
12451      *    configuration
12452      *
12453      */
12454     update: function (options, redraw, animation) {
12455         var point = this,
12456             series = point.series,
12457             graphic = point.graphic,
12458             i,
12459             data = series.data,
12460             chart = series.chart,
12461             seriesOptions = series.options;
12462
12463         redraw = pick(redraw, true);
12464
12465         // fire the event with a default handler of doing the update
12466         point.firePointEvent('update', { options: options }, function () {
12467
12468             point.applyOptions(options);
12469
12470             // update visuals
12471             if (isObject(options)) {
12472                 series.getAttribs();
12473                 if (graphic) {
12474                     if (options.marker && options.marker.symbol) {
12475                         point.graphic = graphic.destroy();
12476                     } else {
12477                         graphic.attr(point.pointAttr[point.state || '']);
12478                     }
12479                 }
12480             }
12481
12482             // record changes in the parallel arrays
12483             i = inArray(point, data);
12484             series.xData[i] = point.x;
12485             series.yData[i] = series.toYData ? series.toYData(point) : point.y;
12486             series.zData[i] = point.z;
12487             seriesOptions.data[i] = point.options;
12488
12489             // redraw
12490             series.isDirty = series.isDirtyData = true;
12491             if (!series.fixedBox && series.hasCartesianSeries) { // #1906, #2320
12492                 chart.isDirtyBox = true;
12493             }
12494             
12495             if (seriesOptions.legendType === 'point') { // #1831, #1885
12496                 chart.legend.destroyItem(point);
12497             }
12498             if (redraw) {
12499                 chart.redraw(animation);
12500             }
12501         });
12502     },
12503
12504     /**
12505      * Remove a point and optionally redraw the series and if necessary the axes
12506      * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
12507      * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
12508      *    configuration
12509      */
12510     remove: function (redraw, animation) {
12511         var point = this,
12512             series = point.series,
12513             points = series.points,
12514             chart = series.chart,
12515             i,
12516             data = series.data;
12517
12518         setAnimation(animation, chart);
12519         redraw = pick(redraw, true);
12520
12521         // fire the event with a default handler of removing the point
12522         point.firePointEvent('remove', null, function () {
12523
12524             // splice all the parallel arrays
12525             i = inArray(point, data);
12526             if (data.length === points.length) {
12527                 points.splice(i, 1);            
12528             }
12529             data.splice(i, 1);
12530             series.options.data.splice(i, 1);
12531             series.xData.splice(i, 1);
12532             series.yData.splice(i, 1);
12533             series.zData.splice(i, 1);
12534
12535             point.destroy();
12536
12537
12538             // redraw
12539             series.isDirty = true;
12540             series.isDirtyData = true;
12541             if (redraw) {
12542                 chart.redraw();
12543             }
12544         });
12545
12546
12547     },
12548
12549     /**
12550      * Fire an event on the Point object. Must not be renamed to fireEvent, as this
12551      * causes a name clash in MooTools
12552      * @param {String} eventType
12553      * @param {Object} eventArgs Additional event arguments
12554      * @param {Function} defaultFunction Default event handler
12555      */
12556     firePointEvent: function (eventType, eventArgs, defaultFunction) {
12557         var point = this,
12558             series = this.series,
12559             seriesOptions = series.options;
12560
12561         // load event handlers on demand to save time on mouseover/out
12562         if (seriesOptions.point.events[eventType] || (point.options && point.options.events && point.options.events[eventType])) {
12563             this.importEvents();
12564         }
12565
12566         // add default handler if in selection mode
12567         if (eventType === 'click' && seriesOptions.allowPointSelect) {
12568             defaultFunction = function (event) {
12569                 // Control key is for Windows, meta (= Cmd key) for Mac, Shift for Opera
12570                 point.select(null, event.ctrlKey || event.metaKey || event.shiftKey);
12571             };
12572         }
12573
12574         fireEvent(this, eventType, eventArgs, defaultFunction);
12575     },
12576     /**
12577      * Import events from the series' and point's options. Only do it on
12578      * demand, to save processing time on hovering.
12579      */
12580     importEvents: function () {
12581         if (!this.hasImportedEvents) {
12582             var point = this,
12583                 options = merge(point.series.options.point, point.options),
12584                 events = options.events,
12585                 eventType;
12586
12587             point.events = events;
12588
12589             for (eventType in events) {
12590                 addEvent(point, eventType, events[eventType]);
12591             }
12592             this.hasImportedEvents = true;
12593
12594         }
12595     },
12596
12597     /**
12598      * Set the point's state
12599      * @param {String} state
12600      */
12601     setState: function (state) {
12602         var point = this,
12603             plotX = point.plotX,
12604             plotY = point.plotY,
12605             series = point.series,
12606             stateOptions = series.options.states,
12607             markerOptions = defaultPlotOptions[series.type].marker && series.options.marker,
12608             normalDisabled = markerOptions && !markerOptions.enabled,
12609             markerStateOptions = markerOptions && markerOptions.states[state],
12610             stateDisabled = markerStateOptions && markerStateOptions.enabled === false,
12611             stateMarkerGraphic = series.stateMarkerGraphic,
12612             pointMarker = point.marker || {},
12613             chart = series.chart,
12614             radius,
12615             newSymbol,
12616             pointAttr = point.pointAttr;
12617
12618         state = state || NORMAL_STATE; // empty string
12619
12620         if (
12621                 // already has this state
12622                 state === point.state ||
12623                 // selected points don't respond to hover
12624                 (point.selected && state !== SELECT_STATE) ||
12625                 // series' state options is disabled
12626                 (stateOptions[state] && stateOptions[state].enabled === false) ||
12627                 // point marker's state options is disabled
12628                 (state && (stateDisabled || (normalDisabled && !markerStateOptions.enabled)))
12629
12630             ) {
12631             return;
12632         }
12633
12634         // apply hover styles to the existing point
12635         if (point.graphic) {
12636             radius = markerOptions && point.graphic.symbolName && pointAttr[state].r;
12637             point.graphic.attr(merge(
12638                 pointAttr[state],
12639                 radius ? { // new symbol attributes (#507, #612)
12640                     x: plotX - radius,
12641                     y: plotY - radius,
12642                     width: 2 * radius,
12643                     height: 2 * radius
12644                 } : {}
12645             ));
12646         } else {
12647             // if a graphic is not applied to each point in the normal state, create a shared
12648             // graphic for the hover state
12649             if (state && markerStateOptions) {
12650                 radius = markerStateOptions.radius;
12651                 newSymbol = pointMarker.symbol || series.symbol;
12652
12653                 // If the point has another symbol than the previous one, throw away the 
12654                 // state marker graphic and force a new one (#1459)
12655                 if (stateMarkerGraphic && stateMarkerGraphic.currentSymbol !== newSymbol) {                
12656                     stateMarkerGraphic = stateMarkerGraphic.destroy();
12657                 }
12658
12659                 // Add a new state marker graphic
12660                 if (!stateMarkerGraphic) {
12661                     series.stateMarkerGraphic = stateMarkerGraphic = chart.renderer.symbol(
12662                         newSymbol,
12663                         plotX - radius,
12664                         plotY - radius,
12665                         2 * radius,
12666                         2 * radius
12667                     )
12668                     .attr(pointAttr[state])
12669                     .add(series.markerGroup);
12670                     stateMarkerGraphic.currentSymbol = newSymbol;
12671                 
12672                 // Move the existing graphic
12673                 } else {
12674                     stateMarkerGraphic.attr({ // #1054
12675                         x: plotX - radius,
12676                         y: plotY - radius
12677                     });
12678                 }
12679             }
12680
12681             if (stateMarkerGraphic) {
12682                 stateMarkerGraphic[state && chart.isInsidePlot(plotX, plotY) ? 'show' : 'hide']();
12683             }
12684         }
12685
12686         point.state = state;
12687     }
12688 };
12689
12690 /**
12691  * @classDescription The base function which all other series types inherit from. The data in the series is stored
12692  * in various arrays.
12693  *
12694  * - First, series.options.data contains all the original config options for
12695  * each point whether added by options or methods like series.addPoint.
12696  * - Next, series.data contains those values converted to points, but in case the series data length
12697  * exceeds the cropThreshold, or if the data is grouped, series.data doesn't contain all the points. It
12698  * only contains the points that have been created on demand.
12699  * - Then there's series.points that contains all currently visible point objects. In case of cropping,
12700  * the cropped-away points are not part of this array. The series.points array starts at series.cropStart
12701  * compared to series.data and series.options.data. If however the series data is grouped, these can't
12702  * be correlated one to one.
12703  * - series.xData and series.processedXData contain clean x values, equivalent to series.data and series.points.
12704  * - series.yData and series.processedYData contain clean x values, equivalent to series.data and series.points.
12705  *
12706  * @param {Object} chart
12707  * @param {Object} options
12708  */
12709 var Series = function () {};
12710
12711 Series.prototype = {
12712
12713     isCartesian: true,
12714     type: 'line',
12715     pointClass: Point,
12716     sorted: true, // requires the data to be sorted
12717     requireSorting: true,
12718     pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
12719         stroke: 'lineColor',
12720         'stroke-width': 'lineWidth',
12721         fill: 'fillColor',
12722         r: 'radius'
12723     },
12724     colorCounter: 0,
12725     init: function (chart, options) {
12726         var series = this,
12727             eventType,
12728             events,
12729             chartSeries = chart.series;
12730
12731         series.chart = chart;
12732         series.options = options = series.setOptions(options); // merge with plotOptions
12733         series.linkedSeries = [];
12734
12735         // bind the axes
12736         series.bindAxes();
12737
12738         // set some variables
12739         extend(series, {
12740             name: options.name,
12741             state: NORMAL_STATE,
12742             pointAttr: {},
12743             visible: options.visible !== false, // true by default
12744             selected: options.selected === true // false by default
12745         });
12746         
12747         // special
12748         if (useCanVG) {
12749             options.animation = false;
12750         }
12751
12752         // register event listeners
12753         events = options.events;
12754         for (eventType in events) {
12755             addEvent(series, eventType, events[eventType]);
12756         }
12757         if (
12758             (events && events.click) ||
12759             (options.point && options.point.events && options.point.events.click) ||
12760             options.allowPointSelect
12761         ) {
12762             chart.runTrackerClick = true;
12763         }
12764
12765         series.getColor();
12766         series.getSymbol();
12767
12768         // set the data
12769         series.setData(options.data, false);
12770         
12771         // Mark cartesian
12772         if (series.isCartesian) {
12773             chart.hasCartesianSeries = true;
12774         }
12775
12776         // Register it in the chart
12777         chartSeries.push(series);
12778         series._i = chartSeries.length - 1;
12779         
12780         // Sort series according to index option (#248, #1123)
12781         stableSort(chartSeries, function (a, b) {
12782             return pick(a.options.index, a._i) - pick(b.options.index, a._i);
12783         });
12784         each(chartSeries, function (series, i) {
12785             series.index = i;
12786             series.name = series.name || 'Series ' + (i + 1);
12787         });
12788
12789     },
12790     
12791     /**
12792      * Set the xAxis and yAxis properties of cartesian series, and register the series
12793      * in the axis.series array
12794      */
12795     bindAxes: function () {
12796         var series = this,
12797             seriesOptions = series.options,
12798             chart = series.chart,
12799             axisOptions;
12800             
12801         if (series.isCartesian) {
12802             
12803             each(['xAxis', 'yAxis'], function (AXIS) { // repeat for xAxis and yAxis
12804                 
12805                 each(chart[AXIS], function (axis) { // loop through the chart's axis objects
12806                     
12807                     axisOptions = axis.options;
12808                     
12809                     // apply if the series xAxis or yAxis option mathches the number of the 
12810                     // axis, or if undefined, use the first axis
12811                     if ((seriesOptions[AXIS] === axisOptions.index) ||
12812                             (seriesOptions[AXIS] !== UNDEFINED && seriesOptions[AXIS] === axisOptions.id) ||
12813                             (seriesOptions[AXIS] === UNDEFINED && axisOptions.index === 0)) {
12814                         
12815                         // register this series in the axis.series lookup
12816                         axis.series.push(series);
12817                         
12818                         // set this series.xAxis or series.yAxis reference
12819                         series[AXIS] = axis;
12820                         
12821                         // mark dirty for redraw
12822                         axis.isDirty = true;
12823                     }
12824                 });
12825
12826                 // The series needs an X and an Y axis
12827                 if (!series[AXIS]) {
12828                     error(18, true);
12829                 }
12830
12831             });
12832         }
12833     },
12834
12835
12836     /**
12837      * Return an auto incremented x value based on the pointStart and pointInterval options.
12838      * This is only used if an x value is not given for the point that calls autoIncrement.
12839      */
12840     autoIncrement: function () {
12841         var series = this,
12842             options = series.options,
12843             xIncrement = series.xIncrement;
12844
12845         xIncrement = pick(xIncrement, options.pointStart, 0);
12846
12847         series.pointInterval = pick(series.pointInterval, options.pointInterval, 1);
12848
12849         series.xIncrement = xIncrement + series.pointInterval;
12850         return xIncrement;
12851     },
12852
12853     /**
12854      * Divide the series data into segments divided by null values.
12855      */
12856     getSegments: function () {
12857         var series = this,
12858             lastNull = -1,
12859             segments = [],
12860             i,
12861             points = series.points,
12862             pointsLength = points.length;
12863
12864         if (pointsLength) { // no action required for []
12865             
12866             // if connect nulls, just remove null points
12867             if (series.options.connectNulls) {
12868                 i = pointsLength;
12869                 while (i--) {
12870                     if (points[i].y === null) {
12871                         points.splice(i, 1);
12872                     }
12873                 }
12874                 if (points.length) {
12875                     segments = [points];
12876                 }
12877                 
12878             // else, split on null points
12879             } else {
12880                 each(points, function (point, i) {
12881                     if (point.y === null) {
12882                         if (i > lastNull + 1) {
12883                             segments.push(points.slice(lastNull + 1, i));
12884                         }
12885                         lastNull = i;
12886                     } else if (i === pointsLength - 1) { // last value
12887                         segments.push(points.slice(lastNull + 1, i + 1));
12888                     }
12889                 });
12890             }
12891         }
12892         
12893         // register it
12894         series.segments = segments;
12895     },
12896     
12897     /**
12898      * Set the series options by merging from the options tree
12899      * @param {Object} itemOptions
12900      */
12901     setOptions: function (itemOptions) {
12902         var chart = this.chart,
12903             chartOptions = chart.options,
12904             plotOptions = chartOptions.plotOptions,
12905             typeOptions = plotOptions[this.type],
12906             options;
12907
12908         this.userOptions = itemOptions;
12909
12910         options = merge(
12911             typeOptions,
12912             plotOptions.series,
12913             itemOptions
12914         );
12915         
12916         // the tooltip options are merged between global and series specific options
12917         this.tooltipOptions = merge(chartOptions.tooltip, options.tooltip);
12918         
12919         // Delte marker object if not allowed (#1125)
12920         if (typeOptions.marker === null) {
12921             delete options.marker;
12922         }
12923         
12924         return options;
12925
12926     },
12927     /**
12928      * Get the series' color
12929      */
12930     getColor: function () {
12931         var options = this.options,
12932             userOptions = this.userOptions,
12933             defaultColors = this.chart.options.colors,
12934             counters = this.chart.counters,
12935             color,
12936             colorIndex;
12937
12938         color = options.color || defaultPlotOptions[this.type].color;
12939
12940         if (!color && !options.colorByPoint) {
12941             if (defined(userOptions._colorIndex)) { // after Series.update()
12942                 colorIndex = userOptions._colorIndex;
12943             } else {
12944                 userOptions._colorIndex = counters.color;
12945                 colorIndex = counters.color++;
12946             }
12947             color = defaultColors[colorIndex];
12948         }
12949         
12950         this.color = color;
12951         counters.wrapColor(defaultColors.length);
12952     },
12953     /**
12954      * Get the series' symbol
12955      */
12956     getSymbol: function () {
12957         var series = this,
12958             userOptions = series.userOptions,
12959             seriesMarkerOption = series.options.marker,
12960             chart = series.chart,
12961             defaultSymbols = chart.options.symbols,
12962             counters = chart.counters,
12963             symbolIndex;
12964
12965         series.symbol = seriesMarkerOption.symbol;
12966         if (!series.symbol) {
12967             if (defined(userOptions._symbolIndex)) { // after Series.update()
12968                 symbolIndex = userOptions._symbolIndex;
12969             } else {
12970                 userOptions._symbolIndex = counters.symbol;
12971                 symbolIndex = counters.symbol++;
12972             }
12973             series.symbol = defaultSymbols[symbolIndex];
12974         }
12975
12976         // don't substract radius in image symbols (#604)
12977         if (/^url/.test(series.symbol)) {
12978             seriesMarkerOption.radius = 0;
12979         }
12980         counters.wrapSymbol(defaultSymbols.length);
12981     },
12982
12983     /**
12984      * Get the series' symbol in the legend. This method should be overridable to create custom 
12985      * symbols through Highcharts.seriesTypes[type].prototype.drawLegendSymbols.
12986      * 
12987      * @param {Object} legend The legend object
12988      */
12989     drawLegendSymbol: function (legend) {
12990         
12991         var options = this.options,
12992             markerOptions = options.marker,
12993             radius,
12994             legendOptions = legend.options,
12995             legendSymbol,
12996             symbolWidth = legendOptions.symbolWidth,
12997             renderer = this.chart.renderer,
12998             legendItemGroup = this.legendGroup,
12999             verticalCenter = legend.baseline - mathRound(renderer.fontMetrics(legendOptions.itemStyle.fontSize).b * 0.3),
13000             attr;
13001             
13002         // Draw the line
13003         if (options.lineWidth) {
13004             attr = {
13005                 'stroke-width': options.lineWidth
13006             };
13007             if (options.dashStyle) {
13008                 attr.dashstyle = options.dashStyle;
13009             }
13010             this.legendLine = renderer.path([
13011                 M,
13012                 0,
13013                 verticalCenter,
13014                 L,
13015                 symbolWidth,
13016                 verticalCenter
13017             ])
13018             .attr(attr)
13019             .add(legendItemGroup);
13020         }
13021         
13022         // Draw the marker
13023         if (markerOptions && markerOptions.enabled) {
13024             radius = markerOptions.radius;
13025             this.legendSymbol = legendSymbol = renderer.symbol(
13026                 this.symbol,
13027                 (symbolWidth / 2) - radius,
13028                 verticalCenter - radius,
13029                 2 * radius,
13030                 2 * radius
13031             )
13032             .add(legendItemGroup);
13033             legendSymbol.isMarker = true;
13034         }
13035     },
13036
13037     /**
13038      * Add a point dynamically after chart load time
13039      * @param {Object} options Point options as given in series.data
13040      * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
13041      * @param {Boolean} shift If shift is true, a point is shifted off the start
13042      *    of the series as one is appended to the end.
13043      * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
13044      *    configuration
13045      */
13046     addPoint: function (options, redraw, shift, animation) {
13047         var series = this,
13048             seriesOptions = series.options,
13049             data = series.data,
13050             graph = series.graph,
13051             area = series.area,
13052             chart = series.chart,
13053             xData = series.xData,
13054             yData = series.yData,
13055             zData = series.zData,
13056             names = series.names,
13057             currentShift = (graph && graph.shift) || 0,
13058             dataOptions = seriesOptions.data,
13059             point,
13060             isInTheMiddle,
13061             x,
13062             i;
13063
13064         setAnimation(animation, chart);
13065
13066         // Make graph animate sideways
13067         if (shift) {
13068             each([graph, area, series.graphNeg, series.areaNeg], function (shape) {
13069                 if (shape) {
13070                     shape.shift = currentShift + 1;
13071                 }
13072             });
13073         }
13074         if (area) {
13075             area.isArea = true; // needed in animation, both with and without shift
13076         }
13077         
13078         // Optional redraw, defaults to true
13079         redraw = pick(redraw, true);
13080
13081         // Get options and push the point to xData, yData and series.options. In series.generatePoints
13082         // the Point instance will be created on demand and pushed to the series.data array.
13083         point = { series: series };
13084         series.pointClass.prototype.applyOptions.apply(point, [options]);
13085         x = point.x;
13086
13087         // Get the insertion point
13088         i = xData.length;
13089         if (series.requireSorting && x < xData[i - 1]) {
13090             isInTheMiddle = true;
13091             while (i && xData[i - 1] > x) {
13092                 i--;
13093             }
13094         }
13095         
13096         xData.splice(i, 0, x);
13097         yData.splice(i, 0, series.toYData ? series.toYData(point) : point.y);
13098         zData.splice(i, 0, point.z);
13099         if (names) {
13100             names[x] = point.name;
13101         }
13102         dataOptions.splice(i, 0, options);
13103
13104         if (isInTheMiddle) {
13105             series.data.splice(i, 0, null);
13106             series.processData();
13107         }
13108         
13109         // Generate points to be added to the legend (#1329) 
13110         if (seriesOptions.legendType === 'point') {
13111             series.generatePoints();
13112         }
13113
13114         // Shift the first point off the parallel arrays
13115         // todo: consider series.removePoint(i) method
13116         if (shift) {
13117             if (data[0] && data[0].remove) {
13118                 data[0].remove(false);
13119             } else {
13120                 data.shift();
13121                 xData.shift();
13122                 yData.shift();
13123                 zData.shift();
13124                 dataOptions.shift();
13125             }
13126         }
13127
13128         // redraw
13129         series.isDirty = true;
13130         series.isDirtyData = true;
13131         if (redraw) {
13132             series.getAttribs(); // #1937
13133             chart.redraw();
13134         }
13135     },
13136
13137     /**
13138      * Replace the series data with a new set of data
13139      * @param {Object} data
13140      * @param {Object} redraw
13141      */
13142     setData: function (data, redraw) {
13143         var series = this,
13144             oldData = series.points,
13145             options = series.options,
13146             chart = series.chart,
13147             firstPoint = null,
13148             xAxis = series.xAxis,
13149             names = xAxis && xAxis.categories && !xAxis.categories.length ? [] : null,
13150             i;
13151
13152         // reset properties
13153         series.xIncrement = null;
13154         series.pointRange = xAxis && xAxis.categories ? 1 : options.pointRange;
13155
13156         series.colorCounter = 0; // for series with colorByPoint (#1547)
13157         
13158         // parallel arrays
13159         var xData = [],
13160             yData = [],
13161             zData = [],
13162             dataLength = data ? data.length : [],
13163             turboThreshold = pick(options.turboThreshold, 1000),
13164             pt,
13165             pointArrayMap = series.pointArrayMap,
13166             valueCount = pointArrayMap && pointArrayMap.length,
13167             hasToYData = !!series.toYData;
13168
13169         // In turbo mode, only one- or twodimensional arrays of numbers are allowed. The
13170         // first value is tested, and we assume that all the rest are defined the same
13171         // way. Although the 'for' loops are similar, they are repeated inside each
13172         // if-else conditional for max performance.
13173         if (turboThreshold && dataLength > turboThreshold) { 
13174             
13175             // find the first non-null point
13176             i = 0;
13177             while (firstPoint === null && i < dataLength) {
13178                 firstPoint = data[i];
13179                 i++;
13180             }
13181         
13182         
13183             if (isNumber(firstPoint)) { // assume all points are numbers
13184                 var x = pick(options.pointStart, 0),
13185                     pointInterval = pick(options.pointInterval, 1);
13186
13187                 for (i = 0; i < dataLength; i++) {
13188                     xData[i] = x;
13189                     yData[i] = data[i];
13190                     x += pointInterval;
13191                 }
13192                 series.xIncrement = x;
13193             } else if (isArray(firstPoint)) { // assume all points are arrays
13194                 if (valueCount) { // [x, low, high] or [x, o, h, l, c]
13195                     for (i = 0; i < dataLength; i++) {
13196                         pt = data[i];
13197                         xData[i] = pt[0];
13198                         yData[i] = pt.slice(1, valueCount + 1);
13199                     }
13200                 } else { // [x, y]
13201                     for (i = 0; i < dataLength; i++) {
13202                         pt = data[i];
13203                         xData[i] = pt[0];
13204                         yData[i] = pt[1];
13205                     }
13206                 }
13207             } else {
13208                 error(12); // Highcharts expects configs to be numbers or arrays in turbo mode
13209             }
13210         } else {
13211             for (i = 0; i < dataLength; i++) {
13212                 if (data[i] !== UNDEFINED) { // stray commas in oldIE
13213                     pt = { series: series };
13214                     series.pointClass.prototype.applyOptions.apply(pt, [data[i]]);
13215                     xData[i] = pt.x;
13216                     yData[i] = hasToYData ? series.toYData(pt) : pt.y;
13217                     zData[i] = pt.z;
13218                     if (names && pt.name) {
13219                         names[pt.x] = pt.name; // #2046
13220                     }
13221                 }
13222             }
13223         }
13224         
13225         // Forgetting to cast strings to numbers is a common caveat when handling CSV or JSON        
13226         if (isString(yData[0])) {
13227             error(14, true);
13228         } 
13229
13230         series.data = [];
13231         series.options.data = data;
13232         series.xData = xData;
13233         series.yData = yData;
13234         series.zData = zData;
13235         series.names = names;
13236
13237         // destroy old points
13238         i = (oldData && oldData.length) || 0;
13239         while (i--) {
13240             if (oldData[i] && oldData[i].destroy) {
13241                 oldData[i].destroy();
13242             }
13243         }
13244
13245         // reset minRange (#878)
13246         if (xAxis) {
13247             xAxis.minRange = xAxis.userMinRange;
13248         }
13249
13250         // redraw
13251         series.isDirty = series.isDirtyData = chart.isDirtyBox = true;
13252         if (pick(redraw, true)) {
13253             chart.redraw(false);
13254         }
13255     },
13256
13257     /**
13258      * Remove a series and optionally redraw the chart
13259      *
13260      * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
13261      * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
13262      *    configuration
13263      */
13264
13265     remove: function (redraw, animation) {
13266         var series = this,
13267             chart = series.chart;
13268         redraw = pick(redraw, true);
13269
13270         if (!series.isRemoving) {  /* prevent triggering native event in jQuery
13271                 (calling the remove function from the remove event) */
13272             series.isRemoving = true;
13273
13274             // fire the event with a default handler of removing the point
13275             fireEvent(series, 'remove', null, function () {
13276
13277
13278                 // destroy elements
13279                 series.destroy();
13280
13281
13282                 // redraw
13283                 chart.isDirtyLegend = chart.isDirtyBox = true;
13284                 chart.linkSeries();
13285                 
13286                 if (redraw) {
13287                     chart.redraw(animation);
13288                 }
13289             });
13290
13291         }
13292         series.isRemoving = false;
13293     },
13294
13295     /**
13296      * Process the data by cropping away unused data points if the series is longer
13297      * than the crop threshold. This saves computing time for lage series.
13298      */
13299     processData: function (force) {
13300         var series = this,
13301             processedXData = series.xData, // copied during slice operation below
13302             processedYData = series.yData,
13303             dataLength = processedXData.length,
13304             croppedData,
13305             cropStart = 0,
13306             cropped,
13307             distance,
13308             closestPointRange,
13309             xAxis = series.xAxis,
13310             i, // loop variable
13311             options = series.options,
13312             cropThreshold = options.cropThreshold,
13313             isCartesian = series.isCartesian;
13314
13315         // If the series data or axes haven't changed, don't go through this. Return false to pass
13316         // the message on to override methods like in data grouping. 
13317         if (isCartesian && !series.isDirty && !xAxis.isDirty && !series.yAxis.isDirty && !force) {
13318             return false;
13319         }
13320         
13321
13322         // optionally filter out points outside the plot area
13323         if (isCartesian && series.sorted && (!cropThreshold || dataLength > cropThreshold || series.forceCrop)) {
13324             var min = xAxis.min,
13325                 max = xAxis.max;
13326
13327             // it's outside current extremes
13328             if (processedXData[dataLength - 1] < min || processedXData[0] > max) {
13329                 processedXData = [];
13330                 processedYData = [];
13331             
13332             // only crop if it's actually spilling out
13333             } else if (processedXData[0] < min || processedXData[dataLength - 1] > max) {
13334                 croppedData = this.cropData(series.xData, series.yData, min, max);
13335                 processedXData = croppedData.xData;
13336                 processedYData = croppedData.yData;
13337                 cropStart = croppedData.start;
13338                 cropped = true;
13339             }
13340         }
13341         
13342         
13343         // Find the closest distance between processed points
13344         for (i = processedXData.length - 1; i >= 0; i--) {
13345             distance = processedXData[i] - processedXData[i - 1];
13346             if (distance > 0 && (closestPointRange === UNDEFINED || distance < closestPointRange)) {
13347                 closestPointRange = distance;
13348
13349             // Unsorted data is not supported by the line tooltip, as well as data grouping and 
13350             // navigation in Stock charts (#725) and width calculation of columns (#1900)
13351             } else if (distance < 0 && series.requireSorting) {
13352                 error(15);
13353             }
13354         }
13355
13356         // Record the properties
13357         series.cropped = cropped; // undefined or true
13358         series.cropStart = cropStart;
13359         series.processedXData = processedXData;
13360         series.processedYData = processedYData;
13361
13362         if (options.pointRange === null) { // null means auto, as for columns, candlesticks and OHLC
13363             series.pointRange = closestPointRange || 1;
13364         }
13365         series.closestPointRange = closestPointRange;
13366         
13367     },
13368
13369     /**
13370      * Iterate over xData and crop values between min and max. Returns object containing crop start/end
13371      * cropped xData with corresponding part of yData, dataMin and dataMax within the cropped range
13372      */
13373     cropData: function (xData, yData, min, max) {
13374         var dataLength = xData.length,
13375             cropStart = 0,
13376             cropEnd = dataLength,
13377             cropShoulder = pick(this.cropShoulder, 1), // line-type series need one point outside
13378             i;
13379
13380         // iterate up to find slice start
13381         for (i = 0; i < dataLength; i++) {
13382             if (xData[i] >= min) {
13383                 cropStart = mathMax(0, i - cropShoulder);
13384                 break;
13385             }
13386         }
13387
13388         // proceed to find slice end
13389         for (; i < dataLength; i++) {
13390             if (xData[i] > max) {
13391                 cropEnd = i + cropShoulder;
13392                 break;
13393             }
13394         }
13395
13396         return {
13397             xData: xData.slice(cropStart, cropEnd),
13398             yData: yData.slice(cropStart, cropEnd),
13399             start: cropStart,
13400             end: cropEnd
13401         };
13402     },
13403
13404
13405     /**
13406      * Generate the data point after the data has been processed by cropping away
13407      * unused points and optionally grouped in Highcharts Stock.
13408      */
13409     generatePoints: function () {
13410         var series = this,
13411             options = series.options,
13412             dataOptions = options.data,
13413             data = series.data,
13414             dataLength,
13415             processedXData = series.processedXData,
13416             processedYData = series.processedYData,
13417             pointClass = series.pointClass,
13418             processedDataLength = processedXData.length,
13419             cropStart = series.cropStart || 0,
13420             cursor,
13421             hasGroupedData = series.hasGroupedData,
13422             point,
13423             points = [],
13424             i;
13425
13426         if (!data && !hasGroupedData) {
13427             var arr = [];
13428             arr.length = dataOptions.length;
13429             data = series.data = arr;
13430         }
13431
13432         for (i = 0; i < processedDataLength; i++) {
13433             cursor = cropStart + i;
13434             if (!hasGroupedData) {
13435                 if (data[cursor]) {
13436                     point = data[cursor];
13437                 } else if (dataOptions[cursor] !== UNDEFINED) { // #970
13438                     data[cursor] = point = (new pointClass()).init(series, dataOptions[cursor], processedXData[i]);
13439                 }
13440                 points[i] = point;
13441             } else {
13442                 // splat the y data in case of ohlc data array
13443                 points[i] = (new pointClass()).init(series, [processedXData[i]].concat(splat(processedYData[i])));
13444             }
13445         }
13446
13447         // Hide cropped-away points - this only runs when the number of points is above cropThreshold, or when
13448         // swithching view from non-grouped data to grouped data (#637)    
13449         if (data && (processedDataLength !== (dataLength = data.length) || hasGroupedData)) {
13450             for (i = 0; i < dataLength; i++) {
13451                 if (i === cropStart && !hasGroupedData) { // when has grouped data, clear all points
13452                     i += processedDataLength;
13453                 }
13454                 if (data[i]) {
13455                     data[i].destroyElements();
13456                     data[i].plotX = UNDEFINED; // #1003
13457                 }
13458             }
13459         }
13460
13461         series.data = data;
13462         series.points = points;
13463     },
13464
13465     /**
13466      * Adds series' points value to corresponding stack
13467      */
13468     setStackedPoints: function () {
13469         if (!this.options.stacking || (this.visible !== true && this.chart.options.chart.ignoreHiddenSeries !== false)) {
13470             return;
13471         }
13472
13473         var series = this,
13474             xData = series.processedXData,
13475             yData = series.processedYData,
13476             stackedYData = [],
13477             yDataLength = yData.length,
13478             seriesOptions = series.options,
13479             threshold = seriesOptions.threshold,
13480             stackOption = seriesOptions.stack,
13481             stacking = seriesOptions.stacking,
13482             stackKey = series.stackKey,
13483             negKey = '-' + stackKey,
13484             negStacks = series.negStacks,
13485             yAxis = series.yAxis,
13486             stacks = yAxis.stacks,
13487             oldStacks = yAxis.oldStacks,
13488             isNegative,
13489             stack,
13490             other,
13491             key,
13492             i,
13493             x,
13494             y;
13495
13496         // loop over the non-null y values and read them into a local array
13497         for (i = 0; i < yDataLength; i++) {
13498             x = xData[i];
13499             y = yData[i];
13500
13501             // Read stacked values into a stack based on the x value,
13502             // the sign of y and the stack key. Stacking is also handled for null values (#739)
13503             isNegative = negStacks && y < threshold;
13504             key = isNegative ? negKey : stackKey;
13505
13506             // Create empty object for this stack if it doesn't exist yet
13507             if (!stacks[key]) {
13508                 stacks[key] = {};
13509             }
13510
13511             // Initialize StackItem for this x
13512             if (!stacks[key][x]) {
13513                 if (oldStacks[key] && oldStacks[key][x]) {
13514                     stacks[key][x] = oldStacks[key][x];
13515                     stacks[key][x].total = null;
13516                 } else {
13517                     stacks[key][x] = new StackItem(yAxis, yAxis.options.stackLabels, isNegative, x, stackOption, stacking);
13518                 }
13519             }
13520
13521             // If the StackItem doesn't exist, create it first
13522             stack = stacks[key][x];
13523             stack.points[series.index] = [stack.cum || 0];
13524
13525             // Add value to the stack total
13526             if (stacking === 'percent') {
13527                 
13528                 // Percent stacked column, totals are the same for the positive and negative stacks
13529                 other = isNegative ? stackKey : negKey;
13530                 if (negStacks && stacks[other] && stacks[other][x]) {
13531                     other = stacks[other][x];
13532                     stack.total = other.total = mathMax(other.total, stack.total) + mathAbs(y) || 0;
13533
13534                 // Percent stacked areas                    
13535                 } else {
13536                     stack.total += mathAbs(y) || 0;
13537                 }
13538             } else {
13539                 stack.total += y || 0;
13540             }
13541
13542             stack.cum = (stack.cum || 0) + (y || 0);
13543
13544             stack.points[series.index].push(stack.cum);
13545             stackedYData[i] = stack.cum;
13546
13547         }
13548
13549         if (stacking === 'percent') {
13550             yAxis.usePercentage = true;
13551         }
13552
13553         this.stackedYData = stackedYData; // To be used in getExtremes
13554         
13555         // Reset old stacks
13556         yAxis.oldStacks = {};
13557     },
13558
13559     /**
13560      * Iterate over all stacks and compute the absolute values to percent
13561      */
13562     setPercentStacks: function () {
13563         var series = this,
13564             stackKey = series.stackKey,
13565             stacks = series.yAxis.stacks;
13566         
13567         each([stackKey, '-' + stackKey], function (key) {
13568             var i = series.xData.length,
13569                 x,
13570                 stack,
13571                 pointExtremes,
13572                 totalFactor;
13573
13574             while (i--) {
13575                 x = series.xData[i];
13576                 stack = stacks[key] && stacks[key][x];
13577                 pointExtremes = stack && stack.points[series.index];
13578                 if (pointExtremes) {
13579                     totalFactor = stack.total ? 100 / stack.total : 0;
13580                     pointExtremes[0] = correctFloat(pointExtremes[0] * totalFactor); // Y bottom value
13581                     pointExtremes[1] = correctFloat(pointExtremes[1] * totalFactor); // Y value
13582                     series.stackedYData[i] = pointExtremes[1];
13583                 }
13584             }
13585         });
13586     },
13587
13588     /**
13589      * Calculate Y extremes for visible data
13590      */
13591     getExtremes: function () {
13592         var xAxis = this.xAxis,
13593             yAxis = this.yAxis,
13594             xData = this.processedXData,
13595             yData = this.stackedYData || this.processedYData,
13596             yDataLength = yData.length,
13597             activeYData = [],
13598             activeCounter = 0,
13599             xExtremes = xAxis.getExtremes(), // #2117, need to compensate for log X axis
13600             xMin = xExtremes.min,
13601             xMax = xExtremes.max,
13602             validValue,
13603             withinRange,
13604             dataMin,
13605             dataMax,
13606             x,
13607             y,
13608             i,
13609             j;
13610
13611         for (i = 0; i < yDataLength; i++) {
13612             
13613             x = xData[i];
13614             y = yData[i];
13615
13616             // For points within the visible range, including the first point outside the
13617             // visible range, consider y extremes
13618             validValue = y !== null && y !== UNDEFINED && (!yAxis.isLog || (y.length || y > 0));
13619             withinRange = this.getExtremesFromAll || this.cropped || ((xData[i + 1] || x) >= xMin && 
13620                 (xData[i - 1] || x) <= xMax);
13621
13622             if (validValue && withinRange) {
13623
13624                 j = y.length;
13625                 if (j) { // array, like ohlc or range data
13626                     while (j--) {
13627                         if (y[j] !== null) {
13628                             activeYData[activeCounter++] = y[j];
13629                         }
13630                     }
13631                 } else {
13632                     activeYData[activeCounter++] = y;
13633                 }
13634             }
13635         }
13636         this.dataMin = pick(dataMin, arrayMin(activeYData));
13637         this.dataMax = pick(dataMax, arrayMax(activeYData));
13638     },
13639
13640     /**
13641      * Translate data points from raw data values to chart specific positioning data
13642      * needed later in drawPoints, drawGraph and drawTracker.
13643      */
13644     translate: function () {
13645         if (!this.processedXData) { // hidden series
13646             this.processData();
13647         }
13648         this.generatePoints();
13649         var series = this,
13650             options = series.options,
13651             stacking = options.stacking,
13652             xAxis = series.xAxis,
13653             categories = xAxis.categories,
13654             yAxis = series.yAxis,
13655             points = series.points,
13656             dataLength = points.length,
13657             hasModifyValue = !!series.modifyValue,
13658             i,
13659             pointPlacement = options.pointPlacement,
13660             dynamicallyPlaced = pointPlacement === 'between' || isNumber(pointPlacement),
13661             threshold = options.threshold;
13662
13663         
13664         // Translate each point
13665         for (i = 0; i < dataLength; i++) {
13666             var point = points[i],
13667                 xValue = point.x,
13668                 yValue = point.y,
13669                 yBottom = point.low,
13670                 stack = yAxis.stacks[(series.negStacks && yValue < threshold ? '-' : '') + series.stackKey],
13671                 pointStack,
13672                 stackValues;
13673
13674             // Discard disallowed y values for log axes
13675             if (yAxis.isLog && yValue <= 0) {
13676                 point.y = yValue = null;
13677             }
13678             
13679             // Get the plotX translation
13680             point.plotX = xAxis.translate(xValue, 0, 0, 0, 1, pointPlacement, this.type === 'flags'); // Math.round fixes #591
13681             
13682
13683             // Calculate the bottom y value for stacked series
13684             if (stacking && series.visible && stack && stack[xValue]) {
13685
13686                 pointStack = stack[xValue];
13687                 stackValues = pointStack.points[series.index];
13688                 yBottom = stackValues[0];
13689                 yValue = stackValues[1];
13690
13691                 if (yBottom === 0) {
13692                     yBottom = pick(threshold, yAxis.min);
13693                 }
13694                 if (yAxis.isLog && yBottom <= 0) { // #1200, #1232
13695                     yBottom = null;
13696                 }
13697
13698                 point.percentage = stacking === 'percent' && yValue;
13699                 point.total = point.stackTotal = pointStack.total;
13700                 point.stackY = yValue;
13701
13702                 // Place the stack label
13703                 pointStack.setOffset(series.pointXOffset || 0, series.barW || 0);
13704                 
13705             }
13706
13707             // Set translated yBottom or remove it
13708             point.yBottom = defined(yBottom) ? 
13709                 yAxis.translate(yBottom, 0, 1, 0, 1) :
13710                 null;
13711                 
13712             // general hook, used for Highstock compare mode
13713             if (hasModifyValue) {
13714                 yValue = series.modifyValue(yValue, point);
13715             }
13716
13717             // Set the the plotY value, reset it for redraws
13718             point.plotY = (typeof yValue === 'number' && yValue !== Infinity) ? 
13719                 //mathRound(yAxis.translate(yValue, 0, 1, 0, 1) * 10) / 10 : // Math.round fixes #591
13720                 yAxis.translate(yValue, 0, 1, 0, 1) : 
13721                 UNDEFINED;
13722             
13723             // Set client related positions for mouse tracking
13724             point.clientX = dynamicallyPlaced ? xAxis.translate(xValue, 0, 0, 0, 1) : point.plotX; // #1514
13725                 
13726             point.negative = point.y < (threshold || 0);
13727
13728             // some API data
13729             point.category = categories && categories[point.x] !== UNDEFINED ?
13730                 categories[point.x] : point.x;
13731
13732
13733         }
13734
13735         // now that we have the cropped data, build the segments
13736         series.getSegments();
13737     },
13738     /**
13739      * Memoize tooltip texts and positions
13740      */
13741     setTooltipPoints: function (renew) {
13742         var series = this,
13743             points = [],
13744             pointsLength,
13745             low,
13746             high,
13747             xAxis = series.xAxis,
13748             xExtremes = xAxis && xAxis.getExtremes(),
13749             axisLength = xAxis ? (xAxis.tooltipLen || xAxis.len) : series.chart.plotSizeX, // tooltipLen and tooltipPosName used in polar
13750             point,
13751             pointX,
13752             nextPoint,
13753             i,
13754             tooltipPoints = []; // a lookup array for each pixel in the x dimension
13755
13756         // don't waste resources if tracker is disabled
13757         if (series.options.enableMouseTracking === false) {
13758             return;
13759         }
13760
13761         // renew
13762         if (renew) {
13763             series.tooltipPoints = null;
13764         }
13765
13766         // concat segments to overcome null values
13767         each(series.segments || series.points, function (segment) {
13768             points = points.concat(segment);
13769         });
13770
13771         // Reverse the points in case the X axis is reversed
13772         if (xAxis && xAxis.reversed) {
13773             points = points.reverse();
13774         }
13775
13776         // Polar needs additional shaping
13777         if (series.orderTooltipPoints) {
13778             series.orderTooltipPoints(points);
13779         }
13780
13781         // Assign each pixel position to the nearest point
13782         pointsLength = points.length;
13783         for (i = 0; i < pointsLength; i++) {
13784             point = points[i];
13785             pointX = point.x;
13786             if (pointX >= xExtremes.min && pointX <= xExtremes.max) { // #1149
13787                 nextPoint = points[i + 1];
13788                 
13789                 // Set this range's low to the last range's high plus one
13790                 low = high === UNDEFINED ? 0 : high + 1;
13791                 // Now find the new high
13792                 high = points[i + 1] ?
13793                     mathMin(mathMax(0, mathFloor( // #2070
13794                         (point.clientX + (nextPoint ? (nextPoint.wrappedClientX || nextPoint.clientX) : axisLength)) / 2
13795                     )), axisLength) :
13796                     axisLength;
13797
13798                 while (low >= 0 && low <= high) {
13799                     tooltipPoints[low++] = point;
13800                 }
13801             }
13802         }
13803         series.tooltipPoints = tooltipPoints;
13804     },
13805
13806     /**
13807      * Format the header of the tooltip
13808      */
13809     tooltipHeaderFormatter: function (point) {
13810         var series = this,
13811             tooltipOptions = series.tooltipOptions,
13812             xDateFormat = tooltipOptions.xDateFormat,
13813             dateTimeLabelFormats = tooltipOptions.dateTimeLabelFormats,
13814             xAxis = series.xAxis,
13815             isDateTime = xAxis && xAxis.options.type === 'datetime',
13816             headerFormat = tooltipOptions.headerFormat,
13817             closestPointRange = xAxis && xAxis.closestPointRange,
13818             n;
13819             
13820         // Guess the best date format based on the closest point distance (#568)
13821         if (isDateTime && !xDateFormat) {
13822             if (closestPointRange) {
13823                 for (n in timeUnits) {
13824                     if (timeUnits[n] >= closestPointRange) {
13825                         xDateFormat = dateTimeLabelFormats[n];
13826                         break;
13827                     }
13828                 }
13829             } else {
13830                 xDateFormat = dateTimeLabelFormats.day;
13831             }
13832         }
13833         
13834         // Insert the header date format if any
13835         if (isDateTime && xDateFormat && isNumber(point.key)) {
13836             headerFormat = headerFormat.replace('{point.key}', '{point.key:' + xDateFormat + '}');
13837         }
13838         
13839         return format(headerFormat, {
13840             point: point,
13841             series: series
13842         });
13843     },
13844
13845     /**
13846      * Series mouse over handler
13847      */
13848     onMouseOver: function () {
13849         var series = this,
13850             chart = series.chart,
13851             hoverSeries = chart.hoverSeries;
13852
13853         // set normal state to previous series
13854         if (hoverSeries && hoverSeries !== series) {
13855             hoverSeries.onMouseOut();
13856         }
13857
13858         // trigger the event, but to save processing time,
13859         // only if defined
13860         if (series.options.events.mouseOver) {
13861             fireEvent(series, 'mouseOver');
13862         }
13863
13864         // hover this
13865         series.setState(HOVER_STATE);
13866         chart.hoverSeries = series;
13867     },
13868
13869     /**
13870      * Series mouse out handler
13871      */
13872     onMouseOut: function () {
13873         // trigger the event only if listeners exist
13874         var series = this,
13875             options = series.options,
13876             chart = series.chart,
13877             tooltip = chart.tooltip,
13878             hoverPoint = chart.hoverPoint;
13879
13880         // trigger mouse out on the point, which must be in this series
13881         if (hoverPoint) {
13882             hoverPoint.onMouseOut();
13883         }
13884
13885         // fire the mouse out event
13886         if (series && options.events.mouseOut) {
13887             fireEvent(series, 'mouseOut');
13888         }
13889
13890
13891         // hide the tooltip
13892         if (tooltip && !options.stickyTracking && (!tooltip.shared || series.noSharedTooltip)) {
13893             tooltip.hide();
13894         }
13895
13896         // set normal state
13897         series.setState();
13898         chart.hoverSeries = null;
13899     },
13900
13901     /**
13902      * Animate in the series
13903      */
13904     animate: function (init) {
13905         var series = this,
13906             chart = series.chart,
13907             renderer = chart.renderer,
13908             clipRect,
13909             markerClipRect,
13910             animation = series.options.animation,
13911             clipBox = chart.clipBox,
13912             inverted = chart.inverted,
13913             sharedClipKey;
13914
13915         // Animation option is set to true
13916         if (animation && !isObject(animation)) {
13917             animation = defaultPlotOptions[series.type].animation;
13918         }
13919         sharedClipKey = '_sharedClip' + animation.duration + animation.easing;
13920
13921         // Initialize the animation. Set up the clipping rectangle.
13922         if (init) { 
13923             
13924             // If a clipping rectangle with the same properties is currently present in the chart, use that. 
13925             clipRect = chart[sharedClipKey];
13926             markerClipRect = chart[sharedClipKey + 'm'];
13927             if (!clipRect) {
13928                 chart[sharedClipKey] = clipRect = renderer.clipRect(
13929                     extend(clipBox, { width: 0 })
13930                 );
13931                 
13932                 chart[sharedClipKey + 'm'] = markerClipRect = renderer.clipRect(
13933                     -99, // include the width of the first marker
13934                     inverted ? -chart.plotLeft : -chart.plotTop, 
13935                     99,
13936                     inverted ? chart.chartWidth : chart.chartHeight
13937                 );
13938             }
13939             series.group.clip(clipRect);
13940             series.markerGroup.clip(markerClipRect);
13941             series.sharedClipKey = sharedClipKey;
13942
13943         // Run the animation
13944         } else { 
13945             clipRect = chart[sharedClipKey];
13946             if (clipRect) {
13947                 clipRect.animate({
13948                     width: chart.plotSizeX
13949                 }, animation);
13950                 chart[sharedClipKey + 'm'].animate({
13951                     width: chart.plotSizeX + 99
13952                 }, animation);
13953             }
13954
13955             // Delete this function to allow it only once
13956             series.animate = null;
13957             
13958             // Call the afterAnimate function on animation complete (but don't overwrite the animation.complete option
13959             // which should be available to the user).
13960             series.animationTimeout = setTimeout(function () {
13961                 series.afterAnimate();
13962             }, animation.duration);
13963         }
13964     },
13965     
13966     /**
13967      * This runs after animation to land on the final plot clipping
13968      */
13969     afterAnimate: function () {
13970         var chart = this.chart,
13971             sharedClipKey = this.sharedClipKey,
13972             group = this.group;
13973             
13974         if (group && this.options.clip !== false) {
13975             group.clip(chart.clipRect);
13976             this.markerGroup.clip(); // no clip
13977         }
13978         
13979         // Remove the shared clipping rectancgle when all series are shown        
13980         setTimeout(function () {
13981             if (sharedClipKey && chart[sharedClipKey]) {
13982                 chart[sharedClipKey] = chart[sharedClipKey].destroy();
13983                 chart[sharedClipKey + 'm'] = chart[sharedClipKey + 'm'].destroy();
13984             }
13985         }, 100);
13986     },
13987
13988     /**
13989      * Draw the markers
13990      */
13991     drawPoints: function () {
13992         var series = this,
13993             pointAttr,
13994             points = series.points,
13995             chart = series.chart,
13996             plotX,
13997             plotY,
13998             i,
13999             point,
14000             radius,
14001             symbol,
14002             isImage,
14003             graphic,
14004             options = series.options,
14005             seriesMarkerOptions = options.marker,
14006             pointMarkerOptions,
14007             enabled,
14008             isInside,
14009             markerGroup = series.markerGroup;
14010
14011         if (seriesMarkerOptions.enabled || series._hasPointMarkers) {
14012             
14013             i = points.length;
14014             while (i--) {
14015                 point = points[i];
14016                 plotX = mathFloor(point.plotX); // #1843
14017                 plotY = point.plotY;
14018                 graphic = point.graphic;
14019                 pointMarkerOptions = point.marker || {};
14020                 enabled = (seriesMarkerOptions.enabled && pointMarkerOptions.enabled === UNDEFINED) || pointMarkerOptions.enabled;
14021                 isInside = chart.isInsidePlot(mathRound(plotX), plotY, chart.inverted); // #1858
14022                 
14023                 // only draw the point if y is defined
14024                 if (enabled && plotY !== UNDEFINED && !isNaN(plotY) && point.y !== null) {
14025
14026                     // shortcuts
14027                     pointAttr = point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE];
14028                     radius = pointAttr.r;
14029                     symbol = pick(pointMarkerOptions.symbol, series.symbol);
14030                     isImage = symbol.indexOf('url') === 0;
14031
14032                     if (graphic) { // update
14033                         graphic
14034                             .attr({ // Since the marker group isn't clipped, each individual marker must be toggled
14035                                 visibility: isInside ? (hasSVG ? 'inherit' : VISIBLE) : HIDDEN
14036                             })
14037                             .animate(extend({
14038                                 x: plotX - radius,
14039                                 y: plotY - radius
14040                             }, graphic.symbolName ? { // don't apply to image symbols #507
14041                                 width: 2 * radius,
14042                                 height: 2 * radius
14043                             } : {}));
14044                     } else if (isInside && (radius > 0 || isImage)) {
14045                         point.graphic = graphic = chart.renderer.symbol(
14046                             symbol,
14047                             plotX - radius,
14048                             plotY - radius,
14049                             2 * radius,
14050                             2 * radius
14051                         )
14052                         .attr(pointAttr)
14053                         .add(markerGroup);
14054                     }
14055                     
14056                 } else if (graphic) {
14057                     point.graphic = graphic.destroy(); // #1269
14058                 }
14059             }
14060         }
14061
14062     },
14063
14064     /**
14065      * Convert state properties from API naming conventions to SVG attributes
14066      *
14067      * @param {Object} options API options object
14068      * @param {Object} base1 SVG attribute object to inherit from
14069      * @param {Object} base2 Second level SVG attribute object to inherit from
14070      */
14071     convertAttribs: function (options, base1, base2, base3) {
14072         var conversion = this.pointAttrToOptions,
14073             attr,
14074             option,
14075             obj = {};
14076
14077         options = options || {};
14078         base1 = base1 || {};
14079         base2 = base2 || {};
14080         base3 = base3 || {};
14081
14082         for (attr in conversion) {
14083             option = conversion[attr];
14084             obj[attr] = pick(options[option], base1[attr], base2[attr], base3[attr]);
14085         }
14086         return obj;
14087     },
14088
14089     /**
14090      * Get the state attributes. Each series type has its own set of attributes
14091      * that are allowed to change on a point's state change. Series wide attributes are stored for
14092      * all series, and additionally point specific attributes are stored for all
14093      * points with individual marker options. If such options are not defined for the point,
14094      * a reference to the series wide attributes is stored in point.pointAttr.
14095      */
14096     getAttribs: function () {
14097         var series = this,
14098             seriesOptions = series.options,
14099             normalOptions = defaultPlotOptions[series.type].marker ? seriesOptions.marker : seriesOptions,
14100             stateOptions = normalOptions.states,
14101             stateOptionsHover = stateOptions[HOVER_STATE],
14102             pointStateOptionsHover,
14103             seriesColor = series.color,
14104             normalDefaults = {
14105                 stroke: seriesColor,
14106                 fill: seriesColor
14107             },
14108             points = series.points || [], // #927
14109             i,
14110             point,
14111             seriesPointAttr = [],
14112             pointAttr,
14113             pointAttrToOptions = series.pointAttrToOptions,
14114             hasPointSpecificOptions,
14115             negativeColor = seriesOptions.negativeColor,
14116             defaultLineColor = normalOptions.lineColor,
14117             key;
14118
14119         // series type specific modifications
14120         if (seriesOptions.marker) { // line, spline, area, areaspline, scatter
14121
14122             // if no hover radius is given, default to normal radius + 2
14123             stateOptionsHover.radius = stateOptionsHover.radius || normalOptions.radius + 2;
14124             stateOptionsHover.lineWidth = stateOptionsHover.lineWidth || normalOptions.lineWidth + 1;
14125             
14126         } else { // column, bar, pie
14127
14128             // if no hover color is given, brighten the normal color
14129             stateOptionsHover.color = stateOptionsHover.color ||
14130                 Color(stateOptionsHover.color || seriesColor)
14131                     .brighten(stateOptionsHover.brightness).get();
14132         }
14133
14134         // general point attributes for the series normal state
14135         seriesPointAttr[NORMAL_STATE] = series.convertAttribs(normalOptions, normalDefaults);
14136
14137         // HOVER_STATE and SELECT_STATE states inherit from normal state except the default radius
14138         each([HOVER_STATE, SELECT_STATE], function (state) {
14139             seriesPointAttr[state] =
14140                     series.convertAttribs(stateOptions[state], seriesPointAttr[NORMAL_STATE]);
14141         });
14142
14143         // set it
14144         series.pointAttr = seriesPointAttr;
14145
14146
14147         // Generate the point-specific attribute collections if specific point
14148         // options are given. If not, create a referance to the series wide point
14149         // attributes
14150         i = points.length;
14151         while (i--) {
14152             point = points[i];
14153             normalOptions = (point.options && point.options.marker) || point.options;
14154             if (normalOptions && normalOptions.enabled === false) {
14155                 normalOptions.radius = 0;
14156             }
14157             
14158             if (point.negative && negativeColor) {
14159                 point.color = point.fillColor = negativeColor;
14160             }
14161             
14162             hasPointSpecificOptions = seriesOptions.colorByPoint || point.color; // #868
14163
14164             // check if the point has specific visual options
14165             if (point.options) {
14166                 for (key in pointAttrToOptions) {
14167                     if (defined(normalOptions[pointAttrToOptions[key]])) {
14168                         hasPointSpecificOptions = true;
14169                     }
14170                 }
14171             }
14172
14173             // a specific marker config object is defined for the individual point:
14174             // create it's own attribute collection
14175             if (hasPointSpecificOptions) {
14176                 normalOptions = normalOptions || {};
14177                 pointAttr = [];
14178                 stateOptions = normalOptions.states || {}; // reassign for individual point
14179                 pointStateOptionsHover = stateOptions[HOVER_STATE] = stateOptions[HOVER_STATE] || {};
14180
14181                 // Handle colors for column and pies
14182                 if (!seriesOptions.marker) { // column, bar, point
14183                     // if no hover color is given, brighten the normal color
14184                     pointStateOptionsHover.color =
14185                         Color(pointStateOptionsHover.color || point.color)
14186                             .brighten(pointStateOptionsHover.brightness ||
14187                                 stateOptionsHover.brightness).get();
14188
14189                 }
14190
14191                 // normal point state inherits series wide normal state
14192                 pointAttr[NORMAL_STATE] = series.convertAttribs(extend({
14193                     color: point.color, // #868
14194                     fillColor: point.color, // Individual point color or negative color markers (#2219)
14195                     lineColor: defaultLineColor === null ? point.color : UNDEFINED // Bubbles take point color, line markers use white
14196                 }, normalOptions), seriesPointAttr[NORMAL_STATE]);
14197
14198                 // inherit from point normal and series hover
14199                 pointAttr[HOVER_STATE] = series.convertAttribs(
14200                     stateOptions[HOVER_STATE],
14201                     seriesPointAttr[HOVER_STATE],
14202                     pointAttr[NORMAL_STATE]
14203                 );
14204                 
14205                 // inherit from point normal and series hover
14206                 pointAttr[SELECT_STATE] = series.convertAttribs(
14207                     stateOptions[SELECT_STATE],
14208                     seriesPointAttr[SELECT_STATE],
14209                     pointAttr[NORMAL_STATE]
14210                 );
14211
14212
14213             // no marker config object is created: copy a reference to the series-wide
14214             // attribute collection
14215             } else {
14216                 pointAttr = seriesPointAttr;
14217             }
14218
14219             point.pointAttr = pointAttr;
14220
14221         }
14222
14223     },
14224     /**
14225      * Update the series with a new set of options
14226      */
14227     update: function (newOptions, redraw) {
14228         var chart = this.chart,
14229             // must use user options when changing type because this.options is merged
14230             // in with type specific plotOptions
14231             oldOptions = this.userOptions,
14232             oldType = this.type,
14233             proto = seriesTypes[oldType].prototype,
14234             n;
14235
14236         // Do the merge, with some forced options
14237         newOptions = merge(oldOptions, {
14238             animation: false,
14239             index: this.index,
14240             pointStart: this.xData[0] // when updating after addPoint
14241         }, { data: this.options.data }, newOptions);
14242
14243         // Destroy the series and reinsert methods from the type prototype
14244         this.remove(false);
14245         for (n in proto) { // Overwrite series-type specific methods (#2270)
14246             if (proto.hasOwnProperty(n)) {
14247                 this[n] = UNDEFINED;
14248             }
14249         }
14250         extend(this, seriesTypes[newOptions.type || oldType].prototype);
14251         
14252
14253         this.init(chart, newOptions);
14254         if (pick(redraw, true)) {
14255             chart.redraw(false);
14256         }
14257     },
14258
14259     /**
14260      * Clear DOM objects and free up memory
14261      */
14262     destroy: function () {
14263         var series = this,
14264             chart = series.chart,
14265             issue134 = /AppleWebKit\/533/.test(userAgent),
14266             destroy,
14267             i,
14268             data = series.data || [],
14269             point,
14270             prop,
14271             axis;
14272
14273         // add event hook
14274         fireEvent(series, 'destroy');
14275
14276         // remove all events
14277         removeEvent(series);
14278         
14279         // erase from axes
14280         each(['xAxis', 'yAxis'], function (AXIS) {
14281             axis = series[AXIS];
14282             if (axis) {
14283                 erase(axis.series, series);
14284                 axis.isDirty = axis.forceRedraw = true;
14285                 axis.stacks = {}; // Rebuild stacks when updating (#2229)
14286             }
14287         });
14288
14289         // remove legend items
14290         if (series.legendItem) {
14291             series.chart.legend.destroyItem(series);
14292         }
14293
14294         // destroy all points with their elements
14295         i = data.length;
14296         while (i--) {
14297             point = data[i];
14298             if (point && point.destroy) {
14299                 point.destroy();
14300             }
14301         }
14302         series.points = null;
14303
14304         // Clear the animation timeout if we are destroying the series during initial animation
14305         clearTimeout(series.animationTimeout);
14306
14307         // destroy all SVGElements associated to the series
14308         each(['area', 'graph', 'dataLabelsGroup', 'group', 'markerGroup', 'tracker',
14309                 'graphNeg', 'areaNeg', 'posClip', 'negClip'], function (prop) {
14310             if (series[prop]) {
14311
14312                 // issue 134 workaround
14313                 destroy = issue134 && prop === 'group' ?
14314                     'hide' :
14315                     'destroy';
14316
14317                 series[prop][destroy]();
14318             }
14319         });
14320
14321         // remove from hoverSeries
14322         if (chart.hoverSeries === series) {
14323             chart.hoverSeries = null;
14324         }
14325         erase(chart.series, series);
14326
14327         // clear all members
14328         for (prop in series) {
14329             delete series[prop];
14330         }
14331     },
14332
14333     /**
14334      * Draw the data labels
14335      */
14336     drawDataLabels: function () {
14337         
14338         var series = this,
14339             seriesOptions = series.options,
14340             options = seriesOptions.dataLabels,
14341             points = series.points,
14342             pointOptions,
14343             generalOptions,
14344             str,
14345             dataLabelsGroup;
14346         
14347         if (options.enabled || series._hasPointLabels) {
14348                         
14349             // Process default alignment of data labels for columns
14350             if (series.dlProcessOptions) {
14351                 series.dlProcessOptions(options);
14352             }
14353
14354             // Create a separate group for the data labels to avoid rotation
14355             dataLabelsGroup = series.plotGroup(
14356                 'dataLabelsGroup', 
14357                 'data-labels', 
14358                 series.visible ? VISIBLE : HIDDEN, 
14359                 options.zIndex || 6
14360             );
14361             
14362             // Make the labels for each point
14363             generalOptions = options;
14364             each(points, function (point) {
14365                 
14366                 var enabled,
14367                     dataLabel = point.dataLabel,
14368                     labelConfig,
14369                     attr,
14370                     name,
14371                     rotation,
14372                     connector = point.connector,
14373                     isNew = true;
14374                 
14375                 // Determine if each data label is enabled
14376                 pointOptions = point.options && point.options.dataLabels;
14377                 enabled = pick(pointOptions && pointOptions.enabled, generalOptions.enabled); // #2282
14378                 
14379                 
14380                 // If the point is outside the plot area, destroy it. #678, #820
14381                 if (dataLabel && !enabled) {
14382                     point.dataLabel = dataLabel.destroy();
14383                 
14384                 // Individual labels are disabled if the are explicitly disabled 
14385                 // in the point options, or if they fall outside the plot area.
14386                 } else if (enabled) {
14387                     
14388                     // Create individual options structure that can be extended without 
14389                     // affecting others
14390                     options = merge(generalOptions, pointOptions);
14391
14392                     rotation = options.rotation;
14393                     
14394                     // Get the string
14395                     labelConfig = point.getLabelConfig();
14396                     str = options.format ?
14397                         format(options.format, labelConfig) : 
14398                         options.formatter.call(labelConfig, options);
14399                     
14400                     // Determine the color
14401                     options.style.color = pick(options.color, options.style.color, series.color, 'black');
14402     
14403                     
14404                     // update existing label
14405                     if (dataLabel) {
14406                         
14407                         if (defined(str)) {
14408                             dataLabel
14409                                 .attr({
14410                                     text: str
14411                                 });
14412                             isNew = false;
14413                         
14414                         } else { // #1437 - the label is shown conditionally
14415                             point.dataLabel = dataLabel = dataLabel.destroy();
14416                             if (connector) {
14417                                 point.connector = connector.destroy();
14418                             }
14419                         }
14420                         
14421                     // create new label
14422                     } else if (defined(str)) {
14423                         attr = {
14424                             //align: align,
14425                             fill: options.backgroundColor,
14426                             stroke: options.borderColor,
14427                             'stroke-width': options.borderWidth,
14428                             r: options.borderRadius || 0,
14429                             rotation: rotation,
14430                             padding: options.padding,
14431                             zIndex: 1
14432                         };
14433                         // Remove unused attributes (#947)
14434                         for (name in attr) {
14435                             if (attr[name] === UNDEFINED) {
14436                                 delete attr[name];
14437                             }
14438                         }
14439                         
14440                         dataLabel = point.dataLabel = series.chart.renderer[rotation ? 'text' : 'label']( // labels don't support rotation
14441                             str,
14442                             0,
14443                             -999,
14444                             null,
14445                             null,
14446                             null,
14447                             options.useHTML
14448                         )
14449                         .attr(attr)
14450                         .css(options.style)
14451                         .add(dataLabelsGroup)
14452                         .shadow(options.shadow);
14453                         
14454                     }
14455                     
14456                     if (dataLabel) {
14457                         // Now the data label is created and placed at 0,0, so we need to align it
14458                         series.alignDataLabel(point, dataLabel, options, null, isNew);
14459                     }
14460                 }
14461             });
14462         }
14463     },
14464     
14465     /**
14466      * Align each individual data label
14467      */
14468     alignDataLabel: function (point, dataLabel, options, alignTo, isNew) {
14469         var chart = this.chart,
14470             inverted = chart.inverted,
14471             plotX = pick(point.plotX, -999),
14472             plotY = pick(point.plotY, -999),
14473             bBox = dataLabel.getBBox(),
14474             visible = this.visible && chart.isInsidePlot(point.plotX, point.plotY, inverted),
14475             alignAttr; // the final position;
14476                 
14477         if (visible) {
14478
14479             // The alignment box is a singular point
14480             alignTo = extend({
14481                 x: inverted ? chart.plotWidth - plotY : plotX,
14482                 y: mathRound(inverted ? chart.plotHeight - plotX : plotY),
14483                 width: 0,
14484                 height: 0
14485             }, alignTo);
14486             
14487             // Add the text size for alignment calculation
14488             extend(options, {
14489                 width: bBox.width,
14490                 height: bBox.height
14491             });
14492
14493             // Allow a hook for changing alignment in the last moment, then do the alignment
14494             if (options.rotation) { // Fancy box alignment isn't supported for rotated text
14495                 alignAttr = {
14496                     align: options.align,
14497                     x: alignTo.x + options.x + alignTo.width / 2,
14498                     y: alignTo.y + options.y + alignTo.height / 2
14499                 };
14500                 dataLabel[isNew ? 'attr' : 'animate'](alignAttr);
14501             } else {
14502                 dataLabel.align(options, null, alignTo);
14503                 alignAttr = dataLabel.alignAttr;
14504
14505                 // Handle justify or crop
14506                 if (pick(options.overflow, 'justify') === 'justify') { // docs: overflow: justify, also crop only applies when not justify
14507                     this.justifyDataLabel(dataLabel, options, alignAttr, bBox, alignTo, isNew);
14508                 
14509                 } else if (pick(options.crop, true)) {
14510                     // Now check that the data label is within the plot area
14511                     visible = chart.isInsidePlot(alignAttr.x, alignAttr.y) && chart.isInsidePlot(alignAttr.x + bBox.width, alignAttr.y + bBox.height);
14512                 
14513                 }
14514             }        
14515         }
14516
14517         // Show or hide based on the final aligned position
14518         if (!visible) {
14519             dataLabel.attr({ y: -999 });
14520         }
14521                 
14522     },
14523     
14524     /**
14525      * If data labels fall partly outside the plot area, align them back in, in a way that
14526      * doesn't hide the point.
14527      */
14528     justifyDataLabel: function (dataLabel, options, alignAttr, bBox, alignTo, isNew) {
14529         var chart = this.chart,
14530             align = options.align,
14531             verticalAlign = options.verticalAlign,
14532             off,
14533             justified;
14534
14535         // Off left
14536         off = alignAttr.x;
14537         if (off < 0) {
14538             if (align === 'right') {
14539                 options.align = 'left';
14540             } else {
14541                 options.x = -off;
14542             }
14543             justified = true;
14544         }
14545
14546         // Off right
14547         off = alignAttr.x + bBox.width;
14548         if (off > chart.plotWidth) {
14549             if (align === 'left') {
14550                 options.align = 'right';
14551             } else {
14552                 options.x = chart.plotWidth - off;
14553             }
14554             justified = true;
14555         }
14556
14557         // Off top
14558         off = alignAttr.y;
14559         if (off < 0) {
14560             if (verticalAlign === 'bottom') {
14561                 options.verticalAlign = 'top';
14562             } else {
14563                 options.y = -off;
14564             }
14565             justified = true;
14566         }
14567
14568         // Off bottom
14569         off = alignAttr.y + bBox.height;
14570         if (off > chart.plotHeight) {
14571             if (verticalAlign === 'top') {
14572                 options.verticalAlign = 'bottom';
14573             } else {
14574                 options.y = chart.plotHeight - off;
14575             }
14576             justified = true;
14577         }
14578         
14579         if (justified) {
14580             dataLabel.placed = !isNew;
14581             dataLabel.align(options, null, alignTo);
14582         }
14583     },
14584     
14585     /**
14586      * Return the graph path of a segment
14587      */
14588     getSegmentPath: function (segment) {        
14589         var series = this,
14590             segmentPath = [],
14591             step = series.options.step;
14592             
14593         // build the segment line
14594         each(segment, function (point, i) {
14595             
14596             var plotX = point.plotX,
14597                 plotY = point.plotY,
14598                 lastPoint;
14599
14600             if (series.getPointSpline) { // generate the spline as defined in the SplineSeries object
14601                 segmentPath.push.apply(segmentPath, series.getPointSpline(segment, point, i));
14602
14603             } else {
14604
14605                 // moveTo or lineTo
14606                 segmentPath.push(i ? L : M);
14607
14608                 // step line?
14609                 if (step && i) {
14610                     lastPoint = segment[i - 1];
14611                     if (step === 'right') {
14612                         segmentPath.push(
14613                             lastPoint.plotX,
14614                             plotY
14615                         );
14616                         
14617                     } else if (step === 'center') {
14618                         segmentPath.push(
14619                             (lastPoint.plotX + plotX) / 2,
14620                             lastPoint.plotY,
14621                             (lastPoint.plotX + plotX) / 2,
14622                             plotY
14623                         );
14624                         
14625                     } else {
14626                         segmentPath.push(
14627                             plotX,
14628                             lastPoint.plotY
14629                         );
14630                     }
14631                 }
14632
14633                 // normal line to next point
14634                 segmentPath.push(
14635                     point.plotX,
14636                     point.plotY
14637                 );
14638             }
14639         });
14640         
14641         return segmentPath;
14642     },
14643
14644     /**
14645      * Get the graph path
14646      */
14647     getGraphPath: function () {
14648         var series = this,
14649             graphPath = [],
14650             segmentPath,
14651             singlePoints = []; // used in drawTracker
14652
14653         // Divide into segments and build graph and area paths
14654         each(series.segments, function (segment) {
14655             
14656             segmentPath = series.getSegmentPath(segment);
14657             
14658             // add the segment to the graph, or a single point for tracking
14659             if (segment.length > 1) {
14660                 graphPath = graphPath.concat(segmentPath);
14661             } else {
14662                 singlePoints.push(segment[0]);
14663             }
14664         });
14665
14666         // Record it for use in drawGraph and drawTracker, and return graphPath
14667         series.singlePoints = singlePoints;
14668         series.graphPath = graphPath;
14669         
14670         return graphPath;
14671         
14672     },
14673     
14674     /**
14675      * Draw the actual graph
14676      */
14677     drawGraph: function () {
14678         var series = this,
14679             options = this.options,
14680             props = [['graph', options.lineColor || this.color]],
14681             lineWidth = options.lineWidth,
14682             dashStyle =  options.dashStyle,
14683             graphPath = this.getGraphPath(),
14684             negativeColor = options.negativeColor;
14685             
14686         if (negativeColor) {
14687             props.push(['graphNeg', negativeColor]);
14688         }
14689         
14690         // draw the graph
14691         each(props, function (prop, i) {
14692             var graphKey = prop[0],
14693                 graph = series[graphKey],
14694                 attribs;
14695             
14696             if (graph) {
14697                 stop(graph); // cancel running animations, #459
14698                 graph.animate({ d: graphPath });
14699     
14700             } else if (lineWidth && graphPath.length) { // #1487
14701                 attribs = {
14702                     stroke: prop[1],
14703                     'stroke-width': lineWidth,
14704                     zIndex: 1 // #1069
14705                 };
14706                 if (dashStyle) {
14707                     attribs.dashstyle = dashStyle;
14708                 } else {
14709                     attribs['stroke-linecap'] = attribs['stroke-linejoin'] = 'round';
14710                 }
14711
14712                 series[graphKey] = series.chart.renderer.path(graphPath)
14713                     .attr(attribs)
14714                     .add(series.group)
14715                     .shadow(!i && options.shadow);
14716             }
14717         });
14718     },
14719     
14720     /**
14721      * Clip the graphs into the positive and negative coloured graphs
14722      */
14723     clipNeg: function () {
14724         var options = this.options,
14725             chart = this.chart,
14726             renderer = chart.renderer,
14727             negativeColor = options.negativeColor || options.negativeFillColor,
14728             translatedThreshold,
14729             posAttr,
14730             negAttr,
14731             graph = this.graph,
14732             area = this.area,
14733             posClip = this.posClip,
14734             negClip = this.negClip,
14735             chartWidth = chart.chartWidth,
14736             chartHeight = chart.chartHeight,
14737             chartSizeMax = mathMax(chartWidth, chartHeight),
14738             yAxis = this.yAxis,
14739             above,
14740             below;
14741         
14742         if (negativeColor && (graph || area)) {
14743             translatedThreshold = mathRound(yAxis.toPixels(options.threshold || 0, true));
14744             above = {
14745                 x: 0,
14746                 y: 0,
14747                 width: chartSizeMax,
14748                 height: translatedThreshold
14749             };
14750             below = {
14751                 x: 0,
14752                 y: translatedThreshold,
14753                 width: chartSizeMax,
14754                 height: chartSizeMax
14755             };
14756             
14757             if (chart.inverted) {
14758
14759                 above.height = below.y = chart.plotWidth - translatedThreshold;
14760                 if (renderer.isVML) {
14761                     above = {
14762                         x: chart.plotWidth - translatedThreshold - chart.plotLeft,
14763                         y: 0,
14764                         width: chartWidth,
14765                         height: chartHeight
14766                     };
14767                     below = {
14768                         x: translatedThreshold + chart.plotLeft - chartWidth,
14769                         y: 0,
14770                         width: chart.plotLeft + translatedThreshold,
14771                         height: chartWidth
14772                     };
14773                 }
14774             }
14775             
14776             if (yAxis.reversed) {
14777                 posAttr = below;
14778                 negAttr = above;
14779             } else {
14780                 posAttr = above;
14781                 negAttr = below;
14782             }
14783         
14784             if (posClip) { // update
14785                 posClip.animate(posAttr);
14786                 negClip.animate(negAttr);
14787             } else {
14788                 
14789                 this.posClip = posClip = renderer.clipRect(posAttr);
14790                 this.negClip = negClip = renderer.clipRect(negAttr);
14791                 
14792                 if (graph && this.graphNeg) {
14793                     graph.clip(posClip);
14794                     this.graphNeg.clip(negClip);    
14795                 }
14796                 
14797                 if (area) {
14798                     area.clip(posClip);
14799                     this.areaNeg.clip(negClip);
14800                 } 
14801             } 
14802         }    
14803     },
14804
14805     /**
14806      * Initialize and perform group inversion on series.group and series.markerGroup
14807      */
14808     invertGroups: function () {
14809         var series = this,
14810             chart = series.chart;
14811
14812         // Pie, go away (#1736)
14813         if (!series.xAxis) {
14814             return;
14815         }
14816         
14817         // A fixed size is needed for inversion to work
14818         function setInvert() {            
14819             var size = {
14820                 width: series.yAxis.len,
14821                 height: series.xAxis.len
14822             };
14823             
14824             each(['group', 'markerGroup'], function (groupName) {
14825                 if (series[groupName]) {
14826                     series[groupName].attr(size).invert();
14827                 }
14828             });
14829         }
14830
14831         addEvent(chart, 'resize', setInvert); // do it on resize
14832         addEvent(series, 'destroy', function () {
14833             removeEvent(chart, 'resize', setInvert);
14834         });
14835
14836         // Do it now
14837         setInvert(); // do it now
14838         
14839         // On subsequent render and redraw, just do setInvert without setting up events again
14840         series.invertGroups = setInvert;
14841     },
14842     
14843     /**
14844      * General abstraction for creating plot groups like series.group, series.dataLabelsGroup and 
14845      * series.markerGroup. On subsequent calls, the group will only be adjusted to the updated plot size.
14846      */
14847     plotGroup: function (prop, name, visibility, zIndex, parent) {
14848         var group = this[prop],
14849             isNew = !group;
14850         
14851         // Generate it on first call
14852         if (isNew) {    
14853             this[prop] = group = this.chart.renderer.g(name)
14854                 .attr({
14855                     visibility: visibility,
14856                     zIndex: zIndex || 0.1 // IE8 needs this
14857                 })
14858                 .add(parent);
14859         }
14860         // Place it on first and subsequent (redraw) calls
14861         group[isNew ? 'attr' : 'animate'](this.getPlotBox());
14862         return group;        
14863     },
14864
14865     /**
14866      * Get the translation and scale for the plot area of this series
14867      */
14868     getPlotBox: function () {
14869         return {
14870             translateX: this.xAxis ? this.xAxis.left : this.chart.plotLeft, 
14871             translateY: this.yAxis ? this.yAxis.top : this.chart.plotTop,
14872             scaleX: 1, // #1623
14873             scaleY: 1
14874         };
14875     },
14876     
14877     /**
14878      * Render the graph and markers
14879      */
14880     render: function () {
14881         var series = this,
14882             chart = series.chart,
14883             group,
14884             options = series.options,
14885             animation = options.animation,
14886             doAnimation = animation && !!series.animate && 
14887                 chart.renderer.isSVG, // this animation doesn't work in IE8 quirks when the group div is hidden,
14888                 // and looks bad in other oldIE
14889             visibility = series.visible ? VISIBLE : HIDDEN,
14890             zIndex = options.zIndex,
14891             hasRendered = series.hasRendered,
14892             chartSeriesGroup = chart.seriesGroup;
14893         
14894         // the group
14895         group = series.plotGroup(
14896             'group', 
14897             'series', 
14898             visibility, 
14899             zIndex, 
14900             chartSeriesGroup
14901         );
14902         
14903         series.markerGroup = series.plotGroup(
14904             'markerGroup', 
14905             'markers', 
14906             visibility, 
14907             zIndex, 
14908             chartSeriesGroup
14909         );
14910         
14911         // initiate the animation
14912         if (doAnimation) {
14913             series.animate(true);
14914         }
14915
14916         // cache attributes for shapes
14917         series.getAttribs();
14918
14919         // SVGRenderer needs to know this before drawing elements (#1089, #1795)
14920         group.inverted = series.isCartesian ? chart.inverted : false;
14921         
14922         // draw the graph if any
14923         if (series.drawGraph) {
14924             series.drawGraph();
14925             series.clipNeg();
14926         }
14927
14928         // draw the data labels (inn pies they go before the points)
14929         series.drawDataLabels();
14930         
14931         // draw the points
14932         series.drawPoints();
14933
14934
14935         // draw the mouse tracking area
14936         if (series.options.enableMouseTracking !== false) {
14937             series.drawTracker();
14938         }
14939         
14940         // Handle inverted series and tracker groups
14941         if (chart.inverted) {
14942             series.invertGroups();
14943         }
14944         
14945         // Initial clipping, must be defined after inverting groups for VML
14946         if (options.clip !== false && !series.sharedClipKey && !hasRendered) {
14947             group.clip(chart.clipRect);
14948         }
14949
14950         // Run the animation
14951         if (doAnimation) {
14952             series.animate();
14953         } else if (!hasRendered) {
14954             series.afterAnimate();
14955         }
14956
14957         series.isDirty = series.isDirtyData = false; // means data is in accordance with what you see
14958         // (See #322) series.isDirty = series.isDirtyData = false; // means data is in accordance with what you see
14959         series.hasRendered = true;
14960     },
14961     
14962     /**
14963      * Redraw the series after an update in the axes.
14964      */
14965     redraw: function () {
14966         var series = this,
14967             chart = series.chart,
14968             wasDirtyData = series.isDirtyData, // cache it here as it is set to false in render, but used after
14969             group = series.group,
14970             xAxis = series.xAxis,
14971             yAxis = series.yAxis;
14972
14973         // reposition on resize
14974         if (group) {
14975             if (chart.inverted) {
14976                 group.attr({
14977                     width: chart.plotWidth,
14978                     height: chart.plotHeight
14979                 });
14980             }
14981
14982             group.animate({
14983                 translateX: pick(xAxis && xAxis.left, chart.plotLeft),
14984                 translateY: pick(yAxis && yAxis.top, chart.plotTop)
14985             });
14986         }
14987
14988         series.translate();
14989         series.setTooltipPoints(true);
14990
14991         series.render();
14992         if (wasDirtyData) {
14993             fireEvent(series, 'updatedData');
14994         }
14995     },
14996
14997     /**
14998      * Set the state of the graph
14999      */
15000     setState: function (state) {
15001         var series = this,
15002             options = series.options,
15003             graph = series.graph,
15004             graphNeg = series.graphNeg,
15005             stateOptions = options.states,
15006             lineWidth = options.lineWidth,
15007             attribs;
15008
15009         state = state || NORMAL_STATE;
15010
15011         if (series.state !== state) {
15012             series.state = state;
15013
15014             if (stateOptions[state] && stateOptions[state].enabled === false) {
15015                 return;
15016             }
15017
15018             if (state) {
15019                 lineWidth = stateOptions[state].lineWidth || lineWidth + 1;
15020             }
15021
15022             if (graph && !graph.dashstyle) { // hover is turned off for dashed lines in VML
15023                 attribs = {
15024                     'stroke-width': lineWidth
15025                 };
15026                 // use attr because animate will cause any other animation on the graph to stop
15027                 graph.attr(attribs);
15028                 if (graphNeg) {
15029                     graphNeg.attr(attribs);
15030                 }
15031             }
15032         }
15033     },
15034
15035     /**
15036      * Set the visibility of the graph
15037      *
15038      * @param vis {Boolean} True to show the series, false to hide. If UNDEFINED,
15039      *        the visibility is toggled.
15040      */
15041     setVisible: function (vis, redraw) {
15042         var series = this,
15043             chart = series.chart,
15044             legendItem = series.legendItem,
15045             showOrHide,
15046             ignoreHiddenSeries = chart.options.chart.ignoreHiddenSeries,
15047             oldVisibility = series.visible;
15048
15049         // if called without an argument, toggle visibility
15050         series.visible = vis = series.userOptions.visible = vis === UNDEFINED ? !oldVisibility : vis;
15051         showOrHide = vis ? 'show' : 'hide';
15052
15053         // show or hide elements
15054         each(['group', 'dataLabelsGroup', 'markerGroup', 'tracker'], function (key) {
15055             if (series[key]) {
15056                 series[key][showOrHide]();
15057             }
15058         });
15059
15060         
15061         // hide tooltip (#1361)
15062         if (chart.hoverSeries === series) {
15063             series.onMouseOut();
15064         }
15065
15066
15067         if (legendItem) {
15068             chart.legend.colorizeItem(series, vis);
15069         }
15070
15071
15072         // rescale or adapt to resized chart
15073         series.isDirty = true;
15074         // in a stack, all other series are affected
15075         if (series.options.stacking) {
15076             each(chart.series, function (otherSeries) {
15077                 if (otherSeries.options.stacking && otherSeries.visible) {
15078                     otherSeries.isDirty = true;
15079                 }
15080             });
15081         }
15082
15083         // show or hide linked series
15084         each(series.linkedSeries, function (otherSeries) {
15085             otherSeries.setVisible(vis, false);
15086         });
15087
15088         if (ignoreHiddenSeries) {
15089             chart.isDirtyBox = true;
15090         }
15091         if (redraw !== false) {
15092             chart.redraw();
15093         }
15094
15095         fireEvent(series, showOrHide);
15096     },
15097
15098     /**
15099      * Show the graph
15100      */
15101     show: function () {
15102         this.setVisible(true);
15103     },
15104
15105     /**
15106      * Hide the graph
15107      */
15108     hide: function () {
15109         this.setVisible(false);
15110     },
15111
15112
15113     /**
15114      * Set the selected state of the graph
15115      *
15116      * @param selected {Boolean} True to select the series, false to unselect. If
15117      *        UNDEFINED, the selection state is toggled.
15118      */
15119     select: function (selected) {
15120         var series = this;
15121         // if called without an argument, toggle
15122         series.selected = selected = (selected === UNDEFINED) ? !series.selected : selected;
15123
15124         if (series.checkbox) {
15125             series.checkbox.checked = selected;
15126         }
15127
15128         fireEvent(series, selected ? 'select' : 'unselect');
15129     },
15130
15131     /**
15132      * Draw the tracker object that sits above all data labels and markers to
15133      * track mouse events on the graph or points. For the line type charts
15134      * the tracker uses the same graphPath, but with a greater stroke width
15135      * for better control.
15136      */
15137     drawTracker: function () {
15138         var series = this,
15139             options = series.options,
15140             trackByArea = options.trackByArea,
15141             trackerPath = [].concat(trackByArea ? series.areaPath : series.graphPath),
15142             trackerPathLength = trackerPath.length,
15143             chart = series.chart,
15144             pointer = chart.pointer,
15145             renderer = chart.renderer,
15146             snap = chart.options.tooltip.snap,
15147             tracker = series.tracker,
15148             cursor = options.cursor,
15149             css = cursor && { cursor: cursor },
15150             singlePoints = series.singlePoints,
15151             singlePoint,
15152             i,
15153             onMouseOver = function () {
15154                 if (chart.hoverSeries !== series) {
15155                     series.onMouseOver();
15156                 }
15157             };
15158
15159         // Extend end points. A better way would be to use round linecaps,
15160         // but those are not clickable in VML.
15161         if (trackerPathLength && !trackByArea) {
15162             i = trackerPathLength + 1;
15163             while (i--) {
15164                 if (trackerPath[i] === M) { // extend left side
15165                     trackerPath.splice(i + 1, 0, trackerPath[i + 1] - snap, trackerPath[i + 2], L);
15166                 }
15167                 if ((i && trackerPath[i] === M) || i === trackerPathLength) { // extend right side
15168                     trackerPath.splice(i, 0, L, trackerPath[i - 2] + snap, trackerPath[i - 1]);
15169                 }
15170             }
15171         }
15172
15173         // handle single points
15174         for (i = 0; i < singlePoints.length; i++) {
15175             singlePoint = singlePoints[i];
15176             trackerPath.push(M, singlePoint.plotX - snap, singlePoint.plotY,
15177                 L, singlePoint.plotX + snap, singlePoint.plotY);
15178         }
15179         
15180         
15181
15182         // draw the tracker
15183         if (tracker) {
15184             tracker.attr({ d: trackerPath });
15185
15186         } else { // create
15187                 
15188             series.tracker = renderer.path(trackerPath)
15189                 .attr({
15190                     'stroke-linejoin': 'round', // #1225
15191                     visibility: series.visible ? VISIBLE : HIDDEN,
15192                     stroke: TRACKER_FILL,
15193                     fill: trackByArea ? TRACKER_FILL : NONE,
15194                     'stroke-width' : options.lineWidth + (trackByArea ? 0 : 2 * snap),
15195                     zIndex: 2
15196                 })
15197                 .add(series.group);
15198                 
15199             // The tracker is added to the series group, which is clipped, but is covered 
15200             // by the marker group. So the marker group also needs to capture events.
15201             each([series.tracker, series.markerGroup], function (tracker) {
15202                 tracker.addClass(PREFIX + 'tracker')
15203                     .on('mouseover', onMouseOver)
15204                     .on('mouseout', function (e) { pointer.onTrackerMouseOut(e); })
15205                     .css(css);
15206
15207                 if (hasTouch) {
15208                     tracker.on('touchstart', onMouseOver);
15209                 } 
15210             });
15211         }
15212
15213     }
15214
15215 }; // end Series prototype
15216
15217
15218 /**
15219  * LineSeries object
15220  */
15221 var LineSeries = extendClass(Series);
15222 seriesTypes.line = LineSeries;
15223
15224 /**
15225  * Set the default options for area
15226  */
15227 defaultPlotOptions.area = merge(defaultSeriesOptions, {
15228     threshold: 0
15229     // trackByArea: false,
15230     // lineColor: null, // overrides color, but lets fillColor be unaltered
15231     // fillOpacity: 0.75,
15232     // fillColor: null
15233 });
15234
15235 /**
15236  * AreaSeries object
15237  */
15238 var AreaSeries = extendClass(Series, {
15239     type: 'area',
15240     
15241     /**
15242      * For stacks, don't split segments on null values. Instead, draw null values with 
15243      * no marker. Also insert dummy points for any X position that exists in other series
15244      * in the stack.
15245      */ 
15246     getSegments: function () {
15247         var segments = [],
15248             segment = [],
15249             keys = [],
15250             xAxis = this.xAxis,
15251             yAxis = this.yAxis,
15252             stack = yAxis.stacks[this.stackKey],
15253             pointMap = {},
15254             plotX,
15255             plotY,
15256             points = this.points,
15257             connectNulls = this.options.connectNulls,
15258             val,
15259             i,
15260             x;
15261
15262         if (this.options.stacking && !this.cropped) { // cropped causes artefacts in Stock, and perf issue
15263             // Create a map where we can quickly look up the points by their X value.
15264             for (i = 0; i < points.length; i++) {
15265                 pointMap[points[i].x] = points[i];
15266             }
15267
15268             // Sort the keys (#1651)
15269             for (x in stack) {
15270                 keys.push(+x);
15271             }
15272             keys.sort(function (a, b) {
15273                 return a - b;
15274             });
15275
15276             each(keys, function (x) {
15277                 if (connectNulls && (!pointMap[x] || pointMap[x].y === null)) { // #1836
15278                     return;
15279
15280                 // The point exists, push it to the segment
15281                 } else if (pointMap[x]) {
15282                     segment.push(pointMap[x]);
15283
15284                 // There is no point for this X value in this series, so we 
15285                 // insert a dummy point in order for the areas to be drawn
15286                 // correctly.
15287                 } else {
15288                     plotX = xAxis.translate(x);
15289                     val = stack[x].percent ? (stack[x].total ? stack[x].cum * 100 / stack[x].total : 0) : stack[x].cum; // #1991
15290                     plotY = yAxis.toPixels(val, true);
15291                     segment.push({ 
15292                         y: null, 
15293                         plotX: plotX,
15294                         clientX: plotX, 
15295                         plotY: plotY, 
15296                         yBottom: plotY,
15297                         onMouseOver: noop
15298                     });
15299                 }
15300             });
15301
15302             if (segment.length) {
15303                 segments.push(segment);
15304             }
15305
15306         } else {
15307             Series.prototype.getSegments.call(this);
15308             segments = this.segments;
15309         }
15310
15311         this.segments = segments;
15312     },
15313     
15314     /**
15315      * Extend the base Series getSegmentPath method by adding the path for the area.
15316      * This path is pushed to the series.areaPath property.
15317      */
15318     getSegmentPath: function (segment) {
15319         
15320         var segmentPath = Series.prototype.getSegmentPath.call(this, segment), // call base method
15321             areaSegmentPath = [].concat(segmentPath), // work on a copy for the area path
15322             i,
15323             options = this.options,
15324             segLength = segmentPath.length,
15325             translatedThreshold = this.yAxis.getThreshold(options.threshold), // #2181
15326             yBottom;
15327         
15328         if (segLength === 3) { // for animation from 1 to two points
15329             areaSegmentPath.push(L, segmentPath[1], segmentPath[2]);
15330         }
15331         if (options.stacking && !this.closedStacks) {
15332             
15333             // Follow stack back. Todo: implement areaspline. A general solution could be to 
15334             // reverse the entire graphPath of the previous series, though may be hard with
15335             // splines and with series with different extremes
15336             for (i = segment.length - 1; i >= 0; i--) {
15337
15338                 yBottom = pick(segment[i].yBottom, translatedThreshold);
15339             
15340                 // step line?
15341                 if (i < segment.length - 1 && options.step) {
15342                     areaSegmentPath.push(segment[i + 1].plotX, yBottom);
15343                 }
15344                 
15345                 areaSegmentPath.push(segment[i].plotX, yBottom);
15346             }
15347
15348         } else { // follow zero line back
15349             this.closeSegment(areaSegmentPath, segment, translatedThreshold);
15350         }
15351         this.areaPath = this.areaPath.concat(areaSegmentPath);
15352         return segmentPath;
15353     },
15354     
15355     /**
15356      * Extendable method to close the segment path of an area. This is overridden in polar 
15357      * charts.
15358      */
15359     closeSegment: function (path, segment, translatedThreshold) {
15360         path.push(
15361             L,
15362             segment[segment.length - 1].plotX,
15363             translatedThreshold,
15364             L,
15365             segment[0].plotX,
15366             translatedThreshold
15367         );
15368     },
15369     
15370     /**
15371      * Draw the graph and the underlying area. This method calls the Series base
15372      * function and adds the area. The areaPath is calculated in the getSegmentPath
15373      * method called from Series.prototype.drawGraph.
15374      */
15375     drawGraph: function () {
15376         
15377         // Define or reset areaPath
15378         this.areaPath = [];
15379         
15380         // Call the base method
15381         Series.prototype.drawGraph.apply(this);
15382         
15383         // Define local variables
15384         var series = this,
15385             areaPath = this.areaPath,
15386             options = this.options,
15387             negativeColor = options.negativeColor,
15388             negativeFillColor = options.negativeFillColor,
15389             props = [['area', this.color, options.fillColor]]; // area name, main color, fill color
15390         
15391         if (negativeColor || negativeFillColor) {
15392             props.push(['areaNeg', negativeColor, negativeFillColor]);
15393         }
15394         
15395         each(props, function (prop) {
15396             var areaKey = prop[0],
15397                 area = series[areaKey];
15398                 
15399             // Create or update the area
15400             if (area) { // update
15401                 area.animate({ d: areaPath });
15402     
15403             } else { // create
15404                 series[areaKey] = series.chart.renderer.path(areaPath)
15405                     .attr({
15406                         fill: pick(
15407                             prop[2],
15408                             Color(prop[1]).setOpacity(pick(options.fillOpacity, 0.75)).get()
15409                         ),
15410                         zIndex: 0 // #1069
15411                     }).add(series.group);
15412             }
15413         });
15414     },
15415     
15416     /**
15417      * Get the series' symbol in the legend
15418      * 
15419      * @param {Object} legend The legend object
15420      * @param {Object} item The series (this) or point
15421      */
15422     drawLegendSymbol: function (legend, item) {
15423         
15424         item.legendSymbol = this.chart.renderer.rect(
15425             0,
15426             legend.baseline - 11,
15427             legend.options.symbolWidth,
15428             12,
15429             2
15430         ).attr({
15431             zIndex: 3
15432         }).add(item.legendGroup);        
15433         
15434     }
15435 });
15436
15437 seriesTypes.area = AreaSeries;/**
15438  * Set the default options for spline
15439  */
15440 defaultPlotOptions.spline = merge(defaultSeriesOptions);
15441
15442 /**
15443  * SplineSeries object
15444  */
15445 var SplineSeries = extendClass(Series, {
15446     type: 'spline',
15447
15448     /**
15449      * Get the spline segment from a given point's previous neighbour to the given point
15450      */
15451     getPointSpline: function (segment, point, i) {
15452         var smoothing = 1.5, // 1 means control points midway between points, 2 means 1/3 from the point, 3 is 1/4 etc
15453             denom = smoothing + 1,
15454             plotX = point.plotX,
15455             plotY = point.plotY,
15456             lastPoint = segment[i - 1],
15457             nextPoint = segment[i + 1],
15458             leftContX,
15459             leftContY,
15460             rightContX,
15461             rightContY,
15462             ret;
15463
15464         // find control points
15465         if (lastPoint && nextPoint) {
15466         
15467             var lastX = lastPoint.plotX,
15468                 lastY = lastPoint.plotY,
15469                 nextX = nextPoint.plotX,
15470                 nextY = nextPoint.plotY,
15471                 correction;
15472
15473             leftContX = (smoothing * plotX + lastX) / denom;
15474             leftContY = (smoothing * plotY + lastY) / denom;
15475             rightContX = (smoothing * plotX + nextX) / denom;
15476             rightContY = (smoothing * plotY + nextY) / denom;
15477
15478             // have the two control points make a straight line through main point
15479             correction = ((rightContY - leftContY) * (rightContX - plotX)) /
15480                 (rightContX - leftContX) + plotY - rightContY;
15481
15482             leftContY += correction;
15483             rightContY += correction;
15484
15485             // to prevent false extremes, check that control points are between
15486             // neighbouring points' y values
15487             if (leftContY > lastY && leftContY > plotY) {
15488                 leftContY = mathMax(lastY, plotY);
15489                 rightContY = 2 * plotY - leftContY; // mirror of left control point
15490             } else if (leftContY < lastY && leftContY < plotY) {
15491                 leftContY = mathMin(lastY, plotY);
15492                 rightContY = 2 * plotY - leftContY;
15493             }
15494             if (rightContY > nextY && rightContY > plotY) {
15495                 rightContY = mathMax(nextY, plotY);
15496                 leftContY = 2 * plotY - rightContY;
15497             } else if (rightContY < nextY && rightContY < plotY) {
15498                 rightContY = mathMin(nextY, plotY);
15499                 leftContY = 2 * plotY - rightContY;
15500             }
15501
15502             // record for drawing in next point
15503             point.rightContX = rightContX;
15504             point.rightContY = rightContY;
15505
15506         }
15507         
15508         // Visualize control points for debugging
15509         /*
15510         if (leftContX) {
15511             this.chart.renderer.circle(leftContX + this.chart.plotLeft, leftContY + this.chart.plotTop, 2)
15512                 .attr({
15513                     stroke: 'red',
15514                     'stroke-width': 1,
15515                     fill: 'none'
15516                 })
15517                 .add();
15518             this.chart.renderer.path(['M', leftContX + this.chart.plotLeft, leftContY + this.chart.plotTop,
15519                 'L', plotX + this.chart.plotLeft, plotY + this.chart.plotTop])
15520                 .attr({
15521                     stroke: 'red',
15522                     'stroke-width': 1
15523                 })
15524                 .add();
15525             this.chart.renderer.circle(rightContX + this.chart.plotLeft, rightContY + this.chart.plotTop, 2)
15526                 .attr({
15527                     stroke: 'green',
15528                     'stroke-width': 1,
15529                     fill: 'none'
15530                 })
15531                 .add();
15532             this.chart.renderer.path(['M', rightContX + this.chart.plotLeft, rightContY + this.chart.plotTop,
15533                 'L', plotX + this.chart.plotLeft, plotY + this.chart.plotTop])
15534                 .attr({
15535                     stroke: 'green',
15536                     'stroke-width': 1
15537                 })
15538                 .add();
15539         }
15540         */
15541
15542         // moveTo or lineTo
15543         if (!i) {
15544             ret = [M, plotX, plotY];
15545         } else { // curve from last point to this
15546             ret = [
15547                 'C',
15548                 lastPoint.rightContX || lastPoint.plotX,
15549                 lastPoint.rightContY || lastPoint.plotY,
15550                 leftContX || plotX,
15551                 leftContY || plotY,
15552                 plotX,
15553                 plotY
15554             ];
15555             lastPoint.rightContX = lastPoint.rightContY = null; // reset for updating series later
15556         }
15557         return ret;
15558     }
15559 });
15560 seriesTypes.spline = SplineSeries;
15561
15562 /**
15563  * Set the default options for areaspline
15564  */
15565 defaultPlotOptions.areaspline = merge(defaultPlotOptions.area);
15566
15567 /**
15568  * AreaSplineSeries object
15569  */
15570 var areaProto = AreaSeries.prototype,
15571     AreaSplineSeries = extendClass(SplineSeries, {
15572         type: 'areaspline',
15573         closedStacks: true, // instead of following the previous graph back, follow the threshold back
15574         
15575         // Mix in methods from the area series
15576         getSegmentPath: areaProto.getSegmentPath,
15577         closeSegment: areaProto.closeSegment,
15578         drawGraph: areaProto.drawGraph,
15579         drawLegendSymbol: areaProto.drawLegendSymbol
15580     });
15581 seriesTypes.areaspline = AreaSplineSeries;
15582
15583 /**
15584  * Set the default options for column
15585  */
15586 defaultPlotOptions.column = merge(defaultSeriesOptions, {
15587     borderColor: '#FFFFFF',
15588     borderWidth: 1,
15589     borderRadius: 0,
15590     //colorByPoint: undefined,
15591     groupPadding: 0.2,
15592     //grouping: true,
15593     marker: null, // point options are specified in the base options
15594     pointPadding: 0.1,
15595     //pointWidth: null,
15596     minPointLength: 0,
15597     cropThreshold: 50, // when there are more points, they will not animate out of the chart on xAxis.setExtremes
15598     pointRange: null, // null means auto, meaning 1 in a categorized axis and least distance between points if not categories
15599     states: {
15600         hover: {
15601             brightness: 0.1,
15602             shadow: false
15603         },
15604         select: {
15605             color: '#C0C0C0',
15606             borderColor: '#000000',
15607             shadow: false
15608         }
15609     },
15610     dataLabels: {
15611         align: null, // auto
15612         verticalAlign: null, // auto
15613         y: null
15614     },
15615     stickyTracking: false,
15616     threshold: 0
15617 });
15618
15619 /**
15620  * ColumnSeries object
15621  */
15622 var ColumnSeries = extendClass(Series, {
15623     type: 'column',
15624     pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
15625         stroke: 'borderColor',
15626         'stroke-width': 'borderWidth',
15627         fill: 'color',
15628         r: 'borderRadius'
15629     },
15630     cropShoulder: 0,
15631     trackerGroups: ['group', 'dataLabelsGroup'],
15632     negStacks: true, // use separate negative stacks, unlike area stacks where a negative 
15633         // point is substracted from previous (#1910)
15634     
15635     /**
15636      * Initialize the series
15637      */
15638     init: function () {
15639         Series.prototype.init.apply(this, arguments);
15640
15641         var series = this,
15642             chart = series.chart;
15643
15644         // if the series is added dynamically, force redraw of other
15645         // series affected by a new column
15646         if (chart.hasRendered) {
15647             each(chart.series, function (otherSeries) {
15648                 if (otherSeries.type === series.type) {
15649                     otherSeries.isDirty = true;
15650                 }
15651             });
15652         }
15653     },
15654
15655     /**
15656      * Return the width and x offset of the columns adjusted for grouping, groupPadding, pointPadding,
15657      * pointWidth etc. 
15658      */
15659     getColumnMetrics: function () {
15660
15661         var series = this,
15662             options = series.options,
15663             xAxis = series.xAxis,
15664             yAxis = series.yAxis,
15665             reversedXAxis = xAxis.reversed,
15666             stackKey,
15667             stackGroups = {},
15668             columnIndex,
15669             columnCount = 0;
15670
15671         // Get the total number of column type series.
15672         // This is called on every series. Consider moving this logic to a
15673         // chart.orderStacks() function and call it on init, addSeries and removeSeries
15674         if (options.grouping === false) {
15675             columnCount = 1;
15676         } else {
15677             each(series.chart.series, function (otherSeries) {
15678                 var otherOptions = otherSeries.options,
15679                     otherYAxis = otherSeries.yAxis;
15680                 if (otherSeries.type === series.type && otherSeries.visible &&
15681                         yAxis.len === otherYAxis.len && yAxis.pos === otherYAxis.pos) {  // #642, #2086
15682                     if (otherOptions.stacking) {
15683                         stackKey = otherSeries.stackKey;
15684                         if (stackGroups[stackKey] === UNDEFINED) {
15685                             stackGroups[stackKey] = columnCount++;
15686                         }
15687                         columnIndex = stackGroups[stackKey];
15688                     } else if (otherOptions.grouping !== false) { // #1162
15689                         columnIndex = columnCount++;
15690                     }
15691                     otherSeries.columnIndex = columnIndex;
15692                 }
15693             });
15694         }
15695
15696         var categoryWidth = mathMin(
15697                 mathAbs(xAxis.transA) * (xAxis.ordinalSlope || options.pointRange || xAxis.closestPointRange || 1), 
15698                 xAxis.len // #1535
15699             ),
15700             groupPadding = categoryWidth * options.groupPadding,
15701             groupWidth = categoryWidth - 2 * groupPadding,
15702             pointOffsetWidth = groupWidth / columnCount,
15703             optionPointWidth = options.pointWidth,
15704             pointPadding = defined(optionPointWidth) ? (pointOffsetWidth - optionPointWidth) / 2 :
15705                 pointOffsetWidth * options.pointPadding,
15706             pointWidth = pick(optionPointWidth, pointOffsetWidth - 2 * pointPadding), // exact point width, used in polar charts
15707             colIndex = (reversedXAxis ? 
15708                 columnCount - (series.columnIndex || 0) : // #1251
15709                 series.columnIndex) || 0,
15710             pointXOffset = pointPadding + (groupPadding + colIndex *
15711                 pointOffsetWidth - (categoryWidth / 2)) *
15712                 (reversedXAxis ? -1 : 1);
15713
15714         // Save it for reading in linked series (Error bars particularly)
15715         return (series.columnMetrics = { 
15716             width: pointWidth, 
15717             offset: pointXOffset 
15718         });
15719             
15720     },
15721
15722     /**
15723      * Translate each point to the plot area coordinate system and find shape positions
15724      */
15725     translate: function () {
15726         var series = this,
15727             chart = series.chart,
15728             options = series.options,
15729             borderWidth = options.borderWidth,
15730             yAxis = series.yAxis,
15731             threshold = options.threshold,
15732             translatedThreshold = series.translatedThreshold = yAxis.getThreshold(threshold),
15733             minPointLength = pick(options.minPointLength, 5),
15734             metrics = series.getColumnMetrics(),
15735             pointWidth = metrics.width,
15736             seriesBarW = series.barW = mathCeil(mathMax(pointWidth, 1 + 2 * borderWidth)), // rounded and postprocessed for border width
15737             pointXOffset = series.pointXOffset = metrics.offset,
15738             xCrisp = -(borderWidth % 2 ? 0.5 : 0),
15739             yCrisp = borderWidth % 2 ? 0.5 : 1;
15740
15741         if (chart.renderer.isVML && chart.inverted) {
15742             yCrisp += 1;
15743         }
15744
15745         Series.prototype.translate.apply(series);
15746
15747         // record the new values
15748         each(series.points, function (point) {
15749             var yBottom = pick(point.yBottom, translatedThreshold),
15750                 plotY = mathMin(mathMax(-999 - yBottom, point.plotY), yAxis.len + 999 + yBottom), // Don't draw too far outside plot area (#1303, #2241)
15751                 barX = point.plotX + pointXOffset,
15752                 barW = seriesBarW,
15753                 barY = mathMin(plotY, yBottom),
15754                 right,
15755                 bottom,
15756                 fromTop,
15757                 fromLeft,
15758                 barH = mathMax(plotY, yBottom) - barY;
15759
15760             // Handle options.minPointLength
15761             if (mathAbs(barH) < minPointLength) {
15762                 if (minPointLength) {
15763                     barH = minPointLength;
15764                     barY =
15765                         mathRound(mathAbs(barY - translatedThreshold) > minPointLength ? // stacked
15766                             yBottom - minPointLength : // keep position
15767                             translatedThreshold - (yAxis.translate(point.y, 0, 1, 0, 1) <= translatedThreshold ? minPointLength : 0)); // use exact yAxis.translation (#1485)
15768                 }
15769             }
15770
15771             // Cache for access in polar
15772             point.barX = barX;
15773             point.pointWidth = pointWidth;
15774
15775
15776             // Round off to obtain crisp edges
15777             fromLeft = mathAbs(barX) < 0.5;
15778             right = mathRound(barX + barW) + xCrisp;
15779             barX = mathRound(barX) + xCrisp;
15780             barW = right - barX;
15781
15782             fromTop = mathAbs(barY) < 0.5;
15783             bottom = mathRound(barY + barH) + yCrisp;
15784             barY = mathRound(barY) + yCrisp;
15785             barH = bottom - barY;
15786
15787             // Top and left edges are exceptions
15788             if (fromLeft) {
15789                 barX += 1;
15790                 barW -= 1;
15791             }
15792             if (fromTop) {
15793                 barY -= 1;
15794                 barH += 1;
15795             }
15796
15797             // Register shape type and arguments to be used in drawPoints
15798             point.shapeType = 'rect';
15799             point.shapeArgs = {
15800                 x: barX,
15801                 y: barY,
15802                 width: barW,
15803                 height: barH
15804             };
15805         });
15806
15807     },
15808
15809     getSymbol: noop,
15810     
15811     /**
15812      * Use a solid rectangle like the area series types
15813      */
15814     drawLegendSymbol: AreaSeries.prototype.drawLegendSymbol,
15815     
15816     
15817     /**
15818      * Columns have no graph
15819      */
15820     drawGraph: noop,
15821
15822     /**
15823      * Draw the columns. For bars, the series.group is rotated, so the same coordinates
15824      * apply for columns and bars. This method is inherited by scatter series.
15825      *
15826      */
15827     drawPoints: function () {
15828         var series = this,
15829             options = series.options,
15830             renderer = series.chart.renderer,
15831             shapeArgs;
15832
15833
15834         // draw the columns
15835         each(series.points, function (point) {
15836             var plotY = point.plotY,
15837                 graphic = point.graphic;
15838
15839             if (plotY !== UNDEFINED && !isNaN(plotY) && point.y !== null) {
15840                 shapeArgs = point.shapeArgs;
15841                 
15842                 if (graphic) { // update
15843                     stop(graphic);
15844                     graphic.animate(merge(shapeArgs));
15845
15846                 } else {
15847                     point.graphic = graphic = renderer[point.shapeType](shapeArgs)
15848                         .attr(point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE])
15849                         .add(series.group)
15850                         .shadow(options.shadow, null, options.stacking && !options.borderRadius);
15851                 }
15852
15853             } else if (graphic) {
15854                 point.graphic = graphic.destroy(); // #1269
15855             }
15856         });
15857     },
15858
15859     /**
15860      * Add tracking event listener to the series group, so the point graphics
15861      * themselves act as trackers
15862      */
15863     drawTracker: function () {
15864         var series = this,
15865             chart = series.chart,
15866             pointer = chart.pointer,
15867             cursor = series.options.cursor,
15868             css = cursor && { cursor: cursor },
15869             onMouseOver = function (e) {
15870                 var target = e.target,
15871                     point;
15872
15873                 if (chart.hoverSeries !== series) {
15874                     series.onMouseOver();
15875                 }
15876                 while (target && !point) {
15877                     point = target.point;
15878                     target = target.parentNode;
15879                 }
15880                 if (point !== UNDEFINED && point !== chart.hoverPoint) { // undefined on graph in scatterchart
15881                     point.onMouseOver(e);
15882                 }
15883             };
15884
15885         // Add reference to the point
15886         each(series.points, function (point) {
15887             if (point.graphic) {
15888                 point.graphic.element.point = point;
15889             }
15890             if (point.dataLabel) {
15891                 point.dataLabel.element.point = point;
15892             }
15893         });
15894
15895         // Add the event listeners, we need to do this only once
15896         if (!series._hasTracking) {
15897             each(series.trackerGroups, function (key) {
15898                 if (series[key]) { // we don't always have dataLabelsGroup
15899                     series[key]
15900                         .addClass(PREFIX + 'tracker')
15901                         .on('mouseover', onMouseOver)
15902                         .on('mouseout', function (e) { pointer.onTrackerMouseOut(e); })
15903                         .css(css);
15904                     if (hasTouch) {
15905                         series[key].on('touchstart', onMouseOver);
15906                     }
15907                 }
15908             });
15909             series._hasTracking = true;
15910         }
15911     },
15912     
15913     /** 
15914      * Override the basic data label alignment by adjusting for the position of the column
15915      */
15916     alignDataLabel: function (point, dataLabel, options,  alignTo, isNew) {
15917         var chart = this.chart,
15918             inverted = chart.inverted,
15919             dlBox = point.dlBox || point.shapeArgs, // data label box for alignment
15920             below = point.below || (point.plotY > pick(this.translatedThreshold, chart.plotSizeY)),
15921             inside = pick(options.inside, !!this.options.stacking); // draw it inside the box?
15922         
15923         // Align to the column itself, or the top of it
15924         if (dlBox) { // Area range uses this method but not alignTo
15925             alignTo = merge(dlBox);
15926             if (inverted) {
15927                 alignTo = {
15928                     x: chart.plotWidth - alignTo.y - alignTo.height,
15929                     y: chart.plotHeight - alignTo.x - alignTo.width,
15930                     width: alignTo.height,
15931                     height: alignTo.width
15932                 };
15933             }
15934                 
15935             // Compute the alignment box
15936             if (!inside) {
15937                 if (inverted) {
15938                     alignTo.x += below ? 0 : alignTo.width;
15939                     alignTo.width = 0;
15940                 } else {
15941                     alignTo.y += below ? alignTo.height : 0;
15942                     alignTo.height = 0;
15943                 }
15944             }
15945         }
15946         
15947         // When alignment is undefined (typically columns and bars), display the individual 
15948         // point below or above the point depending on the threshold
15949         options.align = pick(
15950             options.align, 
15951             !inverted || inside ? 'center' : below ? 'right' : 'left'
15952         );
15953         options.verticalAlign = pick(
15954             options.verticalAlign, 
15955             inverted || inside ? 'middle' : below ? 'top' : 'bottom'
15956         );
15957         
15958         // Call the parent method
15959         Series.prototype.alignDataLabel.call(this, point, dataLabel, options, alignTo, isNew);
15960     },
15961
15962
15963     /**
15964      * Animate the column heights one by one from zero
15965      * @param {Boolean} init Whether to initialize the animation or run it
15966      */
15967     animate: function (init) {
15968         var series = this,
15969             yAxis = this.yAxis,
15970             options = series.options,
15971             inverted = this.chart.inverted,
15972             attr = {},
15973             translatedThreshold;
15974
15975         if (hasSVG) { // VML is too slow anyway
15976             if (init) {
15977                 attr.scaleY = 0.001;
15978                 translatedThreshold = mathMin(yAxis.pos + yAxis.len, mathMax(yAxis.pos, yAxis.toPixels(options.threshold)));
15979                 if (inverted) {
15980                     attr.translateX = translatedThreshold - yAxis.len;
15981                 } else {
15982                     attr.translateY = translatedThreshold;
15983                 }
15984                 series.group.attr(attr);
15985
15986             } else { // run the animation
15987                 
15988                 attr.scaleY = 1;
15989                 attr[inverted ? 'translateX' : 'translateY'] = yAxis.pos;
15990                 series.group.animate(attr, series.options.animation);
15991
15992                 // delete this function to allow it only once
15993                 series.animate = null;
15994             }
15995         }
15996     },
15997     
15998     /**
15999      * Remove this series from the chart
16000      */
16001     remove: function () {
16002         var series = this,
16003             chart = series.chart;
16004
16005         // column and bar series affects other series of the same type
16006         // as they are either stacked or grouped
16007         if (chart.hasRendered) {
16008             each(chart.series, function (otherSeries) {
16009                 if (otherSeries.type === series.type) {
16010                     otherSeries.isDirty = true;
16011                 }
16012             });
16013         }
16014
16015         Series.prototype.remove.apply(series, arguments);
16016     }
16017 });
16018 seriesTypes.column = ColumnSeries;
16019 /**
16020  * Set the default options for bar
16021  */
16022 defaultPlotOptions.bar = merge(defaultPlotOptions.column);
16023 /**
16024  * The Bar series class
16025  */
16026 var BarSeries = extendClass(ColumnSeries, {
16027     type: 'bar',
16028     inverted: true
16029 });
16030 seriesTypes.bar = BarSeries;
16031
16032 /**
16033  * Set the default options for scatter
16034  */
16035 defaultPlotOptions.scatter = merge(defaultSeriesOptions, {
16036     lineWidth: 0,
16037     tooltip: {
16038         headerFormat: '<span style="font-size: 10px; color:{series.color}">{series.name}</span><br/>',
16039         pointFormat: 'x: <b>{point.x}</b><br/>y: <b>{point.y}</b><br/>',
16040         followPointer: true
16041     },
16042     stickyTracking: false
16043 });
16044
16045 /**
16046  * The scatter series class
16047  */
16048 var ScatterSeries = extendClass(Series, {
16049     type: 'scatter',
16050     sorted: false,
16051     requireSorting: false,
16052     noSharedTooltip: true,
16053     trackerGroups: ['markerGroup'],
16054
16055     drawTracker: ColumnSeries.prototype.drawTracker,
16056     
16057     setTooltipPoints: noop
16058 });
16059 seriesTypes.scatter = ScatterSeries;
16060
16061 /**
16062  * Set the default options for pie
16063  */
16064 defaultPlotOptions.pie = merge(defaultSeriesOptions, {
16065     borderColor: '#FFFFFF',
16066     borderWidth: 1,
16067     center: [null, null],
16068     clip: false,
16069     colorByPoint: true, // always true for pies
16070     dataLabels: {
16071         // align: null,
16072         // connectorWidth: 1,
16073         // connectorColor: point.color,
16074         // connectorPadding: 5,
16075         distance: 30,
16076         enabled: true,
16077         formatter: function () {
16078             return this.point.name;
16079         }
16080         // softConnector: true,
16081         //y: 0
16082     },
16083     ignoreHiddenPoint: true,
16084     //innerSize: 0,
16085     legendType: 'point',
16086     marker: null, // point options are specified in the base options
16087     size: null,
16088     showInLegend: false,
16089     slicedOffset: 10,
16090     states: {
16091         hover: {
16092             brightness: 0.1,
16093             shadow: false
16094         }
16095     },
16096     stickyTracking: false,
16097     tooltip: {
16098         followPointer: true
16099     }
16100 });
16101
16102 /**
16103  * Extended point object for pies
16104  */
16105 var PiePoint = extendClass(Point, {
16106     /**
16107      * Initiate the pie slice
16108      */
16109     init: function () {
16110
16111         Point.prototype.init.apply(this, arguments);
16112
16113         var point = this,
16114             toggleSlice;
16115
16116         // Disallow negative values (#1530)
16117         if (point.y < 0) {
16118             point.y = null;
16119         }
16120
16121         //visible: options.visible !== false,
16122         extend(point, {
16123             visible: point.visible !== false,
16124             name: pick(point.name, 'Slice')
16125         });
16126
16127         // add event listener for select
16128         toggleSlice = function (e) {
16129             point.slice(e.type === 'select');
16130         };
16131         addEvent(point, 'select', toggleSlice);
16132         addEvent(point, 'unselect', toggleSlice);
16133
16134         return point;
16135     },
16136
16137     /**
16138      * Toggle the visibility of the pie slice
16139      * @param {Boolean} vis Whether to show the slice or not. If undefined, the
16140      *    visibility is toggled
16141      */
16142     setVisible: function (vis) {
16143         var point = this,
16144             series = point.series,
16145             chart = series.chart,
16146             method;
16147
16148         // if called without an argument, toggle visibility
16149         point.visible = point.options.visible = vis = vis === UNDEFINED ? !point.visible : vis;
16150         series.options.data[inArray(point, series.data)] = point.options; // update userOptions.data
16151         
16152         method = vis ? 'show' : 'hide';
16153
16154         // Show and hide associated elements
16155         each(['graphic', 'dataLabel', 'connector', 'shadowGroup'], function (key) {
16156             if (point[key]) {
16157                 point[key][method]();
16158             }
16159         });
16160
16161         if (point.legendItem) {
16162             chart.legend.colorizeItem(point, vis);
16163         }
16164         
16165         // Handle ignore hidden slices
16166         if (!series.isDirty && series.options.ignoreHiddenPoint) {
16167             series.isDirty = true;
16168             chart.redraw();
16169         }
16170     },
16171
16172     /**
16173      * Set or toggle whether the slice is cut out from the pie
16174      * @param {Boolean} sliced When undefined, the slice state is toggled
16175      * @param {Boolean} redraw Whether to redraw the chart. True by default.
16176      */
16177     slice: function (sliced, redraw, animation) {
16178         var point = this,
16179             series = point.series,
16180             chart = series.chart,
16181             translation;
16182
16183         setAnimation(animation, chart);
16184
16185         // redraw is true by default
16186         redraw = pick(redraw, true);
16187
16188         // if called without an argument, toggle
16189         point.sliced = point.options.sliced = sliced = defined(sliced) ? sliced : !point.sliced;
16190         series.options.data[inArray(point, series.data)] = point.options; // update userOptions.data
16191
16192         translation = sliced ? point.slicedTranslation : {
16193             translateX: 0,
16194             translateY: 0
16195         };
16196
16197         point.graphic.animate(translation);
16198         
16199         if (point.shadowGroup) {
16200             point.shadowGroup.animate(translation);
16201         }
16202
16203     }
16204 });
16205
16206 /**
16207  * The Pie series class
16208  */
16209 var PieSeries = {
16210     type: 'pie',
16211     isCartesian: false,
16212     pointClass: PiePoint,
16213     requireSorting: false,
16214     noSharedTooltip: true,
16215     trackerGroups: ['group', 'dataLabelsGroup'],
16216     pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
16217         stroke: 'borderColor',
16218         'stroke-width': 'borderWidth',
16219         fill: 'color'
16220     },
16221
16222     /**
16223      * Pies have one color each point
16224      */
16225     getColor: noop,
16226
16227     /**
16228      * Animate the pies in
16229      */
16230     animate: function (init) {
16231         var series = this,
16232             points = series.points,
16233             startAngleRad = series.startAngleRad;
16234
16235         if (!init) {
16236             each(points, function (point) {
16237                 var graphic = point.graphic,
16238                     args = point.shapeArgs;
16239
16240                 if (graphic) {
16241                     // start values
16242                     graphic.attr({
16243                         r: series.center[3] / 2, // animate from inner radius (#779)
16244                         start: startAngleRad,
16245                         end: startAngleRad
16246                     });
16247
16248                     // animate
16249                     graphic.animate({
16250                         r: args.r,
16251                         start: args.start,
16252                         end: args.end
16253                     }, series.options.animation);
16254                 }
16255             });
16256
16257             // delete this function to allow it only once
16258             series.animate = null;
16259         }
16260     },
16261
16262     /**
16263      * Extend the basic setData method by running processData and generatePoints immediately,
16264      * in order to access the points from the legend.
16265      */
16266     setData: function (data, redraw) {
16267         Series.prototype.setData.call(this, data, false);
16268         this.processData();
16269         this.generatePoints();
16270         if (pick(redraw, true)) {
16271             this.chart.redraw();
16272         } 
16273     },
16274
16275     /**
16276      * Extend the generatePoints method by adding total and percentage properties to each point
16277      */
16278     generatePoints: function () {
16279         var i,
16280             total = 0,
16281             points,
16282             len,
16283             point,
16284             ignoreHiddenPoint = this.options.ignoreHiddenPoint;
16285
16286         Series.prototype.generatePoints.call(this);
16287
16288         // Populate local vars
16289         points = this.points;
16290         len = points.length;
16291         
16292         // Get the total sum
16293         for (i = 0; i < len; i++) {
16294             point = points[i];
16295             total += (ignoreHiddenPoint && !point.visible) ? 0 : point.y;
16296         }
16297         this.total = total;
16298
16299         // Set each point's properties
16300         for (i = 0; i < len; i++) {
16301             point = points[i];
16302             point.percentage = total > 0 ? (point.y / total) * 100 : 0;
16303             point.total = total;
16304         }
16305         
16306     },
16307     
16308     /**
16309      * Get the center of the pie based on the size and center options relative to the  
16310      * plot area. Borrowed by the polar and gauge series types.
16311      */
16312     getCenter: function () {
16313         
16314         var options = this.options,
16315             chart = this.chart,
16316             slicingRoom = 2 * (options.slicedOffset || 0),
16317             handleSlicingRoom,
16318             plotWidth = chart.plotWidth - 2 * slicingRoom,
16319             plotHeight = chart.plotHeight - 2 * slicingRoom,
16320             centerOption = options.center,
16321             positions = [pick(centerOption[0], '50%'), pick(centerOption[1], '50%'), options.size || '100%', options.innerSize || 0],
16322             smallestSize = mathMin(plotWidth, plotHeight),
16323             isPercent;
16324         
16325         return map(positions, function (length, i) {
16326             isPercent = /%$/.test(length);
16327             handleSlicingRoom = i < 2 || (i === 2 && isPercent);
16328             return (isPercent ?
16329                 // i == 0: centerX, relative to width
16330                 // i == 1: centerY, relative to height
16331                 // i == 2: size, relative to smallestSize
16332                 // i == 4: innerSize, relative to smallestSize
16333                 [plotWidth, plotHeight, smallestSize, smallestSize][i] *
16334                     pInt(length) / 100 :
16335                 length) + (handleSlicingRoom ? slicingRoom : 0);
16336         });
16337     },
16338     
16339     /**
16340      * Do translation for pie slices
16341      */
16342     translate: function (positions) {
16343         this.generatePoints();
16344         
16345         var series = this,
16346             cumulative = 0,
16347             precision = 1000, // issue #172
16348             options = series.options,
16349             slicedOffset = options.slicedOffset,
16350             connectorOffset = slicedOffset + options.borderWidth,
16351             start,
16352             end,
16353             angle,
16354             startAngle = options.startAngle || 0,
16355             startAngleRad = series.startAngleRad = mathPI / 180 * (startAngle - 90),
16356             endAngleRad = series.endAngleRad = mathPI / 180 * ((options.endAngle || (startAngle + 360)) - 90), // docs
16357             circ = endAngleRad - startAngleRad, //2 * mathPI,
16358             points = series.points,
16359             radiusX, // the x component of the radius vector for a given point
16360             radiusY,
16361             labelDistance = options.dataLabels.distance,
16362             ignoreHiddenPoint = options.ignoreHiddenPoint,
16363             i,
16364             len = points.length,
16365             point;
16366
16367         // Get positions - either an integer or a percentage string must be given.
16368         // If positions are passed as a parameter, we're in a recursive loop for adjusting
16369         // space for data labels.
16370         if (!positions) {
16371             series.center = positions = series.getCenter();
16372         }
16373
16374         // utility for getting the x value from a given y, used for anticollision logic in data labels
16375         series.getX = function (y, left) {
16376
16377             angle = math.asin((y - positions[1]) / (positions[2] / 2 + labelDistance));
16378
16379             return positions[0] +
16380                 (left ? -1 : 1) *
16381                 (mathCos(angle) * (positions[2] / 2 + labelDistance));
16382         };
16383
16384         // Calculate the geometry for each point
16385         for (i = 0; i < len; i++) {
16386             
16387             point = points[i];
16388             
16389             // set start and end angle
16390             start = startAngleRad + (cumulative * circ);
16391             if (!ignoreHiddenPoint || point.visible) {
16392                 cumulative += point.percentage / 100;
16393             }
16394             end = startAngleRad + (cumulative * circ);
16395
16396             // set the shape
16397             point.shapeType = 'arc';
16398             point.shapeArgs = {
16399                 x: positions[0],
16400                 y: positions[1],
16401                 r: positions[2] / 2,
16402                 innerR: positions[3] / 2,
16403                 start: mathRound(start * precision) / precision,
16404                 end: mathRound(end * precision) / precision
16405             };
16406
16407             // center for the sliced out slice
16408             angle = (end + start) / 2;
16409             if (angle > 0.75 * circ) {
16410                 angle -= 2 * mathPI;
16411             }
16412             point.slicedTranslation = {
16413                 translateX: mathRound(mathCos(angle) * slicedOffset),
16414                 translateY: mathRound(mathSin(angle) * slicedOffset)
16415             };
16416
16417             // set the anchor point for tooltips
16418             radiusX = mathCos(angle) * positions[2] / 2;
16419             radiusY = mathSin(angle) * positions[2] / 2;
16420             point.tooltipPos = [
16421                 positions[0] + radiusX * 0.7,
16422                 positions[1] + radiusY * 0.7
16423             ];
16424             
16425             point.half = angle < -mathPI / 2 || angle > mathPI / 2 ? 1 : 0;
16426             point.angle = angle;
16427
16428             // set the anchor point for data labels
16429             connectorOffset = mathMin(connectorOffset, labelDistance / 2); // #1678
16430             point.labelPos = [
16431                 positions[0] + radiusX + mathCos(angle) * labelDistance, // first break of connector
16432                 positions[1] + radiusY + mathSin(angle) * labelDistance, // a/a
16433                 positions[0] + radiusX + mathCos(angle) * connectorOffset, // second break, right outside pie
16434                 positions[1] + radiusY + mathSin(angle) * connectorOffset, // a/a
16435                 positions[0] + radiusX, // landing point for connector
16436                 positions[1] + radiusY, // a/a
16437                 labelDistance < 0 ? // alignment
16438                     'center' :
16439                     point.half ? 'right' : 'left', // alignment
16440                 angle // center angle
16441             ];
16442
16443         }
16444     },
16445
16446     setTooltipPoints: noop,
16447     drawGraph: null,
16448
16449     /**
16450      * Draw the data points
16451      */
16452     drawPoints: function () {
16453         var series = this,
16454             chart = series.chart,
16455             renderer = chart.renderer,
16456             groupTranslation,
16457             //center,
16458             graphic,
16459             //group,
16460             shadow = series.options.shadow,
16461             shadowGroup,
16462             shapeArgs;
16463
16464         if (shadow && !series.shadowGroup) {
16465             series.shadowGroup = renderer.g('shadow')
16466                 .add(series.group);
16467         }
16468
16469         // draw the slices
16470         each(series.points, function (point) {
16471             graphic = point.graphic;
16472             shapeArgs = point.shapeArgs;
16473             shadowGroup = point.shadowGroup;
16474
16475             // put the shadow behind all points
16476             if (shadow && !shadowGroup) {
16477                 shadowGroup = point.shadowGroup = renderer.g('shadow')
16478                     .add(series.shadowGroup);
16479             }
16480
16481             // if the point is sliced, use special translation, else use plot area traslation
16482             groupTranslation = point.sliced ? point.slicedTranslation : {
16483                 translateX: 0,
16484                 translateY: 0
16485             };
16486
16487             //group.translate(groupTranslation[0], groupTranslation[1]);
16488             if (shadowGroup) {
16489                 shadowGroup.attr(groupTranslation);
16490             }
16491
16492             // draw the slice
16493             if (graphic) {
16494                 graphic.animate(extend(shapeArgs, groupTranslation));
16495             } else {
16496                 point.graphic = graphic = renderer.arc(shapeArgs)
16497                     .setRadialReference(series.center)
16498                     .attr(
16499                         point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE]
16500                     )
16501                     .attr({ 'stroke-linejoin': 'round' })
16502                     .attr(groupTranslation)
16503                     .add(series.group)
16504                     .shadow(shadow, shadowGroup);    
16505             }
16506
16507             // detect point specific visibility
16508             if (point.visible === false) {
16509                 point.setVisible(false);
16510             }
16511
16512         });
16513
16514     },
16515
16516     /**
16517      * Utility for sorting data labels
16518      */
16519     sortByAngle: function (points, sign) {
16520         points.sort(function (a, b) {
16521             return a.angle !== undefined && (b.angle - a.angle) * sign;
16522         });
16523     },
16524
16525     /**
16526      * Override the base drawDataLabels method by pie specific functionality
16527      */
16528     drawDataLabels: function () {
16529         var series = this,
16530             data = series.data,
16531             point,
16532             chart = series.chart,
16533             options = series.options.dataLabels,
16534             connectorPadding = pick(options.connectorPadding, 10),
16535             connectorWidth = pick(options.connectorWidth, 1),
16536             plotWidth = chart.plotWidth,
16537             plotHeight = chart.plotHeight,
16538             connector,
16539             connectorPath,
16540             softConnector = pick(options.softConnector, true),
16541             distanceOption = options.distance,
16542             seriesCenter = series.center,
16543             radius = seriesCenter[2] / 2,
16544             centerY = seriesCenter[1],
16545             outside = distanceOption > 0,
16546             dataLabel,
16547             dataLabelWidth,
16548             labelPos,
16549             labelHeight,
16550             halves = [// divide the points into right and left halves for anti collision
16551                 [], // right
16552                 []  // left
16553             ],
16554             x,
16555             y,
16556             visibility,
16557             rankArr,
16558             i,
16559             j,
16560             overflow = [0, 0, 0, 0], // top, right, bottom, left
16561             sort = function (a, b) {
16562                 return b.y - a.y;
16563             };
16564
16565         // get out if not enabled
16566         if (!series.visible || (!options.enabled && !series._hasPointLabels)) {
16567             return;
16568         }
16569
16570         // run parent method
16571         Series.prototype.drawDataLabels.apply(series);
16572
16573         // arrange points for detection collision
16574         each(data, function (point) {
16575             if (point.dataLabel) { // it may have been cancelled in the base method (#407)
16576                 halves[point.half].push(point);
16577             }
16578         });
16579
16580         // assume equal label heights
16581         i = 0;
16582         while (!labelHeight && data[i]) { // #1569
16583             labelHeight = data[i] && data[i].dataLabel && (data[i].dataLabel.getBBox().height || 21); // 21 is for #968
16584             i++;
16585         }
16586
16587         /* Loop over the points in each half, starting from the top and bottom
16588          * of the pie to detect overlapping labels.
16589          */
16590         i = 2;
16591         while (i--) {
16592
16593             var slots = [],
16594                 slotsLength,
16595                 usedSlots = [],
16596                 points = halves[i],
16597                 pos,
16598                 length = points.length,
16599                 slotIndex;
16600                 
16601             // Sort by angle
16602             series.sortByAngle(points, i - 0.5);
16603
16604             // Only do anti-collision when we are outside the pie and have connectors (#856)
16605             if (distanceOption > 0) {
16606                 
16607                 // build the slots
16608                 for (pos = centerY - radius - distanceOption; pos <= centerY + radius + distanceOption; pos += labelHeight) {
16609                     slots.push(pos);
16610                     
16611                     // visualize the slot
16612                     /*
16613                     var slotX = series.getX(pos, i) + chart.plotLeft - (i ? 100 : 0),
16614                         slotY = pos + chart.plotTop;
16615                     if (!isNaN(slotX)) {
16616                         chart.renderer.rect(slotX, slotY - 7, 100, labelHeight, 1)
16617                             .attr({
16618                                 'stroke-width': 1,
16619                                 stroke: 'silver'
16620                             })
16621                             .add();
16622                         chart.renderer.text('Slot '+ (slots.length - 1), slotX, slotY + 4)
16623                             .attr({
16624                                 fill: 'silver'
16625                             }).add();
16626                     }
16627                     */
16628                 }
16629                 slotsLength = slots.length;
16630     
16631                 // if there are more values than available slots, remove lowest values
16632                 if (length > slotsLength) {
16633                     // create an array for sorting and ranking the points within each quarter
16634                     rankArr = [].concat(points);
16635                     rankArr.sort(sort);
16636                     j = length;
16637                     while (j--) {
16638                         rankArr[j].rank = j;
16639                     }
16640                     j = length;
16641                     while (j--) {
16642                         if (points[j].rank >= slotsLength) {
16643                             points.splice(j, 1);
16644                         }
16645                     }
16646                     length = points.length;
16647                 }
16648     
16649                 // The label goes to the nearest open slot, but not closer to the edge than
16650                 // the label's index.
16651                 for (j = 0; j < length; j++) {
16652     
16653                     point = points[j];
16654                     labelPos = point.labelPos;
16655     
16656                     var closest = 9999,
16657                         distance,
16658                         slotI;
16659     
16660                     // find the closest slot index
16661                     for (slotI = 0; slotI < slotsLength; slotI++) {
16662                         distance = mathAbs(slots[slotI] - labelPos[1]);
16663                         if (distance < closest) {
16664                             closest = distance;
16665                             slotIndex = slotI;
16666                         }
16667                     }
16668     
16669                     // if that slot index is closer to the edges of the slots, move it
16670                     // to the closest appropriate slot
16671                     if (slotIndex < j && slots[j] !== null) { // cluster at the top
16672                         slotIndex = j;
16673                     } else if (slotsLength  < length - j + slotIndex && slots[j] !== null) { // cluster at the bottom
16674                         slotIndex = slotsLength - length + j;
16675                         while (slots[slotIndex] === null) { // make sure it is not taken
16676                             slotIndex++;
16677                         }
16678                     } else {
16679                         // Slot is taken, find next free slot below. In the next run, the next slice will find the
16680                         // slot above these, because it is the closest one
16681                         while (slots[slotIndex] === null) { // make sure it is not taken
16682                             slotIndex++;
16683                         }
16684                     }
16685     
16686                     usedSlots.push({ i: slotIndex, y: slots[slotIndex] });
16687                     slots[slotIndex] = null; // mark as taken
16688                 }
16689                 // sort them in order to fill in from the top
16690                 usedSlots.sort(sort);
16691             }
16692
16693             // now the used slots are sorted, fill them up sequentially
16694             for (j = 0; j < length; j++) {
16695                 
16696                 var slot, naturalY;
16697
16698                 point = points[j];
16699                 labelPos = point.labelPos;
16700                 dataLabel = point.dataLabel;
16701                 visibility = point.visible === false ? HIDDEN : VISIBLE;
16702                 naturalY = labelPos[1];
16703                 
16704                 if (distanceOption > 0) {
16705                     slot = usedSlots.pop();
16706                     slotIndex = slot.i;
16707
16708                     // if the slot next to currrent slot is free, the y value is allowed
16709                     // to fall back to the natural position
16710                     y = slot.y;
16711                     if ((naturalY > y && slots[slotIndex + 1] !== null) ||
16712                             (naturalY < y &&  slots[slotIndex - 1] !== null)) {
16713                         y = naturalY;
16714                     }
16715                     
16716                 } else {
16717                     y = naturalY;
16718                 }
16719
16720                 // get the x - use the natural x position for first and last slot, to prevent the top
16721                 // and botton slice connectors from touching each other on either side
16722                 x = options.justify ? 
16723                     seriesCenter[0] + (i ? -1 : 1) * (radius + distanceOption) :
16724                     series.getX(slotIndex === 0 || slotIndex === slots.length - 1 ? naturalY : y, i);
16725                 
16726             
16727                 // Record the placement and visibility
16728                 dataLabel._attr = {
16729                     visibility: visibility,
16730                     align: labelPos[6]
16731                 };
16732                 dataLabel._pos = {
16733                     x: x + options.x +
16734                         ({ left: connectorPadding, right: -connectorPadding }[labelPos[6]] || 0),
16735                     y: y + options.y - 10 // 10 is for the baseline (label vs text)
16736                 };
16737                 dataLabel.connX = x;
16738                 dataLabel.connY = y;
16739                 
16740                         
16741                 // Detect overflowing data labels
16742                 if (this.options.size === null) {
16743                     dataLabelWidth = dataLabel.width;
16744                     // Overflow left
16745                     if (x - dataLabelWidth < connectorPadding) {
16746                         overflow[3] = mathMax(mathRound(dataLabelWidth - x + connectorPadding), overflow[3]);
16747                         
16748                     // Overflow right
16749                     } else if (x + dataLabelWidth > plotWidth - connectorPadding) {
16750                         overflow[1] = mathMax(mathRound(x + dataLabelWidth - plotWidth + connectorPadding), overflow[1]);
16751                     }
16752                     
16753                     // Overflow top
16754                     if (y - labelHeight / 2 < 0) {
16755                         overflow[0] = mathMax(mathRound(-y + labelHeight / 2), overflow[0]);
16756                         
16757                     // Overflow left
16758                     } else if (y + labelHeight / 2 > plotHeight) {
16759                         overflow[2] = mathMax(mathRound(y + labelHeight / 2 - plotHeight), overflow[2]);
16760                     }
16761                 }
16762             } // for each point
16763         } // for each half
16764         
16765         // Do not apply the final placement and draw the connectors until we have verified
16766         // that labels are not spilling over. 
16767         if (arrayMax(overflow) === 0 || this.verifyDataLabelOverflow(overflow)) {
16768             
16769             // Place the labels in the final position
16770             this.placeDataLabels();
16771             
16772             // Draw the connectors
16773             if (outside && connectorWidth) {
16774                 each(this.points, function (point) {
16775                     connector = point.connector;
16776                     labelPos = point.labelPos;
16777                     dataLabel = point.dataLabel;
16778                     
16779                     if (dataLabel && dataLabel._pos) {
16780                         visibility = dataLabel._attr.visibility;
16781                         x = dataLabel.connX;
16782                         y = dataLabel.connY;
16783                         connectorPath = softConnector ? [
16784                             M,
16785                             x + (labelPos[6] === 'left' ? 5 : -5), y, // end of the string at the label
16786                             'C',
16787                             x, y, // first break, next to the label
16788                             2 * labelPos[2] - labelPos[4], 2 * labelPos[3] - labelPos[5],
16789                             labelPos[2], labelPos[3], // second break
16790                             L,
16791                             labelPos[4], labelPos[5] // base
16792                         ] : [
16793                             M,
16794                             x + (labelPos[6] === 'left' ? 5 : -5), y, // end of the string at the label
16795                             L,
16796                             labelPos[2], labelPos[3], // second break
16797                             L,
16798                             labelPos[4], labelPos[5] // base
16799                         ];
16800         
16801                         if (connector) {
16802                             connector.animate({ d: connectorPath });
16803                             connector.attr('visibility', visibility);
16804         
16805                         } else {
16806                             point.connector = connector = series.chart.renderer.path(connectorPath).attr({
16807                                 'stroke-width': connectorWidth,
16808                                 stroke: options.connectorColor || point.color || '#606060',
16809                                 visibility: visibility
16810                             })
16811                             .add(series.group);
16812                         }
16813                     } else if (connector) {
16814                         point.connector = connector.destroy();
16815                     }
16816                 });
16817             }            
16818         }
16819     },
16820     
16821     /**
16822      * Verify whether the data labels are allowed to draw, or we should run more translation and data
16823      * label positioning to keep them inside the plot area. Returns true when data labels are ready 
16824      * to draw.
16825      */
16826     verifyDataLabelOverflow: function (overflow) {
16827         
16828         var center = this.center,
16829             options = this.options,
16830             centerOption = options.center,
16831             minSize = options.minSize || 80,
16832             newSize = minSize,
16833             ret;
16834             
16835         // Handle horizontal size and center
16836         if (centerOption[0] !== null) { // Fixed center
16837             newSize = mathMax(center[2] - mathMax(overflow[1], overflow[3]), minSize);
16838             
16839         } else { // Auto center
16840             newSize = mathMax(
16841                 center[2] - overflow[1] - overflow[3], // horizontal overflow                    
16842                 minSize
16843             );
16844             center[0] += (overflow[3] - overflow[1]) / 2; // horizontal center
16845         }
16846         
16847         // Handle vertical size and center
16848         if (centerOption[1] !== null) { // Fixed center
16849             newSize = mathMax(mathMin(newSize, center[2] - mathMax(overflow[0], overflow[2])), minSize);
16850             
16851         } else { // Auto center
16852             newSize = mathMax(
16853                 mathMin(
16854                     newSize,        
16855                     center[2] - overflow[0] - overflow[2] // vertical overflow
16856                 ),
16857                 minSize
16858             );
16859             center[1] += (overflow[0] - overflow[2]) / 2; // vertical center
16860         }
16861         
16862         // If the size must be decreased, we need to run translate and drawDataLabels again
16863         if (newSize < center[2]) {
16864             center[2] = newSize;
16865             this.translate(center);
16866             each(this.points, function (point) {
16867                 if (point.dataLabel) {
16868                     point.dataLabel._pos = null; // reset
16869                 }
16870             });
16871             this.drawDataLabels();
16872             
16873         // Else, return true to indicate that the pie and its labels is within the plot area
16874         } else {
16875             ret = true;
16876         }
16877         return ret;
16878     },
16879     
16880     /**
16881      * Perform the final placement of the data labels after we have verified that they
16882      * fall within the plot area.
16883      */
16884     placeDataLabels: function () {
16885         each(this.points, function (point) {
16886             var dataLabel = point.dataLabel,
16887                 _pos;
16888             
16889             if (dataLabel) {
16890                 _pos = dataLabel._pos;
16891                 if (_pos) {
16892                     dataLabel.attr(dataLabel._attr);            
16893                     dataLabel[dataLabel.moved ? 'animate' : 'attr'](_pos);
16894                     dataLabel.moved = true;
16895                 } else if (dataLabel) {
16896                     dataLabel.attr({ y: -999 });
16897                 }
16898             }
16899         });
16900     },
16901     
16902     alignDataLabel: noop,
16903
16904     /**
16905      * Draw point specific tracker objects. Inherit directly from column series.
16906      */
16907     drawTracker: ColumnSeries.prototype.drawTracker,
16908
16909     /**
16910      * Use a simple symbol from column prototype
16911      */
16912     drawLegendSymbol: AreaSeries.prototype.drawLegendSymbol,
16913
16914     /**
16915      * Pies don't have point marker symbols
16916      */
16917     getSymbol: noop
16918
16919 };
16920 PieSeries = extendClass(Series, PieSeries);
16921 seriesTypes.pie = PieSeries;
16922
16923
16924 // global variables
16925 extend(Highcharts, {
16926     
16927     // Constructors
16928     Axis: Axis,
16929     Chart: Chart,
16930     Color: Color,
16931     Legend: Legend,
16932     Pointer: Pointer,
16933     Point: Point,
16934     Tick: Tick,
16935     Tooltip: Tooltip,
16936     Renderer: Renderer,
16937     Series: Series,
16938     SVGElement: SVGElement,
16939     SVGRenderer: SVGRenderer,
16940     
16941     // Various
16942     arrayMin: arrayMin,
16943     arrayMax: arrayMax,
16944     charts: charts,
16945     dateFormat: dateFormat,
16946     format: format,
16947     pathAnim: pathAnim,
16948     getOptions: getOptions,
16949     hasBidiBug: hasBidiBug,
16950     isTouchDevice: isTouchDevice,
16951     numberFormat: numberFormat,
16952     seriesTypes: seriesTypes,
16953     setOptions: setOptions,
16954     addEvent: addEvent,
16955     removeEvent: removeEvent,
16956     createElement: createElement,
16957     discardElement: discardElement,
16958     css: css,
16959     each: each,
16960     extend: extend,
16961     map: map,
16962     merge: merge,
16963     pick: pick,
16964     splat: splat,
16965     extendClass: extendClass,
16966     pInt: pInt,
16967     wrap: wrap,
16968     svg: hasSVG,
16969     canvas: useCanVG,
16970     vml: !hasSVG && !useCanVG,
16971     product: PRODUCT,
16972     version: VERSION
16973 });
16974 }());