| // ==ClosureCompiler== | 
| // @compilation_level SIMPLE_OPTIMIZATIONS | 
|   | 
| /** | 
|  * @license Highcharts JS v3.0.6 (2013-10-04) | 
|  * | 
|  * (c) 2009-2013 Torstein Hønsi | 
|  * | 
|  * License: www.highcharts.com/license | 
|  */ | 
|   | 
| // JSLint options: | 
| /*global Highcharts, document, window, navigator, setInterval, clearInterval, clearTimeout, setTimeout, location, jQuery, $, console, each, grep */ | 
|   | 
| (function () { | 
| // encapsulated variables | 
| var UNDEFINED, | 
|     doc = document, | 
|     win = window, | 
|     math = Math, | 
|     mathRound = math.round, | 
|     mathFloor = math.floor, | 
|     mathCeil = math.ceil, | 
|     mathMax = math.max, | 
|     mathMin = math.min, | 
|     mathAbs = math.abs, | 
|     mathCos = math.cos, | 
|     mathSin = math.sin, | 
|     mathPI = math.PI, | 
|     deg2rad = mathPI * 2 / 360, | 
|   | 
|   | 
|     // some variables | 
|     userAgent = navigator.userAgent, | 
|     isOpera = win.opera, | 
|     isIE = /msie/i.test(userAgent) && !isOpera, | 
|     docMode8 = doc.documentMode === 8, | 
|     isWebKit = /AppleWebKit/.test(userAgent), | 
|     isFirefox = /Firefox/.test(userAgent), | 
|     isTouchDevice = /(Mobile|Android|Windows Phone)/.test(userAgent), | 
|     SVG_NS = 'http://www.w3.org/2000/svg', | 
|     hasSVG = !!doc.createElementNS && !!doc.createElementNS(SVG_NS, 'svg').createSVGRect, | 
|     hasBidiBug = isFirefox && parseInt(userAgent.split('Firefox/')[1], 10) < 4, // issue #38 | 
|     useCanVG = !hasSVG && !isIE && !!doc.createElement('canvas').getContext, | 
|     Renderer, | 
|     hasTouch = doc.documentElement.ontouchstart !== UNDEFINED, | 
|     symbolSizes = {}, | 
|     idCounter = 0, | 
|     garbageBin, | 
|     defaultOptions, | 
|     dateFormat, // function | 
|     globalAnimation, | 
|     pathAnim, | 
|     timeUnits, | 
|     noop = function () {}, | 
|     charts = [], | 
|     PRODUCT = 'Highcharts', | 
|     VERSION = '3.0.6', | 
|   | 
|     // some constants for frequently used strings | 
|     DIV = 'div', | 
|     ABSOLUTE = 'absolute', | 
|     RELATIVE = 'relative', | 
|     HIDDEN = 'hidden', | 
|     PREFIX = 'highcharts-', | 
|     VISIBLE = 'visible', | 
|     PX = 'px', | 
|     NONE = 'none', | 
|     M = 'M', | 
|     L = 'L', | 
|     /* | 
|      * Empirical lowest possible opacities for TRACKER_FILL | 
|      * IE6: 0.002 | 
|      * IE7: 0.002 | 
|      * IE8: 0.002 | 
|      * IE9: 0.00000000001 (unlimited) | 
|      * IE10: 0.0001 (exporting only) | 
|      * FF: 0.00000000001 (unlimited) | 
|      * Chrome: 0.000001 | 
|      * Safari: 0.000001 | 
|      * Opera: 0.00000000001 (unlimited) | 
|      */ | 
|     TRACKER_FILL = 'rgba(192,192,192,' + (hasSVG ? 0.0001 : 0.002) + ')', // invisible but clickable | 
|     //TRACKER_FILL = 'rgba(192,192,192,0.5)', | 
|     NORMAL_STATE = '', | 
|     HOVER_STATE = 'hover', | 
|     SELECT_STATE = 'select', | 
|     MILLISECOND = 'millisecond', | 
|     SECOND = 'second', | 
|     MINUTE = 'minute', | 
|     HOUR = 'hour', | 
|     DAY = 'day', | 
|     WEEK = 'week', | 
|     MONTH = 'month', | 
|     YEAR = 'year', | 
|   | 
|     // constants for attributes | 
|     LINEAR_GRADIENT = 'linearGradient', | 
|     STOPS = 'stops', | 
|     STROKE_WIDTH = 'stroke-width', | 
|   | 
|     // time methods, changed based on whether or not UTC is used | 
|     makeTime, | 
|     getMinutes, | 
|     getHours, | 
|     getDay, | 
|     getDate, | 
|     getMonth, | 
|     getFullYear, | 
|     setMinutes, | 
|     setHours, | 
|     setDate, | 
|     setMonth, | 
|     setFullYear, | 
|   | 
|   | 
|     // lookup over the types and the associated classes | 
|     seriesTypes = {}; | 
|   | 
| // The Highcharts namespace | 
| win.Highcharts = win.Highcharts ? error(16, true) : {}; | 
|   | 
| /** | 
|  * Extend an object with the members of another | 
|  * @param {Object} a The object to be extended | 
|  * @param {Object} b The object to add to the first one | 
|  */ | 
| function extend(a, b) { | 
|     var n; | 
|     if (!a) { | 
|         a = {}; | 
|     } | 
|     for (n in b) { | 
|         a[n] = b[n]; | 
|     } | 
|     return a; | 
| } | 
|      | 
| /** | 
|  * Deep merge two or more objects and return a third object. | 
|  * Previously this function redirected to jQuery.extend(true), but this had two limitations. | 
|  * First, it deep merged arrays, which lead to workarounds in Highcharts. Second, | 
|  * it copied properties from extended prototypes.  | 
|  */ | 
| function merge() { | 
|     var i, | 
|         len = arguments.length, | 
|         ret = {}, | 
|         doCopy = function (copy, original) { | 
|             var value, key; | 
|   | 
|             // An object is replacing a primitive | 
|             if (typeof copy !== 'object') { | 
|                 copy = {}; | 
|             } | 
|   | 
|             for (key in original) { | 
|                 if (original.hasOwnProperty(key)) { | 
|                     value = original[key]; | 
|   | 
|                     // Copy the contents of objects, but not arrays or DOM nodes | 
|                     if (value && typeof value === 'object' && Object.prototype.toString.call(value) !== '[object Array]' | 
|                             && typeof value.nodeType !== 'number') { | 
|                         copy[key] = doCopy(copy[key] || {}, value); | 
|                  | 
|                     // Primitives and arrays are copied over directly | 
|                     } else { | 
|                         copy[key] = original[key]; | 
|                     } | 
|                 } | 
|             } | 
|             return copy; | 
|         }; | 
|   | 
|     // For each argument, extend the return | 
|     for (i = 0; i < len; i++) { | 
|         ret = doCopy(ret, arguments[i]); | 
|     } | 
|   | 
|     return ret; | 
| } | 
|   | 
| /** | 
|  * Take an array and turn into a hash with even number arguments as keys and odd numbers as | 
|  * values. Allows creating constants for commonly used style properties, attributes etc. | 
|  * Avoid it in performance critical situations like looping | 
|  */ | 
| function hash() { | 
|     var i = 0, | 
|         args = arguments, | 
|         length = args.length, | 
|         obj = {}; | 
|     for (; i < length; i++) { | 
|         obj[args[i++]] = args[i]; | 
|     } | 
|     return obj; | 
| } | 
|   | 
| /** | 
|  * Shortcut for parseInt | 
|  * @param {Object} s | 
|  * @param {Number} mag Magnitude | 
|  */ | 
| function pInt(s, mag) { | 
|     return parseInt(s, mag || 10); | 
| } | 
|   | 
| /** | 
|  * Check for string | 
|  * @param {Object} s | 
|  */ | 
| function isString(s) { | 
|     return typeof s === 'string'; | 
| } | 
|   | 
| /** | 
|  * Check for object | 
|  * @param {Object} obj | 
|  */ | 
| function isObject(obj) { | 
|     return typeof obj === 'object'; | 
| } | 
|   | 
| /** | 
|  * Check for array | 
|  * @param {Object} obj | 
|  */ | 
| function isArray(obj) { | 
|     return Object.prototype.toString.call(obj) === '[object Array]'; | 
| } | 
|   | 
| /** | 
|  * Check for number | 
|  * @param {Object} n | 
|  */ | 
| function isNumber(n) { | 
|     return typeof n === 'number'; | 
| } | 
|   | 
| function log2lin(num) { | 
|     return math.log(num) / math.LN10; | 
| } | 
| function lin2log(num) { | 
|     return math.pow(10, num); | 
| } | 
|   | 
| /** | 
|  * Remove last occurence of an item from an array | 
|  * @param {Array} arr | 
|  * @param {Mixed} item | 
|  */ | 
| function erase(arr, item) { | 
|     var i = arr.length; | 
|     while (i--) { | 
|         if (arr[i] === item) { | 
|             arr.splice(i, 1); | 
|             break; | 
|         } | 
|     } | 
|     //return arr; | 
| } | 
|   | 
| /** | 
|  * Returns true if the object is not null or undefined. Like MooTools' $.defined. | 
|  * @param {Object} obj | 
|  */ | 
| function defined(obj) { | 
|     return obj !== UNDEFINED && obj !== null; | 
| } | 
|   | 
| /** | 
|  * Set or get an attribute or an object of attributes. Can't use jQuery attr because | 
|  * it attempts to set expando properties on the SVG element, which is not allowed. | 
|  * | 
|  * @param {Object} elem The DOM element to receive the attribute(s) | 
|  * @param {String|Object} prop The property or an abject of key-value pairs | 
|  * @param {String} value The value if a single property is set | 
|  */ | 
| function attr(elem, prop, value) { | 
|     var key, | 
|         setAttribute = 'setAttribute', | 
|         ret; | 
|   | 
|     // if the prop is a string | 
|     if (isString(prop)) { | 
|         // set the value | 
|         if (defined(value)) { | 
|   | 
|             elem[setAttribute](prop, value); | 
|   | 
|         // get the value | 
|         } else if (elem && elem.getAttribute) { // elem not defined when printing pie demo... | 
|             ret = elem.getAttribute(prop); | 
|         } | 
|   | 
|     // else if prop is defined, it is a hash of key/value pairs | 
|     } else if (defined(prop) && isObject(prop)) { | 
|         for (key in prop) { | 
|             elem[setAttribute](key, prop[key]); | 
|         } | 
|     } | 
|     return ret; | 
| } | 
| /** | 
|  * Check if an element is an array, and if not, make it into an array. Like | 
|  * MooTools' $.splat. | 
|  */ | 
| function splat(obj) { | 
|     return isArray(obj) ? obj : [obj]; | 
| } | 
|   | 
|   | 
| /** | 
|  * Return the first value that is defined. Like MooTools' $.pick. | 
|  */ | 
| function pick() { | 
|     var args = arguments, | 
|         i, | 
|         arg, | 
|         length = args.length; | 
|     for (i = 0; i < length; i++) { | 
|         arg = args[i]; | 
|         if (typeof arg !== 'undefined' && arg !== null) { | 
|             return arg; | 
|         } | 
|     } | 
| } | 
|   | 
| /** | 
|  * Set CSS on a given element | 
|  * @param {Object} el | 
|  * @param {Object} styles Style object with camel case property names | 
|  */ | 
| function css(el, styles) { | 
|     if (isIE) { | 
|         if (styles && styles.opacity !== UNDEFINED) { | 
|             styles.filter = 'alpha(opacity=' + (styles.opacity * 100) + ')'; | 
|         } | 
|     } | 
|     extend(el.style, styles); | 
| } | 
|   | 
| /** | 
|  * Utility function to create element with attributes and styles | 
|  * @param {Object} tag | 
|  * @param {Object} attribs | 
|  * @param {Object} styles | 
|  * @param {Object} parent | 
|  * @param {Object} nopad | 
|  */ | 
| function createElement(tag, attribs, styles, parent, nopad) { | 
|     var el = doc.createElement(tag); | 
|     if (attribs) { | 
|         extend(el, attribs); | 
|     } | 
|     if (nopad) { | 
|         css(el, {padding: 0, border: NONE, margin: 0}); | 
|     } | 
|     if (styles) { | 
|         css(el, styles); | 
|     } | 
|     if (parent) { | 
|         parent.appendChild(el); | 
|     } | 
|     return el; | 
| } | 
|   | 
| /** | 
|  * Extend a prototyped class by new members | 
|  * @param {Object} parent | 
|  * @param {Object} members | 
|  */ | 
| function extendClass(parent, members) { | 
|     var object = function () {}; | 
|     object.prototype = new parent(); | 
|     extend(object.prototype, members); | 
|     return object; | 
| } | 
|   | 
| /** | 
|  * Format a number and return a string based on input settings | 
|  * @param {Number} number The input number to format | 
|  * @param {Number} decimals The amount of decimals | 
|  * @param {String} decPoint The decimal point, defaults to the one given in the lang options | 
|  * @param {String} thousandsSep The thousands separator, defaults to the one given in the lang options | 
|  */ | 
| function numberFormat(number, decimals, decPoint, thousandsSep) { | 
|     var lang = defaultOptions.lang, | 
|         // http://kevin.vanzonneveld.net/techblog/article/javascript_equivalent_for_phps_number_format/ | 
|         n = +number || 0, | 
|         c = decimals === -1 ? | 
|             (n.toString().split('.')[1] || '').length : // preserve decimals | 
|             (isNaN(decimals = mathAbs(decimals)) ? 2 : decimals), | 
|         d = decPoint === undefined ? lang.decimalPoint : decPoint, | 
|         t = thousandsSep === undefined ? lang.thousandsSep : thousandsSep, | 
|         s = n < 0 ? "-" : "", | 
|         i = String(pInt(n = mathAbs(n).toFixed(c))), | 
|         j = i.length > 3 ? i.length % 3 : 0; | 
|   | 
|     return s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) + | 
|         (c ? d + mathAbs(n - i).toFixed(c).slice(2) : ""); | 
| } | 
|   | 
| /** | 
|  * Pad a string to a given length by adding 0 to the beginning | 
|  * @param {Number} number | 
|  * @param {Number} length | 
|  */ | 
| function pad(number, length) { | 
|     // Create an array of the remaining length +1 and join it with 0's | 
|     return new Array((length || 2) + 1 - String(number).length).join(0) + number; | 
| } | 
|   | 
| /** | 
|  * Wrap a method with extended functionality, preserving the original function | 
|  * @param {Object} obj The context object that the method belongs to  | 
|  * @param {String} method The name of the method to extend | 
|  * @param {Function} func A wrapper function callback. This function is called with the same arguments | 
|  * as the original function, except that the original function is unshifted and passed as the first  | 
|  * argument.  | 
|  */ | 
| function wrap(obj, method, func) { | 
|     var proceed = obj[method]; | 
|     obj[method] = function () { | 
|         var args = Array.prototype.slice.call(arguments); | 
|         args.unshift(proceed); | 
|         return func.apply(this, args); | 
|     }; | 
| } | 
|   | 
| /** | 
|  * Based on http://www.php.net/manual/en/function.strftime.php | 
|  * @param {String} format | 
|  * @param {Number} timestamp | 
|  * @param {Boolean} capitalize | 
|  */ | 
| dateFormat = function (format, timestamp, capitalize) { | 
|     if (!defined(timestamp) || isNaN(timestamp)) { | 
|         return 'Invalid date'; | 
|     } | 
|     format = pick(format, '%Y-%m-%d %H:%M:%S'); | 
|   | 
|     var date = new Date(timestamp), | 
|         key, // used in for constuct below | 
|         // get the basic time values | 
|         hours = date[getHours](), | 
|         day = date[getDay](), | 
|         dayOfMonth = date[getDate](), | 
|         month = date[getMonth](), | 
|         fullYear = date[getFullYear](), | 
|         lang = defaultOptions.lang, | 
|         langWeekdays = lang.weekdays, | 
|   | 
|         // List all format keys. Custom formats can be added from the outside.  | 
|         replacements = extend({ | 
|   | 
|             // Day | 
|             'a': langWeekdays[day].substr(0, 3), // Short weekday, like 'Mon' | 
|             'A': langWeekdays[day], // Long weekday, like 'Monday' | 
|             'd': pad(dayOfMonth), // Two digit day of the month, 01 to 31 | 
|             'e': dayOfMonth, // Day of the month, 1 through 31 | 
|   | 
|             // Week (none implemented) | 
|             //'W': weekNumber(), | 
|   | 
|             // Month | 
|             'b': lang.shortMonths[month], // Short month, like 'Jan' | 
|             'B': lang.months[month], // Long month, like 'January' | 
|             'm': pad(month + 1), // Two digit month number, 01 through 12 | 
|   | 
|             // Year | 
|             'y': fullYear.toString().substr(2, 2), // Two digits year, like 09 for 2009 | 
|             'Y': fullYear, // Four digits year, like 2009 | 
|   | 
|             // Time | 
|             'H': pad(hours), // Two digits hours in 24h format, 00 through 23 | 
|             'I': pad((hours % 12) || 12), // Two digits hours in 12h format, 00 through 11 | 
|             'l': (hours % 12) || 12, // Hours in 12h format, 1 through 12 | 
|             'M': pad(date[getMinutes]()), // Two digits minutes, 00 through 59 | 
|             'p': hours < 12 ? 'AM' : 'PM', // Upper case AM or PM | 
|             'P': hours < 12 ? 'am' : 'pm', // Lower case AM or PM | 
|             'S': pad(date.getSeconds()), // Two digits seconds, 00 through  59 | 
|             'L': pad(mathRound(timestamp % 1000), 3) // Milliseconds (naming from Ruby) | 
|         }, Highcharts.dateFormats); | 
|   | 
|   | 
|     // do the replaces | 
|     for (key in replacements) { | 
|         while (format.indexOf('%' + key) !== -1) { // regex would do it in one line, but this is faster | 
|             format = format.replace('%' + key, typeof replacements[key] === 'function' ? replacements[key](timestamp) : replacements[key]); | 
|         } | 
|     } | 
|   | 
|     // Optionally capitalize the string and return | 
|     return capitalize ? format.substr(0, 1).toUpperCase() + format.substr(1) : format; | 
| }; | 
|   | 
| /**  | 
|  * Format a single variable. Similar to sprintf, without the % prefix. | 
|  */ | 
| function formatSingle(format, val) { | 
|     var floatRegex = /f$/, | 
|         decRegex = /\.([0-9])/, | 
|         lang = defaultOptions.lang, | 
|         decimals; | 
|   | 
|     if (floatRegex.test(format)) { // float | 
|         decimals = format.match(decRegex); | 
|         decimals = decimals ? decimals[1] : -1; | 
|         val = numberFormat( | 
|             val, | 
|             decimals, | 
|             lang.decimalPoint, | 
|             format.indexOf(',') > -1 ? lang.thousandsSep : '' | 
|         ); | 
|     } else { | 
|         val = dateFormat(format, val); | 
|     } | 
|     return val; | 
| } | 
|   | 
| /** | 
|  * Format a string according to a subset of the rules of Python's String.format method. | 
|  */ | 
| function format(str, ctx) { | 
|     var splitter = '{', | 
|         isInside = false, | 
|         segment, | 
|         valueAndFormat, | 
|         path, | 
|         i, | 
|         len, | 
|         ret = [], | 
|         val, | 
|         index; | 
|      | 
|     while ((index = str.indexOf(splitter)) !== -1) { | 
|          | 
|         segment = str.slice(0, index); | 
|         if (isInside) { // we're on the closing bracket looking back | 
|              | 
|             valueAndFormat = segment.split(':'); | 
|             path = valueAndFormat.shift().split('.'); // get first and leave format | 
|             len = path.length; | 
|             val = ctx; | 
|   | 
|             // Assign deeper paths | 
|             for (i = 0; i < len; i++) { | 
|                 val = val[path[i]]; | 
|             } | 
|   | 
|             // Format the replacement | 
|             if (valueAndFormat.length) { | 
|                 val = formatSingle(valueAndFormat.join(':'), val); | 
|             } | 
|   | 
|             // Push the result and advance the cursor | 
|             ret.push(val); | 
|              | 
|         } else { | 
|             ret.push(segment); | 
|              | 
|         } | 
|         str = str.slice(index + 1); // the rest | 
|         isInside = !isInside; // toggle | 
|         splitter = isInside ? '}' : '{'; // now look for next matching bracket | 
|     } | 
|     ret.push(str); | 
|     return ret.join(''); | 
| } | 
|   | 
| /** | 
|  * Get the magnitude of a number | 
|  */ | 
| function getMagnitude(num) { | 
|     return math.pow(10, mathFloor(math.log(num) / math.LN10)); | 
| } | 
|   | 
| /** | 
|  * Take an interval and normalize it to multiples of 1, 2, 2.5 and 5 | 
|  * @param {Number} interval | 
|  * @param {Array} multiples | 
|  * @param {Number} magnitude | 
|  * @param {Object} options | 
|  */ | 
| function normalizeTickInterval(interval, multiples, magnitude, options) { | 
|     var normalized, i; | 
|   | 
|     // round to a tenfold of 1, 2, 2.5 or 5 | 
|     magnitude = pick(magnitude, 1); | 
|     normalized = interval / magnitude; | 
|   | 
|     // multiples for a linear scale | 
|     if (!multiples) { | 
|         multiples = [1, 2, 2.5, 5, 10]; | 
|   | 
|         // the allowDecimals option | 
|         if (options && options.allowDecimals === false) { | 
|             if (magnitude === 1) { | 
|                 multiples = [1, 2, 5, 10]; | 
|             } else if (magnitude <= 0.1) { | 
|                 multiples = [1 / magnitude]; | 
|             } | 
|         } | 
|     } | 
|   | 
|     // normalize the interval to the nearest multiple | 
|     for (i = 0; i < multiples.length; i++) { | 
|         interval = multiples[i]; | 
|         if (normalized <= (multiples[i] + (multiples[i + 1] || multiples[i])) / 2) { | 
|             break; | 
|         } | 
|     } | 
|   | 
|     // multiply back to the correct magnitude | 
|     interval *= magnitude; | 
|   | 
|     return interval; | 
| } | 
|   | 
| /** | 
|  * Get a normalized tick interval for dates. Returns a configuration object with | 
|  * unit range (interval), count and name. Used to prepare data for getTimeTicks.  | 
|  * Previously this logic was part of getTimeTicks, but as getTimeTicks now runs | 
|  * of segments in stock charts, the normalizing logic was extracted in order to  | 
|  * prevent it for running over again for each segment having the same interval.  | 
|  * #662, #697. | 
|  */ | 
| function normalizeTimeTickInterval(tickInterval, unitsOption) { | 
|     var units = unitsOption || [[ | 
|                 MILLISECOND, // unit name | 
|                 [1, 2, 5, 10, 20, 25, 50, 100, 200, 500] // allowed multiples | 
|             ], [ | 
|                 SECOND, | 
|                 [1, 2, 5, 10, 15, 30] | 
|             ], [ | 
|                 MINUTE, | 
|                 [1, 2, 5, 10, 15, 30] | 
|             ], [ | 
|                 HOUR, | 
|                 [1, 2, 3, 4, 6, 8, 12] | 
|             ], [ | 
|                 DAY, | 
|                 [1, 2] | 
|             ], [ | 
|                 WEEK, | 
|                 [1, 2] | 
|             ], [ | 
|                 MONTH, | 
|                 [1, 2, 3, 4, 6] | 
|             ], [ | 
|                 YEAR, | 
|                 null | 
|             ]], | 
|         unit = units[units.length - 1], // default unit is years | 
|         interval = timeUnits[unit[0]], | 
|         multiples = unit[1], | 
|         count, | 
|         i; | 
|          | 
|     // loop through the units to find the one that best fits the tickInterval | 
|     for (i = 0; i < units.length; i++) { | 
|         unit = units[i]; | 
|         interval = timeUnits[unit[0]]; | 
|         multiples = unit[1]; | 
|   | 
|   | 
|         if (units[i + 1]) { | 
|             // lessThan is in the middle between the highest multiple and the next unit. | 
|             var lessThan = (interval * multiples[multiples.length - 1] + | 
|                         timeUnits[units[i + 1][0]]) / 2; | 
|   | 
|             // break and keep the current unit | 
|             if (tickInterval <= lessThan) { | 
|                 break; | 
|             } | 
|         } | 
|     } | 
|   | 
|     // prevent 2.5 years intervals, though 25, 250 etc. are allowed | 
|     if (interval === timeUnits[YEAR] && tickInterval < 5 * interval) { | 
|         multiples = [1, 2, 5]; | 
|     } | 
|   | 
|     // get the count | 
|     count = normalizeTickInterval( | 
|         tickInterval / interval,  | 
|         multiples, | 
|         unit[0] === YEAR ? getMagnitude(tickInterval / interval) : 1 // #1913 | 
|     ); | 
|      | 
|     return { | 
|         unitRange: interval, | 
|         count: count, | 
|         unitName: unit[0] | 
|     }; | 
| } | 
|   | 
| /** | 
|  * Set the tick positions to a time unit that makes sense, for example | 
|  * on the first of each month or on every Monday. Return an array | 
|  * with the time positions. Used in datetime axes as well as for grouping | 
|  * data on a datetime axis. | 
|  * | 
|  * @param {Object} normalizedInterval The interval in axis values (ms) and the count | 
|  * @param {Number} min The minimum in axis values | 
|  * @param {Number} max The maximum in axis values | 
|  * @param {Number} startOfWeek | 
|  */ | 
| function getTimeTicks(normalizedInterval, min, max, startOfWeek) { | 
|     var tickPositions = [], | 
|         i, | 
|         higherRanks = {}, | 
|         useUTC = defaultOptions.global.useUTC, | 
|         minYear, // used in months and years as a basis for Date.UTC() | 
|         minDate = new Date(min), | 
|         interval = normalizedInterval.unitRange, | 
|         count = normalizedInterval.count; | 
|   | 
|     if (defined(min)) { // #1300 | 
|         if (interval >= timeUnits[SECOND]) { // second | 
|             minDate.setMilliseconds(0); | 
|             minDate.setSeconds(interval >= timeUnits[MINUTE] ? 0 : | 
|                 count * mathFloor(minDate.getSeconds() / count)); | 
|         } | 
|      | 
|         if (interval >= timeUnits[MINUTE]) { // minute | 
|             minDate[setMinutes](interval >= timeUnits[HOUR] ? 0 : | 
|                 count * mathFloor(minDate[getMinutes]() / count)); | 
|         } | 
|      | 
|         if (interval >= timeUnits[HOUR]) { // hour | 
|             minDate[setHours](interval >= timeUnits[DAY] ? 0 : | 
|                 count * mathFloor(minDate[getHours]() / count)); | 
|         } | 
|      | 
|         if (interval >= timeUnits[DAY]) { // day | 
|             minDate[setDate](interval >= timeUnits[MONTH] ? 1 : | 
|                 count * mathFloor(minDate[getDate]() / count)); | 
|         } | 
|      | 
|         if (interval >= timeUnits[MONTH]) { // month | 
|             minDate[setMonth](interval >= timeUnits[YEAR] ? 0 : | 
|                 count * mathFloor(minDate[getMonth]() / count)); | 
|             minYear = minDate[getFullYear](); | 
|         } | 
|      | 
|         if (interval >= timeUnits[YEAR]) { // year | 
|             minYear -= minYear % count; | 
|             minDate[setFullYear](minYear); | 
|         } | 
|      | 
|         // week is a special case that runs outside the hierarchy | 
|         if (interval === timeUnits[WEEK]) { | 
|             // get start of current week, independent of count | 
|             minDate[setDate](minDate[getDate]() - minDate[getDay]() + | 
|                 pick(startOfWeek, 1)); | 
|         } | 
|      | 
|      | 
|         // get tick positions | 
|         i = 1; | 
|         minYear = minDate[getFullYear](); | 
|         var time = minDate.getTime(), | 
|             minMonth = minDate[getMonth](), | 
|             minDateDate = minDate[getDate](), | 
|             timezoneOffset = useUTC ?  | 
|                 0 :  | 
|                 (24 * 3600 * 1000 + minDate.getTimezoneOffset() * 60 * 1000) % (24 * 3600 * 1000); // #950 | 
|      | 
|         // iterate and add tick positions at appropriate values | 
|         while (time < max) { | 
|             tickPositions.push(time); | 
|      | 
|             // if the interval is years, use Date.UTC to increase years | 
|             if (interval === timeUnits[YEAR]) { | 
|                 time = makeTime(minYear + i * count, 0); | 
|      | 
|             // if the interval is months, use Date.UTC to increase months | 
|             } else if (interval === timeUnits[MONTH]) { | 
|                 time = makeTime(minYear, minMonth + i * count); | 
|      | 
|             // if we're using global time, the interval is not fixed as it jumps | 
|             // one hour at the DST crossover | 
|             } else if (!useUTC && (interval === timeUnits[DAY] || interval === timeUnits[WEEK])) { | 
|                 time = makeTime(minYear, minMonth, minDateDate + | 
|                     i * count * (interval === timeUnits[DAY] ? 1 : 7)); | 
|      | 
|             // else, the interval is fixed and we use simple addition | 
|             } else { | 
|                 time += interval * count; | 
|             } | 
|      | 
|             i++; | 
|         } | 
|      | 
|         // push the last time | 
|         tickPositions.push(time); | 
|   | 
|   | 
|         // mark new days if the time is dividible by day (#1649, #1760) | 
|         each(grep(tickPositions, function (time) { | 
|             return interval <= timeUnits[HOUR] && time % timeUnits[DAY] === timezoneOffset; | 
|         }), function (time) { | 
|             higherRanks[time] = DAY; | 
|         }); | 
|     } | 
|   | 
|   | 
|     // record information on the chosen unit - for dynamic label formatter | 
|     tickPositions.info = extend(normalizedInterval, { | 
|         higherRanks: higherRanks, | 
|         totalRange: interval * count | 
|     }); | 
|   | 
|     return tickPositions; | 
| } | 
|   | 
| /** | 
|  * Helper class that contains variuos counters that are local to the chart. | 
|  */ | 
| function ChartCounters() { | 
|     this.color = 0; | 
|     this.symbol = 0; | 
| } | 
|   | 
| ChartCounters.prototype =  { | 
|     /** | 
|      * Wraps the color counter if it reaches the specified length. | 
|      */ | 
|     wrapColor: function (length) { | 
|         if (this.color >= length) { | 
|             this.color = 0; | 
|         } | 
|     }, | 
|   | 
|     /** | 
|      * Wraps the symbol counter if it reaches the specified length. | 
|      */ | 
|     wrapSymbol: function (length) { | 
|         if (this.symbol >= length) { | 
|             this.symbol = 0; | 
|         } | 
|     } | 
| }; | 
|   | 
|   | 
| /** | 
|  * Utility method that sorts an object array and keeping the order of equal items. | 
|  * ECMA script standard does not specify the behaviour when items are equal. | 
|  */ | 
| function stableSort(arr, sortFunction) { | 
|     var length = arr.length, | 
|         sortValue, | 
|         i; | 
|   | 
|     // Add index to each item | 
|     for (i = 0; i < length; i++) { | 
|         arr[i].ss_i = i; // stable sort index | 
|     } | 
|   | 
|     arr.sort(function (a, b) { | 
|         sortValue = sortFunction(a, b); | 
|         return sortValue === 0 ? a.ss_i - b.ss_i : sortValue; | 
|     }); | 
|   | 
|     // Remove index from items | 
|     for (i = 0; i < length; i++) { | 
|         delete arr[i].ss_i; // stable sort index | 
|     } | 
| } | 
|   | 
| /** | 
|  * Non-recursive method to find the lowest member of an array. Math.min raises a maximum | 
|  * call stack size exceeded error in Chrome when trying to apply more than 150.000 points. This | 
|  * method is slightly slower, but safe. | 
|  */ | 
| function arrayMin(data) { | 
|     var i = data.length, | 
|         min = data[0]; | 
|   | 
|     while (i--) { | 
|         if (data[i] < min) { | 
|             min = data[i]; | 
|         } | 
|     } | 
|     return min; | 
| } | 
|   | 
| /** | 
|  * Non-recursive method to find the lowest member of an array. Math.min raises a maximum | 
|  * call stack size exceeded error in Chrome when trying to apply more than 150.000 points. This | 
|  * method is slightly slower, but safe. | 
|  */ | 
| function arrayMax(data) { | 
|     var i = data.length, | 
|         max = data[0]; | 
|   | 
|     while (i--) { | 
|         if (data[i] > max) { | 
|             max = data[i]; | 
|         } | 
|     } | 
|     return max; | 
| } | 
|   | 
| /** | 
|  * Utility method that destroys any SVGElement or VMLElement that are properties on the given object. | 
|  * It loops all properties and invokes destroy if there is a destroy method. The property is | 
|  * then delete'ed. | 
|  * @param {Object} The object to destroy properties on | 
|  * @param {Object} Exception, do not destroy this property, only delete it. | 
|  */ | 
| function destroyObjectProperties(obj, except) { | 
|     var n; | 
|     for (n in obj) { | 
|         // If the object is non-null and destroy is defined | 
|         if (obj[n] && obj[n] !== except && obj[n].destroy) { | 
|             // Invoke the destroy | 
|             obj[n].destroy(); | 
|         } | 
|   | 
|         // Delete the property from the object. | 
|         delete obj[n]; | 
|     } | 
| } | 
|   | 
|   | 
| /** | 
|  * Discard an element by moving it to the bin and delete | 
|  * @param {Object} The HTML node to discard | 
|  */ | 
| function discardElement(element) { | 
|     // create a garbage bin element, not part of the DOM | 
|     if (!garbageBin) { | 
|         garbageBin = createElement(DIV); | 
|     } | 
|   | 
|     // move the node and empty bin | 
|     if (element) { | 
|         garbageBin.appendChild(element); | 
|     } | 
|     garbageBin.innerHTML = ''; | 
| } | 
|   | 
| /** | 
|  * Provide error messages for debugging, with links to online explanation  | 
|  */ | 
| function error(code, stop) { | 
|     var msg = 'Highcharts error #' + code + ': www.highcharts.com/errors/' + code; | 
|     if (stop) { | 
|         throw msg; | 
|     } else if (win.console) { | 
|         console.log(msg); | 
|     } | 
| } | 
|   | 
| /** | 
|  * Fix JS round off float errors | 
|  * @param {Number} num | 
|  */ | 
| function correctFloat(num) { | 
|     return parseFloat( | 
|         num.toPrecision(14) | 
|     ); | 
| } | 
|   | 
| /** | 
|  * Set the global animation to either a given value, or fall back to the | 
|  * given chart's animation option | 
|  * @param {Object} animation | 
|  * @param {Object} chart | 
|  */ | 
| function setAnimation(animation, chart) { | 
|     globalAnimation = pick(animation, chart.animation); | 
| } | 
|   | 
| /** | 
|  * The time unit lookup | 
|  */ | 
| /*jslint white: true*/ | 
| timeUnits = hash( | 
|     MILLISECOND, 1, | 
|     SECOND, 1000, | 
|     MINUTE, 60000, | 
|     HOUR, 3600000, | 
|     DAY, 24 * 3600000, | 
|     WEEK, 7 * 24 * 3600000, | 
|     MONTH, 31 * 24 * 3600000, | 
|     YEAR, 31556952000 | 
| ); | 
| /*jslint white: false*/ | 
| /** | 
|  * Path interpolation algorithm used across adapters | 
|  */ | 
| pathAnim = { | 
|     /** | 
|      * Prepare start and end values so that the path can be animated one to one | 
|      */ | 
|     init: function (elem, fromD, toD) { | 
|         fromD = fromD || ''; | 
|         var shift = elem.shift, | 
|             bezier = fromD.indexOf('C') > -1, | 
|             numParams = bezier ? 7 : 3, | 
|             endLength, | 
|             slice, | 
|             i, | 
|             start = fromD.split(' '), | 
|             end = [].concat(toD), // copy | 
|             startBaseLine, | 
|             endBaseLine, | 
|             sixify = function (arr) { // in splines make move points have six parameters like bezier curves | 
|                 i = arr.length; | 
|                 while (i--) { | 
|                     if (arr[i] === M) { | 
|                         arr.splice(i + 1, 0, arr[i + 1], arr[i + 2], arr[i + 1], arr[i + 2]); | 
|                     } | 
|                 } | 
|             }; | 
|   | 
|         if (bezier) { | 
|             sixify(start); | 
|             sixify(end); | 
|         } | 
|   | 
|         // pull out the base lines before padding | 
|         if (elem.isArea) { | 
|             startBaseLine = start.splice(start.length - 6, 6); | 
|             endBaseLine = end.splice(end.length - 6, 6); | 
|         } | 
|   | 
|         // if shifting points, prepend a dummy point to the end path | 
|         if (shift <= end.length / numParams && start.length === end.length) { | 
|             while (shift--) { | 
|                 end = [].concat(end).splice(0, numParams).concat(end); | 
|             } | 
|         } | 
|         elem.shift = 0; // reset for following animations | 
|   | 
|         // copy and append last point until the length matches the end length | 
|         if (start.length) { | 
|             endLength = end.length; | 
|             while (start.length < endLength) { | 
|   | 
|                 //bezier && sixify(start); | 
|                 slice = [].concat(start).splice(start.length - numParams, numParams); | 
|                 if (bezier) { // disable first control point | 
|                     slice[numParams - 6] = slice[numParams - 2]; | 
|                     slice[numParams - 5] = slice[numParams - 1]; | 
|                 } | 
|                 start = start.concat(slice); | 
|             } | 
|         } | 
|   | 
|         if (startBaseLine) { // append the base lines for areas | 
|             start = start.concat(startBaseLine); | 
|             end = end.concat(endBaseLine); | 
|         } | 
|         return [start, end]; | 
|     }, | 
|   | 
|     /** | 
|      * Interpolate each value of the path and return the array | 
|      */ | 
|     step: function (start, end, pos, complete) { | 
|         var ret = [], | 
|             i = start.length, | 
|             startVal; | 
|   | 
|         if (pos === 1) { // land on the final path without adjustment points appended in the ends | 
|             ret = complete; | 
|   | 
|         } else if (i === end.length && pos < 1) { | 
|             while (i--) { | 
|                 startVal = parseFloat(start[i]); | 
|                 ret[i] = | 
|                     isNaN(startVal) ? // a letter instruction like M or L | 
|                         start[i] : | 
|                         pos * (parseFloat(end[i] - startVal)) + startVal; | 
|   | 
|             } | 
|         } else { // if animation is finished or length not matching, land on right value | 
|             ret = end; | 
|         } | 
|         return ret; | 
|     } | 
| }; | 
|   | 
| (function ($) { | 
|     /** | 
|      * The default HighchartsAdapter for jQuery | 
|      */ | 
|     win.HighchartsAdapter = win.HighchartsAdapter || ($ && { | 
|          | 
|         /** | 
|          * Initialize the adapter by applying some extensions to jQuery | 
|          */ | 
|         init: function (pathAnim) { | 
|              | 
|             // extend the animate function to allow SVG animations | 
|             var Fx = $.fx, | 
|                 Step = Fx.step, | 
|                 dSetter, | 
|                 Tween = $.Tween, | 
|                 propHooks = Tween && Tween.propHooks, | 
|                 opacityHook = $.cssHooks.opacity; | 
|              | 
|             /*jslint unparam: true*//* allow unused param x in this function */ | 
|             $.extend($.easing, { | 
|                 easeOutQuad: function (x, t, b, c, d) { | 
|                     return -c * (t /= d) * (t - 2) + b; | 
|                 } | 
|             }); | 
|             /*jslint unparam: false*/ | 
|          | 
|             // extend some methods to check for elem.attr, which means it is a Highcharts SVG object | 
|             $.each(['cur', '_default', 'width', 'height', 'opacity'], function (i, fn) { | 
|                 var obj = Step, | 
|                     base, | 
|                     elem; | 
|                      | 
|                 // Handle different parent objects | 
|                 if (fn === 'cur') { | 
|                     obj = Fx.prototype; // 'cur', the getter, relates to Fx.prototype | 
|                  | 
|                 } else if (fn === '_default' && Tween) { // jQuery 1.8 model | 
|                     obj = propHooks[fn]; | 
|                     fn = 'set'; | 
|                 } | 
|          | 
|                 // Overwrite the method | 
|                 base = obj[fn]; | 
|                 if (base) { // step.width and step.height don't exist in jQuery < 1.7 | 
|          | 
|                     // create the extended function replacement | 
|                     obj[fn] = function (fx) { | 
|          | 
|                         // Fx.prototype.cur does not use fx argument | 
|                         fx = i ? fx : this; | 
|   | 
|                         // Don't run animations on textual properties like align (#1821) | 
|                         if (fx.prop === 'align') { | 
|                             return; | 
|                         } | 
|          | 
|                         // shortcut | 
|                         elem = fx.elem; | 
|          | 
|                         // Fx.prototype.cur returns the current value. The other ones are setters | 
|                         // and returning a value has no effect. | 
|                         return elem.attr ? // is SVG element wrapper | 
|                             elem.attr(fx.prop, fn === 'cur' ? UNDEFINED : fx.now) : // apply the SVG wrapper's method | 
|                             base.apply(this, arguments); // use jQuery's built-in method | 
|                     }; | 
|                 } | 
|             }); | 
|   | 
|             // Extend the opacity getter, needed for fading opacity with IE9 and jQuery 1.10+ | 
|             wrap(opacityHook, 'get', function (proceed, elem, computed) { | 
|                 return elem.attr ? (elem.opacity || 0) : proceed.call(this, elem, computed); | 
|             }); | 
|              | 
|              | 
|             // Define the setter function for d (path definitions) | 
|             dSetter = function (fx) { | 
|                 var elem = fx.elem, | 
|                     ends; | 
|          | 
|                 // Normally start and end should be set in state == 0, but sometimes, | 
|                 // for reasons unknown, this doesn't happen. Perhaps state == 0 is skipped | 
|                 // in these cases | 
|                 if (!fx.started) { | 
|                     ends = pathAnim.init(elem, elem.d, elem.toD); | 
|                     fx.start = ends[0]; | 
|                     fx.end = ends[1]; | 
|                     fx.started = true; | 
|                 } | 
|          | 
|          | 
|                 // interpolate each value of the path | 
|                 elem.attr('d', pathAnim.step(fx.start, fx.end, fx.pos, elem.toD)); | 
|             }; | 
|              | 
|             // jQuery 1.8 style | 
|             if (Tween) { | 
|                 propHooks.d = { | 
|                     set: dSetter | 
|                 }; | 
|             // pre 1.8 | 
|             } else { | 
|                 // animate paths | 
|                 Step.d = dSetter; | 
|             } | 
|              | 
|             /** | 
|              * Utility for iterating over an array. Parameters are reversed compared to jQuery. | 
|              * @param {Array} arr | 
|              * @param {Function} fn | 
|              */ | 
|             this.each = Array.prototype.forEach ? | 
|                 function (arr, fn) { // modern browsers | 
|                     return Array.prototype.forEach.call(arr, fn); | 
|                      | 
|                 } :  | 
|                 function (arr, fn) { // legacy | 
|                     var i = 0,  | 
|                         len = arr.length; | 
|                     for (; i < len; i++) { | 
|                         if (fn.call(arr[i], arr[i], i, arr) === false) { | 
|                             return i; | 
|                         } | 
|                     } | 
|                 }; | 
|              | 
|             /** | 
|              * Register Highcharts as a plugin in the respective framework | 
|              */ | 
|             $.fn.highcharts = function () { | 
|                 var constr = 'Chart', // default constructor | 
|                     args = arguments, | 
|                     options, | 
|                     ret, | 
|                     chart; | 
|   | 
|                 if (isString(args[0])) { | 
|                     constr = args[0]; | 
|                     args = Array.prototype.slice.call(args, 1);  | 
|                 } | 
|                 options = args[0]; | 
|   | 
|                 // Create the chart | 
|                 if (options !== UNDEFINED) { | 
|                     /*jslint unused:false*/ | 
|                     options.chart = options.chart || {}; | 
|                     options.chart.renderTo = this[0]; | 
|                     chart = new Highcharts[constr](options, args[1]); | 
|                     ret = this; | 
|                     /*jslint unused:true*/ | 
|                 } | 
|   | 
|                 // When called without parameters or with the return argument, get a predefined chart | 
|                 if (options === UNDEFINED) { | 
|                     ret = charts[attr(this[0], 'data-highcharts-chart')]; | 
|                 }     | 
|   | 
|                 return ret; | 
|             }; | 
|   | 
|         }, | 
|   | 
|          | 
|         /** | 
|          * Downloads a script and executes a callback when done. | 
|          * @param {String} scriptLocation | 
|          * @param {Function} callback | 
|          */ | 
|         getScript: $.getScript, | 
|          | 
|         /** | 
|          * Return the index of an item in an array, or -1 if not found | 
|          */ | 
|         inArray: $.inArray, | 
|          | 
|         /** | 
|          * A direct link to jQuery methods. MooTools and Prototype adapters must be implemented for each case of method. | 
|          * @param {Object} elem The HTML element | 
|          * @param {String} method Which method to run on the wrapped element | 
|          */ | 
|         adapterRun: function (elem, method) { | 
|             return $(elem)[method](); | 
|         }, | 
|      | 
|         /** | 
|          * Filter an array | 
|          */ | 
|         grep: $.grep, | 
|      | 
|         /** | 
|          * Map an array | 
|          * @param {Array} arr | 
|          * @param {Function} fn | 
|          */ | 
|         map: function (arr, fn) { | 
|             //return jQuery.map(arr, fn); | 
|             var results = [], | 
|                 i = 0, | 
|                 len = arr.length; | 
|             for (; i < len; i++) { | 
|                 results[i] = fn.call(arr[i], arr[i], i, arr); | 
|             } | 
|             return results; | 
|      | 
|         }, | 
|      | 
|         /** | 
|          * Get the position of an element relative to the top left of the page | 
|          */ | 
|         offset: function (el) { | 
|             return $(el).offset(); | 
|         }, | 
|      | 
|         /** | 
|          * Add an event listener | 
|          * @param {Object} el A HTML element or custom object | 
|          * @param {String} event The event type | 
|          * @param {Function} fn The event handler | 
|          */ | 
|         addEvent: function (el, event, fn) { | 
|             $(el).bind(event, fn); | 
|         }, | 
|      | 
|         /** | 
|          * Remove event added with addEvent | 
|          * @param {Object} el The object | 
|          * @param {String} eventType The event type. Leave blank to remove all events. | 
|          * @param {Function} handler The function to remove | 
|          */ | 
|         removeEvent: function (el, eventType, handler) { | 
|             // workaround for jQuery issue with unbinding custom events: | 
|             // http://forum.jQuery.com/topic/javascript-error-when-unbinding-a-custom-event-using-jQuery-1-4-2 | 
|             var func = doc.removeEventListener ? 'removeEventListener' : 'detachEvent'; | 
|             if (doc[func] && el && !el[func]) { | 
|                 el[func] = function () {}; | 
|             } | 
|      | 
|             $(el).unbind(eventType, handler); | 
|         }, | 
|      | 
|         /** | 
|          * Fire an event on a custom object | 
|          * @param {Object} el | 
|          * @param {String} type | 
|          * @param {Object} eventArguments | 
|          * @param {Function} defaultFunction | 
|          */ | 
|         fireEvent: function (el, type, eventArguments, defaultFunction) { | 
|             var event = $.Event(type), | 
|                 detachedType = 'detached' + type, | 
|                 defaultPrevented; | 
|      | 
|             // Remove warnings in Chrome when accessing layerX and layerY. Although Highcharts | 
|             // never uses these properties, Chrome includes them in the default click event and | 
|             // raises the warning when they are copied over in the extend statement below. | 
|             // | 
|             // To avoid problems in IE (see #1010) where we cannot delete the properties and avoid | 
|             // testing if they are there (warning in chrome) the only option is to test if running IE. | 
|             if (!isIE && eventArguments) { | 
|                 delete eventArguments.layerX; | 
|                 delete eventArguments.layerY; | 
|             } | 
|      | 
|             extend(event, eventArguments); | 
|      | 
|             // Prevent jQuery from triggering the object method that is named the | 
|             // same as the event. For example, if the event is 'select', jQuery | 
|             // attempts calling el.select and it goes into a loop. | 
|             if (el[type]) { | 
|                 el[detachedType] = el[type]; | 
|                 el[type] = null; | 
|             } | 
|      | 
|             // Wrap preventDefault and stopPropagation in try/catch blocks in | 
|             // order to prevent JS errors when cancelling events on non-DOM | 
|             // objects. #615. | 
|             /*jslint unparam: true*/ | 
|             $.each(['preventDefault', 'stopPropagation'], function (i, fn) { | 
|                 var base = event[fn]; | 
|                 event[fn] = function () { | 
|                     try { | 
|                         base.call(event); | 
|                     } catch (e) { | 
|                         if (fn === 'preventDefault') { | 
|                             defaultPrevented = true; | 
|                         } | 
|                     } | 
|                 }; | 
|             }); | 
|             /*jslint unparam: false*/ | 
|      | 
|             // trigger it | 
|             $(el).trigger(event); | 
|      | 
|             // attach the method | 
|             if (el[detachedType]) { | 
|                 el[type] = el[detachedType]; | 
|                 el[detachedType] = null; | 
|             } | 
|      | 
|             if (defaultFunction && !event.isDefaultPrevented() && !defaultPrevented) { | 
|                 defaultFunction(event); | 
|             } | 
|         }, | 
|          | 
|         /** | 
|          * Extension method needed for MooTools | 
|          */ | 
|         washMouseEvent: function (e) { | 
|             var ret = e.originalEvent || e; | 
|              | 
|             // computed by jQuery, needed by IE8 | 
|             if (ret.pageX === UNDEFINED) { // #1236 | 
|                 ret.pageX = e.pageX; | 
|                 ret.pageY = e.pageY; | 
|             } | 
|              | 
|             return ret; | 
|         }, | 
|      | 
|         /** | 
|          * Animate a HTML element or SVG element wrapper | 
|          * @param {Object} el | 
|          * @param {Object} params | 
|          * @param {Object} options jQuery-like animation options: duration, easing, callback | 
|          */ | 
|         animate: function (el, params, options) { | 
|             var $el = $(el); | 
|             if (!el.style) { | 
|                 el.style = {}; // #1881 | 
|             } | 
|             if (params.d) { | 
|                 el.toD = params.d; // keep the array form for paths, used in $.fx.step.d | 
|                 params.d = 1; // because in jQuery, animating to an array has a different meaning | 
|             } | 
|      | 
|             $el.stop(); | 
|             if (params.opacity !== UNDEFINED && el.attr) { | 
|                 params.opacity += 'px'; // force jQuery to use same logic as width and height (#2161) | 
|             } | 
|             $el.animate(params, options); | 
|      | 
|         }, | 
|         /** | 
|          * Stop running animation | 
|          */ | 
|         stop: function (el) { | 
|             $(el).stop(); | 
|         } | 
|     }); | 
| }(win.jQuery)); | 
|   | 
|   | 
| // check for a custom HighchartsAdapter defined prior to this file | 
| var globalAdapter = win.HighchartsAdapter, | 
|     adapter = globalAdapter || {}; | 
|      | 
| // Initialize the adapter | 
| if (globalAdapter) { | 
|     globalAdapter.init.call(globalAdapter, pathAnim); | 
| } | 
|   | 
|   | 
| // Utility functions. If the HighchartsAdapter is not defined, adapter is an empty object | 
| // and all the utility functions will be null. In that case they are populated by the | 
| // default adapters below. | 
| var adapterRun = adapter.adapterRun, | 
|     getScript = adapter.getScript, | 
|     inArray = adapter.inArray, | 
|     each = adapter.each, | 
|     grep = adapter.grep, | 
|     offset = adapter.offset, | 
|     map = adapter.map, | 
|     addEvent = adapter.addEvent, | 
|     removeEvent = adapter.removeEvent, | 
|     fireEvent = adapter.fireEvent, | 
|     washMouseEvent = adapter.washMouseEvent, | 
|     animate = adapter.animate, | 
|     stop = adapter.stop; | 
|   | 
|   | 
|   | 
| /* **************************************************************************** | 
|  * Handle the options                                                         * | 
|  *****************************************************************************/ | 
| var | 
|   | 
| defaultLabelOptions = { | 
|     enabled: true, | 
|     // rotation: 0, | 
|     // align: 'center', | 
|     x: 0, | 
|     y: 15, | 
|     /*formatter: function () { | 
|         return this.value; | 
|     },*/ | 
|     style: { | 
|         color: '#666', | 
|         cursor: 'default', | 
|         fontSize: '11px', | 
|         lineHeight: '14px' | 
|     } | 
| }; | 
|   | 
| defaultOptions = { | 
|     colors: ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970', | 
|         '#f28f43', '#77a1e5', '#c42525', '#a6c96a'], | 
|     symbols: ['circle', 'diamond', 'square', 'triangle', 'triangle-down'], | 
|     lang: { | 
|         loading: 'Loading...', | 
|         months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', | 
|                 'August', 'September', 'October', 'November', 'December'], | 
|         shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], | 
|         weekdays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], | 
|         decimalPoint: '.', | 
|         numericSymbols: ['k', 'M', 'G', 'T', 'P', 'E'], // SI prefixes used in axis labels | 
|         resetZoom: 'Reset zoom', | 
|         resetZoomTitle: 'Reset zoom level 1:1', | 
|         thousandsSep: ',' | 
|     }, | 
|     global: { | 
|         useUTC: true, | 
|         canvasToolsURL: 'http://code.highcharts.com/3.0.6/modules/canvas-tools.js', | 
|         VMLRadialGradientURL: 'http://code.highcharts.com/3.0.6/gfx/vml-radial-gradient.png' | 
|     }, | 
|     chart: { | 
|         //animation: true, | 
|         //alignTicks: false, | 
|         //reflow: true, | 
|         //className: null, | 
|         //events: { load, selection }, | 
|         //margin: [null], | 
|         //marginTop: null, | 
|         //marginRight: null, | 
|         //marginBottom: null, | 
|         //marginLeft: null, | 
|         borderColor: '#4572A7', | 
|         //borderWidth: 0, | 
|         borderRadius: 5, | 
|         defaultSeriesType: 'line', | 
|         ignoreHiddenSeries: true, | 
|         //inverted: false, | 
|         //shadow: false, | 
|         spacing: [10, 10, 15, 10], | 
|         //spacingTop: 10, | 
|         //spacingRight: 10, | 
|         //spacingBottom: 15, | 
|         //spacingLeft: 10, | 
|         style: { | 
|             fontFamily: '"Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, Helvetica, sans-serif', // default font | 
|             fontSize: '12px' | 
|         }, | 
|         backgroundColor: '#FFFFFF', | 
|         //plotBackgroundColor: null, | 
|         plotBorderColor: '#C0C0C0', | 
|         //plotBorderWidth: 0, | 
|         //plotShadow: false, | 
|         //zoomType: '' | 
|         resetZoomButton: { | 
|             theme: { | 
|                 zIndex: 20 | 
|             }, | 
|             position: { | 
|                 align: 'right', | 
|                 x: -10, | 
|                 //verticalAlign: 'top', | 
|                 y: 10 | 
|             } | 
|             // relativeTo: 'plot' | 
|         } | 
|     }, | 
|     title: { | 
|         text: 'Chart title', | 
|         align: 'center', | 
|         // floating: false, | 
|         margin: 15, | 
|         // x: 0, | 
|         // verticalAlign: 'top', | 
|         // y: null, | 
|         style: { | 
|             color: '#274b6d',//#3E576F', | 
|             fontSize: '16px' | 
|         } | 
|   | 
|     }, | 
|     subtitle: { | 
|         text: '', | 
|         align: 'center', | 
|         // floating: false | 
|         // x: 0, | 
|         // verticalAlign: 'top', | 
|         // y: null, | 
|         style: { | 
|             color: '#4d759e' | 
|         } | 
|     }, | 
|   | 
|     plotOptions: { | 
|         line: { // base series options | 
|             allowPointSelect: false, | 
|             showCheckbox: false, | 
|             animation: { | 
|                 duration: 1000 | 
|             }, | 
|             //connectNulls: false, | 
|             //cursor: 'default', | 
|             //clip: true, | 
|             //dashStyle: null, | 
|             //enableMouseTracking: true, | 
|             events: {}, | 
|             //legendIndex: 0, | 
|             lineWidth: 2, | 
|             //shadow: false, | 
|             // stacking: null, | 
|             marker: { | 
|                 enabled: true, | 
|                 //symbol: null, | 
|                 lineWidth: 0, | 
|                 radius: 4, | 
|                 lineColor: '#FFFFFF', | 
|                 //fillColor: null, | 
|                 states: { // states for a single point | 
|                     hover: { | 
|                         enabled: true | 
|                         //radius: base + 2 | 
|                     }, | 
|                     select: { | 
|                         fillColor: '#FFFFFF', | 
|                         lineColor: '#000000', | 
|                         lineWidth: 2 | 
|                     } | 
|                 } | 
|             }, | 
|             point: { | 
|                 events: {} | 
|             }, | 
|             dataLabels: merge(defaultLabelOptions, { | 
|                 align: 'center', | 
|                 enabled: false, | 
|                 formatter: function () { | 
|                     return this.y === null ? '' : numberFormat(this.y, -1); | 
|                 }, | 
|                 verticalAlign: 'bottom', // above singular point | 
|                 y: 0 | 
|                 // backgroundColor: undefined, | 
|                 // borderColor: undefined, | 
|                 // borderRadius: undefined, | 
|                 // borderWidth: undefined, | 
|                 // padding: 3, | 
|                 // shadow: false | 
|             }), | 
|             cropThreshold: 300, // draw points outside the plot area when the number of points is less than this | 
|             pointRange: 0, | 
|             //pointStart: 0, | 
|             //pointInterval: 1, | 
|             showInLegend: true, | 
|             states: { // states for the entire series | 
|                 hover: { | 
|                     //enabled: false, | 
|                     //lineWidth: base + 1, | 
|                     marker: { | 
|                         // lineWidth: base + 1, | 
|                         // radius: base + 1 | 
|                     } | 
|                 }, | 
|                 select: { | 
|                     marker: {} | 
|                 } | 
|             }, | 
|             stickyTracking: true | 
|             //tooltip: { | 
|                 //pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.y}</b>' | 
|                 //valueDecimals: null, | 
|                 //xDateFormat: '%A, %b %e, %Y', | 
|                 //valuePrefix: '', | 
|                 //ySuffix: ''                 | 
|             //} | 
|             // turboThreshold: 1000 | 
|             // zIndex: null | 
|         } | 
|     }, | 
|     labels: { | 
|         //items: [], | 
|         style: { | 
|             //font: defaultFont, | 
|             position: ABSOLUTE, | 
|             color: '#3E576F' | 
|         } | 
|     }, | 
|     legend: { | 
|         enabled: true, | 
|         align: 'center', | 
|         //floating: false, | 
|         layout: 'horizontal', | 
|         labelFormatter: function () { | 
|             return this.name; | 
|         }, | 
|         borderWidth: 1, | 
|         borderColor: '#909090', | 
|         borderRadius: 5, | 
|         navigation: { | 
|             // animation: true, | 
|             activeColor: '#274b6d', | 
|             // arrowSize: 12 | 
|             inactiveColor: '#CCC' | 
|             // style: {} // text styles | 
|         }, | 
|         // margin: 10, | 
|         // reversed: false, | 
|         shadow: false, | 
|         // backgroundColor: null, | 
|         /*style: { | 
|             padding: '5px' | 
|         },*/ | 
|         itemStyle: { | 
|             cursor: 'pointer', | 
|             color: '#274b6d', | 
|             fontSize: '12px' | 
|         }, | 
|         itemHoverStyle: { | 
|             //cursor: 'pointer', removed as of #601 | 
|             color: '#000' | 
|         }, | 
|         itemHiddenStyle: { | 
|             color: '#CCC' | 
|         }, | 
|         itemCheckboxStyle: { | 
|             position: ABSOLUTE, | 
|             width: '13px', // for IE precision | 
|             height: '13px' | 
|         }, | 
|         // itemWidth: undefined, | 
|         symbolWidth: 16, | 
|         symbolPadding: 5, | 
|         verticalAlign: 'bottom', | 
|         // width: undefined, | 
|         x: 0, | 
|         y: 0, | 
|         title: { | 
|             //text: null, | 
|             style: { | 
|                 fontWeight: 'bold' | 
|             } | 
|         }             | 
|     }, | 
|   | 
|     loading: { | 
|         // hideDuration: 100, | 
|         labelStyle: { | 
|             fontWeight: 'bold', | 
|             position: RELATIVE, | 
|             top: '1em' | 
|         }, | 
|         // showDuration: 0, | 
|         style: { | 
|             position: ABSOLUTE, | 
|             backgroundColor: 'white', | 
|             opacity: 0.5, | 
|             textAlign: 'center' | 
|         } | 
|     }, | 
|   | 
|     tooltip: { | 
|         enabled: true, | 
|         animation: hasSVG, | 
|         //crosshairs: null, | 
|         backgroundColor: 'rgba(255, 255, 255, .85)', | 
|         borderWidth: 1, | 
|         borderRadius: 3, | 
|         dateTimeLabelFormats: {  | 
|             millisecond: '%A, %b %e, %H:%M:%S.%L', | 
|             second: '%A, %b %e, %H:%M:%S', | 
|             minute: '%A, %b %e, %H:%M', | 
|             hour: '%A, %b %e, %H:%M', | 
|             day: '%A, %b %e, %Y', | 
|             week: 'Week from %A, %b %e, %Y', | 
|             month: '%B %Y', | 
|             year: '%Y' | 
|         }, | 
|         //formatter: defaultFormatter, | 
|         headerFormat: '<span style="font-size: 10px">{point.key}</span><br/>', | 
|         pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.y}</b><br/>', | 
|         shadow: true, | 
|         //shared: false, | 
|         snap: isTouchDevice ? 25 : 10, | 
|         style: { | 
|             color: '#333333', | 
|             cursor: 'default', | 
|             fontSize: '12px', | 
|             padding: '8px', | 
|             whiteSpace: 'nowrap' | 
|         } | 
|         //xDateFormat: '%A, %b %e, %Y', | 
|         //valueDecimals: null, | 
|         //valuePrefix: '', | 
|         //valueSuffix: '' | 
|     }, | 
|   | 
|     credits: { | 
|         enabled: true, | 
|         text: 'Highcharts.com', | 
|         href: 'http://www.highcharts.com', | 
|         position: { | 
|             align: 'right', | 
|             x: -10, | 
|             verticalAlign: 'bottom', | 
|             y: -5 | 
|         }, | 
|         style: { | 
|             cursor: 'pointer', | 
|             color: '#909090', | 
|             fontSize: '9px' | 
|         } | 
|     } | 
| }; | 
|   | 
|   | 
|   | 
|   | 
| // Series defaults | 
| var defaultPlotOptions = defaultOptions.plotOptions, | 
|     defaultSeriesOptions = defaultPlotOptions.line; | 
|   | 
| // set the default time methods | 
| setTimeMethods(); | 
|   | 
|   | 
|   | 
| /** | 
|  * Set the time methods globally based on the useUTC option. Time method can be either | 
|  * local time or UTC (default). | 
|  */ | 
| function setTimeMethods() { | 
|     var useUTC = defaultOptions.global.useUTC, | 
|         GET = useUTC ? 'getUTC' : 'get', | 
|         SET = useUTC ? 'setUTC' : 'set'; | 
|   | 
|     makeTime = useUTC ? Date.UTC : function (year, month, date, hours, minutes, seconds) { | 
|         return new Date( | 
|             year, | 
|             month, | 
|             pick(date, 1), | 
|             pick(hours, 0), | 
|             pick(minutes, 0), | 
|             pick(seconds, 0) | 
|         ).getTime(); | 
|     }; | 
|     getMinutes =  GET + 'Minutes'; | 
|     getHours =    GET + 'Hours'; | 
|     getDay =      GET + 'Day'; | 
|     getDate =     GET + 'Date'; | 
|     getMonth =    GET + 'Month'; | 
|     getFullYear = GET + 'FullYear'; | 
|     setMinutes =  SET + 'Minutes'; | 
|     setHours =    SET + 'Hours'; | 
|     setDate =     SET + 'Date'; | 
|     setMonth =    SET + 'Month'; | 
|     setFullYear = SET + 'FullYear'; | 
|   | 
| } | 
|   | 
| /** | 
|  * Merge the default options with custom options and return the new options structure | 
|  * @param {Object} options The new custom options | 
|  */ | 
| function setOptions(options) { | 
|      | 
|     // Pull out axis options and apply them to the respective default axis options  | 
|     /*defaultXAxisOptions = merge(defaultXAxisOptions, options.xAxis); | 
|     defaultYAxisOptions = merge(defaultYAxisOptions, options.yAxis); | 
|     options.xAxis = options.yAxis = UNDEFINED;*/ | 
|      | 
|     // Merge in the default options | 
|     defaultOptions = merge(defaultOptions, options); | 
|      | 
|     // Apply UTC | 
|     setTimeMethods(); | 
|   | 
|     return defaultOptions; | 
| } | 
|   | 
| /** | 
|  * Get the updated default options. Merely exposing defaultOptions for outside modules | 
|  * isn't enough because the setOptions method creates a new object. | 
|  */ | 
| function getOptions() { | 
|     return defaultOptions; | 
| } | 
|   | 
|   | 
| /** | 
|  * Handle color operations. The object methods are chainable. | 
|  * @param {String} input The input color in either rbga or hex format | 
|  */ | 
| var Color = function (input) { | 
|     // declare variables | 
|     var rgba = [], result, stops; | 
|   | 
|     /** | 
|      * Parse the input color to rgba array | 
|      * @param {String} input | 
|      */ | 
|     function init(input) { | 
|   | 
|         // Gradients | 
|         if (input && input.stops) { | 
|             stops = map(input.stops, function (stop) { | 
|                 return Color(stop[1]); | 
|             }); | 
|   | 
|         // Solid colors | 
|         } else { | 
|             // rgba | 
|             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); | 
|             if (result) { | 
|                 rgba = [pInt(result[1]), pInt(result[2]), pInt(result[3]), parseFloat(result[4], 10)]; | 
|             } else {  | 
|                 // hex | 
|                 result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(input); | 
|                 if (result) { | 
|                     rgba = [pInt(result[1], 16), pInt(result[2], 16), pInt(result[3], 16), 1]; | 
|                 } else { | 
|                     // rgb | 
|                     result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(input); | 
|                     if (result) { | 
|                         rgba = [pInt(result[1]), pInt(result[2]), pInt(result[3]), 1]; | 
|                     } | 
|                 } | 
|             } | 
|         }         | 
|   | 
|     } | 
|     /** | 
|      * Return the color a specified format | 
|      * @param {String} format | 
|      */ | 
|     function get(format) { | 
|         var ret; | 
|   | 
|         if (stops) { | 
|             ret = merge(input); | 
|             ret.stops = [].concat(ret.stops); | 
|             each(stops, function (stop, i) { | 
|                 ret.stops[i] = [ret.stops[i][0], stop.get(format)]; | 
|             }); | 
|   | 
|         // it's NaN if gradient colors on a column chart | 
|         } else if (rgba && !isNaN(rgba[0])) { | 
|             if (format === 'rgb') { | 
|                 ret = 'rgb(' + rgba[0] + ',' + rgba[1] + ',' + rgba[2] + ')'; | 
|             } else if (format === 'a') { | 
|                 ret = rgba[3]; | 
|             } else { | 
|                 ret = 'rgba(' + rgba.join(',') + ')'; | 
|             } | 
|         } else { | 
|             ret = input; | 
|         } | 
|         return ret; | 
|     } | 
|   | 
|     /** | 
|      * Brighten the color | 
|      * @param {Number} alpha | 
|      */ | 
|     function brighten(alpha) { | 
|         if (stops) { | 
|             each(stops, function (stop) { | 
|                 stop.brighten(alpha); | 
|             }); | 
|          | 
|         } else if (isNumber(alpha) && alpha !== 0) { | 
|             var i; | 
|             for (i = 0; i < 3; i++) { | 
|                 rgba[i] += pInt(alpha * 255); | 
|   | 
|                 if (rgba[i] < 0) { | 
|                     rgba[i] = 0; | 
|                 } | 
|                 if (rgba[i] > 255) { | 
|                     rgba[i] = 255; | 
|                 } | 
|             } | 
|         } | 
|         return this; | 
|     } | 
|     /** | 
|      * Set the color's opacity to a given alpha value | 
|      * @param {Number} alpha | 
|      */ | 
|     function setOpacity(alpha) { | 
|         rgba[3] = alpha; | 
|         return this; | 
|     } | 
|   | 
|     // initialize: parse the input | 
|     init(input); | 
|   | 
|     // public methods | 
|     return { | 
|         get: get, | 
|         brighten: brighten, | 
|         rgba: rgba, | 
|         setOpacity: setOpacity | 
|     }; | 
| }; | 
|   | 
|   | 
| /** | 
|  * A wrapper object for SVG elements | 
|  */ | 
| function SVGElement() {} | 
|   | 
| SVGElement.prototype = { | 
|     /** | 
|      * Initialize the SVG renderer | 
|      * @param {Object} renderer | 
|      * @param {String} nodeName | 
|      */ | 
|     init: function (renderer, nodeName) { | 
|         var wrapper = this; | 
|         wrapper.element = nodeName === 'span' ? | 
|             createElement(nodeName) : | 
|             doc.createElementNS(SVG_NS, nodeName); | 
|         wrapper.renderer = renderer; | 
|         /** | 
|          * A collection of attribute setters. These methods, if defined, are called right before a certain | 
|          * attribute is set on an element wrapper. Returning false prevents the default attribute | 
|          * setter to run. Returning a value causes the default setter to set that value. Used in | 
|          * Renderer.label. | 
|          */ | 
|         wrapper.attrSetters = {}; | 
|     }, | 
|     /** | 
|      * Default base for animation | 
|      */ | 
|     opacity: 1, | 
|     /** | 
|      * Animate a given attribute | 
|      * @param {Object} params | 
|      * @param {Number} options The same options as in jQuery animation | 
|      * @param {Function} complete Function to perform at the end of animation | 
|      */ | 
|     animate: function (params, options, complete) { | 
|         var animOptions = pick(options, globalAnimation, true); | 
|         stop(this); // stop regardless of animation actually running, or reverting to .attr (#607) | 
|         if (animOptions) { | 
|             animOptions = merge(animOptions); | 
|             if (complete) { // allows using a callback with the global animation without overwriting it | 
|                 animOptions.complete = complete; | 
|             } | 
|             animate(this, params, animOptions); | 
|         } else { | 
|             this.attr(params); | 
|             if (complete) { | 
|                 complete(); | 
|             } | 
|         } | 
|     }, | 
|     /** | 
|      * Set or get a given attribute | 
|      * @param {Object|String} hash | 
|      * @param {Mixed|Undefined} val | 
|      */ | 
|     attr: function (hash, val) { | 
|         var wrapper = this, | 
|             key, | 
|             value, | 
|             result, | 
|             i, | 
|             child, | 
|             element = wrapper.element, | 
|             nodeName = element.nodeName.toLowerCase(), // Android2 requires lower for "text" | 
|             renderer = wrapper.renderer, | 
|             skipAttr, | 
|             titleNode, | 
|             attrSetters = wrapper.attrSetters, | 
|             shadows = wrapper.shadows, | 
|             hasSetSymbolSize, | 
|             doTransform, | 
|             ret = wrapper; | 
|   | 
|         // single key-value pair | 
|         if (isString(hash) && defined(val)) { | 
|             key = hash; | 
|             hash = {}; | 
|             hash[key] = val; | 
|         } | 
|   | 
|         // used as a getter: first argument is a string, second is undefined | 
|         if (isString(hash)) { | 
|             key = hash; | 
|             if (nodeName === 'circle') { | 
|                 key = { x: 'cx', y: 'cy' }[key] || key; | 
|             } else if (key === 'strokeWidth') { | 
|                 key = 'stroke-width'; | 
|             } | 
|             ret = attr(element, key) || wrapper[key] || 0; | 
|             if (key !== 'd' && key !== 'visibility' && key !== 'fill') { // 'd' is string in animation step | 
|                 ret = parseFloat(ret); | 
|             } | 
|   | 
|         // setter | 
|         } else { | 
|   | 
|             for (key in hash) { | 
|                 skipAttr = false; // reset | 
|                 value = hash[key]; | 
|   | 
|                 // check for a specific attribute setter | 
|                 result = attrSetters[key] && attrSetters[key].call(wrapper, value, key); | 
|   | 
|                 if (result !== false) { | 
|                     if (result !== UNDEFINED) { | 
|                         value = result; // the attribute setter has returned a new value to set | 
|                     } | 
|   | 
|   | 
|                     // paths | 
|                     if (key === 'd') { | 
|                         if (value && value.join) { // join path | 
|                             value = value.join(' '); | 
|                         } | 
|                         if (/(NaN| {2}|^$)/.test(value)) { | 
|                             value = 'M 0 0'; | 
|                         } | 
|                         //wrapper.d = value; // shortcut for animations | 
|   | 
|                     // update child tspans x values | 
|                     } else if (key === 'x' && nodeName === 'text') { | 
|                         for (i = 0; i < element.childNodes.length; i++) { | 
|                             child = element.childNodes[i]; | 
|                             // if the x values are equal, the tspan represents a linebreak | 
|                             if (attr(child, 'x') === attr(element, 'x')) { | 
|                                 //child.setAttribute('x', value); | 
|                                 attr(child, 'x', value); | 
|                             } | 
|                         } | 
|   | 
|                     } else if (wrapper.rotation && (key === 'x' || key === 'y')) { | 
|                         doTransform = true; | 
|   | 
|                     // apply gradients | 
|                     } else if (key === 'fill') { | 
|                         value = renderer.color(value, element, key); | 
|   | 
|                     // circle x and y | 
|                     } else if (nodeName === 'circle' && (key === 'x' || key === 'y')) { | 
|                         key = { x: 'cx', y: 'cy' }[key] || key; | 
|   | 
|                     // rectangle border radius | 
|                     } else if (nodeName === 'rect' && key === 'r') { | 
|                         attr(element, { | 
|                             rx: value, | 
|                             ry: value | 
|                         }); | 
|                         skipAttr = true; | 
|   | 
|                     // translation and text rotation | 
|                     } else if (key === 'translateX' || key === 'translateY' || key === 'rotation' || | 
|                             key === 'verticalAlign' || key === 'scaleX' || key === 'scaleY') { | 
|                         doTransform = true; | 
|                         skipAttr = true; | 
|   | 
|                     // apply opacity as subnode (required by legacy WebKit and Batik) | 
|                     } else if (key === 'stroke') { | 
|                         value = renderer.color(value, element, key); | 
|   | 
|                     // emulate VML's dashstyle implementation | 
|                     } else if (key === 'dashstyle') { | 
|                         key = 'stroke-dasharray'; | 
|                         value = value && value.toLowerCase(); | 
|                         if (value === 'solid') { | 
|                             value = NONE; | 
|                         } else if (value) { | 
|                             value = value | 
|                                 .replace('shortdashdotdot', '3,1,1,1,1,1,') | 
|                                 .replace('shortdashdot', '3,1,1,1') | 
|                                 .replace('shortdot', '1,1,') | 
|                                 .replace('shortdash', '3,1,') | 
|                                 .replace('longdash', '8,3,') | 
|                                 .replace(/dot/g, '1,3,') | 
|                                 .replace('dash', '4,3,') | 
|                                 .replace(/,$/, '') | 
|                                 .split(','); // ending comma | 
|   | 
|                             i = value.length; | 
|                             while (i--) { | 
|                                 value[i] = pInt(value[i]) * pick(hash['stroke-width'], wrapper['stroke-width']); | 
|                             } | 
|                             value = value.join(','); | 
|                         } | 
|   | 
|                     // IE9/MooTools combo: MooTools returns objects instead of numbers and IE9 Beta 2 | 
|                     // is unable to cast them. Test again with final IE9. | 
|                     } else if (key === 'width') { | 
|                         value = pInt(value); | 
|   | 
|                     // Text alignment | 
|                     } else if (key === 'align') { | 
|                         key = 'text-anchor'; | 
|                         value = { left: 'start', center: 'middle', right: 'end' }[value]; | 
|   | 
|                     // Title requires a subnode, #431 | 
|                     } else if (key === 'title') { | 
|                         titleNode = element.getElementsByTagName('title')[0]; | 
|                         if (!titleNode) { | 
|                             titleNode = doc.createElementNS(SVG_NS, 'title'); | 
|                             element.appendChild(titleNode); | 
|                         } | 
|                         titleNode.textContent = value; | 
|                     } | 
|   | 
|                     // jQuery animate changes case | 
|                     if (key === 'strokeWidth') { | 
|                         key = 'stroke-width'; | 
|                     } | 
|   | 
|                     // In Chrome/Win < 6 as well as Batik, the stroke attribute can't be set when the stroke- | 
|                     // width is 0. #1369 | 
|                     if (key === 'stroke-width' || key === 'stroke') { | 
|                         wrapper[key] = value; | 
|                         // Only apply the stroke attribute if the stroke width is defined and larger than 0 | 
|                         if (wrapper.stroke && wrapper['stroke-width']) { | 
|                             attr(element, 'stroke', wrapper.stroke); | 
|                             attr(element, 'stroke-width', wrapper['stroke-width']); | 
|                             wrapper.hasStroke = true; | 
|                         } else if (key === 'stroke-width' && value === 0 && wrapper.hasStroke) { | 
|                             element.removeAttribute('stroke'); | 
|                             wrapper.hasStroke = false; | 
|                         } | 
|                         skipAttr = true; | 
|                     } | 
|   | 
|                     // symbols | 
|                     if (wrapper.symbolName && /^(x|y|width|height|r|start|end|innerR|anchorX|anchorY)/.test(key)) { | 
|   | 
|   | 
|                         if (!hasSetSymbolSize) { | 
|                             wrapper.symbolAttr(hash); | 
|                             hasSetSymbolSize = true; | 
|                         } | 
|                         skipAttr = true; | 
|                     } | 
|   | 
|                     // let the shadow follow the main element | 
|                     if (shadows && /^(width|height|visibility|x|y|d|transform|cx|cy|r)$/.test(key)) { | 
|                         i = shadows.length; | 
|                         while (i--) { | 
|                             attr( | 
|                                 shadows[i], | 
|                                 key, | 
|                                 key === 'height' ? | 
|                                     mathMax(value - (shadows[i].cutHeight || 0), 0) : | 
|                                     value | 
|                             ); | 
|                         } | 
|                     } | 
|   | 
|                     // validate heights | 
|                     if ((key === 'width' || key === 'height') && nodeName === 'rect' && value < 0) { | 
|                         value = 0; | 
|                     } | 
|   | 
|                     // Record for animation and quick access without polling the DOM | 
|                     wrapper[key] = value; | 
|   | 
|   | 
|                     if (key === 'text') { | 
|                         // Delete bBox memo when the text changes | 
|                         if (value !== wrapper.textStr) { | 
|                             delete wrapper.bBox; | 
|                         } | 
|                         wrapper.textStr = value; | 
|                         if (wrapper.added) { | 
|                             renderer.buildText(wrapper); | 
|                         } | 
|                     } else if (!skipAttr) { | 
|                         attr(element, key, value); | 
|                     } | 
|   | 
|                 } | 
|   | 
|             } | 
|   | 
|             // Update transform. Do this outside the loop to prevent redundant updating for batch setting | 
|             // of attributes. | 
|             if (doTransform) { | 
|                 wrapper.updateTransform(); | 
|             } | 
|   | 
|         } | 
|   | 
|         return ret; | 
|     }, | 
|   | 
|   | 
|     /** | 
|      * Add a class name to an element | 
|      */ | 
|     addClass: function (className) { | 
|         var element = this.element, | 
|             currentClassName = attr(element, 'class') || ''; | 
|   | 
|         if (currentClassName.indexOf(className) === -1) { | 
|             attr(element, 'class', currentClassName + ' ' + className); | 
|         } | 
|         return this; | 
|     }, | 
|     /* hasClass and removeClass are not (yet) needed | 
|     hasClass: function (className) { | 
|         return attr(this.element, 'class').indexOf(className) !== -1; | 
|     }, | 
|     removeClass: function (className) { | 
|         attr(this.element, 'class', attr(this.element, 'class').replace(className, '')); | 
|         return this; | 
|     }, | 
|     */ | 
|   | 
|     /** | 
|      * If one of the symbol size affecting parameters are changed, | 
|      * check all the others only once for each call to an element's | 
|      * .attr() method | 
|      * @param {Object} hash | 
|      */ | 
|     symbolAttr: function (hash) { | 
|         var wrapper = this; | 
|   | 
|         each(['x', 'y', 'r', 'start', 'end', 'width', 'height', 'innerR', 'anchorX', 'anchorY'], function (key) { | 
|             wrapper[key] = pick(hash[key], wrapper[key]); | 
|         }); | 
|   | 
|         wrapper.attr({ | 
|             d: wrapper.renderer.symbols[wrapper.symbolName]( | 
|                 wrapper.x, | 
|                 wrapper.y, | 
|                 wrapper.width, | 
|                 wrapper.height, | 
|                 wrapper | 
|             ) | 
|         }); | 
|     }, | 
|   | 
|     /** | 
|      * Apply a clipping path to this object | 
|      * @param {String} id | 
|      */ | 
|     clip: function (clipRect) { | 
|         return this.attr('clip-path', clipRect ? 'url(' + this.renderer.url + '#' + clipRect.id + ')' : NONE); | 
|     }, | 
|   | 
|     /** | 
|      * Calculate the coordinates needed for drawing a rectangle crisply and return the | 
|      * calculated attributes | 
|      * @param {Number} strokeWidth | 
|      * @param {Number} x | 
|      * @param {Number} y | 
|      * @param {Number} width | 
|      * @param {Number} height | 
|      */ | 
|     crisp: function (strokeWidth, x, y, width, height) { | 
|   | 
|         var wrapper = this, | 
|             key, | 
|             attribs = {}, | 
|             values = {}, | 
|             normalizer; | 
|   | 
|         strokeWidth = strokeWidth || wrapper.strokeWidth || (wrapper.attr && wrapper.attr('stroke-width')) || 0; | 
|         normalizer = mathRound(strokeWidth) % 2 / 2; // mathRound because strokeWidth can sometimes have roundoff errors | 
|   | 
|         // normalize for crisp edges | 
|         values.x = mathFloor(x || wrapper.x || 0) + normalizer; | 
|         values.y = mathFloor(y || wrapper.y || 0) + normalizer; | 
|         values.width = mathFloor((width || wrapper.width || 0) - 2 * normalizer); | 
|         values.height = mathFloor((height || wrapper.height || 0) - 2 * normalizer); | 
|         values.strokeWidth = strokeWidth; | 
|   | 
|         for (key in values) { | 
|             if (wrapper[key] !== values[key]) { // only set attribute if changed | 
|                 wrapper[key] = attribs[key] = values[key]; | 
|             } | 
|         } | 
|   | 
|         return attribs; | 
|     }, | 
|   | 
|     /** | 
|      * Set styles for the element | 
|      * @param {Object} styles | 
|      */ | 
|     css: function (styles) { | 
|         /*jslint unparam: true*//* allow unused param a in the regexp function below */ | 
|         var elemWrapper = this, | 
|             elem = elemWrapper.element, | 
|             textWidth = styles && styles.width && elem.nodeName.toLowerCase() === 'text', | 
|             n, | 
|             serializedCss = '', | 
|             hyphenate = function (a, b) { return '-' + b.toLowerCase(); }; | 
|         /*jslint unparam: false*/ | 
|   | 
|         // convert legacy | 
|         if (styles && styles.color) { | 
|             styles.fill = styles.color; | 
|         } | 
|   | 
|         // Merge the new styles with the old ones | 
|         styles = extend( | 
|             elemWrapper.styles, | 
|             styles | 
|         ); | 
|   | 
|         // store object | 
|         elemWrapper.styles = styles; | 
|   | 
|   | 
|         // Don't handle line wrap on canvas | 
|         if (useCanVG && textWidth) { | 
|             delete styles.width; | 
|         } | 
|   | 
|         // serialize and set style attribute | 
|         if (isIE && !hasSVG) { // legacy IE doesn't support setting style attribute | 
|             if (textWidth) { | 
|                 delete styles.width; | 
|             } | 
|             css(elemWrapper.element, styles); | 
|         } else { | 
|             for (n in styles) { | 
|                 serializedCss += n.replace(/([A-Z])/g, hyphenate) + ':' + styles[n] + ';'; | 
|             } | 
|             attr(elem, 'style', serializedCss); // #1881 | 
|         } | 
|   | 
|   | 
|         // re-build text | 
|         if (textWidth && elemWrapper.added) { | 
|             elemWrapper.renderer.buildText(elemWrapper); | 
|         } | 
|   | 
|         return elemWrapper; | 
|     }, | 
|   | 
|     /** | 
|      * Add an event listener | 
|      * @param {String} eventType | 
|      * @param {Function} handler | 
|      */ | 
|     on: function (eventType, handler) { | 
|         var svgElement = this, | 
|             element = svgElement.element; | 
|          | 
|         // touch | 
|         if (hasTouch && eventType === 'click') { | 
|             element.ontouchstart = function (e) {             | 
|                 svgElement.touchEventFired = Date.now();                 | 
|                 e.preventDefault(); | 
|                 handler.call(element, e); | 
|             }; | 
|             element.onclick = function (e) {                                                 | 
|                 if (userAgent.indexOf('Android') === -1 || Date.now() - (svgElement.touchEventFired || 0) > 1100) { // #2269 | 
|                     handler.call(element, e); | 
|                 } | 
|             };             | 
|         } else { | 
|             // simplest possible event model for internal use | 
|             element['on' + eventType] = handler; | 
|         } | 
|         return this; | 
|     }, | 
|   | 
|     /** | 
|      * Set the coordinates needed to draw a consistent radial gradient across | 
|      * pie slices regardless of positioning inside the chart. The format is | 
|      * [centerX, centerY, diameter] in pixels. | 
|      */ | 
|     setRadialReference: function (coordinates) { | 
|         this.element.radialReference = coordinates; | 
|         return this; | 
|     }, | 
|   | 
|     /** | 
|      * Move an object and its children by x and y values | 
|      * @param {Number} x | 
|      * @param {Number} y | 
|      */ | 
|     translate: function (x, y) { | 
|         return this.attr({ | 
|             translateX: x, | 
|             translateY: y | 
|         }); | 
|     }, | 
|   | 
|     /** | 
|      * Invert a group, rotate and flip | 
|      */ | 
|     invert: function () { | 
|         var wrapper = this; | 
|         wrapper.inverted = true; | 
|         wrapper.updateTransform(); | 
|         return wrapper; | 
|     }, | 
|   | 
|     /** | 
|      * Apply CSS to HTML elements. This is used in text within SVG rendering and | 
|      * by the VML renderer | 
|      */ | 
|     htmlCss: function (styles) { | 
|         var wrapper = this, | 
|             element = wrapper.element, | 
|             textWidth = styles && element.tagName === 'SPAN' && styles.width; | 
|   | 
|         if (textWidth) { | 
|             delete styles.width; | 
|             wrapper.textWidth = textWidth; | 
|             wrapper.updateTransform(); | 
|         } | 
|   | 
|         wrapper.styles = extend(wrapper.styles, styles); | 
|         css(wrapper.element, styles); | 
|   | 
|         return wrapper; | 
|     }, | 
|   | 
|   | 
|   | 
|     /** | 
|      * VML and useHTML method for calculating the bounding box based on offsets | 
|      * @param {Boolean} refresh Whether to force a fresh value from the DOM or to | 
|      * use the cached value | 
|      * | 
|      * @return {Object} A hash containing values for x, y, width and height | 
|      */ | 
|   | 
|     htmlGetBBox: function () { | 
|         var wrapper = this, | 
|             element = wrapper.element, | 
|             bBox = wrapper.bBox; | 
|   | 
|         // faking getBBox in exported SVG in legacy IE | 
|         if (!bBox) { | 
|             // faking getBBox in exported SVG in legacy IE (is this a duplicate of the fix for #1079?) | 
|             if (element.nodeName === 'text') { | 
|                 element.style.position = ABSOLUTE; | 
|             } | 
|   | 
|             bBox = wrapper.bBox = { | 
|                 x: element.offsetLeft, | 
|                 y: element.offsetTop, | 
|                 width: element.offsetWidth, | 
|                 height: element.offsetHeight | 
|             }; | 
|         } | 
|   | 
|         return bBox; | 
|     }, | 
|   | 
|     /** | 
|      * VML override private method to update elements based on internal | 
|      * properties based on SVG transform | 
|      */ | 
|     htmlUpdateTransform: function () { | 
|         // aligning non added elements is expensive | 
|         if (!this.added) { | 
|             this.alignOnAdd = true; | 
|             return; | 
|         } | 
|   | 
|         var wrapper = this, | 
|             renderer = wrapper.renderer, | 
|             elem = wrapper.element, | 
|             translateX = wrapper.translateX || 0, | 
|             translateY = wrapper.translateY || 0, | 
|             x = wrapper.x || 0, | 
|             y = wrapper.y || 0, | 
|             align = wrapper.textAlign || 'left', | 
|             alignCorrection = { left: 0, center: 0.5, right: 1 }[align], | 
|             nonLeft = align && align !== 'left', | 
|             shadows = wrapper.shadows; | 
|   | 
|         // apply translate | 
|         css(elem, { | 
|             marginLeft: translateX, | 
|             marginTop: translateY | 
|         }); | 
|         if (shadows) { // used in labels/tooltip | 
|             each(shadows, function (shadow) { | 
|                 css(shadow, { | 
|                     marginLeft: translateX + 1, | 
|                     marginTop: translateY + 1 | 
|                 }); | 
|             }); | 
|         } | 
|   | 
|         // apply inversion | 
|         if (wrapper.inverted) { // wrapper is a group | 
|             each(elem.childNodes, function (child) { | 
|                 renderer.invertChild(child, elem); | 
|             }); | 
|         } | 
|   | 
|         if (elem.tagName === 'SPAN') { | 
|   | 
|             var width, height, | 
|                 rotation = wrapper.rotation, | 
|                 baseline, | 
|                 radians = 0, | 
|                 costheta = 1, | 
|                 sintheta = 0, | 
|                 quad, | 
|                 textWidth = pInt(wrapper.textWidth), | 
|                 xCorr = wrapper.xCorr || 0, | 
|                 yCorr = wrapper.yCorr || 0, | 
|                 currentTextTransform = [rotation, align, elem.innerHTML, wrapper.textWidth].join(','); | 
|   | 
|             if (currentTextTransform !== wrapper.cTT) { // do the calculations and DOM access only if properties changed | 
|   | 
|                 if (defined(rotation)) { | 
|   | 
|                     radians = rotation * deg2rad; // deg to rad | 
|                     costheta = mathCos(radians); | 
|                     sintheta = mathSin(radians); | 
|   | 
|                     wrapper.setSpanRotation(rotation, sintheta, costheta); | 
|   | 
|                 } | 
|   | 
|                 width = pick(wrapper.elemWidth, elem.offsetWidth); | 
|                 height = pick(wrapper.elemHeight, elem.offsetHeight); | 
|   | 
|                 // update textWidth | 
|                 if (width > textWidth && /[ \-]/.test(elem.textContent || elem.innerText)) { // #983, #1254 | 
|                     css(elem, { | 
|                         width: textWidth + PX, | 
|                         display: 'block', | 
|                         whiteSpace: 'normal' | 
|                     }); | 
|                     width = textWidth; | 
|                 } | 
|   | 
|                 // correct x and y | 
|                 baseline = renderer.fontMetrics(elem.style.fontSize).b; | 
|                 xCorr = costheta < 0 && -width; | 
|                 yCorr = sintheta < 0 && -height; | 
|   | 
|                 // correct for baseline and corners spilling out after rotation | 
|                 quad = costheta * sintheta < 0; | 
|                 xCorr += sintheta * baseline * (quad ? 1 - alignCorrection : alignCorrection); | 
|                 yCorr -= costheta * baseline * (rotation ? (quad ? alignCorrection : 1 - alignCorrection) : 1); | 
|   | 
|                 // correct for the length/height of the text | 
|                 if (nonLeft) { | 
|                     xCorr -= width * alignCorrection * (costheta < 0 ? -1 : 1); | 
|                     if (rotation) { | 
|                         yCorr -= height * alignCorrection * (sintheta < 0 ? -1 : 1); | 
|                     } | 
|                     css(elem, { | 
|                         textAlign: align | 
|                     }); | 
|                 } | 
|   | 
|                 // record correction | 
|                 wrapper.xCorr = xCorr; | 
|                 wrapper.yCorr = yCorr; | 
|             } | 
|   | 
|             // apply position with correction | 
|             css(elem, { | 
|                 left: (x + xCorr) + PX, | 
|                 top: (y + yCorr) + PX | 
|             }); | 
|   | 
|             // force reflow in webkit to apply the left and top on useHTML element (#1249) | 
|             if (isWebKit) { | 
|                 height = elem.offsetHeight; // assigned to height for JSLint purpose | 
|             } | 
|   | 
|             // record current text transform | 
|             wrapper.cTT = currentTextTransform; | 
|         } | 
|     }, | 
|   | 
|     /** | 
|      * Set the rotation of an individual HTML span | 
|      */ | 
|     setSpanRotation: function (rotation) { | 
|         var rotationStyle = {}, | 
|             cssTransformKey = isIE ? '-ms-transform' : isWebKit ? '-webkit-transform' : isFirefox ? 'MozTransform' : isOpera ? '-o-transform' : ''; | 
|   | 
|         rotationStyle[cssTransformKey] = rotationStyle.transform = 'rotate(' + rotation + 'deg)'; | 
|         css(this.element, rotationStyle); | 
|     }, | 
|   | 
|     /** | 
|      * Private method to update the transform attribute based on internal | 
|      * properties | 
|      */ | 
|     updateTransform: function () { | 
|         var wrapper = this, | 
|             translateX = wrapper.translateX || 0, | 
|             translateY = wrapper.translateY || 0, | 
|             scaleX = wrapper.scaleX, | 
|             scaleY = wrapper.scaleY, | 
|             inverted = wrapper.inverted, | 
|             rotation = wrapper.rotation, | 
|             transform; | 
|   | 
|         // flipping affects translate as adjustment for flipping around the group's axis | 
|         if (inverted) { | 
|             translateX += wrapper.attr('width'); | 
|             translateY += wrapper.attr('height'); | 
|         } | 
|   | 
|         // Apply translate. Nearly all transformed elements have translation, so instead | 
|         // of checking for translate = 0, do it always (#1767, #1846). | 
|         transform = ['translate(' + translateX + ',' + translateY + ')']; | 
|   | 
|         // apply rotation | 
|         if (inverted) { | 
|             transform.push('rotate(90) scale(-1,1)'); | 
|         } else if (rotation) { // text rotation | 
|             transform.push('rotate(' + rotation + ' ' + (wrapper.x || 0) + ' ' + (wrapper.y || 0) + ')'); | 
|         } | 
|   | 
|         // apply scale | 
|         if (defined(scaleX) || defined(scaleY)) { | 
|             transform.push('scale(' + pick(scaleX, 1) + ' ' + pick(scaleY, 1) + ')'); | 
|         } | 
|   | 
|         if (transform.length) { | 
|             attr(wrapper.element, 'transform', transform.join(' ')); | 
|         } | 
|     }, | 
|     /** | 
|      * Bring the element to the front | 
|      */ | 
|     toFront: function () { | 
|         var element = this.element; | 
|         element.parentNode.appendChild(element); | 
|         return this; | 
|     }, | 
|   | 
|   | 
|     /** | 
|      * Break down alignment options like align, verticalAlign, x and y | 
|      * to x and y relative to the chart. | 
|      * | 
|      * @param {Object} alignOptions | 
|      * @param {Boolean} alignByTranslate | 
|      * @param {String[Object} box The box to align to, needs a width and height. When the | 
|      *        box is a string, it refers to an object in the Renderer. For example, when | 
|      *        box is 'spacingBox', it refers to Renderer.spacingBox which holds width, height | 
|      *        x and y properties. | 
|      * | 
|      */ | 
|     align: function (alignOptions, alignByTranslate, box) { | 
|         var align, | 
|             vAlign, | 
|             x, | 
|             y, | 
|             attribs = {}, | 
|             alignTo, | 
|             renderer = this.renderer, | 
|             alignedObjects = renderer.alignedObjects; | 
|   | 
|         // First call on instanciate | 
|         if (alignOptions) { | 
|             this.alignOptions = alignOptions; | 
|             this.alignByTranslate = alignByTranslate; | 
|             if (!box || isString(box)) { // boxes other than renderer handle this internally | 
|                 this.alignTo = alignTo = box || 'renderer'; | 
|                 erase(alignedObjects, this); // prevent duplicates, like legendGroup after resize | 
|                 alignedObjects.push(this); | 
|                 box = null; // reassign it below | 
|             } | 
|   | 
|         // When called on resize, no arguments are supplied | 
|         } else { | 
|             alignOptions = this.alignOptions; | 
|             alignByTranslate = this.alignByTranslate; | 
|             alignTo = this.alignTo; | 
|         } | 
|   | 
|         box = pick(box, renderer[alignTo], renderer); | 
|   | 
|         // Assign variables | 
|         align = alignOptions.align; | 
|         vAlign = alignOptions.verticalAlign; | 
|         x = (box.x || 0) + (alignOptions.x || 0); // default: left align | 
|         y = (box.y || 0) + (alignOptions.y || 0); // default: top align | 
|   | 
|         // Align | 
|         if (align === 'right' || align === 'center') { | 
|             x += (box.width - (alignOptions.width || 0)) / | 
|                     { right: 1, center: 2 }[align]; | 
|         } | 
|         attribs[alignByTranslate ? 'translateX' : 'x'] = mathRound(x); | 
|   | 
|   | 
|         // Vertical align | 
|         if (vAlign === 'bottom' || vAlign === 'middle') { | 
|             y += (box.height - (alignOptions.height || 0)) / | 
|                     ({ bottom: 1, middle: 2 }[vAlign] || 1); | 
|   | 
|         } | 
|         attribs[alignByTranslate ? 'translateY' : 'y'] = mathRound(y); | 
|   | 
|         // Animate only if already placed | 
|         this[this.placed ? 'animate' : 'attr'](attribs); | 
|         this.placed = true; | 
|         this.alignAttr = attribs; | 
|   | 
|         return this; | 
|     }, | 
|   | 
|     /** | 
|      * Get the bounding box (width, height, x and y) for the element | 
|      */ | 
|     getBBox: function () { | 
|         var wrapper = this, | 
|             bBox = wrapper.bBox, | 
|             renderer = wrapper.renderer, | 
|             width, | 
|             height, | 
|             rotation = wrapper.rotation, | 
|             element = wrapper.element, | 
|             styles = wrapper.styles, | 
|             rad = rotation * deg2rad; | 
|   | 
|         if (!bBox) { | 
|             // SVG elements | 
|             if (element.namespaceURI === SVG_NS || renderer.forExport) { | 
|                 try { // Fails in Firefox if the container has display: none. | 
|   | 
|                     bBox = element.getBBox ? | 
|                         // SVG: use extend because IE9 is not allowed to change width and height in case | 
|                         // of rotation (below) | 
|                         extend({}, element.getBBox()) : | 
|                         // Canvas renderer and legacy IE in export mode | 
|                         { | 
|                             width: element.offsetWidth, | 
|                             height: element.offsetHeight | 
|                         }; | 
|                 } catch (e) {} | 
|   | 
|                 // If the bBox is not set, the try-catch block above failed. The other condition | 
|                 // is for Opera that returns a width of -Infinity on hidden elements. | 
|                 if (!bBox || bBox.width < 0) { | 
|                     bBox = { width: 0, height: 0 }; | 
|                 } | 
|   | 
|   | 
|             // VML Renderer or useHTML within SVG | 
|             } else { | 
|   | 
|                 bBox = wrapper.htmlGetBBox(); | 
|   | 
|             } | 
|   | 
|             // True SVG elements as well as HTML elements in modern browsers using the .useHTML option | 
|             // need to compensated for rotation | 
|             if (renderer.isSVG) { | 
|                 width = bBox.width; | 
|                 height = bBox.height; | 
|   | 
|                 // Workaround for wrong bounding box in IE9 and IE10 (#1101, #1505, #1669) | 
|                 if (isIE && styles && styles.fontSize === '11px' && height.toPrecision(3) === '22.7') { | 
|                     bBox.height = height = 14; | 
|                 } | 
|   | 
|                 // Adjust for rotated text | 
|                 if (rotation) { | 
|                     bBox.width = mathAbs(height * mathSin(rad)) + mathAbs(width * mathCos(rad)); | 
|                     bBox.height = mathAbs(height * mathCos(rad)) + mathAbs(width * mathSin(rad)); | 
|                 } | 
|             } | 
|   | 
|             wrapper.bBox = bBox; | 
|         } | 
|         return bBox; | 
|     }, | 
|   | 
|     /** | 
|      * Show the element | 
|      */ | 
|     show: function () { | 
|         return this.attr({ visibility: VISIBLE }); | 
|     }, | 
|   | 
|     /** | 
|      * Hide the element | 
|      */ | 
|     hide: function () { | 
|         return this.attr({ visibility: HIDDEN }); | 
|     }, | 
|   | 
|     fadeOut: function (duration) { | 
|         var elemWrapper = this; | 
|         elemWrapper.animate({ | 
|             opacity: 0 | 
|         }, { | 
|             duration: duration || 150, | 
|             complete: function () { | 
|                 elemWrapper.hide(); | 
|             } | 
|         }); | 
|     }, | 
|   | 
|     /** | 
|      * Add the element | 
|      * @param {Object|Undefined} parent Can be an element, an element wrapper or undefined | 
|      *    to append the element to the renderer.box. | 
|      */ | 
|     add: function (parent) { | 
|   | 
|         var renderer = this.renderer, | 
|             parentWrapper = parent || renderer, | 
|             parentNode = parentWrapper.element || renderer.box, | 
|             childNodes = parentNode.childNodes, | 
|             element = this.element, | 
|             zIndex = attr(element, 'zIndex'), | 
|             otherElement, | 
|             otherZIndex, | 
|             i, | 
|             inserted; | 
|   | 
|         if (parent) { | 
|             this.parentGroup = parent; | 
|         } | 
|   | 
|         // mark as inverted | 
|         this.parentInverted = parent && parent.inverted; | 
|   | 
|         // build formatted text | 
|         if (this.textStr !== undefined) { | 
|             renderer.buildText(this); | 
|         } | 
|   | 
|         // mark the container as having z indexed children | 
|         if (zIndex) { | 
|             parentWrapper.handleZ = true; | 
|             zIndex = pInt(zIndex); | 
|         } | 
|   | 
|         // insert according to this and other elements' zIndex | 
|         if (parentWrapper.handleZ) { // this element or any of its siblings has a z index | 
|             for (i = 0; i < childNodes.length; i++) { | 
|                 otherElement = childNodes[i]; | 
|                 otherZIndex = attr(otherElement, 'zIndex'); | 
|                 if (otherElement !== element && ( | 
|                         // insert before the first element with a higher zIndex | 
|                         pInt(otherZIndex) > zIndex || | 
|                         // if no zIndex given, insert before the first element with a zIndex | 
|                         (!defined(zIndex) && defined(otherZIndex)) | 
|   | 
|                         )) { | 
|                     parentNode.insertBefore(element, otherElement); | 
|                     inserted = true; | 
|                     break; | 
|                 } | 
|             } | 
|         } | 
|   | 
|         // default: append at the end | 
|         if (!inserted) { | 
|             parentNode.appendChild(element); | 
|         } | 
|   | 
|         // mark as added | 
|         this.added = true; | 
|   | 
|         // fire an event for internal hooks | 
|         fireEvent(this, 'add'); | 
|   | 
|         return this; | 
|     }, | 
|   | 
|     /** | 
|      * Removes a child either by removeChild or move to garbageBin. | 
|      * Issue 490; in VML removeChild results in Orphaned nodes according to sIEve, discardElement does not. | 
|      */ | 
|     safeRemoveChild: function (element) { | 
|         var parentNode = element.parentNode; | 
|         if (parentNode) { | 
|             parentNode.removeChild(element); | 
|         } | 
|     }, | 
|   | 
|     /** | 
|      * Destroy the element and element wrapper | 
|      */ | 
|     destroy: function () { | 
|         var wrapper = this, | 
|             element = wrapper.element || {}, | 
|             shadows = wrapper.shadows, | 
|             parentToClean = wrapper.renderer.isSVG && element.nodeName === 'SPAN' && element.parentNode, | 
|             grandParent, | 
|             key, | 
|             i; | 
|   | 
|         // remove events | 
|         element.onclick = element.onmouseout = element.onmouseover = element.onmousemove = element.point = null; | 
|         stop(wrapper); // stop running animations | 
|   | 
|         if (wrapper.clipPath) { | 
|             wrapper.clipPath = wrapper.clipPath.destroy(); | 
|         } | 
|   | 
|         // Destroy stops in case this is a gradient object | 
|         if (wrapper.stops) { | 
|             for (i = 0; i < wrapper.stops.length; i++) { | 
|                 wrapper.stops[i] = wrapper.stops[i].destroy(); | 
|             } | 
|             wrapper.stops = null; | 
|         } | 
|   | 
|         // remove element | 
|         wrapper.safeRemoveChild(element); | 
|   | 
|         // destroy shadows | 
|         if (shadows) { | 
|             each(shadows, function (shadow) { | 
|                 wrapper.safeRemoveChild(shadow); | 
|             }); | 
|         } | 
|   | 
|         // In case of useHTML, clean up empty containers emulating SVG groups (#1960). | 
|         while (parentToClean && parentToClean.childNodes.length === 0) { | 
|             grandParent = parentToClean.parentNode; | 
|             wrapper.safeRemoveChild(parentToClean); | 
|             parentToClean = grandParent; | 
|         } | 
|   | 
|         // remove from alignObjects | 
|         if (wrapper.alignTo) { | 
|             erase(wrapper.renderer.alignedObjects, wrapper); | 
|         } | 
|   | 
|         for (key in wrapper) { | 
|             delete wrapper[key]; | 
|         } | 
|   | 
|         return null; | 
|     }, | 
|   | 
|     /** | 
|      * Add a shadow to the element. Must be done after the element is added to the DOM | 
|      * @param {Boolean|Object} shadowOptions | 
|      */ | 
|     shadow: function (shadowOptions, group, cutOff) { | 
|         var shadows = [], | 
|             i, | 
|             shadow, | 
|             element = this.element, | 
|             strokeWidth, | 
|             shadowWidth, | 
|             shadowElementOpacity, | 
|   | 
|             // compensate for inverted plot area | 
|             transform; | 
|   | 
|   | 
|         if (shadowOptions) { | 
|             shadowWidth = pick(shadowOptions.width, 3); | 
|             shadowElementOpacity = (shadowOptions.opacity || 0.15) / shadowWidth; | 
|             transform = this.parentInverted ? | 
|                 '(-1,-1)' : | 
|                 '(' + pick(shadowOptions.offsetX, 1) + ', ' + pick(shadowOptions.offsetY, 1) + ')'; | 
|             for (i = 1; i <= shadowWidth; i++) { | 
|                 shadow = element.cloneNode(0); | 
|                 strokeWidth = (shadowWidth * 2) + 1 - (2 * i); | 
|                 attr(shadow, { | 
|                     'isShadow': 'true', | 
|                     'stroke': shadowOptions.color || 'black', | 
|                     'stroke-opacity': shadowElementOpacity * i, | 
|                     'stroke-width': strokeWidth, | 
|                     'transform': 'translate' + transform, | 
|                     'fill': NONE | 
|                 }); | 
|                 if (cutOff) { | 
|                     attr(shadow, 'height', mathMax(attr(shadow, 'height') - strokeWidth, 0)); | 
|                     shadow.cutHeight = strokeWidth; | 
|                 } | 
|   | 
|                 if (group) { | 
|                     group.element.appendChild(shadow); | 
|                 } else { | 
|                     element.parentNode.insertBefore(shadow, element); | 
|                 } | 
|   | 
|                 shadows.push(shadow); | 
|             } | 
|   | 
|             this.shadows = shadows; | 
|         } | 
|         return this; | 
|   | 
|     } | 
| }; | 
|   | 
|   | 
| /** | 
|  * The default SVG renderer | 
|  */ | 
| var SVGRenderer = function () { | 
|     this.init.apply(this, arguments); | 
| }; | 
| SVGRenderer.prototype = { | 
|     Element: SVGElement, | 
|   | 
|     /** | 
|      * Initialize the SVGRenderer | 
|      * @param {Object} container | 
|      * @param {Number} width | 
|      * @param {Number} height | 
|      * @param {Boolean} forExport | 
|      */ | 
|     init: function (container, width, height, forExport) { | 
|         var renderer = this, | 
|             loc = location, | 
|             boxWrapper, | 
|             element, | 
|             desc; | 
|   | 
|         boxWrapper = renderer.createElement('svg') | 
|             .attr({ | 
|                 version: '1.1' | 
|             }); | 
|         element = boxWrapper.element; | 
|         container.appendChild(element); | 
|   | 
|         // For browsers other than IE, add the namespace attribute (#1978) | 
|         if (container.innerHTML.indexOf('xmlns') === -1) { | 
|             attr(element, 'xmlns', SVG_NS); | 
|         } | 
|   | 
|         // object properties | 
|         renderer.isSVG = true; | 
|         renderer.box = element; | 
|         renderer.boxWrapper = boxWrapper; | 
|         renderer.alignedObjects = []; | 
|   | 
|         // Page url used for internal references. #24, #672, #1070 | 
|         renderer.url = (isFirefox || isWebKit) && doc.getElementsByTagName('base').length ? | 
|             loc.href | 
|                 .replace(/#.*?$/, '') // remove the hash | 
|                 .replace(/([\('\)])/g, '\\$1') // escape parantheses and quotes | 
|                 .replace(/ /g, '%20') : // replace spaces (needed for Safari only) | 
|             ''; | 
|   | 
|         // Add description | 
|         desc = this.createElement('desc').add(); | 
|         desc.element.appendChild(doc.createTextNode('Created with ' + PRODUCT + ' ' + VERSION)); | 
|   | 
|   | 
|         renderer.defs = this.createElement('defs').add(); | 
|         renderer.forExport = forExport; | 
|         renderer.gradients = {}; // Object where gradient SvgElements are stored | 
|   | 
|         renderer.setSize(width, height, false); | 
|   | 
|   | 
|   | 
|         // Issue 110 workaround: | 
|         // In Firefox, if a div is positioned by percentage, its pixel position may land | 
|         // between pixels. The container itself doesn't display this, but an SVG element | 
|         // inside this container will be drawn at subpixel precision. In order to draw | 
|         // sharp lines, this must be compensated for. This doesn't seem to work inside | 
|         // iframes though (like in jsFiddle). | 
|         var subPixelFix, rect; | 
|         if (isFirefox && container.getBoundingClientRect) { | 
|             renderer.subPixelFix = subPixelFix = function () { | 
|                 css(container, { left: 0, top: 0 }); | 
|                 rect = container.getBoundingClientRect(); | 
|                 css(container, { | 
|                     left: (mathCeil(rect.left) - rect.left) + PX, | 
|                     top: (mathCeil(rect.top) - rect.top) + PX | 
|                 }); | 
|             }; | 
|   | 
|             // run the fix now | 
|             subPixelFix(); | 
|   | 
|             // run it on resize | 
|             addEvent(win, 'resize', subPixelFix); | 
|         } | 
|     }, | 
|   | 
|     /** | 
|      * Detect whether the renderer is hidden. This happens when one of the parent elements | 
|      * has display: none. #608. | 
|      */ | 
|     isHidden: function () { | 
|         return !this.boxWrapper.getBBox().width; | 
|     }, | 
|   | 
|     /** | 
|      * Destroys the renderer and its allocated members. | 
|      */ | 
|     destroy: function () { | 
|         var renderer = this, | 
|             rendererDefs = renderer.defs; | 
|         renderer.box = null; | 
|         renderer.boxWrapper = renderer.boxWrapper.destroy(); | 
|   | 
|         // Call destroy on all gradient elements | 
|         destroyObjectProperties(renderer.gradients || {}); | 
|         renderer.gradients = null; | 
|   | 
|         // Defs are null in VMLRenderer | 
|         // Otherwise, destroy them here. | 
|         if (rendererDefs) { | 
|             renderer.defs = rendererDefs.destroy(); | 
|         } | 
|   | 
|         // Remove sub pixel fix handler | 
|         // We need to check that there is a handler, otherwise all functions that are registered for event 'resize' are removed | 
|         // See issue #982 | 
|         if (renderer.subPixelFix) { | 
|             removeEvent(win, 'resize', renderer.subPixelFix); | 
|         } | 
|   | 
|         renderer.alignedObjects = null; | 
|   | 
|         return null; | 
|     }, | 
|   | 
|     /** | 
|      * Create a wrapper for an SVG element | 
|      * @param {Object} nodeName | 
|      */ | 
|     createElement: function (nodeName) { | 
|         var wrapper = new this.Element(); | 
|         wrapper.init(this, nodeName); | 
|         return wrapper; | 
|     }, | 
|   | 
|     /** | 
|      * Dummy function for use in canvas renderer | 
|      */ | 
|     draw: function () {}, | 
|   | 
|     /** | 
|      * Parse a simple HTML string into SVG tspans | 
|      * | 
|      * @param {Object} textNode The parent text SVG node | 
|      */ | 
|     buildText: function (wrapper) { | 
|         var textNode = wrapper.element, | 
|             renderer = this, | 
|             forExport = renderer.forExport, | 
|             lines = pick(wrapper.textStr, '').toString() | 
|                 .replace(/<(b|strong)>/g, '<span style="font-weight:bold">') | 
|                 .replace(/<(i|em)>/g, '<span style="font-style:italic">') | 
|                 .replace(/<a/g, '<span') | 
|                 .replace(/<\/(b|strong|i|em|a)>/g, '</span>') | 
|                 .split(/<br.*?>/g), | 
|             childNodes = textNode.childNodes, | 
|             styleRegex = /style="([^"]+)"/, | 
|             hrefRegex = /href="(http[^"]+)"/, | 
|             parentX = attr(textNode, 'x'), | 
|             textStyles = wrapper.styles, | 
|             width = textStyles && textStyles.width && pInt(textStyles.width), | 
|             textLineHeight = textStyles && textStyles.lineHeight, | 
|             i = childNodes.length; | 
|   | 
|         /// remove old text | 
|         while (i--) { | 
|             textNode.removeChild(childNodes[i]); | 
|         } | 
|   | 
|         if (width && !wrapper.added) { | 
|             this.box.appendChild(textNode); // attach it to the DOM to read offset width | 
|         } | 
|   | 
|         // remove empty line at end | 
|         if (lines[lines.length - 1] === '') { | 
|             lines.pop(); | 
|         } | 
|   | 
|         // build the lines | 
|         each(lines, function (line, lineNo) { | 
|             var spans, spanNo = 0; | 
|   | 
|             line = line.replace(/<span/g, '|||<span').replace(/<\/span>/g, '</span>|||'); | 
|             spans = line.split('|||'); | 
|   | 
|             each(spans, function (span) { | 
|                 if (span !== '' || spans.length === 1) { | 
|                     var attributes = {}, | 
|                         tspan = doc.createElementNS(SVG_NS, 'tspan'), | 
|                         spanStyle; // #390 | 
|                     if (styleRegex.test(span)) { | 
|                         spanStyle = span.match(styleRegex)[1].replace(/(;| |^)color([ :])/, '$1fill$2'); | 
|                         attr(tspan, 'style', spanStyle); | 
|                     } | 
|                     if (hrefRegex.test(span) && !forExport) { // Not for export - #1529 | 
|                         attr(tspan, 'onclick', 'location.href=\"' + span.match(hrefRegex)[1] + '\"'); | 
|                         css(tspan, { cursor: 'pointer' }); | 
|                     } | 
|   | 
|                     span = (span.replace(/<(.|\n)*?>/g, '') || ' ') | 
|                         .replace(/</g, '<') | 
|                         .replace(/>/g, '>'); | 
|   | 
|                     // Nested tags aren't supported, and cause crash in Safari (#1596) | 
|                     if (span !== ' ') { | 
|   | 
|                         // add the text node | 
|                         tspan.appendChild(doc.createTextNode(span)); | 
|   | 
|                         if (!spanNo) { // first span in a line, align it to the left | 
|                             attributes.x = parentX; | 
|                         } else { | 
|                             attributes.dx = 0; // #16 | 
|                         } | 
|   | 
|                         // add attributes | 
|                         attr(tspan, attributes); | 
|   | 
|                         // first span on subsequent line, add the line height | 
|                         if (!spanNo && lineNo) { | 
|   | 
|                             // allow getting the right offset height in exporting in IE | 
|                             if (!hasSVG && forExport) { | 
|                                 css(tspan, { display: 'block' }); | 
|                             } | 
|   | 
|                             // Set the line height based on the font size of either | 
|                             // the text element or the tspan element | 
|                             attr( | 
|                                 tspan, | 
|                                 'dy', | 
|                                 textLineHeight || renderer.fontMetrics( | 
|                                     /px$/.test(tspan.style.fontSize) ? | 
|                                         tspan.style.fontSize : | 
|                                         textStyles.fontSize | 
|                                 ).h, | 
|                                 // Safari 6.0.2 - too optimized for its own good (#1539) | 
|                                 // TODO: revisit this with future versions of Safari | 
|                                 isWebKit && tspan.offsetHeight | 
|                             ); | 
|                         } | 
|   | 
|                         // Append it | 
|                         textNode.appendChild(tspan); | 
|   | 
|                         spanNo++; | 
|   | 
|                         // check width and apply soft breaks | 
|                         if (width) { | 
|                             var words = span.replace(/([^\^])-/g, '$1- ').split(' '), // #1273 | 
|                                 tooLong, | 
|                                 actualWidth, | 
|                                 clipHeight = wrapper._clipHeight, | 
|                                 rest = [], | 
|                                 dy = pInt(textLineHeight || 16), | 
|                                 softLineNo = 1, | 
|                                 bBox; | 
|   | 
|                             while (words.length || rest.length) { | 
|                                 delete wrapper.bBox; // delete cache | 
|                                 bBox = wrapper.getBBox(); | 
|                                 actualWidth = bBox.width; | 
|                                 tooLong = actualWidth > width; | 
|                                 if (!tooLong || words.length === 1) { // new line needed | 
|                                     words = rest; | 
|                                     rest = []; | 
|                                     if (words.length) { | 
|                                         softLineNo++; | 
|   | 
|                                         if (clipHeight && softLineNo * dy > clipHeight) { | 
|                                             words = ['...']; | 
|                                             wrapper.attr('title', wrapper.textStr); | 
|                                         } else { | 
|   | 
|                                             tspan = doc.createElementNS(SVG_NS, 'tspan'); | 
|                                             attr(tspan, { | 
|                                                 dy: dy, | 
|                                                 x: parentX | 
|                                             }); | 
|                                             if (spanStyle) { // #390 | 
|                                                 attr(tspan, 'style', spanStyle); | 
|                                             } | 
|                                             textNode.appendChild(tspan); | 
|   | 
|                                             if (actualWidth > width) { // a single word is pressing it out | 
|                                                 width = actualWidth; | 
|                                             } | 
|                                         } | 
|                                     } | 
|                                 } else { // append to existing line tspan | 
|                                     tspan.removeChild(tspan.firstChild); | 
|                                     rest.unshift(words.pop()); | 
|                                 } | 
|                                 if (words.length) { | 
|                                     tspan.appendChild(doc.createTextNode(words.join(' ').replace(/- /g, '-'))); | 
|                                 } | 
|                             } | 
|                         } | 
|                     } | 
|                 } | 
|             }); | 
|         }); | 
|     }, | 
|   | 
|     /** | 
|      * Create a button with preset states | 
|      * @param {String} text | 
|      * @param {Number} x | 
|      * @param {Number} y | 
|      * @param {Function} callback | 
|      * @param {Object} normalState | 
|      * @param {Object} hoverState | 
|      * @param {Object} pressedState | 
|      */ | 
|     button: function (text, x, y, callback, normalState, hoverState, pressedState, disabledState) { | 
|         var label = this.label(text, x, y, null, null, null, null, null, 'button'), | 
|             curState = 0, | 
|             stateOptions, | 
|             stateStyle, | 
|             normalStyle, | 
|             hoverStyle, | 
|             pressedStyle, | 
|             disabledStyle, | 
|             STYLE = 'style', | 
|             verticalGradient = { x1: 0, y1: 0, x2: 0, y2: 1 }; | 
|   | 
|         // Normal state - prepare the attributes | 
|         normalState = merge({ | 
|             'stroke-width': 1, | 
|             stroke: '#CCCCCC', | 
|             fill: { | 
|                 linearGradient: verticalGradient, | 
|                 stops: [ | 
|                     [0, '#FEFEFE'], | 
|                     [1, '#F6F6F6'] | 
|                 ] | 
|             }, | 
|             r: 2, | 
|             padding: 5, | 
|             style: { | 
|                 color: 'black' | 
|             } | 
|         }, normalState); | 
|         normalStyle = normalState[STYLE]; | 
|         delete normalState[STYLE]; | 
|   | 
|         // Hover state | 
|         hoverState = merge(normalState, { | 
|             stroke: '#68A', | 
|             fill: { | 
|                 linearGradient: verticalGradient, | 
|                 stops: [ | 
|                     [0, '#FFF'], | 
|                     [1, '#ACF'] | 
|                 ] | 
|             } | 
|         }, hoverState); | 
|         hoverStyle = hoverState[STYLE]; | 
|         delete hoverState[STYLE]; | 
|   | 
|         // Pressed state | 
|         pressedState = merge(normalState, { | 
|             stroke: '#68A', | 
|             fill: { | 
|                 linearGradient: verticalGradient, | 
|                 stops: [ | 
|                     [0, '#9BD'], | 
|                     [1, '#CDF'] | 
|                 ] | 
|             } | 
|         }, pressedState); | 
|         pressedStyle = pressedState[STYLE]; | 
|         delete pressedState[STYLE]; | 
|   | 
|         // Disabled state | 
|         disabledState = merge(normalState, { | 
|             style: { | 
|                 color: '#CCC' | 
|             } | 
|         }, disabledState); | 
|         disabledStyle = disabledState[STYLE]; | 
|         delete disabledState[STYLE]; | 
|   | 
|         // Add the events. IE9 and IE10 need mouseover and mouseout to funciton (#667). | 
|         addEvent(label.element, isIE ? 'mouseover' : 'mouseenter', function () { | 
|             if (curState !== 3) { | 
|                 label.attr(hoverState) | 
|                     .css(hoverStyle); | 
|             } | 
|         }); | 
|         addEvent(label.element, isIE ? 'mouseout' : 'mouseleave', function () { | 
|             if (curState !== 3) { | 
|                 stateOptions = [normalState, hoverState, pressedState][curState]; | 
|                 stateStyle = [normalStyle, hoverStyle, pressedStyle][curState]; | 
|                 label.attr(stateOptions) | 
|                     .css(stateStyle); | 
|             } | 
|         }); | 
|   | 
|         label.setState = function (state) { | 
|             label.state = curState = state; | 
|             if (!state) { | 
|                 label.attr(normalState) | 
|                     .css(normalStyle); | 
|             } else if (state === 2) { | 
|                 label.attr(pressedState) | 
|                     .css(pressedStyle); | 
|             } else if (state === 3) { | 
|                 label.attr(disabledState) | 
|                     .css(disabledStyle); | 
|             } | 
|         }; | 
|   | 
|         return label | 
|             .on('click', function () { | 
|                 if (curState !== 3) { | 
|                     callback.call(label); | 
|                 } | 
|             }) | 
|             .attr(normalState) | 
|             .css(extend({ cursor: 'default' }, normalStyle)); | 
|     }, | 
|   | 
|     /** | 
|      * Make a straight line crisper by not spilling out to neighbour pixels | 
|      * @param {Array} points | 
|      * @param {Number} width | 
|      */ | 
|     crispLine: function (points, width) { | 
|         // points format: [M, 0, 0, L, 100, 0] | 
|         // normalize to a crisp line | 
|         if (points[1] === points[4]) { | 
|             // Substract due to #1129. Now bottom and left axis gridlines behave the same. | 
|             points[1] = points[4] = mathRound(points[1]) - (width % 2 / 2); | 
|         } | 
|         if (points[2] === points[5]) { | 
|             points[2] = points[5] = mathRound(points[2]) + (width % 2 / 2); | 
|         } | 
|         return points; | 
|     }, | 
|   | 
|   | 
|     /** | 
|      * Draw a path | 
|      * @param {Array} path An SVG path in array form | 
|      */ | 
|     path: function (path) { | 
|         var attr = { | 
|             fill: NONE | 
|         }; | 
|         if (isArray(path)) { | 
|             attr.d = path; | 
|         } else if (isObject(path)) { // attributes | 
|             extend(attr, path); | 
|         } | 
|         return this.createElement('path').attr(attr); | 
|     }, | 
|   | 
|     /** | 
|      * Draw and return an SVG circle | 
|      * @param {Number} x The x position | 
|      * @param {Number} y The y position | 
|      * @param {Number} r The radius | 
|      */ | 
|     circle: function (x, y, r) { | 
|         var attr = isObject(x) ? | 
|             x : | 
|             { | 
|                 x: x, | 
|                 y: y, | 
|                 r: r | 
|             }; | 
|   | 
|         return this.createElement('circle').attr(attr); | 
|     }, | 
|   | 
|     /** | 
|      * Draw and return an arc | 
|      * @param {Number} x X position | 
|      * @param {Number} y Y position | 
|      * @param {Number} r Radius | 
|      * @param {Number} innerR Inner radius like used in donut charts | 
|      * @param {Number} start Starting angle | 
|      * @param {Number} end Ending angle | 
|      */ | 
|     arc: function (x, y, r, innerR, start, end) { | 
|         var arc; | 
|   | 
|         if (isObject(x)) { | 
|             y = x.y; | 
|             r = x.r; | 
|             innerR = x.innerR; | 
|             start = x.start; | 
|             end = x.end; | 
|             x = x.x; | 
|         } | 
|   | 
|         // Arcs are defined as symbols for the ability to set | 
|         // attributes in attr and animate | 
|         arc = this.symbol('arc', x || 0, y || 0, r || 0, r || 0, { | 
|             innerR: innerR || 0, | 
|             start: start || 0, | 
|             end: end || 0 | 
|         }); | 
|         arc.r = r; // #959 | 
|         return arc; | 
|     }, | 
|   | 
|     /** | 
|      * Draw and return a rectangle | 
|      * @param {Number} x Left position | 
|      * @param {Number} y Top position | 
|      * @param {Number} width | 
|      * @param {Number} height | 
|      * @param {Number} r Border corner radius | 
|      * @param {Number} strokeWidth A stroke width can be supplied to allow crisp drawing | 
|      */ | 
|     rect: function (x, y, width, height, r, strokeWidth) { | 
|   | 
|         r = isObject(x) ? x.r : r; | 
|   | 
|         var wrapper = this.createElement('rect').attr({ | 
|                 rx: r, | 
|                 ry: r, | 
|                 fill: NONE | 
|             }); | 
|         return wrapper.attr( | 
|                 isObject(x) ? | 
|                     x : | 
|                     // do not crispify when an object is passed in (as in column charts) | 
|                     wrapper.crisp(strokeWidth, x, y, mathMax(width, 0), mathMax(height, 0)) | 
|             ); | 
|     }, | 
|   | 
|     /** | 
|      * Resize the box and re-align all aligned elements | 
|      * @param {Object} width | 
|      * @param {Object} height | 
|      * @param {Boolean} animate | 
|      * | 
|      */ | 
|     setSize: function (width, height, animate) { | 
|         var renderer = this, | 
|             alignedObjects = renderer.alignedObjects, | 
|             i = alignedObjects.length; | 
|   | 
|         renderer.width = width; | 
|         renderer.height = height; | 
|   | 
|         renderer.boxWrapper[pick(animate, true) ? 'animate' : 'attr']({ | 
|             width: width, | 
|             height: height | 
|         }); | 
|   | 
|         while (i--) { | 
|             alignedObjects[i].align(); | 
|         } | 
|     }, | 
|   | 
|     /** | 
|      * Create a group | 
|      * @param {String} name The group will be given a class name of 'highcharts-{name}'. | 
|      *     This can be used for styling and scripting. | 
|      */ | 
|     g: function (name) { | 
|         var elem = this.createElement('g'); | 
|         return defined(name) ? elem.attr({ 'class': PREFIX + name }) : elem; | 
|     }, | 
|   | 
|     /** | 
|      * Display an image | 
|      * @param {String} src | 
|      * @param {Number} x | 
|      * @param {Number} y | 
|      * @param {Number} width | 
|      * @param {Number} height | 
|      */ | 
|     image: function (src, x, y, width, height) { | 
|         var attribs = { | 
|                 preserveAspectRatio: NONE | 
|             }, | 
|             elemWrapper; | 
|   | 
|         // optional properties | 
|         if (arguments.length > 1) { | 
|             extend(attribs, { | 
|                 x: x, | 
|                 y: y, | 
|                 width: width, | 
|                 height: height | 
|             }); | 
|         } | 
|   | 
|         elemWrapper = this.createElement('image').attr(attribs); | 
|   | 
|         // set the href in the xlink namespace | 
|         if (elemWrapper.element.setAttributeNS) { | 
|             elemWrapper.element.setAttributeNS('http://www.w3.org/1999/xlink', | 
|                 'href', src); | 
|         } else { | 
|             // could be exporting in IE | 
|             // using href throws "not supported" in ie7 and under, requries regex shim to fix later | 
|             elemWrapper.element.setAttribute('hc-svg-href', src); | 
|     } | 
|   | 
|         return elemWrapper; | 
|     }, | 
|   | 
|     /** | 
|      * Draw a symbol out of pre-defined shape paths from the namespace 'symbol' object. | 
|      * | 
|      * @param {Object} symbol | 
|      * @param {Object} x | 
|      * @param {Object} y | 
|      * @param {Object} radius | 
|      * @param {Object} options | 
|      */ | 
|     symbol: function (symbol, x, y, width, height, options) { | 
|   | 
|         var obj, | 
|   | 
|             // get the symbol definition function | 
|             symbolFn = this.symbols[symbol], | 
|   | 
|             // check if there's a path defined for this symbol | 
|             path = symbolFn && symbolFn( | 
|                 mathRound(x), | 
|                 mathRound(y), | 
|                 width, | 
|                 height, | 
|                 options | 
|             ), | 
|   | 
|             imageElement, | 
|             imageRegex = /^url\((.*?)\)$/, | 
|             imageSrc, | 
|             imageSize, | 
|             centerImage; | 
|   | 
|         if (path) { | 
|   | 
|             obj = this.path(path); | 
|             // expando properties for use in animate and attr | 
|             extend(obj, { | 
|                 symbolName: symbol, | 
|                 x: x, | 
|                 y: y, | 
|                 width: width, | 
|                 height: height | 
|             }); | 
|             if (options) { | 
|                 extend(obj, options); | 
|             } | 
|   | 
|   | 
|         // image symbols | 
|         } else if (imageRegex.test(symbol)) { | 
|   | 
|             // On image load, set the size and position | 
|             centerImage = function (img, size) { | 
|                 if (img.element) { // it may be destroyed in the meantime (#1390) | 
|                     img.attr({ | 
|                         width: size[0], | 
|                         height: size[1] | 
|                     }); | 
|   | 
|                     if (!img.alignByTranslate) { // #185 | 
|                         img.translate( | 
|                             mathRound((width - size[0]) / 2), // #1378 | 
|                             mathRound((height - size[1]) / 2) | 
|                         ); | 
|                     } | 
|                 } | 
|             }; | 
|   | 
|             imageSrc = symbol.match(imageRegex)[1]; | 
|             imageSize = symbolSizes[imageSrc]; | 
|   | 
|             // Ireate the image synchronously, add attribs async | 
|             obj = this.image(imageSrc) | 
|                 .attr({ | 
|                     x: x, | 
|                     y: y | 
|                 }); | 
|             obj.isImg = true; | 
|   | 
|             if (imageSize) { | 
|                 centerImage(obj, imageSize); | 
|             } else { | 
|                 // Initialize image to be 0 size so export will still function if there's no cached sizes. | 
|                 // | 
|                 obj.attr({ width: 0, height: 0 }); | 
|   | 
|                 // Create a dummy JavaScript image to get the width and height. Due to a bug in IE < 8, | 
|                 // the created element must be assigned to a variable in order to load (#292). | 
|                 imageElement = createElement('img', { | 
|                     onload: function () { | 
|                         centerImage(obj, symbolSizes[imageSrc] = [this.width, this.height]); | 
|                     }, | 
|                     src: imageSrc | 
|                 }); | 
|             } | 
|         } | 
|   | 
|         return obj; | 
|     }, | 
|   | 
|     /** | 
|      * An extendable collection of functions for defining symbol paths. | 
|      */ | 
|     symbols: { | 
|         'circle': function (x, y, w, h) { | 
|             var cpw = 0.166 * w; | 
|             return [ | 
|                 M, x + w / 2, y, | 
|                 'C', x + w + cpw, y, x + w + cpw, y + h, x + w / 2, y + h, | 
|                 'C', x - cpw, y + h, x - cpw, y, x + w / 2, y, | 
|                 'Z' | 
|             ]; | 
|         }, | 
|   | 
|         'square': function (x, y, w, h) { | 
|             return [ | 
|                 M, x, y, | 
|                 L, x + w, y, | 
|                 x + w, y + h, | 
|                 x, y + h, | 
|                 'Z' | 
|             ]; | 
|         }, | 
|   | 
|         'triangle': function (x, y, w, h) { | 
|             return [ | 
|                 M, x + w / 2, y, | 
|                 L, x + w, y + h, | 
|                 x, y + h, | 
|                 'Z' | 
|             ]; | 
|         }, | 
|   | 
|         'triangle-down': function (x, y, w, h) { | 
|             return [ | 
|                 M, x, y, | 
|                 L, x + w, y, | 
|                 x + w / 2, y + h, | 
|                 'Z' | 
|             ]; | 
|         }, | 
|         'diamond': function (x, y, w, h) { | 
|             return [ | 
|                 M, x + w / 2, y, | 
|                 L, x + w, y + h / 2, | 
|                 x + w / 2, y + h, | 
|                 x, y + h / 2, | 
|                 'Z' | 
|             ]; | 
|         }, | 
|         'arc': function (x, y, w, h, options) { | 
|             var start = options.start, | 
|                 radius = options.r || w || h, | 
|                 end = options.end - 0.001, // to prevent cos and sin of start and end from becoming equal on 360 arcs (related: #1561) | 
|                 innerRadius = options.innerR, | 
|                 open = options.open, | 
|                 cosStart = mathCos(start), | 
|                 sinStart = mathSin(start), | 
|                 cosEnd = mathCos(end), | 
|                 sinEnd = mathSin(end), | 
|                 longArc = options.end - start < mathPI ? 0 : 1; | 
|   | 
|             return [ | 
|                 M, | 
|                 x + radius * cosStart, | 
|                 y + radius * sinStart, | 
|                 'A', // arcTo | 
|                 radius, // x radius | 
|                 radius, // y radius | 
|                 0, // slanting | 
|                 longArc, // long or short arc | 
|                 1, // clockwise | 
|                 x + radius * cosEnd, | 
|                 y + radius * sinEnd, | 
|                 open ? M : L, | 
|                 x + innerRadius * cosEnd, | 
|                 y + innerRadius * sinEnd, | 
|                 'A', // arcTo | 
|                 innerRadius, // x radius | 
|                 innerRadius, // y radius | 
|                 0, // slanting | 
|                 longArc, // long or short arc | 
|                 0, // clockwise | 
|                 x + innerRadius * cosStart, | 
|                 y + innerRadius * sinStart, | 
|   | 
|                 open ? '' : 'Z' // close | 
|             ]; | 
|         } | 
|     }, | 
|   | 
|     /** | 
|      * Define a clipping rectangle | 
|      * @param {String} id | 
|      * @param {Number} x | 
|      * @param {Number} y | 
|      * @param {Number} width | 
|      * @param {Number} height | 
|      */ | 
|     clipRect: function (x, y, width, height) { | 
|         var wrapper, | 
|             id = PREFIX + idCounter++, | 
|   | 
|             clipPath = this.createElement('clipPath').attr({ | 
|                 id: id | 
|             }).add(this.defs); | 
|   | 
|         wrapper = this.rect(x, y, width, height, 0).add(clipPath); | 
|         wrapper.id = id; | 
|         wrapper.clipPath = clipPath; | 
|   | 
|         return wrapper; | 
|     }, | 
|   | 
|   | 
|     /** | 
|      * Take a color and return it if it's a string, make it a gradient if it's a | 
|      * gradient configuration object. Prior to Highstock, an array was used to define | 
|      * a linear gradient with pixel positions relative to the SVG. In newer versions | 
|      * we change the coordinates to apply relative to the shape, using coordinates | 
|      * 0-1 within the shape. To preserve backwards compatibility, linearGradient | 
|      * in this definition is an object of x1, y1, x2 and y2. | 
|      * | 
|      * @param {Object} color The color or config object | 
|      */ | 
|     color: function (color, elem, prop) { | 
|         var renderer = this, | 
|             colorObject, | 
|             regexRgba = /^rgba/, | 
|             gradName, | 
|             gradAttr, | 
|             gradients, | 
|             gradientObject, | 
|             stops, | 
|             stopColor, | 
|             stopOpacity, | 
|             radialReference, | 
|             n, | 
|             id, | 
|             key = []; | 
|   | 
|         // Apply linear or radial gradients | 
|         if (color && color.linearGradient) { | 
|             gradName = 'linearGradient'; | 
|         } else if (color && color.radialGradient) { | 
|             gradName = 'radialGradient'; | 
|         } | 
|   | 
|         if (gradName) { | 
|             gradAttr = color[gradName]; | 
|             gradients = renderer.gradients; | 
|             stops = color.stops; | 
|             radialReference = elem.radialReference; | 
|   | 
|             // Keep < 2.2 kompatibility | 
|             if (isArray(gradAttr)) { | 
|                 color[gradName] = gradAttr = { | 
|                     x1: gradAttr[0], | 
|                     y1: gradAttr[1], | 
|                     x2: gradAttr[2], | 
|                     y2: gradAttr[3], | 
|                     gradientUnits: 'userSpaceOnUse' | 
|                 }; | 
|             } | 
|   | 
|             // Correct the radial gradient for the radial reference system | 
|             if (gradName === 'radialGradient' && radialReference && !defined(gradAttr.gradientUnits)) { | 
|                 gradAttr = merge(gradAttr, { | 
|                     cx: (radialReference[0] - radialReference[2] / 2) + gradAttr.cx * radialReference[2], | 
|                     cy: (radialReference[1] - radialReference[2] / 2) + gradAttr.cy * radialReference[2], | 
|                     r: gradAttr.r * radialReference[2], | 
|                     gradientUnits: 'userSpaceOnUse' | 
|                 }); | 
|             } | 
|   | 
|             // Build the unique key to detect whether we need to create a new element (#1282) | 
|             for (n in gradAttr) { | 
|                 if (n !== 'id') { | 
|                     key.push(n, gradAttr[n]); | 
|                 } | 
|             } | 
|             for (n in stops) { | 
|                 key.push(stops[n]); | 
|             } | 
|             key = key.join(','); | 
|   | 
|             // Check if a gradient object with the same config object is created within this renderer | 
|             if (gradients[key]) { | 
|                 id = gradients[key].id; | 
|   | 
|             } else { | 
|   | 
|                 // Set the id and create the element | 
|                 gradAttr.id = id = PREFIX + idCounter++; | 
|                 gradients[key] = gradientObject = renderer.createElement(gradName) | 
|                     .attr(gradAttr) | 
|                     .add(renderer.defs); | 
|   | 
|   | 
|                 // The gradient needs to keep a list of stops to be able to destroy them | 
|                 gradientObject.stops = []; | 
|                 each(stops, function (stop) { | 
|                     var stopObject; | 
|                     if (regexRgba.test(stop[1])) { | 
|                         colorObject = Color(stop[1]); | 
|                         stopColor = colorObject.get('rgb'); | 
|                         stopOpacity = colorObject.get('a'); | 
|                     } else { | 
|                         stopColor = stop[1]; | 
|                         stopOpacity = 1; | 
|                     } | 
|                     stopObject = renderer.createElement('stop').attr({ | 
|                         offset: stop[0], | 
|                         'stop-color': stopColor, | 
|                         'stop-opacity': stopOpacity | 
|                     }).add(gradientObject); | 
|   | 
|                     // Add the stop element to the gradient | 
|                     gradientObject.stops.push(stopObject); | 
|                 }); | 
|             } | 
|   | 
|             // Return the reference to the gradient object | 
|             return 'url(' + renderer.url + '#' + id + ')'; | 
|   | 
|         // Webkit and Batik can't show rgba. | 
|         } else if (regexRgba.test(color)) { | 
|             colorObject = Color(color); | 
|             attr(elem, prop + '-opacity', colorObject.get('a')); | 
|   | 
|             return colorObject.get('rgb'); | 
|   | 
|   | 
|         } else { | 
|             // Remove the opacity attribute added above. Does not throw if the attribute is not there. | 
|             elem.removeAttribute(prop + '-opacity'); | 
|   | 
|             return color; | 
|         } | 
|   | 
|     }, | 
|   | 
|   | 
|     /** | 
|      * Add text to the SVG object | 
|      * @param {String} str | 
|      * @param {Number} x Left position | 
|      * @param {Number} y Top position | 
|      * @param {Boolean} useHTML Use HTML to render the text | 
|      */ | 
|     text: function (str, x, y, useHTML) { | 
|   | 
|         // declare variables | 
|         var renderer = this, | 
|             defaultChartStyle = defaultOptions.chart.style, | 
|             fakeSVG = useCanVG || (!hasSVG && renderer.forExport), | 
|             wrapper; | 
|   | 
|         if (useHTML && !renderer.forExport) { | 
|             return renderer.html(str, x, y); | 
|         } | 
|   | 
|         x = mathRound(pick(x, 0)); | 
|         y = mathRound(pick(y, 0)); | 
|   | 
|         wrapper = renderer.createElement('text') | 
|             .attr({ | 
|                 x: x, | 
|                 y: y, | 
|                 text: str | 
|             }) | 
|             .css({ | 
|                 fontFamily: defaultChartStyle.fontFamily, | 
|                 fontSize: defaultChartStyle.fontSize | 
|             }); | 
|   | 
|         // Prevent wrapping from creating false offsetWidths in export in legacy IE (#1079, #1063) | 
|         if (fakeSVG) { | 
|             wrapper.css({ | 
|                 position: ABSOLUTE | 
|             }); | 
|         } | 
|   | 
|         wrapper.x = x; | 
|         wrapper.y = y; | 
|         return wrapper; | 
|     }, | 
|   | 
|   | 
|     /** | 
|      * Create HTML text node. This is used by the VML renderer as well as the SVG | 
|      * renderer through the useHTML option. | 
|      * | 
|      * @param {String} str | 
|      * @param {Number} x | 
|      * @param {Number} y | 
|      */ | 
|     html: function (str, x, y) { | 
|         var defaultChartStyle = defaultOptions.chart.style, | 
|             wrapper = this.createElement('span'), | 
|             attrSetters = wrapper.attrSetters, | 
|             element = wrapper.element, | 
|             renderer = wrapper.renderer; | 
|   | 
|         // Text setter | 
|         attrSetters.text = function (value) { | 
|             if (value !== element.innerHTML) { | 
|                 delete this.bBox; | 
|             } | 
|             element.innerHTML = value; | 
|             return false; | 
|         }; | 
|   | 
|         // Various setters which rely on update transform | 
|         attrSetters.x = attrSetters.y = attrSetters.align = function (value, key) { | 
|             if (key === 'align') { | 
|                 key = 'textAlign'; // Do not overwrite the SVGElement.align method. Same as VML. | 
|             } | 
|             wrapper[key] = value; | 
|             wrapper.htmlUpdateTransform(); | 
|             return false; | 
|         }; | 
|   | 
|         // Set the default attributes | 
|         wrapper.attr({ | 
|                 text: str, | 
|                 x: mathRound(x), | 
|                 y: mathRound(y) | 
|             }) | 
|             .css({ | 
|                 position: ABSOLUTE, | 
|                 whiteSpace: 'nowrap', | 
|                 fontFamily: defaultChartStyle.fontFamily, | 
|                 fontSize: defaultChartStyle.fontSize | 
|             }); | 
|   | 
|         // Use the HTML specific .css method | 
|         wrapper.css = wrapper.htmlCss; | 
|   | 
|         // This is specific for HTML within SVG | 
|         if (renderer.isSVG) { | 
|             wrapper.add = function (svgGroupWrapper) { | 
|   | 
|                 var htmlGroup, | 
|                     container = renderer.box.parentNode, | 
|                     parentGroup, | 
|                     parents = []; | 
|   | 
|                 // Create a mock group to hold the HTML elements | 
|                 if (svgGroupWrapper) { | 
|                     htmlGroup = svgGroupWrapper.div; | 
|                     if (!htmlGroup) { | 
|   | 
|                         // Read the parent chain into an array and read from top down | 
|                         parentGroup = svgGroupWrapper; | 
|                         while (parentGroup) { | 
|   | 
|                             parents.push(parentGroup); | 
|   | 
|                             // Move up to the next parent group | 
|                             parentGroup = parentGroup.parentGroup; | 
|                         } | 
|   | 
|                         // Ensure dynamically updating position when any parent is translated | 
|                         each(parents.reverse(), function (parentGroup) { | 
|                             var htmlGroupStyle; | 
|   | 
|                             // Create a HTML div and append it to the parent div to emulate | 
|                             // the SVG group structure | 
|                             htmlGroup = parentGroup.div = parentGroup.div || createElement(DIV, { | 
|                                 className: attr(parentGroup.element, 'class') | 
|                             }, { | 
|                                 position: ABSOLUTE, | 
|                                 left: (parentGroup.translateX || 0) + PX, | 
|                                 top: (parentGroup.translateY || 0) + PX | 
|                             }, htmlGroup || container); // the top group is appended to container | 
|   | 
|                             // Shortcut | 
|                             htmlGroupStyle = htmlGroup.style; | 
|   | 
|                             // Set listeners to update the HTML div's position whenever the SVG group | 
|                             // position is changed | 
|                             extend(parentGroup.attrSetters, { | 
|                                 translateX: function (value) { | 
|                                     htmlGroupStyle.left = value + PX; | 
|                                 }, | 
|                                 translateY: function (value) { | 
|                                     htmlGroupStyle.top = value + PX; | 
|                                 }, | 
|                                 visibility: function (value, key) { | 
|                                     htmlGroupStyle[key] = value; | 
|                                 } | 
|                             }); | 
|                         }); | 
|   | 
|                     } | 
|                 } else { | 
|                     htmlGroup = container; | 
|                 } | 
|   | 
|                 htmlGroup.appendChild(element); | 
|   | 
|                 // Shared with VML: | 
|                 wrapper.added = true; | 
|                 if (wrapper.alignOnAdd) { | 
|                     wrapper.htmlUpdateTransform(); | 
|                 } | 
|   | 
|                 return wrapper; | 
|             }; | 
|         } | 
|         return wrapper; | 
|     }, | 
|   | 
|     /** | 
|      * Utility to return the baseline offset and total line height from the font size | 
|      */ | 
|     fontMetrics: function (fontSize) { | 
|         fontSize = pInt(fontSize || 11); | 
|   | 
|         // Empirical values found by comparing font size and bounding box height. | 
|         // Applies to the default font family. http://jsfiddle.net/highcharts/7xvn7/ | 
|         var lineHeight = fontSize < 24 ? fontSize + 4 : mathRound(fontSize * 1.2), | 
|             baseline = mathRound(lineHeight * 0.8); | 
|   | 
|         return { | 
|             h: lineHeight, | 
|             b: baseline | 
|         }; | 
|     }, | 
|   | 
|     /** | 
|      * Add a label, a text item that can hold a colored or gradient background | 
|      * as well as a border and shadow. | 
|      * @param {string} str | 
|      * @param {Number} x | 
|      * @param {Number} y | 
|      * @param {String} shape | 
|      * @param {Number} anchorX In case the shape has a pointer, like a flag, this is the | 
|      *    coordinates it should be pinned to | 
|      * @param {Number} anchorY | 
|      * @param {Boolean} baseline Whether to position the label relative to the text baseline, | 
|      *    like renderer.text, or to the upper border of the rectangle. | 
|      * @param {String} className Class name for the group | 
|      */ | 
|     label: function (str, x, y, shape, anchorX, anchorY, useHTML, baseline, className) { | 
|   | 
|         var renderer = this, | 
|             wrapper = renderer.g(className), | 
|             text = renderer.text('', 0, 0, useHTML) | 
|                 .attr({ | 
|                     zIndex: 1 | 
|                 }), | 
|                 //.add(wrapper), | 
|             box, | 
|             bBox, | 
|             alignFactor = 0, | 
|             padding = 3, | 
|             paddingLeft = 0, | 
|             width, | 
|             height, | 
|             wrapperX, | 
|             wrapperY, | 
|             crispAdjust = 0, | 
|             deferredAttr = {}, | 
|             baselineOffset, | 
|             attrSetters = wrapper.attrSetters, | 
|             needsBox; | 
|   | 
|         /** | 
|          * This function runs after the label is added to the DOM (when the bounding box is | 
|          * available), and after the text of the label is updated to detect the new bounding | 
|          * box and reflect it in the border box. | 
|          */ | 
|         function updateBoxSize() { | 
|             var boxX, | 
|                 boxY, | 
|                 style = text.element.style; | 
|   | 
|             bBox = (width === undefined || height === undefined || wrapper.styles.textAlign) && | 
|                 text.getBBox(); | 
|             wrapper.width = (width || bBox.width || 0) + 2 * padding + paddingLeft; | 
|             wrapper.height = (height || bBox.height || 0) + 2 * padding; | 
|   | 
|             // update the label-scoped y offset | 
|             baselineOffset = padding + renderer.fontMetrics(style && style.fontSize).b; | 
|   | 
|             if (needsBox) { | 
|   | 
|                 // create the border box if it is not already present | 
|                 if (!box) { | 
|                     boxX = mathRound(-alignFactor * padding); | 
|                     boxY = baseline ? -baselineOffset : 0; | 
|   | 
|                     wrapper.box = box = shape ? | 
|                         renderer.symbol(shape, boxX, boxY, wrapper.width, wrapper.height) : | 
|                         renderer.rect(boxX, boxY, wrapper.width, wrapper.height, 0, deferredAttr[STROKE_WIDTH]); | 
|                     box.add(wrapper); | 
|                 } | 
|   | 
|                 // apply the box attributes | 
|                 if (!box.isImg) { // #1630 | 
|                     box.attr(merge({ | 
|                         width: wrapper.width, | 
|                         height: wrapper.height | 
|                     }, deferredAttr)); | 
|                 } | 
|                 deferredAttr = null; | 
|             } | 
|         } | 
|   | 
|         /** | 
|          * This function runs after setting text or padding, but only if padding is changed | 
|          */ | 
|         function updateTextPadding() { | 
|             var styles = wrapper.styles, | 
|                 textAlign = styles && styles.textAlign, | 
|                 x = paddingLeft + padding * (1 - alignFactor), | 
|                 y; | 
|   | 
|             // determin y based on the baseline | 
|             y = baseline ? 0 : baselineOffset; | 
|   | 
|             // compensate for alignment | 
|             if (defined(width) && (textAlign === 'center' || textAlign === 'right')) { | 
|                 x += { center: 0.5, right: 1 }[textAlign] * (width - bBox.width); | 
|             } | 
|   | 
|             // update if anything changed | 
|             if (x !== text.x || y !== text.y) { | 
|                 text.attr({ | 
|                     x: x, | 
|                     y: y | 
|                 }); | 
|             } | 
|   | 
|             // record current values | 
|             text.x = x; | 
|             text.y = y; | 
|         } | 
|   | 
|         /** | 
|          * Set a box attribute, or defer it if the box is not yet created | 
|          * @param {Object} key | 
|          * @param {Object} value | 
|          */ | 
|         function boxAttr(key, value) { | 
|             if (box) { | 
|                 box.attr(key, value); | 
|             } else { | 
|                 deferredAttr[key] = value; | 
|             } | 
|         } | 
|   | 
|         function getSizeAfterAdd() { | 
|             text.add(wrapper); | 
|             wrapper.attr({ | 
|                 text: str, // alignment is available now | 
|                 x: x, | 
|                 y: y | 
|             }); | 
|   | 
|             if (box && defined(anchorX)) { | 
|                 wrapper.attr({ | 
|                     anchorX: anchorX, | 
|                     anchorY: anchorY | 
|                 }); | 
|             } | 
|         } | 
|   | 
|         /** | 
|          * After the text element is added, get the desired size of the border box | 
|          * and add it before the text in the DOM. | 
|          */ | 
|         addEvent(wrapper, 'add', getSizeAfterAdd); | 
|   | 
|         /* | 
|          * Add specific attribute setters. | 
|          */ | 
|   | 
|         // only change local variables | 
|         attrSetters.width = function (value) { | 
|             width = value; | 
|             return false; | 
|         }; | 
|         attrSetters.height = function (value) { | 
|             height = value; | 
|             return false; | 
|         }; | 
|         attrSetters.padding =  function (value) { | 
|             if (defined(value) && value !== padding) { | 
|                 padding = value; | 
|                 updateTextPadding(); | 
|             } | 
|             return false; | 
|         }; | 
|         attrSetters.paddingLeft =  function (value) { | 
|             if (defined(value) && value !== paddingLeft) { | 
|                 paddingLeft = value; | 
|                 updateTextPadding(); | 
|             } | 
|             return false; | 
|         }; | 
|   | 
|   | 
|         // change local variable and set attribue as well | 
|         attrSetters.align = function (value) { | 
|             alignFactor = { left: 0, center: 0.5, right: 1 }[value]; | 
|             return false; // prevent setting text-anchor on the group | 
|         }; | 
|   | 
|         // apply these to the box and the text alike | 
|         attrSetters.text = function (value, key) { | 
|             text.attr(key, value); | 
|             updateBoxSize(); | 
|             updateTextPadding(); | 
|             return false; | 
|         }; | 
|   | 
|         // apply these to the box but not to the text | 
|         attrSetters[STROKE_WIDTH] = function (value, key) { | 
|             needsBox = true; | 
|             crispAdjust = value % 2 / 2; | 
|             boxAttr(key, value); | 
|             return false; | 
|         }; | 
|         attrSetters.stroke = attrSetters.fill = attrSetters.r = function (value, key) { | 
|             if (key === 'fill') { | 
|                 needsBox = true; | 
|             } | 
|             boxAttr(key, value); | 
|             return false; | 
|         }; | 
|         attrSetters.anchorX = function (value, key) { | 
|             anchorX = value; | 
|             boxAttr(key, value + crispAdjust - wrapperX); | 
|             return false; | 
|         }; | 
|         attrSetters.anchorY = function (value, key) { | 
|             anchorY = value; | 
|             boxAttr(key, value - wrapperY); | 
|             return false; | 
|         }; | 
|   | 
|         // rename attributes | 
|         attrSetters.x = function (value) { | 
|             wrapper.x = value; // for animation getter | 
|             value -= alignFactor * ((width || bBox.width) + padding); | 
|             wrapperX = mathRound(value); | 
|   | 
|             wrapper.attr('translateX', wrapperX); | 
|             return false; | 
|         }; | 
|         attrSetters.y = function (value) { | 
|             wrapperY = wrapper.y = mathRound(value); | 
|             wrapper.attr('translateY', wrapperY); | 
|             return false; | 
|         }; | 
|   | 
|         // Redirect certain methods to either the box or the text | 
|         var baseCss = wrapper.css; | 
|         return extend(wrapper, { | 
|             /** | 
|              * Pick up some properties and apply them to the text instead of the wrapper | 
|              */ | 
|             css: function (styles) { | 
|                 if (styles) { | 
|                     var textStyles = {}; | 
|                     styles = merge(styles); // create a copy to avoid altering the original object (#537) | 
|                     each(['fontSize', 'fontWeight', 'fontFamily', 'color', 'lineHeight', 'width', 'textDecoration', 'textShadow'], function (prop) { | 
|                         if (styles[prop] !== UNDEFINED) { | 
|                             textStyles[prop] = styles[prop]; | 
|                             delete styles[prop]; | 
|                         } | 
|                     }); | 
|                     text.css(textStyles); | 
|                 } | 
|                 return baseCss.call(wrapper, styles); | 
|             }, | 
|             /** | 
|              * Return the bounding box of the box, not the group | 
|              */ | 
|             getBBox: function () { | 
|                 return { | 
|                     width: bBox.width + 2 * padding, | 
|                     height: bBox.height + 2 * padding, | 
|                     x: bBox.x - padding, | 
|                     y: bBox.y - padding | 
|                 }; | 
|             }, | 
|             /** | 
|              * Apply the shadow to the box | 
|              */ | 
|             shadow: function (b) { | 
|                 if (box) { | 
|                     box.shadow(b); | 
|                 } | 
|                 return wrapper; | 
|             }, | 
|             /** | 
|              * Destroy and release memory. | 
|              */ | 
|             destroy: function () { | 
|                 removeEvent(wrapper, 'add', getSizeAfterAdd); | 
|   | 
|                 // Added by button implementation | 
|                 removeEvent(wrapper.element, 'mouseenter'); | 
|                 removeEvent(wrapper.element, 'mouseleave'); | 
|   | 
|                 if (text) { | 
|                     text = text.destroy(); | 
|                 } | 
|                 if (box) { | 
|                     box = box.destroy(); | 
|                 } | 
|                 // Call base implementation to destroy the rest | 
|                 SVGElement.prototype.destroy.call(wrapper); | 
|   | 
|                 // Release local pointers (#1298) | 
|                 wrapper = renderer = updateBoxSize = updateTextPadding = boxAttr = getSizeAfterAdd = null; | 
|             } | 
|         }); | 
|     } | 
| }; // end SVGRenderer | 
|   | 
|   | 
| // general renderer | 
| Renderer = SVGRenderer; | 
|   | 
|   | 
| /* **************************************************************************** | 
|  *                                                                            * | 
|  * START OF INTERNET EXPLORER <= 8 SPECIFIC CODE                              * | 
|  *                                                                            * | 
|  * For applications and websites that don't need IE support, like platform    * | 
|  * targeted mobile apps and web apps, this code can be removed.               * | 
|  *                                                                            * | 
|  *****************************************************************************/ | 
|   | 
| /** | 
|  * @constructor | 
|  */ | 
| var VMLRenderer, VMLElement; | 
| if (!hasSVG && !useCanVG) { | 
|   | 
| /** | 
|  * The VML element wrapper. | 
|  */ | 
| Highcharts.VMLElement = VMLElement = { | 
|   | 
|     /** | 
|      * Initialize a new VML element wrapper. It builds the markup as a string | 
|      * to minimize DOM traffic. | 
|      * @param {Object} renderer | 
|      * @param {Object} nodeName | 
|      */ | 
|     init: function (renderer, nodeName) { | 
|         var wrapper = this, | 
|             markup =  ['<', nodeName, ' filled="f" stroked="f"'], | 
|             style = ['position: ', ABSOLUTE, ';'], | 
|             isDiv = nodeName === DIV; | 
|   | 
|         // divs and shapes need size | 
|         if (nodeName === 'shape' || isDiv) { | 
|             style.push('left:0;top:0;width:1px;height:1px;'); | 
|         } | 
|         style.push('visibility: ', isDiv ? HIDDEN : VISIBLE); | 
|   | 
|         markup.push(' style="', style.join(''), '"/>'); | 
|   | 
|         // create element with default attributes and style | 
|         if (nodeName) { | 
|             markup = isDiv || nodeName === 'span' || nodeName === 'img' ? | 
|                 markup.join('') | 
|                 : renderer.prepVML(markup); | 
|             wrapper.element = createElement(markup); | 
|         } | 
|   | 
|         wrapper.renderer = renderer; | 
|         wrapper.attrSetters = {}; | 
|     }, | 
|   | 
|     /** | 
|      * Add the node to the given parent | 
|      * @param {Object} parent | 
|      */ | 
|     add: function (parent) { | 
|         var wrapper = this, | 
|             renderer = wrapper.renderer, | 
|             element = wrapper.element, | 
|             box = renderer.box, | 
|             inverted = parent && parent.inverted, | 
|   | 
|             // get the parent node | 
|             parentNode = parent ? | 
|                 parent.element || parent : | 
|                 box; | 
|   | 
|   | 
|         // if the parent group is inverted, apply inversion on all children | 
|         if (inverted) { // only on groups | 
|             renderer.invertChild(element, parentNode); | 
|         } | 
|   | 
|         // append it | 
|         parentNode.appendChild(element); | 
|   | 
|         // align text after adding to be able to read offset | 
|         wrapper.added = true; | 
|         if (wrapper.alignOnAdd && !wrapper.deferUpdateTransform) { | 
|             wrapper.updateTransform(); | 
|         } | 
|   | 
|         // fire an event for internal hooks | 
|         fireEvent(wrapper, 'add'); | 
|   | 
|         return wrapper; | 
|     }, | 
|   | 
|     /** | 
|      * VML always uses htmlUpdateTransform | 
|      */ | 
|     updateTransform: SVGElement.prototype.htmlUpdateTransform, | 
|   | 
|     /** | 
|      * Set the rotation of a span with oldIE's filter | 
|      */ | 
|     setSpanRotation: function (rotation, sintheta, costheta) { | 
|         // Adjust for alignment and rotation. Rotation of useHTML content is not yet implemented | 
|         // but it can probably be implemented for Firefox 3.5+ on user request. FF3.5+ | 
|         // has support for CSS3 transform. The getBBox method also needs to be updated | 
|         // to compensate for the rotation, like it currently does for SVG. | 
|         // Test case: http://highcharts.com/tests/?file=text-rotation | 
|         css(this.element, { | 
|             filter: rotation ? ['progid:DXImageTransform.Microsoft.Matrix(M11=', costheta, | 
|                 ', M12=', -sintheta, ', M21=', sintheta, ', M22=', costheta, | 
|                 ', sizingMethod=\'auto expand\')'].join('') : NONE | 
|         }); | 
|     }, | 
|   | 
|     /** | 
|      * Converts a subset of an SVG path definition to its VML counterpart. Takes an array | 
|      * as the parameter and returns a string. | 
|      */ | 
|     pathToVML: function (value) { | 
|         // convert paths | 
|         var i = value.length, | 
|             path = [], | 
|             clockwise; | 
|   | 
|         while (i--) { | 
|   | 
|             // Multiply by 10 to allow subpixel precision. | 
|             // Substracting half a pixel seems to make the coordinates | 
|             // align with SVG, but this hasn't been tested thoroughly | 
|             if (isNumber(value[i])) { | 
|                 path[i] = mathRound(value[i] * 10) - 5; | 
|             } else if (value[i] === 'Z') { // close the path | 
|                 path[i] = 'x'; | 
|             } else { | 
|                 path[i] = value[i]; | 
|   | 
|                 // When the start X and end X coordinates of an arc are too close, | 
|                 // they are rounded to the same value above. In this case, substract 1 from the end X | 
|                 // position. #760, #1371. | 
|                 if (value.isArc && (value[i] === 'wa' || value[i] === 'at')) { | 
|                     clockwise = value[i] === 'wa' ? 1 : -1; // #1642 | 
|                     if (path[i + 5] === path[i + 7]) { | 
|                         path[i + 7] -= clockwise; | 
|                     } | 
|                     // Start and end Y (#1410) | 
|                     if (path[i + 6] === path[i + 8]) { | 
|                         path[i + 8] -= clockwise; | 
|                     } | 
|                 } | 
|             } | 
|         } | 
|         // Loop up again to handle path shortcuts (#2132) | 
|         /*while (i++ < path.length) { | 
|             if (path[i] === 'H') { // horizontal line to | 
|                 path[i] = 'L'; | 
|                 path.splice(i + 2, 0, path[i - 1]); | 
|             } else if (path[i] === 'V') { // vertical line to | 
|                 path[i] = 'L'; | 
|                 path.splice(i + 1, 0, path[i - 2]); | 
|             } | 
|         }*/ | 
|         return path.join(' ') || 'x'; | 
|     }, | 
|   | 
|     /** | 
|      * Get or set attributes | 
|      */ | 
|     attr: function (hash, val) { | 
|         var wrapper = this, | 
|             key, | 
|             value, | 
|             i, | 
|             result, | 
|             element = wrapper.element || {}, | 
|             elemStyle = element.style, | 
|             nodeName = element.nodeName, | 
|             renderer = wrapper.renderer, | 
|             symbolName = wrapper.symbolName, | 
|             hasSetSymbolSize, | 
|             shadows = wrapper.shadows, | 
|             skipAttr, | 
|             attrSetters = wrapper.attrSetters, | 
|             ret = wrapper; | 
|   | 
|         // single key-value pair | 
|         if (isString(hash) && defined(val)) { | 
|             key = hash; | 
|             hash = {}; | 
|             hash[key] = val; | 
|         } | 
|   | 
|         // used as a getter, val is undefined | 
|         if (isString(hash)) { | 
|             key = hash; | 
|             if (key === 'strokeWidth' || key === 'stroke-width') { | 
|                 ret = wrapper.strokeweight; | 
|             } else { | 
|                 ret = wrapper[key]; | 
|             } | 
|   | 
|         // setter | 
|         } else { | 
|             for (key in hash) { | 
|                 value = hash[key]; | 
|                 skipAttr = false; | 
|   | 
|                 // check for a specific attribute setter | 
|                 result = attrSetters[key] && attrSetters[key].call(wrapper, value, key); | 
|   | 
|                 if (result !== false && value !== null) { // #620 | 
|   | 
|                     if (result !== UNDEFINED) { | 
|                         value = result; // the attribute setter has returned a new value to set | 
|                     } | 
|   | 
|   | 
|                     // prepare paths | 
|                     // symbols | 
|                     if (symbolName && /^(x|y|r|start|end|width|height|innerR|anchorX|anchorY)/.test(key)) { | 
|                         // if one of the symbol size affecting parameters are changed, | 
|                         // check all the others only once for each call to an element's | 
|                         // .attr() method | 
|                         if (!hasSetSymbolSize) { | 
|                             wrapper.symbolAttr(hash); | 
|   | 
|                             hasSetSymbolSize = true; | 
|                         } | 
|                         skipAttr = true; | 
|   | 
|                     } else if (key === 'd') { | 
|                         value = value || []; | 
|                         wrapper.d = value.join(' '); // used in getter for animation | 
|   | 
|                         element.path = value = wrapper.pathToVML(value); | 
|   | 
|                         // update shadows | 
|                         if (shadows) { | 
|                             i = shadows.length; | 
|                             while (i--) { | 
|                                 shadows[i].path = shadows[i].cutOff ? this.cutOffPath(value, shadows[i].cutOff) : value; | 
|                             } | 
|                         } | 
|                         skipAttr = true; | 
|   | 
|                     // handle visibility | 
|                     } else if (key === 'visibility') { | 
|   | 
|                         // let the shadow follow the main element | 
|                         if (shadows) { | 
|                             i = shadows.length; | 
|                             while (i--) { | 
|                                 shadows[i].style[key] = value; | 
|                             } | 
|                         } | 
|   | 
|                         // Instead of toggling the visibility CSS property, move the div out of the viewport. | 
|                         // This works around #61 and #586 | 
|                         if (nodeName === 'DIV') { | 
|                             value = value === HIDDEN ? '-999em' : 0; | 
|   | 
|                             // In order to redraw, IE7 needs the div to be visible when tucked away | 
|                             // outside the viewport. So the visibility is actually opposite of | 
|                             // the expected value. This applies to the tooltip only. | 
|                             if (!docMode8) { | 
|                                 elemStyle[key] = value ? VISIBLE : HIDDEN; | 
|                             } | 
|                             key = 'top'; | 
|                         } | 
|                         elemStyle[key] = value; | 
|                         skipAttr = true; | 
|   | 
|                     // directly mapped to css | 
|                     } else if (key === 'zIndex') { | 
|   | 
|                         if (value) { | 
|                             elemStyle[key] = value; | 
|                         } | 
|                         skipAttr = true; | 
|   | 
|                     // x, y, width, height | 
|                     } else if (inArray(key, ['x', 'y', 'width', 'height']) !== -1) { | 
|   | 
|                         wrapper[key] = value; // used in getter | 
|   | 
|                         if (key === 'x' || key === 'y') { | 
|                             key = { x: 'left', y: 'top' }[key]; | 
|                         } else { | 
|                             value = mathMax(0, value); // don't set width or height below zero (#311) | 
|                         } | 
|   | 
|                         // clipping rectangle special | 
|                         if (wrapper.updateClipping) { | 
|                             wrapper[key] = value; // the key is now 'left' or 'top' for 'x' and 'y' | 
|                             wrapper.updateClipping(); | 
|                         } else { | 
|                             // normal | 
|                             elemStyle[key] = value; | 
|                         } | 
|   | 
|                         skipAttr = true; | 
|   | 
|                     // class name | 
|                     } else if (key === 'class' && nodeName === 'DIV') { | 
|                         // IE8 Standards mode has problems retrieving the className | 
|                         element.className = value; | 
|   | 
|                     // stroke | 
|                     } else if (key === 'stroke') { | 
|   | 
|                         value = renderer.color(value, element, key); | 
|   | 
|                         key = 'strokecolor'; | 
|   | 
|                     // stroke width | 
|                     } else if (key === 'stroke-width' || key === 'strokeWidth') { | 
|                         element.stroked = value ? true : false; | 
|                         key = 'strokeweight'; | 
|                         wrapper[key] = value; // used in getter, issue #113 | 
|                         if (isNumber(value)) { | 
|                             value += PX; | 
|                         } | 
|   | 
|                     // dashStyle | 
|                     } else if (key === 'dashstyle') { | 
|                         var strokeElem = element.getElementsByTagName('stroke')[0] || | 
|                             createElement(renderer.prepVML(['<stroke/>']), null, null, element); | 
|                         strokeElem[key] = value || 'solid'; | 
|                         wrapper.dashstyle = value; /* because changing stroke-width will change the dash length | 
|                             and cause an epileptic effect */ | 
|                         skipAttr = true; | 
|   | 
|                     // fill | 
|                     } else if (key === 'fill') { | 
|   | 
|                         if (nodeName === 'SPAN') { // text color | 
|                             elemStyle.color = value; | 
|                         } else if (nodeName !== 'IMG') { // #1336 | 
|                             element.filled = value !== NONE ? true : false; | 
|   | 
|                             value = renderer.color(value, element, key, wrapper); | 
|   | 
|                             key = 'fillcolor'; | 
|                         } | 
|   | 
|                     // opacity: don't bother - animation is too slow and filters introduce artifacts | 
|                     } else if (key === 'opacity') { | 
|                         /*css(element, { | 
|                             opacity: value | 
|                         });*/ | 
|                         skipAttr = true; | 
|   | 
|                     // rotation on VML elements | 
|                     } else if (nodeName === 'shape' && key === 'rotation') { | 
|   | 
|                         wrapper[key] = element.style[key] = value; // style is for #1873 | 
|   | 
|                         // Correction for the 1x1 size of the shape container. Used in gauge needles. | 
|                         element.style.left = -mathRound(mathSin(value * deg2rad) + 1) + PX; | 
|                         element.style.top = mathRound(mathCos(value * deg2rad)) + PX; | 
|   | 
|                     // translation for animation | 
|                     } else if (key === 'translateX' || key === 'translateY' || key === 'rotation') { | 
|                         wrapper[key] = value; | 
|                         wrapper.updateTransform(); | 
|   | 
|                         skipAttr = true; | 
|   | 
|                     // text for rotated and non-rotated elements | 
|                     } else if (key === 'text') { | 
|                         this.bBox = null; | 
|                         element.innerHTML = value; | 
|                         skipAttr = true; | 
|                     } | 
|   | 
|   | 
|                     if (!skipAttr) { | 
|                         if (docMode8) { // IE8 setAttribute bug | 
|                             element[key] = value; | 
|                         } else { | 
|                             attr(element, key, value); | 
|                         } | 
|                     } | 
|   | 
|                 } | 
|             } | 
|         } | 
|         return ret; | 
|     }, | 
|   | 
|     /** | 
|      * Set the element's clipping to a predefined rectangle | 
|      * | 
|      * @param {String} id The id of the clip rectangle | 
|      */ | 
|     clip: function (clipRect) { | 
|         var wrapper = this, | 
|             clipMembers, | 
|             cssRet; | 
|   | 
|         if (clipRect) { | 
|             clipMembers = clipRect.members; | 
|             erase(clipMembers, wrapper); // Ensure unique list of elements (#1258) | 
|             clipMembers.push(wrapper); | 
|             wrapper.destroyClip = function () { | 
|                 erase(clipMembers, wrapper); | 
|             }; | 
|             cssRet = clipRect.getCSS(wrapper); | 
|   | 
|         } else { | 
|             if (wrapper.destroyClip) { | 
|                 wrapper.destroyClip(); | 
|             } | 
|             cssRet = { clip: docMode8 ? 'inherit' : 'rect(auto)' }; // #1214 | 
|         } | 
|   | 
|         return wrapper.css(cssRet); | 
|   | 
|     }, | 
|   | 
|     /** | 
|      * Set styles for the element | 
|      * @param {Object} styles | 
|      */ | 
|     css: SVGElement.prototype.htmlCss, | 
|   | 
|     /** | 
|      * Removes a child either by removeChild or move to garbageBin. | 
|      * Issue 490; in VML removeChild results in Orphaned nodes according to sIEve, discardElement does not. | 
|      */ | 
|     safeRemoveChild: function (element) { | 
|         // discardElement will detach the node from its parent before attaching it | 
|         // to the garbage bin. Therefore it is important that the node is attached and have parent. | 
|         if (element.parentNode) { | 
|             discardElement(element); | 
|         } | 
|     }, | 
|   | 
|     /** | 
|      * Extend element.destroy by removing it from the clip members array | 
|      */ | 
|     destroy: function () { | 
|         if (this.destroyClip) { | 
|             this.destroyClip(); | 
|         } | 
|   | 
|         return SVGElement.prototype.destroy.apply(this); | 
|     }, | 
|   | 
|     /** | 
|      * Add an event listener. VML override for normalizing event parameters. | 
|      * @param {String} eventType | 
|      * @param {Function} handler | 
|      */ | 
|     on: function (eventType, handler) { | 
|         // simplest possible event model for internal use | 
|         this.element['on' + eventType] = function () { | 
|             var evt = win.event; | 
|             evt.target = evt.srcElement; | 
|             handler(evt); | 
|         }; | 
|         return this; | 
|     }, | 
|   | 
|     /** | 
|      * In stacked columns, cut off the shadows so that they don't overlap | 
|      */ | 
|     cutOffPath: function (path, length) { | 
|   | 
|         var len; | 
|   | 
|         path = path.split(/[ ,]/); | 
|         len = path.length; | 
|   | 
|         if (len === 9 || len === 11) { | 
|             path[len - 4] = path[len - 2] = pInt(path[len - 2]) - 10 * length; | 
|         } | 
|         return path.join(' '); | 
|     }, | 
|   | 
|     /** | 
|      * Apply a drop shadow by copying elements and giving them different strokes | 
|      * @param {Boolean|Object} shadowOptions | 
|      */ | 
|     shadow: function (shadowOptions, group, cutOff) { | 
|         var shadows = [], | 
|             i, | 
|             element = this.element, | 
|             renderer = this.renderer, | 
|             shadow, | 
|             elemStyle = element.style, | 
|             markup, | 
|             path = element.path, | 
|             strokeWidth, | 
|             modifiedPath, | 
|             shadowWidth, | 
|             shadowElementOpacity; | 
|   | 
|         // some times empty paths are not strings | 
|         if (path && typeof path.value !== 'string') { | 
|             path = 'x'; | 
|         } | 
|         modifiedPath = path; | 
|   | 
|         if (shadowOptions) { | 
|             shadowWidth = pick(shadowOptions.width, 3); | 
|             shadowElementOpacity = (shadowOptions.opacity || 0.15) / shadowWidth; | 
|             for (i = 1; i <= 3; i++) { | 
|   | 
|                 strokeWidth = (shadowWidth * 2) + 1 - (2 * i); | 
|   | 
|                 // Cut off shadows for stacked column items | 
|                 if (cutOff) { | 
|                     modifiedPath = this.cutOffPath(path.value, strokeWidth + 0.5); | 
|                 } | 
|   | 
|                 markup = ['<shape isShadow="true" strokeweight="', strokeWidth, | 
|                     '" filled="false" path="', modifiedPath, | 
|                     '" coordsize="10 10" style="', element.style.cssText, '" />']; | 
|   | 
|                 shadow = createElement(renderer.prepVML(markup), | 
|                     null, { | 
|                         left: pInt(elemStyle.left) + pick(shadowOptions.offsetX, 1), | 
|                         top: pInt(elemStyle.top) + pick(shadowOptions.offsetY, 1) | 
|                     } | 
|                 ); | 
|                 if (cutOff) { | 
|                     shadow.cutOff = strokeWidth + 1; | 
|                 } | 
|   | 
|                 // apply the opacity | 
|                 markup = ['<stroke color="', shadowOptions.color || 'black', '" opacity="', shadowElementOpacity * i, '"/>']; | 
|                 createElement(renderer.prepVML(markup), null, null, shadow); | 
|   | 
|   | 
|                 // insert it | 
|                 if (group) { | 
|                     group.element.appendChild(shadow); | 
|                 } else { | 
|                     element.parentNode.insertBefore(shadow, element); | 
|                 } | 
|   | 
|                 // record it | 
|                 shadows.push(shadow); | 
|   | 
|             } | 
|   | 
|             this.shadows = shadows; | 
|         } | 
|         return this; | 
|   | 
|     } | 
| }; | 
| VMLElement = extendClass(SVGElement, VMLElement); | 
|   | 
| /** | 
|  * The VML renderer | 
|  */ | 
| var VMLRendererExtension = { // inherit SVGRenderer | 
|   | 
|     Element: VMLElement, | 
|     isIE8: userAgent.indexOf('MSIE 8.0') > -1, | 
|   | 
|   | 
|     /** | 
|      * Initialize the VMLRenderer | 
|      * @param {Object} container | 
|      * @param {Number} width | 
|      * @param {Number} height | 
|      */ | 
|     init: function (container, width, height) { | 
|         var renderer = this, | 
|             boxWrapper, | 
|             box; | 
|   | 
|         renderer.alignedObjects = []; | 
|   | 
|         boxWrapper = renderer.createElement(DIV); | 
|         box = boxWrapper.element; | 
|         box.style.position = RELATIVE; // for freeform drawing using renderer directly | 
|         container.appendChild(boxWrapper.element); | 
|   | 
|   | 
|         // generate the containing box | 
|         renderer.isVML = true; | 
|         renderer.box = box; | 
|         renderer.boxWrapper = boxWrapper; | 
|   | 
|   | 
|         renderer.setSize(width, height, false); | 
|   | 
|         // The only way to make IE6 and IE7 print is to use a global namespace. However, | 
|         // with IE8 the only way to make the dynamic shapes visible in screen and print mode | 
|         // seems to be to add the xmlns attribute and the behaviour style inline. | 
|         if (!doc.namespaces.hcv) { | 
|   | 
|             doc.namespaces.add('hcv', 'urn:schemas-microsoft-com:vml'); | 
|   | 
|             // Setup default CSS (#2153) | 
|             (doc.styleSheets.length ? doc.styleSheets[0] : doc.createStyleSheet()).cssText += | 
|                 'hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke' + | 
|                 '{ behavior:url(#default#VML); display: inline-block; } '; | 
|   | 
|         } | 
|     }, | 
|   | 
|   | 
|     /** | 
|      * Detect whether the renderer is hidden. This happens when one of the parent elements | 
|      * has display: none | 
|      */ | 
|     isHidden: function () { | 
|         return !this.box.offsetWidth; | 
|     }, | 
|   | 
|     /** | 
|      * Define a clipping rectangle. In VML it is accomplished by storing the values | 
|      * for setting the CSS style to all associated members. | 
|      * | 
|      * @param {Number} x | 
|      * @param {Number} y | 
|      * @param {Number} width | 
|      * @param {Number} height | 
|      */ | 
|     clipRect: function (x, y, width, height) { | 
|   | 
|         // create a dummy element | 
|         var clipRect = this.createElement(), | 
|             isObj = isObject(x); | 
|   | 
|         // mimic a rectangle with its style object for automatic updating in attr | 
|         return extend(clipRect, { | 
|             members: [], | 
|             left: (isObj ? x.x : x) + 1, | 
|             top: (isObj ? x.y : y) + 1, | 
|             width: (isObj ? x.width : width) - 1, | 
|             height: (isObj ? x.height : height) - 1, | 
|             getCSS: function (wrapper) { | 
|                 var element = wrapper.element, | 
|                     nodeName = element.nodeName, | 
|                     isShape = nodeName === 'shape', | 
|                     inverted = wrapper.inverted, | 
|                     rect = this, | 
|                     top = rect.top - (isShape ? element.offsetTop : 0), | 
|                     left = rect.left, | 
|                     right = left + rect.width, | 
|                     bottom = top + rect.height, | 
|                     ret = { | 
|                         clip: 'rect(' + | 
|                             mathRound(inverted ? left : top) + 'px,' + | 
|                             mathRound(inverted ? bottom : right) + 'px,' + | 
|                             mathRound(inverted ? right : bottom) + 'px,' + | 
|                             mathRound(inverted ? top : left) + 'px)' | 
|                     }; | 
|   | 
|                 // issue 74 workaround | 
|                 if (!inverted && docMode8 && nodeName === 'DIV') { | 
|                     extend(ret, { | 
|                         width: right + PX, | 
|                         height: bottom + PX | 
|                     }); | 
|                 } | 
|                 return ret; | 
|             }, | 
|   | 
|             // used in attr and animation to update the clipping of all members | 
|             updateClipping: function () { | 
|                 each(clipRect.members, function (member) { | 
|                     member.css(clipRect.getCSS(member)); | 
|                 }); | 
|             } | 
|         }); | 
|   | 
|     }, | 
|   | 
|   | 
|     /** | 
|      * Take a color and return it if it's a string, make it a gradient if it's a | 
|      * gradient configuration object, and apply opacity. | 
|      * | 
|      * @param {Object} color The color or config object | 
|      */ | 
|     color: function (color, elem, prop, wrapper) { | 
|         var renderer = this, | 
|             colorObject, | 
|             regexRgba = /^rgba/, | 
|             markup, | 
|             fillType, | 
|             ret = NONE; | 
|   | 
|         // Check for linear or radial gradient | 
|         if (color && color.linearGradient) { | 
|             fillType = 'gradient'; | 
|         } else if (color && color.radialGradient) { | 
|             fillType = 'pattern'; | 
|         } | 
|   | 
|   | 
|         if (fillType) { | 
|   | 
|             var stopColor, | 
|                 stopOpacity, | 
|                 gradient = color.linearGradient || color.radialGradient, | 
|                 x1, | 
|                 y1, | 
|                 x2, | 
|                 y2, | 
|                 opacity1, | 
|                 opacity2, | 
|                 color1, | 
|                 color2, | 
|                 fillAttr = '', | 
|                 stops = color.stops, | 
|                 firstStop, | 
|                 lastStop, | 
|                 colors = [], | 
|                 addFillNode = function () { | 
|                     // Add the fill subnode. When colors attribute is used, the meanings of opacity and o:opacity2 | 
|                     // are reversed. | 
|                     markup = ['<fill colors="' + colors.join(',') + '" opacity="', opacity2, '" o:opacity2="', opacity1, | 
|                         '" type="', fillType, '" ', fillAttr, 'focus="100%" method="any" />']; | 
|                     createElement(renderer.prepVML(markup), null, null, elem); | 
|                 }; | 
|   | 
|             // Extend from 0 to 1 | 
|             firstStop = stops[0]; | 
|             lastStop = stops[stops.length - 1]; | 
|             if (firstStop[0] > 0) { | 
|                 stops.unshift([ | 
|                     0, | 
|                     firstStop[1] | 
|                 ]); | 
|             } | 
|             if (lastStop[0] < 1) { | 
|                 stops.push([ | 
|                     1, | 
|                     lastStop[1] | 
|                 ]); | 
|             } | 
|   | 
|             // Compute the stops | 
|             each(stops, function (stop, i) { | 
|                 if (regexRgba.test(stop[1])) { | 
|                     colorObject = Color(stop[1]); | 
|                     stopColor = colorObject.get('rgb'); | 
|                     stopOpacity = colorObject.get('a'); | 
|                 } else { | 
|                     stopColor = stop[1]; | 
|                     stopOpacity = 1; | 
|                 } | 
|   | 
|                 // Build the color attribute | 
|                 colors.push((stop[0] * 100) + '% ' + stopColor); | 
|   | 
|                 // Only start and end opacities are allowed, so we use the first and the last | 
|                 if (!i) { | 
|                     opacity1 = stopOpacity; | 
|                     color2 = stopColor; | 
|                 } else { | 
|                     opacity2 = stopOpacity; | 
|                     color1 = stopColor; | 
|                 } | 
|             }); | 
|   | 
|             // Apply the gradient to fills only. | 
|             if (prop === 'fill') { | 
|   | 
|                 // Handle linear gradient angle | 
|                 if (fillType === 'gradient') { | 
|                     x1 = gradient.x1 || gradient[0] || 0; | 
|                     y1 = gradient.y1 || gradient[1] || 0; | 
|                     x2 = gradient.x2 || gradient[2] || 0; | 
|                     y2 = gradient.y2 || gradient[3] || 0; | 
|                     fillAttr = 'angle="' + (90  - math.atan( | 
|                         (y2 - y1) / // y vector | 
|                         (x2 - x1) // x vector | 
|                         ) * 180 / mathPI) + '"'; | 
|   | 
|                     addFillNode(); | 
|   | 
|                 // Radial (circular) gradient | 
|                 } else { | 
|   | 
|                     var r = gradient.r, | 
|                         sizex = r * 2, | 
|                         sizey = r * 2, | 
|                         cx = gradient.cx, | 
|                         cy = gradient.cy, | 
|                         radialReference = elem.radialReference, | 
|                         bBox, | 
|                         applyRadialGradient = function () { | 
|                             if (radialReference) { | 
|                                 bBox = wrapper.getBBox(); | 
|                                 cx += (radialReference[0] - bBox.x) / bBox.width - 0.5; | 
|                                 cy += (radialReference[1] - bBox.y) / bBox.height - 0.5; | 
|                                 sizex *= radialReference[2] / bBox.width; | 
|                                 sizey *= radialReference[2] / bBox.height; | 
|                             } | 
|                             fillAttr = 'src="' + defaultOptions.global.VMLRadialGradientURL + '" ' + | 
|                                 'size="' + sizex + ',' + sizey + '" ' + | 
|                                 'origin="0.5,0.5" ' + | 
|                                 'position="' + cx + ',' + cy + '" ' + | 
|                                 'color2="' + color2 + '" '; | 
|   | 
|                             addFillNode(); | 
|                         }; | 
|   | 
|                     // Apply radial gradient | 
|                     if (wrapper.added) { | 
|                         applyRadialGradient(); | 
|                     } else { | 
|                         // We need to know the bounding box to get the size and position right | 
|                         addEvent(wrapper, 'add', applyRadialGradient); | 
|                     } | 
|   | 
|                     // The fill element's color attribute is broken in IE8 standards mode, so we | 
|                     // need to set the parent shape's fillcolor attribute instead. | 
|                     ret = color1; | 
|                 } | 
|   | 
|             // Gradients are not supported for VML stroke, return the first color. #722. | 
|             } else { | 
|                 ret = stopColor; | 
|             } | 
|   | 
|         // if the color is an rgba color, split it and add a fill node | 
|         // to hold the opacity component | 
|         } else if (regexRgba.test(color) && elem.tagName !== 'IMG') { | 
|   | 
|             colorObject = Color(color); | 
|   | 
|             markup = ['<', prop, ' opacity="', colorObject.get('a'), '"/>']; | 
|             createElement(this.prepVML(markup), null, null, elem); | 
|   | 
|             ret = colorObject.get('rgb'); | 
|   | 
|   | 
|         } else { | 
|             var propNodes = elem.getElementsByTagName(prop); // 'stroke' or 'fill' node | 
|             if (propNodes.length) { | 
|                 propNodes[0].opacity = 1; | 
|                 propNodes[0].type = 'solid'; | 
|             } | 
|             ret = color; | 
|         } | 
|   | 
|         return ret; | 
|     }, | 
|   | 
|     /** | 
|      * Take a VML string and prepare it for either IE8 or IE6/IE7. | 
|      * @param {Array} markup A string array of the VML markup to prepare | 
|      */ | 
|     prepVML: function (markup) { | 
|         var vmlStyle = 'display:inline-block;behavior:url(#default#VML);', | 
|             isIE8 = this.isIE8; | 
|   | 
|         markup = markup.join(''); | 
|   | 
|         if (isIE8) { // add xmlns and style inline | 
|             markup = markup.replace('/>', ' xmlns="urn:schemas-microsoft-com:vml" />'); | 
|             if (markup.indexOf('style="') === -1) { | 
|                 markup = markup.replace('/>', ' style="' + vmlStyle + '" />'); | 
|             } else { | 
|                 markup = markup.replace('style="', 'style="' + vmlStyle); | 
|             } | 
|   | 
|         } else { // add namespace | 
|             markup = markup.replace('<', '<hcv:'); | 
|         } | 
|   | 
|         return markup; | 
|     }, | 
|   | 
|     /** | 
|      * Create rotated and aligned text | 
|      * @param {String} str | 
|      * @param {Number} x | 
|      * @param {Number} y | 
|      */ | 
|     text: SVGRenderer.prototype.html, | 
|   | 
|     /** | 
|      * Create and return a path element | 
|      * @param {Array} path | 
|      */ | 
|     path: function (path) { | 
|         var attr = { | 
|             // subpixel precision down to 0.1 (width and height = 1px) | 
|             coordsize: '10 10' | 
|         }; | 
|         if (isArray(path)) { | 
|             attr.d = path; | 
|         } else if (isObject(path)) { // attributes | 
|             extend(attr, path); | 
|         } | 
|         // create the shape | 
|         return this.createElement('shape').attr(attr); | 
|     }, | 
|   | 
|     /** | 
|      * Create and return a circle element. In VML circles are implemented as | 
|      * shapes, which is faster than v:oval | 
|      * @param {Number} x | 
|      * @param {Number} y | 
|      * @param {Number} r | 
|      */ | 
|     circle: function (x, y, r) { | 
|         var circle = this.symbol('circle'); | 
|         if (isObject(x)) { | 
|             r = x.r; | 
|             y = x.y; | 
|             x = x.x; | 
|         } | 
|         circle.isCircle = true; // Causes x and y to mean center (#1682) | 
|         circle.r = r; | 
|         return circle.attr({ x: x, y: y }); | 
|     }, | 
|   | 
|     /** | 
|      * Create a group using an outer div and an inner v:group to allow rotating | 
|      * and flipping. A simple v:group would have problems with positioning | 
|      * child HTML elements and CSS clip. | 
|      * | 
|      * @param {String} name The name of the group | 
|      */ | 
|     g: function (name) { | 
|         var wrapper, | 
|             attribs; | 
|   | 
|         // set the class name | 
|         if (name) { | 
|             attribs = { 'className': PREFIX + name, 'class': PREFIX + name }; | 
|         } | 
|   | 
|         // the div to hold HTML and clipping | 
|         wrapper = this.createElement(DIV).attr(attribs); | 
|   | 
|         return wrapper; | 
|     }, | 
|   | 
|     /** | 
|      * VML override to create a regular HTML image | 
|      * @param {String} src | 
|      * @param {Number} x | 
|      * @param {Number} y | 
|      * @param {Number} width | 
|      * @param {Number} height | 
|      */ | 
|     image: function (src, x, y, width, height) { | 
|         var obj = this.createElement('img') | 
|             .attr({ src: src }); | 
|   | 
|         if (arguments.length > 1) { | 
|             obj.attr({ | 
|                 x: x, | 
|                 y: y, | 
|                 width: width, | 
|                 height: height | 
|             }); | 
|         } | 
|         return obj; | 
|     }, | 
|   | 
|     /** | 
|      * VML uses a shape for rect to overcome bugs and rotation problems | 
|      */ | 
|     rect: function (x, y, width, height, r, strokeWidth) { | 
|   | 
|         var wrapper = this.symbol('rect'); | 
|         wrapper.r = isObject(x) ? x.r : r; | 
|   | 
|         //return wrapper.attr(wrapper.crisp(strokeWidth, x, y, mathMax(width, 0), mathMax(height, 0))); | 
|         return wrapper.attr( | 
|                 isObject(x) ? | 
|                     x : | 
|                     // do not crispify when an object is passed in (as in column charts) | 
|                     wrapper.crisp(strokeWidth, x, y, mathMax(width, 0), mathMax(height, 0)) | 
|             ); | 
|     }, | 
|   | 
|     /** | 
|      * In the VML renderer, each child of an inverted div (group) is inverted | 
|      * @param {Object} element | 
|      * @param {Object} parentNode | 
|      */ | 
|     invertChild: function (element, parentNode) { | 
|         var parentStyle = parentNode.style; | 
|         css(element, { | 
|             flip: 'x', | 
|             left: pInt(parentStyle.width) - 1, | 
|             top: pInt(parentStyle.height) - 1, | 
|             rotation: -90 | 
|         }); | 
|     }, | 
|   | 
|     /** | 
|      * Symbol definitions that override the parent SVG renderer's symbols | 
|      * | 
|      */ | 
|     symbols: { | 
|         // VML specific arc function | 
|         arc: function (x, y, w, h, options) { | 
|             var start = options.start, | 
|                 end = options.end, | 
|                 radius = options.r || w || h, | 
|                 innerRadius = options.innerR, | 
|                 cosStart = mathCos(start), | 
|                 sinStart = mathSin(start), | 
|                 cosEnd = mathCos(end), | 
|                 sinEnd = mathSin(end), | 
|                 ret; | 
|   | 
|             if (end - start === 0) { // no angle, don't show it. | 
|                 return ['x']; | 
|             } | 
|   | 
|             ret = [ | 
|                 'wa', // clockwise arc to | 
|                 x - radius, // left | 
|                 y - radius, // top | 
|                 x + radius, // right | 
|                 y + radius, // bottom | 
|                 x + radius * cosStart, // start x | 
|                 y + radius * sinStart, // start y | 
|                 x + radius * cosEnd, // end x | 
|                 y + radius * sinEnd  // end y | 
|             ]; | 
|   | 
|             if (options.open && !innerRadius) { | 
|                 ret.push( | 
|                     'e', | 
|                     M, | 
|                     x,// - innerRadius, | 
|                     y// - innerRadius | 
|                 ); | 
|             } | 
|   | 
|             ret.push( | 
|                 'at', // anti clockwise arc to | 
|                 x - innerRadius, // left | 
|                 y - innerRadius, // top | 
|                 x + innerRadius, // right | 
|                 y + innerRadius, // bottom | 
|                 x + innerRadius * cosEnd, // start x | 
|                 y + innerRadius * sinEnd, // start y | 
|                 x + innerRadius * cosStart, // end x | 
|                 y + innerRadius * sinStart, // end y | 
|                 'x', // finish path | 
|                 'e' // close | 
|             ); | 
|   | 
|             ret.isArc = true; | 
|             return ret; | 
|   | 
|         }, | 
|         // Add circle symbol path. This performs significantly faster than v:oval. | 
|         circle: function (x, y, w, h, wrapper) { | 
|   | 
|             if (wrapper) { | 
|                 w = h = 2 * wrapper.r; | 
|             } | 
|   | 
|             // Center correction, #1682 | 
|             if (wrapper && wrapper.isCircle) { | 
|                 x -= w / 2; | 
|                 y -= h / 2; | 
|             } | 
|   | 
|             // Return the path | 
|             return [ | 
|                 'wa', // clockwisearcto | 
|                 x, // left | 
|                 y, // top | 
|                 x + w, // right | 
|                 y + h, // bottom | 
|                 x + w, // start x | 
|                 y + h / 2,     // start y | 
|                 x + w, // end x | 
|                 y + h / 2,     // end y | 
|                 //'x', // finish path | 
|                 'e' // close | 
|             ]; | 
|         }, | 
|         /** | 
|          * Add rectangle symbol path which eases rotation and omits arcsize problems | 
|          * compared to the built-in VML roundrect shape | 
|          * | 
|          * @param {Number} left Left position | 
|          * @param {Number} top Top position | 
|          * @param {Number} r Border radius | 
|          * @param {Object} options Width and height | 
|          */ | 
|   | 
|         rect: function (left, top, width, height, options) { | 
|   | 
|             var right = left + width, | 
|                 bottom = top + height, | 
|                 ret, | 
|                 r; | 
|   | 
|             // No radius, return the more lightweight square | 
|             if (!defined(options) || !options.r) { | 
|                 ret = SVGRenderer.prototype.symbols.square.apply(0, arguments); | 
|   | 
|             // Has radius add arcs for the corners | 
|             } else { | 
|   | 
|                 r = mathMin(options.r, width, height); | 
|                 ret = [ | 
|                     M, | 
|                     left + r, top, | 
|   | 
|                     L, | 
|                     right - r, top, | 
|                     'wa', | 
|                     right - 2 * r, top, | 
|                     right, top + 2 * r, | 
|                     right - r, top, | 
|                     right, top + r, | 
|   | 
|                     L, | 
|                     right, bottom - r, | 
|                     'wa', | 
|                     right - 2 * r, bottom - 2 * r, | 
|                     right, bottom, | 
|                     right, bottom - r, | 
|                     right - r, bottom, | 
|   | 
|                     L, | 
|                     left + r, bottom, | 
|                     'wa', | 
|                     left, bottom - 2 * r, | 
|                     left + 2 * r, bottom, | 
|                     left + r, bottom, | 
|                     left, bottom - r, | 
|   | 
|                     L, | 
|                     left, top + r, | 
|                     'wa', | 
|                     left, top, | 
|                     left + 2 * r, top + 2 * r, | 
|                     left, top + r, | 
|                     left + r, top, | 
|   | 
|   | 
|                     'x', | 
|                     'e' | 
|                 ]; | 
|             } | 
|             return ret; | 
|         } | 
|     } | 
| }; | 
| Highcharts.VMLRenderer = VMLRenderer = function () { | 
|     this.init.apply(this, arguments); | 
| }; | 
| VMLRenderer.prototype = merge(SVGRenderer.prototype, VMLRendererExtension); | 
|   | 
|     // general renderer | 
|     Renderer = VMLRenderer; | 
| } | 
|   | 
| /* **************************************************************************** | 
|  *                                                                            * | 
|  * END OF INTERNET EXPLORER <= 8 SPECIFIC CODE                                * | 
|  *                                                                            * | 
|  *****************************************************************************/ | 
| /* **************************************************************************** | 
|  *                                                                            * | 
|  * START OF ANDROID < 3 SPECIFIC CODE. THIS CAN BE REMOVED IF YOU'RE NOT      * | 
|  * TARGETING THAT SYSTEM.                                                     * | 
|  *                                                                            * | 
|  *****************************************************************************/ | 
| var CanVGRenderer, | 
|     CanVGController; | 
|   | 
| if (useCanVG) { | 
|     /** | 
|      * The CanVGRenderer is empty from start to keep the source footprint small. | 
|      * When requested, the CanVGController downloads the rest of the source packaged | 
|      * together with the canvg library. | 
|      */ | 
|     Highcharts.CanVGRenderer = CanVGRenderer = function () { | 
|         // Override the global SVG namespace to fake SVG/HTML that accepts CSS | 
|         SVG_NS = 'http://www.w3.org/1999/xhtml'; | 
|     }; | 
|   | 
|     /** | 
|      * Start with an empty symbols object. This is needed when exporting is used (exporting.src.js will add a few symbols), but  | 
|      * the implementation from SvgRenderer will not be merged in until first render. | 
|      */ | 
|     CanVGRenderer.prototype.symbols = {}; | 
|   | 
|     /** | 
|      * Handles on demand download of canvg rendering support. | 
|      */ | 
|     CanVGController = (function () { | 
|         // List of renderering calls | 
|         var deferredRenderCalls = []; | 
|   | 
|         /** | 
|          * When downloaded, we are ready to draw deferred charts. | 
|          */ | 
|         function drawDeferred() { | 
|             var callLength = deferredRenderCalls.length, | 
|                 callIndex; | 
|   | 
|             // Draw all pending render calls | 
|             for (callIndex = 0; callIndex < callLength; callIndex++) { | 
|                 deferredRenderCalls[callIndex](); | 
|             } | 
|             // Clear the list | 
|             deferredRenderCalls = []; | 
|         } | 
|   | 
|         return { | 
|             push: function (func, scriptLocation) { | 
|                 // Only get the script once | 
|                 if (deferredRenderCalls.length === 0) { | 
|                     getScript(scriptLocation, drawDeferred); | 
|                 } | 
|                 // Register render call | 
|                 deferredRenderCalls.push(func); | 
|             } | 
|         }; | 
|     }()); | 
|   | 
|     Renderer = CanVGRenderer; | 
| } // end CanVGRenderer | 
|   | 
| /* **************************************************************************** | 
|  *                                                                            * | 
|  * END OF ANDROID < 3 SPECIFIC CODE                                           * | 
|  *                                                                            * | 
|  *****************************************************************************/ | 
|   | 
| /** | 
|  * The Tick class | 
|  */ | 
| function Tick(axis, pos, type, noLabel) { | 
|     this.axis = axis; | 
|     this.pos = pos; | 
|     this.type = type || ''; | 
|     this.isNew = true; | 
|   | 
|     if (!type && !noLabel) { | 
|         this.addLabel(); | 
|     } | 
| } | 
|   | 
| Tick.prototype = { | 
|     /** | 
|      * Write the tick label | 
|      */ | 
|     addLabel: function () { | 
|         var tick = this, | 
|             axis = tick.axis, | 
|             options = axis.options, | 
|             chart = axis.chart, | 
|             horiz = axis.horiz, | 
|             categories = axis.categories, | 
|             names = axis.series[0] && axis.series[0].names, | 
|             pos = tick.pos, | 
|             labelOptions = options.labels, | 
|             str, | 
|             tickPositions = axis.tickPositions, | 
|             width = (horiz && categories && | 
|                 !labelOptions.step && !labelOptions.staggerLines && | 
|                 !labelOptions.rotation && | 
|                 chart.plotWidth / tickPositions.length) || | 
|                 (!horiz && (chart.margin[3] || chart.chartWidth * 0.33)), // #1580, #1931 | 
|             isFirst = pos === tickPositions[0], | 
|             isLast = pos === tickPositions[tickPositions.length - 1], | 
|             css, | 
|             attr, | 
|             value = categories ? | 
|                 pick(categories[pos], names && names[pos], pos) :  | 
|                 pos, | 
|             label = tick.label, | 
|             tickPositionInfo = tickPositions.info, | 
|             dateTimeLabelFormat; | 
|   | 
|         // Set the datetime label format. If a higher rank is set for this position, use that. If not, | 
|         // use the general format. | 
|         if (axis.isDatetimeAxis && tickPositionInfo) { | 
|             dateTimeLabelFormat = options.dateTimeLabelFormats[tickPositionInfo.higherRanks[pos] || tickPositionInfo.unitName]; | 
|         } | 
|   | 
|         // set properties for access in render method | 
|         tick.isFirst = isFirst; | 
|         tick.isLast = isLast; | 
|   | 
|         // get the string | 
|         str = axis.labelFormatter.call({ | 
|             axis: axis, | 
|             chart: chart, | 
|             isFirst: isFirst, | 
|             isLast: isLast, | 
|             dateTimeLabelFormat: dateTimeLabelFormat, | 
|             value: axis.isLog ? correctFloat(lin2log(value)) : value | 
|         }); | 
|   | 
|         // prepare CSS | 
|         css = width && { width: mathMax(1, mathRound(width - 2 * (labelOptions.padding || 10))) + PX }; | 
|         css = extend(css, labelOptions.style); | 
|   | 
|         // first call | 
|         if (!defined(label)) { | 
|             attr = { | 
|                 align: axis.labelAlign | 
|             }; | 
|             if (isNumber(labelOptions.rotation)) { | 
|                 attr.rotation = labelOptions.rotation; | 
|             } | 
|             if (width && labelOptions.ellipsis) { | 
|                 attr._clipHeight = axis.len / tickPositions.length; | 
|             } | 
|   | 
|             tick.label = | 
|                 defined(str) && labelOptions.enabled ? | 
|                     chart.renderer.text( | 
|                             str, | 
|                             0, | 
|                             0, | 
|                             labelOptions.useHTML | 
|                         ) | 
|                         .attr(attr) | 
|                         // without position absolute, IE export sometimes is wrong | 
|                         .css(css) | 
|                         .add(axis.labelGroup) : | 
|                     null; | 
|   | 
|         // update | 
|         } else if (label) { | 
|             label.attr({ | 
|                     text: str | 
|                 }) | 
|                 .css(css); | 
|         } | 
|     }, | 
|   | 
|     /** | 
|      * Get the offset height or width of the label | 
|      */ | 
|     getLabelSize: function () { | 
|         var label = this.label, | 
|             axis = this.axis; | 
|         return label ? | 
|             ((this.labelBBox = label.getBBox()))[axis.horiz ? 'height' : 'width'] : | 
|             0; | 
|     }, | 
|   | 
|     /** | 
|      * Find how far the labels extend to the right and left of the tick's x position. Used for anti-collision | 
|      * detection with overflow logic. | 
|      */ | 
|     getLabelSides: function () { | 
|         var bBox = this.labelBBox, // assume getLabelSize has run at this point | 
|             axis = this.axis, | 
|             options = axis.options, | 
|             labelOptions = options.labels, | 
|             width = bBox.width, | 
|             leftSide = width * { left: 0, center: 0.5, right: 1 }[axis.labelAlign] - labelOptions.x; | 
|   | 
|         return [-leftSide, width - leftSide]; | 
|     }, | 
|   | 
|     /** | 
|      * Handle the label overflow by adjusting the labels to the left and right edge, or | 
|      * hide them if they collide into the neighbour label. | 
|      */ | 
|     handleOverflow: function (index, xy) { | 
|         var show = true, | 
|             axis = this.axis, | 
|             chart = axis.chart, | 
|             isFirst = this.isFirst, | 
|             isLast = this.isLast, | 
|             x = xy.x, | 
|             reversed = axis.reversed, | 
|             tickPositions = axis.tickPositions; | 
|   | 
|         if (isFirst || isLast) { | 
|   | 
|             var sides = this.getLabelSides(), | 
|                 leftSide = sides[0], | 
|                 rightSide = sides[1], | 
|                 plotLeft = chart.plotLeft, | 
|                 plotRight = plotLeft + axis.len, | 
|                 neighbour = axis.ticks[tickPositions[index + (isFirst ? 1 : -1)]], | 
|                 neighbourEdge = neighbour && neighbour.label.xy && neighbour.label.xy.x + neighbour.getLabelSides()[isFirst ? 0 : 1]; | 
|   | 
|             if ((isFirst && !reversed) || (isLast && reversed)) { | 
|                 // Is the label spilling out to the left of the plot area? | 
|                 if (x + leftSide < plotLeft) { | 
|   | 
|                     // Align it to plot left | 
|                     x = plotLeft - leftSide; | 
|   | 
|                     // Hide it if it now overlaps the neighbour label | 
|                     if (neighbour && x + rightSide > neighbourEdge) { | 
|                         show = false; | 
|                     } | 
|                 } | 
|   | 
|             } else { | 
|                 // Is the label spilling out to the right of the plot area? | 
|                 if (x + rightSide > plotRight) { | 
|   | 
|                     // Align it to plot right | 
|                     x = plotRight - rightSide; | 
|   | 
|                     // Hide it if it now overlaps the neighbour label | 
|                     if (neighbour && x + leftSide < neighbourEdge) { | 
|                         show = false; | 
|                     } | 
|   | 
|                 } | 
|             } | 
|   | 
|             // Set the modified x position of the label | 
|             xy.x = x; | 
|         } | 
|         return show; | 
|     }, | 
|   | 
|     /** | 
|      * Get the x and y position for ticks and labels | 
|      */ | 
|     getPosition: function (horiz, pos, tickmarkOffset, old) { | 
|         var axis = this.axis, | 
|             chart = axis.chart, | 
|             cHeight = (old && chart.oldChartHeight) || chart.chartHeight; | 
|          | 
|         return { | 
|             x: horiz ? | 
|                 axis.translate(pos + tickmarkOffset, null, null, old) + axis.transB : | 
|                 axis.left + axis.offset + (axis.opposite ? ((old && chart.oldChartWidth) || chart.chartWidth) - axis.right - axis.left : 0), | 
|   | 
|             y: horiz ? | 
|                 cHeight - axis.bottom + axis.offset - (axis.opposite ? axis.height : 0) : | 
|                 cHeight - axis.translate(pos + tickmarkOffset, null, null, old) - axis.transB | 
|         }; | 
|          | 
|     }, | 
|      | 
|     /** | 
|      * Get the x, y position of the tick label | 
|      */ | 
|     getLabelPosition: function (x, y, label, horiz, labelOptions, tickmarkOffset, index, step) { | 
|         var axis = this.axis, | 
|             transA = axis.transA, | 
|             reversed = axis.reversed, | 
|             staggerLines = axis.staggerLines, | 
|             baseline = axis.chart.renderer.fontMetrics(labelOptions.style.fontSize).b, | 
|             rotation = labelOptions.rotation; | 
|              | 
|         x = x + labelOptions.x - (tickmarkOffset && horiz ? | 
|             tickmarkOffset * transA * (reversed ? -1 : 1) : 0); | 
|         y = y + labelOptions.y - (tickmarkOffset && !horiz ? | 
|             tickmarkOffset * transA * (reversed ? 1 : -1) : 0); | 
|   | 
|         // Correct for rotation (#1764) | 
|         if (rotation && axis.side === 2) { | 
|             y -= baseline - baseline * mathCos(rotation * deg2rad); | 
|         } | 
|          | 
|         // Vertically centered | 
|         if (!defined(labelOptions.y) && !rotation) { // #1951 | 
|             y += baseline - label.getBBox().height / 2; | 
|         } | 
|          | 
|         // Correct for staggered labels | 
|         if (staggerLines) { | 
|             y += (index / (step || 1) % staggerLines) * (axis.labelOffset / staggerLines); | 
|         } | 
|          | 
|         return { | 
|             x: x, | 
|             y: y | 
|         }; | 
|     }, | 
|      | 
|     /** | 
|      * Extendible method to return the path of the marker | 
|      */ | 
|     getMarkPath: function (x, y, tickLength, tickWidth, horiz, renderer) { | 
|         return renderer.crispLine([ | 
|                 M, | 
|                 x, | 
|                 y, | 
|                 L, | 
|                 x + (horiz ? 0 : -tickLength), | 
|                 y + (horiz ? tickLength : 0) | 
|             ], tickWidth); | 
|     }, | 
|   | 
|     /** | 
|      * Put everything in place | 
|      * | 
|      * @param index {Number} | 
|      * @param old {Boolean} Use old coordinates to prepare an animation into new position | 
|      */ | 
|     render: function (index, old, opacity) { | 
|         var tick = this, | 
|             axis = tick.axis, | 
|             options = axis.options, | 
|             chart = axis.chart, | 
|             renderer = chart.renderer, | 
|             horiz = axis.horiz, | 
|             type = tick.type, | 
|             label = tick.label, | 
|             pos = tick.pos, | 
|             labelOptions = options.labels, | 
|             gridLine = tick.gridLine, | 
|             gridPrefix = type ? type + 'Grid' : 'grid', | 
|             tickPrefix = type ? type + 'Tick' : 'tick', | 
|             gridLineWidth = options[gridPrefix + 'LineWidth'], | 
|             gridLineColor = options[gridPrefix + 'LineColor'], | 
|             dashStyle = options[gridPrefix + 'LineDashStyle'], | 
|             tickLength = options[tickPrefix + 'Length'], | 
|             tickWidth = options[tickPrefix + 'Width'] || 0, | 
|             tickColor = options[tickPrefix + 'Color'], | 
|             tickPosition = options[tickPrefix + 'Position'], | 
|             gridLinePath, | 
|             mark = tick.mark, | 
|             markPath, | 
|             step = labelOptions.step, | 
|             attribs, | 
|             show = true, | 
|             tickmarkOffset = axis.tickmarkOffset, | 
|             xy = tick.getPosition(horiz, pos, tickmarkOffset, old), | 
|             x = xy.x, | 
|             y = xy.y, | 
|             reverseCrisp = ((horiz && x === axis.pos + axis.len) || (!horiz && y === axis.pos)) ? -1 : 1, // #1480, #1687 | 
|             staggerLines = axis.staggerLines; | 
|   | 
|         this.isActive = true; | 
|          | 
|         // create the grid line | 
|         if (gridLineWidth) { | 
|             gridLinePath = axis.getPlotLinePath(pos + tickmarkOffset, gridLineWidth * reverseCrisp, old, true); | 
|   | 
|             if (gridLine === UNDEFINED) { | 
|                 attribs = { | 
|                     stroke: gridLineColor, | 
|                     'stroke-width': gridLineWidth | 
|                 }; | 
|                 if (dashStyle) { | 
|                     attribs.dashstyle = dashStyle; | 
|                 } | 
|                 if (!type) { | 
|                     attribs.zIndex = 1; | 
|                 } | 
|                 if (old) { | 
|                     attribs.opacity = 0; | 
|                 } | 
|                 tick.gridLine = gridLine = | 
|                     gridLineWidth ? | 
|                         renderer.path(gridLinePath) | 
|                             .attr(attribs).add(axis.gridGroup) : | 
|                         null; | 
|             } | 
|   | 
|             // If the parameter 'old' is set, the current call will be followed | 
|             // by another call, therefore do not do any animations this time | 
|             if (!old && gridLine && gridLinePath) { | 
|                 gridLine[tick.isNew ? 'attr' : 'animate']({ | 
|                     d: gridLinePath, | 
|                     opacity: opacity | 
|                 }); | 
|             } | 
|         } | 
|   | 
|         // create the tick mark | 
|         if (tickWidth && tickLength) { | 
|   | 
|             // negate the length | 
|             if (tickPosition === 'inside') { | 
|                 tickLength = -tickLength; | 
|             } | 
|             if (axis.opposite) { | 
|                 tickLength = -tickLength; | 
|             } | 
|   | 
|             markPath = tick.getMarkPath(x, y, tickLength, tickWidth * reverseCrisp, horiz, renderer); | 
|   | 
|             if (mark) { // updating | 
|                 mark.animate({ | 
|                     d: markPath, | 
|                     opacity: opacity | 
|                 }); | 
|             } else { // first time | 
|                 tick.mark = renderer.path( | 
|                     markPath | 
|                 ).attr({ | 
|                     stroke: tickColor, | 
|                     'stroke-width': tickWidth, | 
|                     opacity: opacity | 
|                 }).add(axis.axisGroup); | 
|             } | 
|         } | 
|   | 
|         // the label is created on init - now move it into place | 
|         if (label && !isNaN(x)) { | 
|             label.xy = xy = tick.getLabelPosition(x, y, label, horiz, labelOptions, tickmarkOffset, index, step); | 
|   | 
|             // Apply show first and show last. If the tick is both first and last, it is  | 
|             // a single centered tick, in which case we show the label anyway (#2100). | 
|             if ((tick.isFirst && !tick.isLast && !pick(options.showFirstLabel, 1)) || | 
|                     (tick.isLast && !tick.isFirst && !pick(options.showLastLabel, 1))) { | 
|                 show = false; | 
|   | 
|             // Handle label overflow and show or hide accordingly | 
|             } else if (!staggerLines && horiz && labelOptions.overflow === 'justify' && !tick.handleOverflow(index, xy)) { | 
|                 show = false; | 
|             } | 
|   | 
|             // apply step | 
|             if (step && index % step) { | 
|                 // show those indices dividable by step | 
|                 show = false; | 
|             } | 
|   | 
|             // Set the new position, and show or hide | 
|             if (show && !isNaN(xy.y)) { | 
|                 xy.opacity = opacity; | 
|                 label[tick.isNew ? 'attr' : 'animate'](xy); | 
|                 tick.isNew = false; | 
|             } else { | 
|                 label.attr('y', -9999); // #1338 | 
|             } | 
|         } | 
|     }, | 
|   | 
|     /** | 
|      * Destructor for the tick prototype | 
|      */ | 
|     destroy: function () { | 
|         destroyObjectProperties(this, this.axis); | 
|     } | 
| }; | 
|   | 
| /** | 
|  * The object wrapper for plot lines and plot bands | 
|  * @param {Object} options | 
|  */ | 
| function PlotLineOrBand(axis, options) { | 
|     this.axis = axis; | 
|   | 
|     if (options) { | 
|         this.options = options; | 
|         this.id = options.id; | 
|     } | 
| } | 
|   | 
| PlotLineOrBand.prototype = { | 
|      | 
|     /** | 
|      * Render the plot line or plot band. If it is already existing, | 
|      * move it. | 
|      */ | 
|     render: function () { | 
|         var plotLine = this, | 
|             axis = plotLine.axis, | 
|             horiz = axis.horiz, | 
|             halfPointRange = (axis.pointRange || 0) / 2, | 
|             options = plotLine.options, | 
|             optionsLabel = options.label, | 
|             label = plotLine.label, | 
|             width = options.width, | 
|             to = options.to, | 
|             from = options.from, | 
|             isBand = defined(from) && defined(to), | 
|             value = options.value, | 
|             dashStyle = options.dashStyle, | 
|             svgElem = plotLine.svgElem, | 
|             path = [], | 
|             addEvent, | 
|             eventType, | 
|             xs, | 
|             ys, | 
|             x, | 
|             y, | 
|             color = options.color, | 
|             zIndex = options.zIndex, | 
|             events = options.events, | 
|             attribs, | 
|             renderer = axis.chart.renderer; | 
|   | 
|         // logarithmic conversion | 
|         if (axis.isLog) { | 
|             from = log2lin(from); | 
|             to = log2lin(to); | 
|             value = log2lin(value); | 
|         } | 
|   | 
|         // plot line | 
|         if (width) { | 
|             path = axis.getPlotLinePath(value, width); | 
|             attribs = { | 
|                 stroke: color, | 
|                 'stroke-width': width | 
|             }; | 
|             if (dashStyle) { | 
|                 attribs.dashstyle = dashStyle; | 
|             } | 
|         } else if (isBand) { // plot band | 
|              | 
|             // keep within plot area | 
|             from = mathMax(from, axis.min - halfPointRange); | 
|             to = mathMin(to, axis.max + halfPointRange); | 
|              | 
|             path = axis.getPlotBandPath(from, to, options); | 
|             attribs = { | 
|                 fill: color | 
|             }; | 
|             if (options.borderWidth) { | 
|                 attribs.stroke = options.borderColor; | 
|                 attribs['stroke-width'] = options.borderWidth; | 
|             } | 
|         } else { | 
|             return; | 
|         } | 
|         // zIndex | 
|         if (defined(zIndex)) { | 
|             attribs.zIndex = zIndex; | 
|         } | 
|   | 
|         // common for lines and bands | 
|         if (svgElem) { | 
|             if (path) { | 
|                 svgElem.animate({ | 
|                     d: path | 
|                 }, null, svgElem.onGetPath); | 
|             } else { | 
|                 svgElem.hide(); | 
|                 svgElem.onGetPath = function () { | 
|                     svgElem.show(); | 
|                 }; | 
|             } | 
|         } else if (path && path.length) { | 
|             plotLine.svgElem = svgElem = renderer.path(path) | 
|                 .attr(attribs).add(); | 
|   | 
|             // events | 
|             if (events) { | 
|                 addEvent = function (eventType) { | 
|                     svgElem.on(eventType, function (e) { | 
|                         events[eventType].apply(plotLine, [e]); | 
|                     }); | 
|                 }; | 
|                 for (eventType in events) { | 
|                     addEvent(eventType); | 
|                 } | 
|             } | 
|         } | 
|   | 
|         // the plot band/line label | 
|         if (optionsLabel && defined(optionsLabel.text) && path && path.length && axis.width > 0 && axis.height > 0) { | 
|             // apply defaults | 
|             optionsLabel = merge({ | 
|                 align: horiz && isBand && 'center', | 
|                 x: horiz ? !isBand && 4 : 10, | 
|                 verticalAlign : !horiz && isBand && 'middle', | 
|                 y: horiz ? isBand ? 16 : 10 : isBand ? 6 : -4, | 
|                 rotation: horiz && !isBand && 90 | 
|             }, optionsLabel); | 
|   | 
|             // add the SVG element | 
|             if (!label) { | 
|                 plotLine.label = label = renderer.text( | 
|                         optionsLabel.text, | 
|                         0, | 
|                         0, | 
|                         optionsLabel.useHTML | 
|                     ) | 
|                     .attr({ | 
|                         align: optionsLabel.textAlign || optionsLabel.align, | 
|                         rotation: optionsLabel.rotation, | 
|                         zIndex: zIndex | 
|                     }) | 
|                     .css(optionsLabel.style) | 
|                     .add(); | 
|             } | 
|   | 
|             // get the bounding box and align the label | 
|             xs = [path[1], path[4], pick(path[6], path[1])]; | 
|             ys = [path[2], path[5], pick(path[7], path[2])]; | 
|             x = arrayMin(xs); | 
|             y = arrayMin(ys); | 
|   | 
|             label.align(optionsLabel, false, { | 
|                 x: x, | 
|                 y: y, | 
|                 width: arrayMax(xs) - x, | 
|                 height: arrayMax(ys) - y | 
|             }); | 
|             label.show(); | 
|   | 
|         } else if (label) { // move out of sight | 
|             label.hide(); | 
|         } | 
|   | 
|         // chainable | 
|         return plotLine; | 
|     }, | 
|   | 
|     /** | 
|      * Remove the plot line or band | 
|      */ | 
|     destroy: function () { | 
|         // remove it from the lookup | 
|         erase(this.axis.plotLinesAndBands, this); | 
|          | 
|         delete this.axis; | 
|         destroyObjectProperties(this); | 
|     } | 
| }; | 
| /** | 
|  * The class for stack items | 
|  */ | 
| function StackItem(axis, options, isNegative, x, stackOption, stacking) { | 
|      | 
|     var inverted = axis.chart.inverted; | 
|   | 
|     this.axis = axis; | 
|   | 
|     // Tells if the stack is negative | 
|     this.isNegative = isNegative; | 
|   | 
|     // Save the options to be able to style the label | 
|     this.options = options; | 
|   | 
|     // Save the x value to be able to position the label later | 
|     this.x = x; | 
|   | 
|     // Initialize total value | 
|     this.total = null; | 
|   | 
|     // This will keep each points' extremes stored by series.index | 
|     this.points = {}; | 
|   | 
|     // Save the stack option on the series configuration object, and whether to treat it as percent | 
|     this.stack = stackOption; | 
|     this.percent = stacking === 'percent'; | 
|   | 
|     // The align options and text align varies on whether the stack is negative and | 
|     // if the chart is inverted or not. | 
|     // First test the user supplied value, then use the dynamic. | 
|     this.alignOptions = { | 
|         align: options.align || (inverted ? (isNegative ? 'left' : 'right') : 'center'), | 
|         verticalAlign: options.verticalAlign || (inverted ? 'middle' : (isNegative ? 'bottom' : 'top')), | 
|         y: pick(options.y, inverted ? 4 : (isNegative ? 14 : -6)), | 
|         x: pick(options.x, inverted ? (isNegative ? -6 : 6) : 0) | 
|     }; | 
|   | 
|     this.textAlign = options.textAlign || (inverted ? (isNegative ? 'right' : 'left') : 'center'); | 
| } | 
|   | 
| StackItem.prototype = { | 
|     destroy: function () { | 
|         destroyObjectProperties(this, this.axis); | 
|     }, | 
|   | 
|     /** | 
|      * Renders the stack total label and adds it to the stack label group. | 
|      */ | 
|     render: function (group) { | 
|         var options = this.options, | 
|             formatOption = options.format, | 
|             str = formatOption ? | 
|                 format(formatOption, this) :  | 
|                 options.formatter.call(this);  // format the text in the label | 
|   | 
|         // Change the text to reflect the new total and set visibility to hidden in case the serie is hidden | 
|         if (this.label) { | 
|             this.label.attr({text: str, visibility: HIDDEN}); | 
|         // Create new label | 
|         } else { | 
|             this.label = | 
|                 this.axis.chart.renderer.text(str, 0, 0, options.useHTML)        // dummy positions, actual position updated with setOffset method in columnseries | 
|                     .css(options.style)                // apply style | 
|                     .attr({ | 
|                         align: this.textAlign,                // fix the text-anchor | 
|                         rotation: options.rotation,    // rotation | 
|                         visibility: HIDDEN                    // hidden until setOffset is called | 
|                     })                 | 
|                     .add(group);                            // add to the labels-group | 
|         } | 
|     }, | 
|   | 
|     /** | 
|      * Sets the offset that the stack has from the x value and repositions the label. | 
|      */ | 
|     setOffset: function (xOffset, xWidth) { | 
|         var stackItem = this, | 
|             axis = stackItem.axis, | 
|             chart = axis.chart, | 
|             inverted = chart.inverted, | 
|             neg = this.isNegative,                            // special treatment is needed for negative stacks | 
|             y = axis.translate(this.percent ? 100 : this.total, 0, 0, 0, 1), // stack value translated mapped to chart coordinates | 
|             yZero = axis.translate(0),                        // stack origin | 
|             h = mathAbs(y - yZero),                            // stack height | 
|             x = chart.xAxis[0].translate(this.x) + xOffset,    // stack x position | 
|             plotHeight = chart.plotHeight, | 
|             stackBox = {    // this is the box for the complete stack | 
|                 x: inverted ? (neg ? y : y - h) : x, | 
|                 y: inverted ? plotHeight - x - xWidth : (neg ? (plotHeight - y - h) : plotHeight - y), | 
|                 width: inverted ? h : xWidth, | 
|                 height: inverted ? xWidth : h | 
|             }, | 
|             label = this.label, | 
|             alignAttr; | 
|          | 
|         if (label) { | 
|             label.align(this.alignOptions, null, stackBox);    // align the label to the box | 
|                  | 
|             // Set visibility (#678) | 
|             alignAttr = label.alignAttr; | 
|             label.attr({  | 
|                 visibility: this.options.crop === false || chart.isInsidePlot(alignAttr.x, alignAttr.y) ?  | 
|                     (hasSVG ? 'inherit' : VISIBLE) :  | 
|                     HIDDEN | 
|             }); | 
|         } | 
|     } | 
| }; | 
| /** | 
|  * Create a new axis object | 
|  * @param {Object} chart | 
|  * @param {Object} options | 
|  */ | 
| function Axis() { | 
|     this.init.apply(this, arguments); | 
| } | 
|   | 
| Axis.prototype = { | 
|      | 
|     /** | 
|      * Default options for the X axis - the Y axis has extended defaults  | 
|      */ | 
|     defaultOptions: { | 
|         // allowDecimals: null, | 
|         // alternateGridColor: null, | 
|         // categories: [], | 
|         dateTimeLabelFormats: { | 
|             millisecond: '%H:%M:%S.%L', | 
|             second: '%H:%M:%S', | 
|             minute: '%H:%M', | 
|             hour: '%H:%M', | 
|             day: '%e. %b', | 
|             week: '%e. %b', | 
|             month: '%b \'%y', | 
|             year: '%Y' | 
|         }, | 
|         endOnTick: false, | 
|         gridLineColor: '#C0C0C0', | 
|         // gridLineDashStyle: 'solid', | 
|         // gridLineWidth: 0, | 
|         // reversed: false, | 
|      | 
|         labels: defaultLabelOptions, | 
|             // { step: null }, | 
|         lineColor: '#C0D0E0', | 
|         lineWidth: 1, | 
|         //linkedTo: null, | 
|         //max: undefined, | 
|         //min: undefined, | 
|         minPadding: 0.01, | 
|         maxPadding: 0.01, | 
|         //minRange: null, | 
|         minorGridLineColor: '#E0E0E0', | 
|         // minorGridLineDashStyle: null, | 
|         minorGridLineWidth: 1, | 
|         minorTickColor: '#A0A0A0', | 
|         //minorTickInterval: null, | 
|         minorTickLength: 2, | 
|         minorTickPosition: 'outside', // inside or outside | 
|         //minorTickWidth: 0, | 
|         //opposite: false, | 
|         //offset: 0, | 
|         //plotBands: [{ | 
|         //    events: {}, | 
|         //    zIndex: 1, | 
|         //    labels: { align, x, verticalAlign, y, style, rotation, textAlign } | 
|         //}], | 
|         //plotLines: [{ | 
|         //    events: {} | 
|         //  dashStyle: {} | 
|         //    zIndex: | 
|         //    labels: { align, x, verticalAlign, y, style, rotation, textAlign } | 
|         //}], | 
|         //reversed: false, | 
|         // showFirstLabel: true, | 
|         // showLastLabel: true, | 
|         startOfWeek: 1, | 
|         startOnTick: false, | 
|         tickColor: '#C0D0E0', | 
|         //tickInterval: null, | 
|         tickLength: 5, | 
|         tickmarkPlacement: 'between', // on or between | 
|         tickPixelInterval: 100, | 
|         tickPosition: 'outside', | 
|         tickWidth: 1, | 
|         title: { | 
|             //text: null, | 
|             align: 'middle', // low, middle or high | 
|             //margin: 0 for horizontal, 10 for vertical axes, | 
|             //rotation: 0, | 
|             //side: 'outside', | 
|             style: { | 
|                 color: '#4d759e', | 
|                 //font: defaultFont.replace('normal', 'bold') | 
|                 fontWeight: 'bold' | 
|             } | 
|             //x: 0, | 
|             //y: 0 | 
|         }, | 
|         type: 'linear' // linear, logarithmic or datetime | 
|     }, | 
|      | 
|     /** | 
|      * This options set extends the defaultOptions for Y axes | 
|      */ | 
|     defaultYAxisOptions: { | 
|         endOnTick: true, | 
|         gridLineWidth: 1, | 
|         tickPixelInterval: 72, | 
|         showLastLabel: true, | 
|         labels: { | 
|             x: -8, | 
|             y: 3 | 
|         }, | 
|         lineWidth: 0, | 
|         maxPadding: 0.05, | 
|         minPadding: 0.05, | 
|         startOnTick: true, | 
|         tickWidth: 0, | 
|         title: { | 
|             rotation: 270, | 
|             text: 'Values' | 
|         }, | 
|         stackLabels: { | 
|             enabled: false, | 
|             //align: dynamic, | 
|             //y: dynamic, | 
|             //x: dynamic, | 
|             //verticalAlign: dynamic, | 
|             //textAlign: dynamic, | 
|             //rotation: 0, | 
|             formatter: function () { | 
|                 return numberFormat(this.total, -1); | 
|             }, | 
|             style: defaultLabelOptions.style | 
|         } | 
|     }, | 
|      | 
|     /** | 
|      * These options extend the defaultOptions for left axes | 
|      */ | 
|     defaultLeftAxisOptions: { | 
|         labels: { | 
|             x: -8, | 
|             y: null | 
|         }, | 
|         title: { | 
|             rotation: 270 | 
|         } | 
|     }, | 
|      | 
|     /** | 
|      * These options extend the defaultOptions for right axes | 
|      */ | 
|     defaultRightAxisOptions: { | 
|         labels: { | 
|             x: 8, | 
|             y: null | 
|         }, | 
|         title: { | 
|             rotation: 90 | 
|         } | 
|     }, | 
|      | 
|     /** | 
|      * These options extend the defaultOptions for bottom axes | 
|      */ | 
|     defaultBottomAxisOptions: { | 
|         labels: { | 
|             x: 0, | 
|             y: 14 | 
|             // overflow: undefined, | 
|             // staggerLines: null | 
|         }, | 
|         title: { | 
|             rotation: 0 | 
|         } | 
|     }, | 
|     /** | 
|      * These options extend the defaultOptions for left axes | 
|      */ | 
|     defaultTopAxisOptions: { | 
|         labels: { | 
|             x: 0, | 
|             y: -5 | 
|             // overflow: undefined | 
|             // staggerLines: null | 
|         }, | 
|         title: { | 
|             rotation: 0 | 
|         } | 
|     }, | 
|      | 
|     /** | 
|      * Initialize the axis | 
|      */ | 
|     init: function (chart, userOptions) { | 
|              | 
|          | 
|         var isXAxis = userOptions.isX, | 
|             axis = this; | 
|      | 
|         // Flag, is the axis horizontal | 
|         axis.horiz = chart.inverted ? !isXAxis : isXAxis; | 
|          | 
|         // Flag, isXAxis | 
|         axis.isXAxis = isXAxis; | 
|         axis.xOrY = isXAxis ? 'x' : 'y'; | 
|      | 
|      | 
|         axis.opposite = userOptions.opposite; // needed in setOptions | 
|         axis.side = axis.horiz ? | 
|                 (axis.opposite ? 0 : 2) : // top : bottom | 
|                 (axis.opposite ? 1 : 3);  // right : left | 
|      | 
|         axis.setOptions(userOptions); | 
|          | 
|      | 
|         var options = this.options, | 
|             type = options.type, | 
|             isDatetimeAxis = type === 'datetime'; | 
|      | 
|         axis.labelFormatter = options.labels.formatter || axis.defaultLabelFormatter; // can be overwritten by dynamic format | 
|      | 
|      | 
|         // Flag, stagger lines or not | 
|         axis.userOptions = userOptions; | 
|      | 
|         //axis.axisTitleMargin = UNDEFINED,// = options.title.margin, | 
|         axis.minPixelPadding = 0; | 
|         //axis.ignoreMinPadding = UNDEFINED; // can be set to true by a column or bar series | 
|         //axis.ignoreMaxPadding = UNDEFINED; | 
|      | 
|         axis.chart = chart; | 
|         axis.reversed = options.reversed; | 
|         axis.zoomEnabled = options.zoomEnabled !== false; | 
|      | 
|         // Initial categories | 
|         axis.categories = options.categories || type === 'category'; | 
|      | 
|         // Elements | 
|         //axis.axisGroup = UNDEFINED; | 
|         //axis.gridGroup = UNDEFINED; | 
|         //axis.axisTitle = UNDEFINED; | 
|         //axis.axisLine = UNDEFINED; | 
|      | 
|         // Shorthand types | 
|         axis.isLog = type === 'logarithmic'; | 
|         axis.isDatetimeAxis = isDatetimeAxis; | 
|      | 
|         // Flag, if axis is linked to another axis | 
|         axis.isLinked = defined(options.linkedTo); | 
|         // Linked axis. | 
|         //axis.linkedParent = UNDEFINED;     | 
|          | 
|         // Tick positions | 
|         //axis.tickPositions = UNDEFINED; // array containing predefined positions | 
|         // Tick intervals | 
|         //axis.tickInterval = UNDEFINED; | 
|         //axis.minorTickInterval = UNDEFINED; | 
|          | 
|         axis.tickmarkOffset = (axis.categories && options.tickmarkPlacement === 'between') ? 0.5 : 0; | 
|      | 
|         // Major ticks | 
|         axis.ticks = {}; | 
|         // Minor ticks | 
|         axis.minorTicks = {}; | 
|         //axis.tickAmount = UNDEFINED; | 
|      | 
|         // List of plotLines/Bands | 
|         axis.plotLinesAndBands = []; | 
|      | 
|         // Alternate bands | 
|         axis.alternateBands = {}; | 
|      | 
|         // Axis metrics | 
|         //axis.left = UNDEFINED; | 
|         //axis.top = UNDEFINED; | 
|         //axis.width = UNDEFINED; | 
|         //axis.height = UNDEFINED; | 
|         //axis.bottom = UNDEFINED; | 
|         //axis.right = UNDEFINED; | 
|         //axis.transA = UNDEFINED; | 
|         //axis.transB = UNDEFINED; | 
|         //axis.oldTransA = UNDEFINED; | 
|         axis.len = 0; | 
|         //axis.oldMin = UNDEFINED; | 
|         //axis.oldMax = UNDEFINED; | 
|         //axis.oldUserMin = UNDEFINED; | 
|         //axis.oldUserMax = UNDEFINED; | 
|         //axis.oldAxisLength = UNDEFINED; | 
|         axis.minRange = axis.userMinRange = options.minRange || options.maxZoom; | 
|         axis.range = options.range; | 
|         axis.offset = options.offset || 0; | 
|      | 
|      | 
|         // Dictionary for stacks | 
|         axis.stacks = {}; | 
|         axis.oldStacks = {}; | 
|   | 
|         // Dictionary for stacks max values | 
|         axis.stackExtremes = {}; | 
|   | 
|         // Min and max in the data | 
|         //axis.dataMin = UNDEFINED, | 
|         //axis.dataMax = UNDEFINED, | 
|      | 
|         // The axis range | 
|         axis.max = null; | 
|         axis.min = null; | 
|      | 
|         // User set min and max | 
|         //axis.userMin = UNDEFINED, | 
|         //axis.userMax = UNDEFINED, | 
|   | 
|         // Run Axis | 
|          | 
|         var eventType, | 
|             events = axis.options.events; | 
|   | 
|         // Register | 
|         if (inArray(axis, chart.axes) === -1) { // don't add it again on Axis.update() | 
|             chart.axes.push(axis); | 
|             chart[isXAxis ? 'xAxis' : 'yAxis'].push(axis); | 
|         } | 
|   | 
|         axis.series = axis.series || []; // populated by Series | 
|   | 
|         // inverted charts have reversed xAxes as default | 
|         if (chart.inverted && isXAxis && axis.reversed === UNDEFINED) { | 
|             axis.reversed = true; | 
|         } | 
|   | 
|         axis.removePlotBand = axis.removePlotBandOrLine; | 
|         axis.removePlotLine = axis.removePlotBandOrLine; | 
|   | 
|   | 
|         // register event listeners | 
|         for (eventType in events) { | 
|             addEvent(axis, eventType, events[eventType]); | 
|         } | 
|   | 
|         // extend logarithmic axis | 
|         if (axis.isLog) { | 
|             axis.val2lin = log2lin; | 
|             axis.lin2val = lin2log; | 
|         } | 
|     }, | 
|      | 
|     /** | 
|      * Merge and set options | 
|      */ | 
|     setOptions: function (userOptions) { | 
|         this.options = merge( | 
|             this.defaultOptions, | 
|             this.isXAxis ? {} : this.defaultYAxisOptions, | 
|             [this.defaultTopAxisOptions, this.defaultRightAxisOptions, | 
|                 this.defaultBottomAxisOptions, this.defaultLeftAxisOptions][this.side], | 
|             merge( | 
|                 defaultOptions[this.isXAxis ? 'xAxis' : 'yAxis'], // if set in setOptions (#1053) | 
|                 userOptions | 
|             ) | 
|         ); | 
|     }, | 
|   | 
|     /** | 
|      * Update the axis with a new options structure | 
|      */ | 
|     update: function (newOptions, redraw) { | 
|         var chart = this.chart; | 
|   | 
|         newOptions = chart.options[this.xOrY + 'Axis'][this.options.index] = merge(this.userOptions, newOptions); | 
|   | 
|         this.destroy(true); | 
|         this._addedPlotLB = this.userMin = this.userMax = UNDEFINED; // #1611, #2306 | 
|   | 
|         this.init(chart, extend(newOptions, { events: UNDEFINED })); | 
|   | 
|         chart.isDirtyBox = true; | 
|         if (pick(redraw, true)) { | 
|             chart.redraw(); | 
|         } | 
|     },     | 
|      | 
|     /** | 
|      * Remove the axis from the chart | 
|      */ | 
|     remove: function (redraw) { | 
|         var chart = this.chart, | 
|             key = this.xOrY + 'Axis'; // xAxis or yAxis | 
|   | 
|         // Remove associated series | 
|         each(this.series, function (series) { | 
|             series.remove(false); | 
|         }); | 
|   | 
|         // Remove the axis | 
|         erase(chart.axes, this); | 
|         erase(chart[key], this); | 
|         chart.options[key].splice(this.options.index, 1); | 
|         each(chart[key], function (axis, i) { // Re-index, #1706 | 
|             axis.options.index = i; | 
|         }); | 
|         this.destroy(); | 
|         chart.isDirtyBox = true; | 
|   | 
|         if (pick(redraw, true)) { | 
|             chart.redraw(); | 
|         } | 
|     }, | 
|      | 
|     /**  | 
|      * The default label formatter. The context is a special config object for the label. | 
|      */ | 
|     defaultLabelFormatter: function () { | 
|         var axis = this.axis, | 
|             value = this.value, | 
|             categories = axis.categories,  | 
|             dateTimeLabelFormat = this.dateTimeLabelFormat, | 
|             numericSymbols = defaultOptions.lang.numericSymbols, | 
|             i = numericSymbols && numericSymbols.length, | 
|             multi, | 
|             ret, | 
|             formatOption = axis.options.labels.format, | 
|              | 
|             // make sure the same symbol is added for all labels on a linear axis | 
|             numericSymbolDetector = axis.isLog ? value : axis.tickInterval; | 
|   | 
|         if (formatOption) { | 
|             ret = format(formatOption, this); | 
|          | 
|         } else if (categories) { | 
|             ret = value; | 
|          | 
|         } else if (dateTimeLabelFormat) { // datetime axis | 
|             ret = dateFormat(dateTimeLabelFormat, value); | 
|          | 
|         } else if (i && numericSymbolDetector >= 1000) { | 
|             // Decide whether we should add a numeric symbol like k (thousands) or M (millions). | 
|             // If we are to enable this in tooltip or other places as well, we can move this | 
|             // logic to the numberFormatter and enable it by a parameter. | 
|             while (i-- && ret === UNDEFINED) { | 
|                 multi = Math.pow(1000, i + 1); | 
|                 if (numericSymbolDetector >= multi && numericSymbols[i] !== null) { | 
|                     ret = numberFormat(value / multi, -1) + numericSymbols[i]; | 
|                 } | 
|             } | 
|         } | 
|          | 
|         if (ret === UNDEFINED) { | 
|             if (value >= 1000) { // add thousands separators | 
|                 ret = numberFormat(value, 0); | 
|   | 
|             } else { // small numbers | 
|                 ret = numberFormat(value, -1); | 
|             } | 
|         } | 
|          | 
|         return ret; | 
|     }, | 
|   | 
|     /** | 
|      * Get the minimum and maximum for the series of each axis | 
|      */ | 
|     getSeriesExtremes: function () { | 
|         var axis = this, | 
|             chart = axis.chart; | 
|   | 
|         axis.hasVisibleSeries = false; | 
|   | 
|         // reset dataMin and dataMax in case we're redrawing | 
|         axis.dataMin = axis.dataMax = null; | 
|   | 
|         // reset cached stacking extremes | 
|         axis.stackExtremes = {}; | 
|   | 
|         axis.buildStacks(); | 
|   | 
|         // loop through this axis' series | 
|         each(axis.series, function (series) { | 
|   | 
|             if (series.visible || !chart.options.chart.ignoreHiddenSeries) { | 
|   | 
|                 var seriesOptions = series.options, | 
|                     xData, | 
|                     threshold = seriesOptions.threshold, | 
|                     seriesDataMin, | 
|                     seriesDataMax; | 
|   | 
|                 axis.hasVisibleSeries = true; | 
|   | 
|                 // Validate threshold in logarithmic axes | 
|                 if (axis.isLog && threshold <= 0) { | 
|                     threshold = null; | 
|                 } | 
|   | 
|                 // Get dataMin and dataMax for X axes | 
|                 if (axis.isXAxis) { | 
|                     xData = series.xData; | 
|                     if (xData.length) { | 
|                         axis.dataMin = mathMin(pick(axis.dataMin, xData[0]), arrayMin(xData)); | 
|                         axis.dataMax = mathMax(pick(axis.dataMax, xData[0]), arrayMax(xData)); | 
|                     } | 
|   | 
|                 // Get dataMin and dataMax for Y axes, as well as handle stacking and processed data | 
|                 } else { | 
|   | 
|                     // Get this particular series extremes | 
|                     series.getExtremes(); | 
|                     seriesDataMax = series.dataMax; | 
|                     seriesDataMin = series.dataMin; | 
|   | 
|                     // Get the dataMin and dataMax so far. If percentage is used, the min and max are | 
|                     // always 0 and 100. If seriesDataMin and seriesDataMax is null, then series | 
|                     // doesn't have active y data, we continue with nulls | 
|                     if (defined(seriesDataMin) && defined(seriesDataMax)) { | 
|                         axis.dataMin = mathMin(pick(axis.dataMin, seriesDataMin), seriesDataMin); | 
|                         axis.dataMax = mathMax(pick(axis.dataMax, seriesDataMax), seriesDataMax); | 
|                     } | 
|   | 
|                     // Adjust to threshold | 
|                     if (defined(threshold)) { | 
|                         if (axis.dataMin >= threshold) { | 
|                             axis.dataMin = threshold; | 
|                             axis.ignoreMinPadding = true; | 
|                         } else if (axis.dataMax < threshold) { | 
|                             axis.dataMax = threshold; | 
|                             axis.ignoreMaxPadding = true; | 
|                         } | 
|                     } | 
|                 } | 
|             } | 
|         }); | 
|     }, | 
|   | 
|     /** | 
|      * Translate from axis value to pixel position on the chart, or back | 
|      * | 
|      */ | 
|     translate: function (val, backwards, cvsCoord, old, handleLog, pointPlacement) { | 
|         var axis = this, | 
|             axisLength = axis.len, | 
|             sign = 1, | 
|             cvsOffset = 0, | 
|             localA = old ? axis.oldTransA : axis.transA, | 
|             localMin = old ? axis.oldMin : axis.min, | 
|             returnValue, | 
|             minPixelPadding = axis.minPixelPadding, | 
|             postTranslate = (axis.options.ordinal || (axis.isLog && handleLog)) && axis.lin2val; | 
|   | 
|         if (!localA) { | 
|             localA = axis.transA; | 
|         } | 
|   | 
|         // In vertical axes, the canvas coordinates start from 0 at the top like in  | 
|         // SVG.  | 
|         if (cvsCoord) { | 
|             sign *= -1; // canvas coordinates inverts the value | 
|             cvsOffset = axisLength; | 
|         } | 
|   | 
|         // Handle reversed axis | 
|         if (axis.reversed) {  | 
|             sign *= -1; | 
|             cvsOffset -= sign * axisLength; | 
|         } | 
|   | 
|         // From pixels to value | 
|         if (backwards) { // reverse translation | 
|              | 
|             val = val * sign + cvsOffset; | 
|             val -= minPixelPadding; | 
|             returnValue = val / localA + localMin; // from chart pixel to value | 
|             if (postTranslate) { // log and ordinal axes | 
|                 returnValue = axis.lin2val(returnValue); | 
|             } | 
|   | 
|         // From value to pixels | 
|         } else { | 
|             if (postTranslate) { // log and ordinal axes | 
|                 val = axis.val2lin(val); | 
|             } | 
|             if (pointPlacement === 'between') { | 
|                 pointPlacement = 0.5; | 
|             } | 
|             returnValue = sign * (val - localMin) * localA + cvsOffset + (sign * minPixelPadding) + | 
|                 (isNumber(pointPlacement) ? localA * pointPlacement * axis.pointRange : 0); | 
|         } | 
|   | 
|         return returnValue; | 
|     }, | 
|   | 
|     /** | 
|      * Utility method to translate an axis value to pixel position.  | 
|      * @param {Number} value A value in terms of axis units | 
|      * @param {Boolean} paneCoordinates Whether to return the pixel coordinate relative to the chart | 
|      *        or just the axis/pane itself. | 
|      */ | 
|     toPixels: function (value, paneCoordinates) { | 
|         return this.translate(value, false, !this.horiz, null, true) + (paneCoordinates ? 0 : this.pos); | 
|     }, | 
|   | 
|     /* | 
|      * Utility method to translate a pixel position in to an axis value | 
|      * @param {Number} pixel The pixel value coordinate | 
|      * @param {Boolean} paneCoordiantes Whether the input pixel is relative to the chart or just the | 
|      *        axis/pane itself. | 
|      */ | 
|     toValue: function (pixel, paneCoordinates) { | 
|         return this.translate(pixel - (paneCoordinates ? 0 : this.pos), true, !this.horiz, null, true); | 
|     }, | 
|   | 
|     /** | 
|      * Create the path for a plot line that goes from the given value on | 
|      * this axis, across the plot to the opposite side | 
|      * @param {Number} value | 
|      * @param {Number} lineWidth Used for calculation crisp line | 
|      * @param {Number] old Use old coordinates (for resizing and rescaling) | 
|      */ | 
|     getPlotLinePath: function (value, lineWidth, old, force) { | 
|         var axis = this, | 
|             chart = axis.chart, | 
|             axisLeft = axis.left, | 
|             axisTop = axis.top, | 
|             x1, | 
|             y1, | 
|             x2, | 
|             y2, | 
|             translatedValue = axis.translate(value, null, null, old), | 
|             cHeight = (old && chart.oldChartHeight) || chart.chartHeight, | 
|             cWidth = (old && chart.oldChartWidth) || chart.chartWidth, | 
|             skip, | 
|             transB = axis.transB; | 
|   | 
|         x1 = x2 = mathRound(translatedValue + transB); | 
|         y1 = y2 = mathRound(cHeight - translatedValue - transB); | 
|   | 
|         if (isNaN(translatedValue)) { // no min or max | 
|             skip = true; | 
|   | 
|         } else if (axis.horiz) { | 
|             y1 = axisTop; | 
|             y2 = cHeight - axis.bottom; | 
|             if (x1 < axisLeft || x1 > axisLeft + axis.width) { | 
|                 skip = true; | 
|             } | 
|         } else { | 
|             x1 = axisLeft; | 
|             x2 = cWidth - axis.right; | 
|   | 
|             if (y1 < axisTop || y1 > axisTop + axis.height) { | 
|                 skip = true; | 
|             } | 
|         } | 
|         return skip && !force ? | 
|             null : | 
|             chart.renderer.crispLine([M, x1, y1, L, x2, y2], lineWidth || 0); | 
|     }, | 
|      | 
|     /** | 
|      * Create the path for a plot band | 
|      */ | 
|     getPlotBandPath: function (from, to) { | 
|   | 
|         var toPath = this.getPlotLinePath(to), | 
|             path = this.getPlotLinePath(from); | 
|              | 
|         if (path && toPath) { | 
|             path.push( | 
|                 toPath[4], | 
|                 toPath[5], | 
|                 toPath[1], | 
|                 toPath[2] | 
|             ); | 
|         } else { // outside the axis area | 
|             path = null; | 
|         } | 
|          | 
|         return path; | 
|     }, | 
|      | 
|     /** | 
|      * Set the tick positions of a linear axis to round values like whole tens or every five. | 
|      */ | 
|     getLinearTickPositions: function (tickInterval, min, max) { | 
|         var pos, | 
|             lastPos, | 
|             roundedMin = correctFloat(mathFloor(min / tickInterval) * tickInterval), | 
|             roundedMax = correctFloat(mathCeil(max / tickInterval) * tickInterval), | 
|             tickPositions = []; | 
|   | 
|         // Populate the intermediate values | 
|         pos = roundedMin; | 
|         while (pos <= roundedMax) { | 
|   | 
|             // Place the tick on the rounded value | 
|             tickPositions.push(pos); | 
|   | 
|             // Always add the raw tickInterval, not the corrected one. | 
|             pos = correctFloat(pos + tickInterval); | 
|   | 
|             // If the interval is not big enough in the current min - max range to actually increase | 
|             // the loop variable, we need to break out to prevent endless loop. Issue #619 | 
|             if (pos === lastPos) { | 
|                 break; | 
|             } | 
|   | 
|             // Record the last value | 
|             lastPos = pos; | 
|         } | 
|         return tickPositions; | 
|     }, | 
|      | 
|     /** | 
|      * Set the tick positions of a logarithmic axis | 
|      */ | 
|     getLogTickPositions: function (interval, min, max, minor) { | 
|         var axis = this, | 
|             options = axis.options, | 
|             axisLength = axis.len, | 
|             // Since we use this method for both major and minor ticks, | 
|             // use a local variable and return the result | 
|             positions = [];  | 
|          | 
|         // Reset | 
|         if (!minor) { | 
|             axis._minorAutoInterval = null; | 
|         } | 
|          | 
|         // First case: All ticks fall on whole logarithms: 1, 10, 100 etc. | 
|         if (interval >= 0.5) { | 
|             interval = mathRound(interval); | 
|             positions = axis.getLinearTickPositions(interval, min, max); | 
|              | 
|         // Second case: We need intermediary ticks. For example  | 
|         // 1, 2, 4, 6, 8, 10, 20, 40 etc.  | 
|         } else if (interval >= 0.08) { | 
|             var roundedMin = mathFloor(min), | 
|                 intermediate, | 
|                 i, | 
|                 j, | 
|                 len, | 
|                 pos, | 
|                 lastPos, | 
|                 break2; | 
|                  | 
|             if (interval > 0.3) { | 
|                 intermediate = [1, 2, 4]; | 
|             } else if (interval > 0.15) { // 0.2 equals five minor ticks per 1, 10, 100 etc | 
|                 intermediate = [1, 2, 4, 6, 8]; | 
|             } else { // 0.1 equals ten minor ticks per 1, 10, 100 etc | 
|                 intermediate = [1, 2, 3, 4, 5, 6, 7, 8, 9]; | 
|             } | 
|              | 
|             for (i = roundedMin; i < max + 1 && !break2; i++) { | 
|                 len = intermediate.length; | 
|                 for (j = 0; j < len && !break2; j++) { | 
|                     pos = log2lin(lin2log(i) * intermediate[j]); | 
|                      | 
|                     if (pos > min && (!minor || lastPos <= max)) { // #1670 | 
|                         positions.push(lastPos); | 
|                     } | 
|                      | 
|                     if (lastPos > max) { | 
|                         break2 = true; | 
|                     } | 
|                     lastPos = pos; | 
|                 } | 
|             } | 
|              | 
|         // Third case: We are so deep in between whole logarithmic values that | 
|         // we might as well handle the tick positions like a linear axis. For | 
|         // example 1.01, 1.02, 1.03, 1.04. | 
|         } else { | 
|             var realMin = lin2log(min), | 
|                 realMax = lin2log(max), | 
|                 tickIntervalOption = options[minor ? 'minorTickInterval' : 'tickInterval'], | 
|                 filteredTickIntervalOption = tickIntervalOption === 'auto' ? null : tickIntervalOption, | 
|                 tickPixelIntervalOption = options.tickPixelInterval / (minor ? 5 : 1), | 
|                 totalPixelLength = minor ? axisLength / axis.tickPositions.length : axisLength; | 
|              | 
|             interval = pick( | 
|                 filteredTickIntervalOption, | 
|                 axis._minorAutoInterval, | 
|                 (realMax - realMin) * tickPixelIntervalOption / (totalPixelLength || 1) | 
|             ); | 
|              | 
|             interval = normalizeTickInterval( | 
|                 interval,  | 
|                 null,  | 
|                 getMagnitude(interval) | 
|             ); | 
|              | 
|             positions = map(axis.getLinearTickPositions( | 
|                 interval,  | 
|                 realMin, | 
|                 realMax     | 
|             ), log2lin); | 
|              | 
|             if (!minor) { | 
|                 axis._minorAutoInterval = interval / 5; | 
|             } | 
|         } | 
|          | 
|         // Set the axis-level tickInterval variable  | 
|         if (!minor) { | 
|             axis.tickInterval = interval; | 
|         } | 
|         return positions; | 
|     }, | 
|   | 
|     /** | 
|      * Return the minor tick positions. For logarithmic axes, reuse the same logic | 
|      * as for major ticks. | 
|      */ | 
|     getMinorTickPositions: function () { | 
|         var axis = this, | 
|             options = axis.options, | 
|             tickPositions = axis.tickPositions, | 
|             minorTickInterval = axis.minorTickInterval, | 
|             minorTickPositions = [], | 
|             pos, | 
|             i, | 
|             len; | 
|          | 
|         if (axis.isLog) { | 
|             len = tickPositions.length; | 
|             for (i = 1; i < len; i++) { | 
|                 minorTickPositions = minorTickPositions.concat( | 
|                     axis.getLogTickPositions(minorTickInterval, tickPositions[i - 1], tickPositions[i], true) | 
|                 );     | 
|             } | 
|         } else if (axis.isDatetimeAxis && options.minorTickInterval === 'auto') { // #1314 | 
|             minorTickPositions = minorTickPositions.concat( | 
|                 getTimeTicks( | 
|                     normalizeTimeTickInterval(minorTickInterval), | 
|                     axis.min, | 
|                     axis.max, | 
|                     options.startOfWeek | 
|                 ) | 
|             ); | 
|             if (minorTickPositions[0] < axis.min) { | 
|                 minorTickPositions.shift(); | 
|             } | 
|         } else {             | 
|             for (pos = axis.min + (tickPositions[0] - axis.min) % minorTickInterval; pos <= axis.max; pos += minorTickInterval) { | 
|                 minorTickPositions.push(pos); | 
|             } | 
|         } | 
|         return minorTickPositions; | 
|     }, | 
|   | 
|     /** | 
|      * Adjust the min and max for the minimum range. Keep in mind that the series data is  | 
|      * not yet processed, so we don't have information on data cropping and grouping, or  | 
|      * updated axis.pointRange or series.pointRange. The data can't be processed until | 
|      * we have finally established min and max. | 
|      */ | 
|     adjustForMinRange: function () { | 
|         var axis = this, | 
|             options = axis.options, | 
|             min = axis.min, | 
|             max = axis.max, | 
|             zoomOffset, | 
|             spaceAvailable = axis.dataMax - axis.dataMin >= axis.minRange, | 
|             closestDataRange, | 
|             i, | 
|             distance, | 
|             xData, | 
|             loopLength, | 
|             minArgs, | 
|             maxArgs; | 
|   | 
|         // Set the automatic minimum range based on the closest point distance | 
|         if (axis.isXAxis && axis.minRange === UNDEFINED && !axis.isLog) { | 
|   | 
|             if (defined(options.min) || defined(options.max)) { | 
|                 axis.minRange = null; // don't do this again | 
|   | 
|             } else { | 
|   | 
|                 // Find the closest distance between raw data points, as opposed to | 
|                 // closestPointRange that applies to processed points (cropped and grouped) | 
|                 each(axis.series, function (series) { | 
|                     xData = series.xData; | 
|                     loopLength = series.xIncrement ? 1 : xData.length - 1; | 
|                     for (i = loopLength; i > 0; i--) { | 
|                         distance = xData[i] - xData[i - 1]; | 
|                         if (closestDataRange === UNDEFINED || distance < closestDataRange) { | 
|                             closestDataRange = distance; | 
|                         } | 
|                     } | 
|                 }); | 
|                 axis.minRange = mathMin(closestDataRange * 5, axis.dataMax - axis.dataMin); | 
|             } | 
|         } | 
|   | 
|         // if minRange is exceeded, adjust | 
|         if (max - min < axis.minRange) { | 
|             var minRange = axis.minRange; | 
|             zoomOffset = (minRange - max + min) / 2; | 
|   | 
|             // if min and max options have been set, don't go beyond it | 
|             minArgs = [min - zoomOffset, pick(options.min, min - zoomOffset)]; | 
|             if (spaceAvailable) { // if space is available, stay within the data range | 
|                 minArgs[2] = axis.dataMin; | 
|             } | 
|             min = arrayMax(minArgs); | 
|   | 
|             maxArgs = [min + minRange, pick(options.max, min + minRange)]; | 
|             if (spaceAvailable) { // if space is availabe, stay within the data range | 
|                 maxArgs[2] = axis.dataMax; | 
|             } | 
|   | 
|             max = arrayMin(maxArgs); | 
|   | 
|             // now if the max is adjusted, adjust the min back | 
|             if (max - min < minRange) { | 
|                 minArgs[0] = max - minRange; | 
|                 minArgs[1] = pick(options.min, max - minRange); | 
|                 min = arrayMax(minArgs); | 
|             } | 
|         } | 
|          | 
|         // Record modified extremes | 
|         axis.min = min; | 
|         axis.max = max; | 
|     }, | 
|   | 
|     /** | 
|      * Update translation information | 
|      */ | 
|     setAxisTranslation: function (saveOld) { | 
|         var axis = this, | 
|             range = axis.max - axis.min, | 
|             pointRange = 0, | 
|             closestPointRange, | 
|             minPointOffset = 0, | 
|             pointRangePadding = 0, | 
|             linkedParent = axis.linkedParent, | 
|             ordinalCorrection, | 
|             transA = axis.transA; | 
|   | 
|         // adjust translation for padding | 
|         if (axis.isXAxis) { | 
|             if (linkedParent) { | 
|                 minPointOffset = linkedParent.minPointOffset; | 
|                 pointRangePadding = linkedParent.pointRangePadding; | 
|                  | 
|             } else { | 
|                 each(axis.series, function (series) { | 
|                     var seriesPointRange = series.pointRange, | 
|                         pointPlacement = series.options.pointPlacement, | 
|                         seriesClosestPointRange = series.closestPointRange; | 
|   | 
|                     if (seriesPointRange > range) { // #1446 | 
|                         seriesPointRange = 0; | 
|                     } | 
|                     pointRange = mathMax(pointRange, seriesPointRange); | 
|                      | 
|                     // minPointOffset is the value padding to the left of the axis in order to make | 
|                     // room for points with a pointRange, typically columns. When the pointPlacement option | 
|                     // is 'between' or 'on', this padding does not apply. | 
|                     minPointOffset = mathMax( | 
|                         minPointOffset,  | 
|                         isString(pointPlacement) ? 0 : seriesPointRange / 2 | 
|                     ); | 
|                      | 
|                     // Determine the total padding needed to the length of the axis to make room for the  | 
|                     // pointRange. If the series' pointPlacement is 'on', no padding is added. | 
|                     pointRangePadding = mathMax( | 
|                         pointRangePadding, | 
|                         pointPlacement === 'on' ? 0 : seriesPointRange | 
|                     ); | 
|   | 
|                     // Set the closestPointRange | 
|                     if (!series.noSharedTooltip && defined(seriesClosestPointRange)) { | 
|                         closestPointRange = defined(closestPointRange) ? | 
|                             mathMin(closestPointRange, seriesClosestPointRange) : | 
|                             seriesClosestPointRange; | 
|                     } | 
|                 }); | 
|             } | 
|              | 
|             // Record minPointOffset and pointRangePadding | 
|             ordinalCorrection = axis.ordinalSlope && closestPointRange ? axis.ordinalSlope / closestPointRange : 1; // #988, #1853 | 
|             axis.minPointOffset = minPointOffset = minPointOffset * ordinalCorrection; | 
|             axis.pointRangePadding = pointRangePadding = pointRangePadding * ordinalCorrection; | 
|   | 
|             // pointRange means the width reserved for each point, like in a column chart | 
|             axis.pointRange = mathMin(pointRange, range); | 
|   | 
|             // closestPointRange means the closest distance between points. In columns | 
|             // it is mostly equal to pointRange, but in lines pointRange is 0 while closestPointRange | 
|             // is some other value | 
|             axis.closestPointRange = closestPointRange; | 
|         } | 
|   | 
|         // Secondary values | 
|         if (saveOld) { | 
|             axis.oldTransA = transA; | 
|         } | 
|         axis.translationSlope = axis.transA = transA = axis.len / ((range + pointRangePadding) || 1); | 
|         axis.transB = axis.horiz ? axis.left : axis.bottom; // translation addend | 
|         axis.minPixelPadding = transA * minPointOffset; | 
|     }, | 
|   | 
|     /** | 
|      * Set the tick positions to round values and optionally extend the extremes | 
|      * to the nearest tick | 
|      */ | 
|     setTickPositions: function (secondPass) { | 
|         var axis = this, | 
|             chart = axis.chart, | 
|             options = axis.options, | 
|             isLog = axis.isLog, | 
|             isDatetimeAxis = axis.isDatetimeAxis, | 
|             isXAxis = axis.isXAxis, | 
|             isLinked = axis.isLinked, | 
|             tickPositioner = axis.options.tickPositioner, | 
|             maxPadding = options.maxPadding, | 
|             minPadding = options.minPadding, | 
|             length, | 
|             linkedParentExtremes, | 
|             tickIntervalOption = options.tickInterval, | 
|             minTickIntervalOption = options.minTickInterval, | 
|             tickPixelIntervalOption = options.tickPixelInterval, | 
|             tickPositions, | 
|             keepTwoTicksOnly, | 
|             categories = axis.categories; | 
|   | 
|         // linked axis gets the extremes from the parent axis | 
|         if (isLinked) { | 
|             axis.linkedParent = chart[isXAxis ? 'xAxis' : 'yAxis'][options.linkedTo]; | 
|             linkedParentExtremes = axis.linkedParent.getExtremes(); | 
|             axis.min = pick(linkedParentExtremes.min, linkedParentExtremes.dataMin); | 
|             axis.max = pick(linkedParentExtremes.max, linkedParentExtremes.dataMax); | 
|             if (options.type !== axis.linkedParent.options.type) { | 
|                 error(11, 1); // Can't link axes of different type | 
|             } | 
|         } else { // initial min and max from the extreme data values | 
|             axis.min = pick(axis.userMin, options.min, axis.dataMin); | 
|             axis.max = pick(axis.userMax, options.max, axis.dataMax); | 
|         } | 
|   | 
|         if (isLog) { | 
|             if (!secondPass && mathMin(axis.min, pick(axis.dataMin, axis.min)) <= 0) { // #978 | 
|                 error(10, 1); // Can't plot negative values on log axis | 
|             } | 
|             axis.min = correctFloat(log2lin(axis.min)); // correctFloat cures #934 | 
|             axis.max = correctFloat(log2lin(axis.max)); | 
|         } | 
|   | 
|         // handle zoomed range | 
|         if (axis.range) { | 
|             axis.userMin = axis.min = mathMax(axis.min, axis.max - axis.range); // #618 | 
|             axis.userMax = axis.max; | 
|             if (secondPass) { | 
|                 axis.range = null;  // don't use it when running setExtremes | 
|             } | 
|         } | 
|          | 
|         // Hook for adjusting this.min and this.max. Used by bubble series. | 
|         if (axis.beforePadding) { | 
|             axis.beforePadding(); | 
|         } | 
|   | 
|         // adjust min and max for the minimum range | 
|         axis.adjustForMinRange(); | 
|          | 
|         // Pad the values to get clear of the chart's edges. To avoid tickInterval taking the padding | 
|         // into account, we do this after computing tick interval (#1337). | 
|         if (!categories && !axis.usePercentage && !isLinked && defined(axis.min) && defined(axis.max)) { | 
|             length = axis.max - axis.min; | 
|             if (length) { | 
|                 if (!defined(options.min) && !defined(axis.userMin) && minPadding && (axis.dataMin < 0 || !axis.ignoreMinPadding)) { | 
|                     axis.min -= length * minPadding; | 
|                 } | 
|                 if (!defined(options.max) && !defined(axis.userMax)  && maxPadding && (axis.dataMax > 0 || !axis.ignoreMaxPadding)) { | 
|                     axis.max += length * maxPadding; | 
|                 } | 
|             } | 
|         } | 
|   | 
|         // get tickInterval | 
|         if (axis.min === axis.max || axis.min === undefined || axis.max === undefined) { | 
|             axis.tickInterval = 1; | 
|         } else if (isLinked && !tickIntervalOption && | 
|                 tickPixelIntervalOption === axis.linkedParent.options.tickPixelInterval) { | 
|             axis.tickInterval = axis.linkedParent.tickInterval; | 
|         } else { | 
|             axis.tickInterval = pick( | 
|                 tickIntervalOption, | 
|                 categories ? // for categoried axis, 1 is default, for linear axis use tickPix | 
|                     1 : | 
|                     // don't let it be more than the data range | 
|                     (axis.max - axis.min) * tickPixelIntervalOption / mathMax(axis.len, tickPixelIntervalOption) | 
|             ); | 
|             // For squished axes, set only two ticks | 
|             if (!defined(tickIntervalOption) && axis.len < tickPixelIntervalOption && !this.isRadial) { | 
|                 keepTwoTicksOnly = true; | 
|                 axis.tickInterval /= 4; // tick extremes closer to the real values | 
|             } | 
|         } | 
|   | 
|         // Now we're finished detecting min and max, crop and group series data. This | 
|         // is in turn needed in order to find tick positions in ordinal axes.  | 
|         if (isXAxis && !secondPass) { | 
|             each(axis.series, function (series) { | 
|                 series.processData(axis.min !== axis.oldMin || axis.max !== axis.oldMax); | 
|             }); | 
|         } | 
|   | 
|         // set the translation factor used in translate function | 
|         axis.setAxisTranslation(true); | 
|   | 
|         // hook for ordinal axes and radial axes | 
|         if (axis.beforeSetTickPositions) { | 
|             axis.beforeSetTickPositions(); | 
|         } | 
|          | 
|         // hook for extensions, used in Highstock ordinal axes | 
|         if (axis.postProcessTickInterval) { | 
|             axis.tickInterval = axis.postProcessTickInterval(axis.tickInterval); | 
|         } | 
|   | 
|         // In column-like charts, don't cramp in more ticks than there are points (#1943) | 
|         if (axis.pointRange) { | 
|             axis.tickInterval = mathMax(axis.pointRange, axis.tickInterval); | 
|         } | 
|          | 
|         // Before normalizing the tick interval, handle minimum tick interval. This applies only if tickInterval is not defined. | 
|         if (!tickIntervalOption && axis.tickInterval < minTickIntervalOption) { | 
|             axis.tickInterval = minTickIntervalOption; | 
|         } | 
|   | 
|         // for linear axes, get magnitude and normalize the interval | 
|         if (!isDatetimeAxis && !isLog) { // linear | 
|             if (!tickIntervalOption) { | 
|                 axis.tickInterval = normalizeTickInterval(axis.tickInterval, null, getMagnitude(axis.tickInterval), options); | 
|             } | 
|         } | 
|   | 
|         // get minorTickInterval | 
|         axis.minorTickInterval = options.minorTickInterval === 'auto' && axis.tickInterval ? | 
|                 axis.tickInterval / 5 : options.minorTickInterval; | 
|   | 
|         // find the tick positions | 
|         axis.tickPositions = tickPositions = options.tickPositions ? | 
|             [].concat(options.tickPositions) : // Work on a copy (#1565) | 
|             (tickPositioner && tickPositioner.apply(axis, [axis.min, axis.max])); | 
|         if (!tickPositions) { | 
|              | 
|             // Too many ticks | 
|             if (!axis.ordinalPositions && (axis.max - axis.min) / axis.tickInterval > mathMax(2 * axis.len, 200)) { | 
|                 error(19, true); | 
|             } | 
|              | 
|             if (isDatetimeAxis) { | 
|                 tickPositions = (axis.getNonLinearTimeTicks || getTimeTicks)( | 
|                     normalizeTimeTickInterval(axis.tickInterval, options.units), | 
|                     axis.min, | 
|                     axis.max, | 
|                     options.startOfWeek, | 
|                     axis.ordinalPositions, | 
|                     axis.closestPointRange, | 
|                     true | 
|                 ); | 
|             } else if (isLog) { | 
|                 tickPositions = axis.getLogTickPositions(axis.tickInterval, axis.min, axis.max); | 
|             } else { | 
|                 tickPositions = axis.getLinearTickPositions(axis.tickInterval, axis.min, axis.max); | 
|             } | 
|             if (keepTwoTicksOnly) { | 
|                 tickPositions.splice(1, tickPositions.length - 2); | 
|             } | 
|   | 
|             axis.tickPositions = tickPositions; | 
|         } | 
|   | 
|         if (!isLinked) { | 
|   | 
|             // reset min/max or remove extremes based on start/end on tick | 
|             var roundedMin = tickPositions[0], | 
|                 roundedMax = tickPositions[tickPositions.length - 1], | 
|                 minPointOffset = axis.minPointOffset || 0, | 
|                 singlePad; | 
|   | 
|             if (options.startOnTick) { | 
|                 axis.min = roundedMin; | 
|             } else if (axis.min - minPointOffset > roundedMin) { | 
|                 tickPositions.shift(); | 
|             } | 
|   | 
|             if (options.endOnTick) { | 
|                 axis.max = roundedMax; | 
|             } else if (axis.max + minPointOffset < roundedMax) { | 
|                 tickPositions.pop(); | 
|             } | 
|              | 
|             // When there is only one point, or all points have the same value on this axis, then min | 
|             // and max are equal and tickPositions.length is 1. In this case, add some padding | 
|             // in order to center the point, but leave it with one tick. #1337. | 
|             if (tickPositions.length === 1) { | 
|                 singlePad = 0.001; // The lowest possible number to avoid extra padding on columns | 
|                 axis.min -= singlePad; | 
|                 axis.max += singlePad; | 
|             } | 
|         } | 
|     }, | 
|      | 
|     /** | 
|      * Set the max ticks of either the x and y axis collection | 
|      */ | 
|     setMaxTicks: function () { | 
|          | 
|         var chart = this.chart, | 
|             maxTicks = chart.maxTicks || {}, | 
|             tickPositions = this.tickPositions, | 
|             key = this._maxTicksKey = [this.xOrY, this.pos, this.len].join('-'); | 
|          | 
|         if (!this.isLinked && !this.isDatetimeAxis && tickPositions && tickPositions.length > (maxTicks[key] || 0) && this.options.alignTicks !== false) { | 
|             maxTicks[key] = tickPositions.length; | 
|         } | 
|         chart.maxTicks = maxTicks; | 
|     }, | 
|   | 
|     /** | 
|      * When using multiple axes, adjust the number of ticks to match the highest | 
|      * number of ticks in that group | 
|      */ | 
|     adjustTickAmount: function () { | 
|         var axis = this, | 
|             chart = axis.chart, | 
|             key = axis._maxTicksKey, | 
|             tickPositions = axis.tickPositions, | 
|             maxTicks = chart.maxTicks; | 
|   | 
|         if (maxTicks && maxTicks[key] && !axis.isDatetimeAxis && !axis.categories && !axis.isLinked && axis.options.alignTicks !== false) { // only apply to linear scale | 
|             var oldTickAmount = axis.tickAmount, | 
|                 calculatedTickAmount = tickPositions.length, | 
|                 tickAmount; | 
|   | 
|             // set the axis-level tickAmount to use below | 
|             axis.tickAmount = tickAmount = maxTicks[key]; | 
|   | 
|             if (calculatedTickAmount < tickAmount) { | 
|                 while (tickPositions.length < tickAmount) { | 
|                     tickPositions.push(correctFloat( | 
|                         tickPositions[tickPositions.length - 1] + axis.tickInterval | 
|                     )); | 
|                 } | 
|                 axis.transA *= (calculatedTickAmount - 1) / (tickAmount - 1); | 
|                 axis.max = tickPositions[tickPositions.length - 1]; | 
|   | 
|             } | 
|             if (defined(oldTickAmount) && tickAmount !== oldTickAmount) { | 
|                 axis.isDirty = true; | 
|             } | 
|         } | 
|     }, | 
|   | 
|     /** | 
|      * Set the scale based on data min and max, user set min and max or options | 
|      * | 
|      */ | 
|     setScale: function () { | 
|         var axis = this, | 
|             stacks = axis.stacks, | 
|             type, | 
|             i, | 
|             isDirtyData, | 
|             isDirtyAxisLength; | 
|   | 
|         axis.oldMin = axis.min; | 
|         axis.oldMax = axis.max; | 
|         axis.oldAxisLength = axis.len; | 
|   | 
|         // set the new axisLength | 
|         axis.setAxisSize(); | 
|         //axisLength = horiz ? axisWidth : axisHeight; | 
|         isDirtyAxisLength = axis.len !== axis.oldAxisLength; | 
|   | 
|         // is there new data? | 
|         each(axis.series, function (series) { | 
|             if (series.isDirtyData || series.isDirty || | 
|                     series.xAxis.isDirty) { // when x axis is dirty, we need new data extremes for y as well | 
|                 isDirtyData = true; | 
|             } | 
|         }); | 
|   | 
|         // do we really need to go through all this? | 
|         if (isDirtyAxisLength || isDirtyData || axis.isLinked || axis.forceRedraw || | 
|             axis.userMin !== axis.oldUserMin || axis.userMax !== axis.oldUserMax) { | 
|              | 
|             // reset stacks | 
|             if (!axis.isXAxis) { | 
|                 for (type in stacks) { | 
|                     delete stacks[type]; | 
|                 } | 
|             } | 
|   | 
|             axis.forceRedraw = false; | 
|   | 
|             // get data extremes if needed | 
|             axis.getSeriesExtremes(); | 
|   | 
|             // get fixed positions based on tickInterval | 
|             axis.setTickPositions(); | 
|   | 
|             // record old values to decide whether a rescale is necessary later on (#540) | 
|             axis.oldUserMin = axis.userMin; | 
|             axis.oldUserMax = axis.userMax; | 
|   | 
|             // Mark as dirty if it is not already set to dirty and extremes have changed. #595. | 
|             if (!axis.isDirty) { | 
|                 axis.isDirty = isDirtyAxisLength || axis.min !== axis.oldMin || axis.max !== axis.oldMax; | 
|             } | 
|         } else if (!axis.isXAxis) { | 
|             if (axis.oldStacks) { | 
|                 stacks = axis.stacks = axis.oldStacks; | 
|             } | 
|   | 
|             // reset stacks | 
|             for (type in stacks) { | 
|                 for (i in stacks[type]) { | 
|                     stacks[type][i].cum = stacks[type][i].total; | 
|                 } | 
|             } | 
|         } | 
|          | 
|         // Set the maximum tick amount | 
|         axis.setMaxTicks(); | 
|     }, | 
|   | 
|     /** | 
|      * Set the extremes and optionally redraw | 
|      * @param {Number} newMin | 
|      * @param {Number} newMax | 
|      * @param {Boolean} redraw | 
|      * @param {Boolean|Object} animation Whether to apply animation, and optionally animation | 
|      *    configuration | 
|      * @param {Object} eventArguments  | 
|      * | 
|      */ | 
|     setExtremes: function (newMin, newMax, redraw, animation, eventArguments) { | 
|         var axis = this, | 
|             chart = axis.chart; | 
|   | 
|         redraw = pick(redraw, true); // defaults to true | 
|   | 
|         // Extend the arguments with min and max | 
|         eventArguments = extend(eventArguments, { | 
|             min: newMin, | 
|             max: newMax | 
|         }); | 
|   | 
|         // Fire the event | 
|         fireEvent(axis, 'setExtremes', eventArguments, function () { // the default event handler | 
|   | 
|             axis.userMin = newMin; | 
|             axis.userMax = newMax; | 
|             axis.eventArgs = eventArguments; | 
|   | 
|             // Mark for running afterSetExtremes | 
|             axis.isDirtyExtremes = true; | 
|   | 
|             // redraw | 
|             if (redraw) { | 
|                 chart.redraw(animation); | 
|             } | 
|         }); | 
|     }, | 
|      | 
|     /** | 
|      * Overridable method for zooming chart. Pulled out in a separate method to allow overriding | 
|      * in stock charts. | 
|      */ | 
|     zoom: function (newMin, newMax) { | 
|   | 
|         // Prevent pinch zooming out of range. Check for defined is for #1946. | 
|         if (!this.allowZoomOutside) { | 
|             if (defined(this.dataMin) && newMin <= this.dataMin) { | 
|                 newMin = UNDEFINED; | 
|             } | 
|             if (defined(this.dataMax) && newMax >= this.dataMax) { | 
|                 newMax = UNDEFINED; | 
|             } | 
|         } | 
|   | 
|         // In full view, displaying the reset zoom button is not required | 
|         this.displayBtn = newMin !== UNDEFINED || newMax !== UNDEFINED; | 
|          | 
|         // Do it | 
|         this.setExtremes( | 
|             newMin, | 
|             newMax, | 
|             false,  | 
|             UNDEFINED,  | 
|             { trigger: 'zoom' } | 
|         ); | 
|         return true; | 
|     }, | 
|      | 
|     /** | 
|      * Update the axis metrics | 
|      */ | 
|     setAxisSize: function () { | 
|         var chart = this.chart, | 
|             options = this.options, | 
|             offsetLeft = options.offsetLeft || 0, | 
|             offsetRight = options.offsetRight || 0, | 
|             horiz = this.horiz, | 
|             width, | 
|             height, | 
|             top, | 
|             left; | 
|   | 
|         // Expose basic values to use in Series object and navigator | 
|         this.left = left = pick(options.left, chart.plotLeft + offsetLeft); | 
|         this.top = top = pick(options.top, chart.plotTop); | 
|         this.width = width = pick(options.width, chart.plotWidth - offsetLeft + offsetRight); | 
|         this.height = height = pick(options.height, chart.plotHeight); | 
|         this.bottom = chart.chartHeight - height - top; | 
|         this.right = chart.chartWidth - width - left; | 
|   | 
|         // Direction agnostic properties | 
|         this.len = mathMax(horiz ? width : height, 0); // mathMax fixes #905 | 
|         this.pos = horiz ? left : top; // distance from SVG origin | 
|     }, | 
|   | 
|     /** | 
|      * Get the actual axis extremes | 
|      */ | 
|     getExtremes: function () { | 
|         var axis = this, | 
|             isLog = axis.isLog; | 
|   | 
|         return { | 
|             min: isLog ? correctFloat(lin2log(axis.min)) : axis.min, | 
|             max: isLog ? correctFloat(lin2log(axis.max)) : axis.max, | 
|             dataMin: axis.dataMin, | 
|             dataMax: axis.dataMax, | 
|             userMin: axis.userMin, | 
|             userMax: axis.userMax | 
|         }; | 
|     }, | 
|   | 
|     /** | 
|      * Get the zero plane either based on zero or on the min or max value. | 
|      * Used in bar and area plots | 
|      */ | 
|     getThreshold: function (threshold) { | 
|         var axis = this, | 
|             isLog = axis.isLog; | 
|   | 
|         var realMin = isLog ? lin2log(axis.min) : axis.min, | 
|             realMax = isLog ? lin2log(axis.max) : axis.max; | 
|          | 
|         if (realMin > threshold || threshold === null) { | 
|             threshold = realMin; | 
|         } else if (realMax < threshold) { | 
|             threshold = realMax; | 
|         } | 
|   | 
|         return axis.translate(threshold, 0, 1, 0, 1); | 
|     }, | 
|   | 
|     addPlotBand: function (options) { | 
|         this.addPlotBandOrLine(options, 'plotBands'); | 
|     }, | 
|      | 
|     addPlotLine: function (options) { | 
|         this.addPlotBandOrLine(options, 'plotLines'); | 
|     }, | 
|   | 
|     /** | 
|      * Add a plot band or plot line after render time | 
|      * | 
|      * @param options {Object} The plotBand or plotLine configuration object | 
|      */ | 
|     addPlotBandOrLine: function (options, coll) { | 
|         var obj = new PlotLineOrBand(this, options).render(), | 
|             userOptions = this.userOptions; | 
|   | 
|         if (obj) { // #2189 | 
|             // Add it to the user options for exporting and Axis.update | 
|             if (coll) { | 
|                 userOptions[coll] = userOptions[coll] || []; | 
|                 userOptions[coll].push(options);  | 
|             } | 
|             this.plotLinesAndBands.push(obj);  | 
|         } | 
|          | 
|         return obj; | 
|     }, | 
|   | 
|     /** | 
|      * Compute auto alignment for the axis label based on which side the axis is on  | 
|      * and the given rotation for the label | 
|      */ | 
|     autoLabelAlign: function (rotation) { | 
|         var ret,  | 
|             angle = (pick(rotation, 0) - (this.side * 90) + 720) % 360; | 
|   | 
|         if (angle > 15 && angle < 165) { | 
|             ret = 'right'; | 
|         } else if (angle > 195 && angle < 345) { | 
|             ret = 'left'; | 
|         } else { | 
|             ret = 'center'; | 
|         } | 
|         return ret; | 
|     }, | 
|   | 
|     /** | 
|      * Render the tick labels to a preliminary position to get their sizes | 
|      */ | 
|     getOffset: function () { | 
|         var axis = this, | 
|             chart = axis.chart, | 
|             renderer = chart.renderer, | 
|             options = axis.options, | 
|             tickPositions = axis.tickPositions, | 
|             ticks = axis.ticks, | 
|             horiz = axis.horiz, | 
|             side = axis.side, | 
|             invertedSide = chart.inverted ? [1, 0, 3, 2][side] : side, | 
|             hasData, | 
|             showAxis, | 
|             titleOffset = 0, | 
|             titleOffsetOption, | 
|             titleMargin = 0, | 
|             axisTitleOptions = options.title, | 
|             labelOptions = options.labels, | 
|             labelOffset = 0, // reset | 
|             axisOffset = chart.axisOffset, | 
|             clipOffset = chart.clipOffset, | 
|             directionFactor = [-1, 1, 1, -1][side], | 
|             n, | 
|             i, | 
|             autoStaggerLines = 1, | 
|             maxStaggerLines = pick(labelOptions.maxStaggerLines, 5), | 
|             sortedPositions, | 
|             lastRight, | 
|             overlap, | 
|             pos, | 
|             bBox, | 
|             x, | 
|             w, | 
|             lineNo; | 
|              | 
|         // For reuse in Axis.render | 
|         axis.hasData = hasData = (axis.hasVisibleSeries || (defined(axis.min) && defined(axis.max) && !!tickPositions)); | 
|         axis.showAxis = showAxis = hasData || pick(options.showEmpty, true); | 
|   | 
|         // Set/reset staggerLines | 
|         axis.staggerLines = axis.horiz && labelOptions.staggerLines; | 
|          | 
|         // Create the axisGroup and gridGroup elements on first iteration | 
|         if (!axis.axisGroup) { | 
|             axis.gridGroup = renderer.g('grid') | 
|                 .attr({ zIndex: options.gridZIndex || 1 }) | 
|                 .add(); | 
|             axis.axisGroup = renderer.g('axis') | 
|                 .attr({ zIndex: options.zIndex || 2 }) | 
|                 .add(); | 
|             axis.labelGroup = renderer.g('axis-labels') | 
|                 .attr({ zIndex: labelOptions.zIndex || 7 }) | 
|                 .add(); | 
|         } | 
|   | 
|         if (hasData || axis.isLinked) { | 
|              | 
|             // Set the explicit or automatic label alignment | 
|             axis.labelAlign = pick(labelOptions.align || axis.autoLabelAlign(labelOptions.rotation)); | 
|   | 
|             each(tickPositions, function (pos) { | 
|                 if (!ticks[pos]) { | 
|                     ticks[pos] = new Tick(axis, pos); | 
|                 } else { | 
|                     ticks[pos].addLabel(); // update labels depending on tick interval | 
|                 } | 
|             }); | 
|   | 
|             // Handle automatic stagger lines | 
|             if (axis.horiz && !axis.staggerLines && maxStaggerLines && !labelOptions.rotation) { | 
|                 sortedPositions = axis.reversed ? [].concat(tickPositions).reverse() : tickPositions; | 
|                 while (autoStaggerLines < maxStaggerLines) { | 
|                     lastRight = []; | 
|                     overlap = false; | 
|                      | 
|                     for (i = 0; i < sortedPositions.length; i++) { | 
|                         pos = sortedPositions[i]; | 
|                         bBox = ticks[pos].label && ticks[pos].label.getBBox(); | 
|                         w = bBox ? bBox.width : 0; | 
|                         lineNo = i % autoStaggerLines; | 
|                          | 
|                         if (w) { | 
|                             x = axis.translate(pos); // don't handle log | 
|                             if (lastRight[lineNo] !== UNDEFINED && x < lastRight[lineNo]) { | 
|                                 overlap = true; | 
|                             } | 
|                             lastRight[lineNo] = x + w; | 
|                         } | 
|                     } | 
|                     if (overlap) { | 
|                         autoStaggerLines++; | 
|                     } else { | 
|                         break; | 
|                     } | 
|                 } | 
|   | 
|                 if (autoStaggerLines > 1) { | 
|                     axis.staggerLines = autoStaggerLines; | 
|                 } | 
|             } | 
|   | 
|   | 
|             each(tickPositions, function (pos) { | 
|                 // left side must be align: right and right side must have align: left for labels | 
|                 if (side === 0 || side === 2 || { 1: 'left', 3: 'right' }[side] === axis.labelAlign) { | 
|   | 
|                     // get the highest offset | 
|                     labelOffset = mathMax( | 
|                         ticks[pos].getLabelSize(), | 
|                         labelOffset | 
|                     ); | 
|                 } | 
|   | 
|             }); | 
|             if (axis.staggerLines) { | 
|                 labelOffset *= axis.staggerLines; | 
|                 axis.labelOffset = labelOffset; | 
|             } | 
|              | 
|   | 
|         } else { // doesn't have data | 
|             for (n in ticks) { | 
|                 ticks[n].destroy(); | 
|                 delete ticks[n]; | 
|             } | 
|         } | 
|   | 
|         if (axisTitleOptions && axisTitleOptions.text && axisTitleOptions.enabled !== false) {  | 
|             if (!axis.axisTitle) { | 
|                 axis.axisTitle = renderer.text( | 
|                     axisTitleOptions.text, | 
|                     0, | 
|                     0, | 
|                     axisTitleOptions.useHTML | 
|                 ) | 
|                 .attr({ | 
|                     zIndex: 7, | 
|                     rotation: axisTitleOptions.rotation || 0, | 
|                     align: | 
|                         axisTitleOptions.textAlign || | 
|                         { low: 'left', middle: 'center', high: 'right' }[axisTitleOptions.align] | 
|                 }) | 
|                 .css(axisTitleOptions.style) | 
|                 .add(axis.axisGroup); | 
|                 axis.axisTitle.isNew = true; | 
|             } | 
|   | 
|             if (showAxis) { | 
|                 titleOffset = axis.axisTitle.getBBox()[horiz ? 'height' : 'width']; | 
|                 titleMargin = pick(axisTitleOptions.margin, horiz ? 5 : 10); | 
|                 titleOffsetOption = axisTitleOptions.offset; | 
|             } | 
|   | 
|             // hide or show the title depending on whether showEmpty is set | 
|             axis.axisTitle[showAxis ? 'show' : 'hide'](); | 
|         } | 
|          | 
|         // handle automatic or user set offset | 
|         axis.offset = directionFactor * pick(options.offset, axisOffset[side]); | 
|          | 
|         axis.axisTitleMargin = | 
|             pick(titleOffsetOption, | 
|                 labelOffset + titleMargin + | 
|                 (side !== 2 && labelOffset && directionFactor * options.labels[horiz ? 'y' : 'x']) | 
|             ); | 
|   | 
|         axisOffset[side] = mathMax( | 
|             axisOffset[side], | 
|             axis.axisTitleMargin + titleOffset + directionFactor * axis.offset | 
|         ); | 
|         clipOffset[invertedSide] = mathMax(clipOffset[invertedSide], mathFloor(options.lineWidth / 2) * 2); | 
|     }, | 
|      | 
|     /** | 
|      * Get the path for the axis line | 
|      */ | 
|     getLinePath: function (lineWidth) { | 
|         var chart = this.chart, | 
|             opposite = this.opposite, | 
|             offset = this.offset, | 
|             horiz = this.horiz, | 
|             lineLeft = this.left + (opposite ? this.width : 0) + offset, | 
|             lineTop = chart.chartHeight - this.bottom - (opposite ? this.height : 0) + offset; | 
|              | 
|         if (opposite) { | 
|             lineWidth *= -1; // crispify the other way - #1480, #1687 | 
|         } | 
|   | 
|         return chart.renderer.crispLine([ | 
|                 M, | 
|                 horiz ? | 
|                     this.left : | 
|                     lineLeft, | 
|                 horiz ? | 
|                     lineTop : | 
|                     this.top, | 
|                 L, | 
|                 horiz ? | 
|                     chart.chartWidth - this.right : | 
|                     lineLeft, | 
|                 horiz ? | 
|                     lineTop : | 
|                     chart.chartHeight - this.bottom | 
|             ], lineWidth); | 
|     }, | 
|      | 
|     /** | 
|      * Position the title | 
|      */ | 
|     getTitlePosition: function () { | 
|         // compute anchor points for each of the title align options | 
|         var horiz = this.horiz, | 
|             axisLeft = this.left, | 
|             axisTop = this.top, | 
|             axisLength = this.len, | 
|             axisTitleOptions = this.options.title,             | 
|             margin = horiz ? axisLeft : axisTop, | 
|             opposite = this.opposite, | 
|             offset = this.offset, | 
|             fontSize = pInt(axisTitleOptions.style.fontSize || 12), | 
|              | 
|             // the position in the length direction of the axis | 
|             alongAxis = { | 
|                 low: margin + (horiz ? 0 : axisLength), | 
|                 middle: margin + axisLength / 2, | 
|                 high: margin + (horiz ? axisLength : 0) | 
|             }[axisTitleOptions.align], | 
|      | 
|             // the position in the perpendicular direction of the axis | 
|             offAxis = (horiz ? axisTop + this.height : axisLeft) + | 
|                 (horiz ? 1 : -1) * // horizontal axis reverses the margin | 
|                 (opposite ? -1 : 1) * // so does opposite axes | 
|                 this.axisTitleMargin + | 
|                 (this.side === 2 ? fontSize : 0); | 
|   | 
|         return { | 
|             x: horiz ? | 
|                 alongAxis : | 
|                 offAxis + (opposite ? this.width : 0) + offset + | 
|                     (axisTitleOptions.x || 0), // x | 
|             y: horiz ? | 
|                 offAxis - (opposite ? this.height : 0) + offset : | 
|                 alongAxis + (axisTitleOptions.y || 0) // y | 
|         }; | 
|     }, | 
|      | 
|     /** | 
|      * Render the axis | 
|      */ | 
|     render: function () { | 
|         var axis = this, | 
|             chart = axis.chart, | 
|             renderer = chart.renderer, | 
|             options = axis.options, | 
|             isLog = axis.isLog, | 
|             isLinked = axis.isLinked, | 
|             tickPositions = axis.tickPositions, | 
|             axisTitle = axis.axisTitle, | 
|             stacks = axis.stacks, | 
|             ticks = axis.ticks, | 
|             minorTicks = axis.minorTicks, | 
|             alternateBands = axis.alternateBands, | 
|             stackLabelOptions = options.stackLabels, | 
|             alternateGridColor = options.alternateGridColor, | 
|             tickmarkOffset = axis.tickmarkOffset, | 
|             lineWidth = options.lineWidth, | 
|             linePath, | 
|             hasRendered = chart.hasRendered, | 
|             slideInTicks = hasRendered && defined(axis.oldMin) && !isNaN(axis.oldMin), | 
|             hasData = axis.hasData, | 
|             showAxis = axis.showAxis, | 
|             from, | 
|             to; | 
|   | 
|         // Mark all elements inActive before we go over and mark the active ones | 
|         each([ticks, minorTicks, alternateBands], function (coll) { | 
|             var pos; | 
|             for (pos in coll) { | 
|                 coll[pos].isActive = false; | 
|             } | 
|         }); | 
|   | 
|         // If the series has data draw the ticks. Else only the line and title | 
|         if (hasData || isLinked) { | 
|   | 
|             // minor ticks | 
|             if (axis.minorTickInterval && !axis.categories) { | 
|                 each(axis.getMinorTickPositions(), function (pos) { | 
|                     if (!minorTicks[pos]) { | 
|                         minorTicks[pos] = new Tick(axis, pos, 'minor'); | 
|                     } | 
|   | 
|                     // render new ticks in old position | 
|                     if (slideInTicks && minorTicks[pos].isNew) { | 
|                         minorTicks[pos].render(null, true); | 
|                     } | 
|   | 
|                     minorTicks[pos].render(null, false, 1); | 
|                 }); | 
|             } | 
|   | 
|             // Major ticks. Pull out the first item and render it last so that | 
|             // we can get the position of the neighbour label. #808. | 
|             if (tickPositions.length) { // #1300 | 
|                 each(tickPositions.slice(1).concat([tickPositions[0]]), function (pos, i) { | 
|      | 
|                     // Reorganize the indices | 
|                     i = (i === tickPositions.length - 1) ? 0 : i + 1; | 
|      | 
|                     // linked axes need an extra check to find out if | 
|                     if (!isLinked || (pos >= axis.min && pos <= axis.max)) { | 
|      | 
|                         if (!ticks[pos]) { | 
|                             ticks[pos] = new Tick(axis, pos); | 
|                         } | 
|      | 
|                         // render new ticks in old position | 
|                         if (slideInTicks && ticks[pos].isNew) { | 
|                             ticks[pos].render(i, true); | 
|                         } | 
|      | 
|                         ticks[pos].render(i, false, 1); | 
|                     } | 
|      | 
|                 }); | 
|                 // In a categorized axis, the tick marks are displayed between labels. So | 
|                 // we need to add a tick mark and grid line at the left edge of the X axis. | 
|                 if (tickmarkOffset && axis.min === 0) { | 
|                     if (!ticks[-1]) { | 
|                         ticks[-1] = new Tick(axis, -1, null, true); | 
|                     } | 
|                     ticks[-1].render(-1); | 
|                 } | 
|                  | 
|             } | 
|   | 
|             // alternate grid color | 
|             if (alternateGridColor) { | 
|                 each(tickPositions, function (pos, i) { | 
|                     if (i % 2 === 0 && pos < axis.max) { | 
|                         if (!alternateBands[pos]) { | 
|                             alternateBands[pos] = new PlotLineOrBand(axis); | 
|                         } | 
|                         from = pos + tickmarkOffset; // #949 | 
|                         to = tickPositions[i + 1] !== UNDEFINED ? tickPositions[i + 1] + tickmarkOffset : axis.max; | 
|                         alternateBands[pos].options = { | 
|                             from: isLog ? lin2log(from) : from, | 
|                             to: isLog ? lin2log(to) : to, | 
|                             color: alternateGridColor | 
|                         }; | 
|                         alternateBands[pos].render(); | 
|                         alternateBands[pos].isActive = true; | 
|                     } | 
|                 }); | 
|             } | 
|   | 
|             // custom plot lines and bands | 
|             if (!axis._addedPlotLB) { // only first time | 
|                 each((options.plotLines || []).concat(options.plotBands || []), function (plotLineOptions) { | 
|                     axis.addPlotBandOrLine(plotLineOptions); | 
|                 }); | 
|                 axis._addedPlotLB = true; | 
|             } | 
|   | 
|         } // end if hasData | 
|   | 
|         // Remove inactive ticks | 
|         each([ticks, minorTicks, alternateBands], function (coll) { | 
|             var pos,  | 
|                 i, | 
|                 forDestruction = [], | 
|                 delay = globalAnimation ? globalAnimation.duration || 500 : 0, | 
|                 destroyInactiveItems = function () { | 
|                     i = forDestruction.length; | 
|                     while (i--) { | 
|                         // When resizing rapidly, the same items may be destroyed in different timeouts, | 
|                         // or the may be reactivated | 
|                         if (coll[forDestruction[i]] && !coll[forDestruction[i]].isActive) { | 
|                             coll[forDestruction[i]].destroy(); | 
|                             delete coll[forDestruction[i]]; | 
|                         } | 
|                     } | 
|                      | 
|                 }; | 
|   | 
|             for (pos in coll) { | 
|   | 
|                 if (!coll[pos].isActive) { | 
|                     // Render to zero opacity | 
|                     coll[pos].render(pos, false, 0); | 
|                     coll[pos].isActive = false; | 
|                     forDestruction.push(pos); | 
|                 } | 
|             } | 
|   | 
|             // When the objects are finished fading out, destroy them | 
|             if (coll === alternateBands || !chart.hasRendered || !delay) { | 
|                 destroyInactiveItems(); | 
|             } else if (delay) { | 
|                 setTimeout(destroyInactiveItems, delay); | 
|             } | 
|         }); | 
|   | 
|         // Static items. As the axis group is cleared on subsequent calls | 
|         // to render, these items are added outside the group. | 
|         // axis line | 
|         if (lineWidth) { | 
|             linePath = axis.getLinePath(lineWidth); | 
|             if (!axis.axisLine) { | 
|                 axis.axisLine = renderer.path(linePath) | 
|                     .attr({ | 
|                         stroke: options.lineColor, | 
|                         'stroke-width': lineWidth, | 
|                         zIndex: 7 | 
|                     }) | 
|                     .add(axis.axisGroup); | 
|             } else { | 
|                 axis.axisLine.animate({ d: linePath }); | 
|             } | 
|   | 
|             // show or hide the line depending on options.showEmpty | 
|             axis.axisLine[showAxis ? 'show' : 'hide'](); | 
|         } | 
|   | 
|         if (axisTitle && showAxis) { | 
|              | 
|             axisTitle[axisTitle.isNew ? 'attr' : 'animate']( | 
|                 axis.getTitlePosition() | 
|             ); | 
|             axisTitle.isNew = false; | 
|         } | 
|   | 
|         // Stacked totals: | 
|         if (stackLabelOptions && stackLabelOptions.enabled) { | 
|             var stackKey, oneStack, stackCategory, | 
|                 stackTotalGroup = axis.stackTotalGroup; | 
|   | 
|             // Create a separate group for the stack total labels | 
|             if (!stackTotalGroup) { | 
|                 axis.stackTotalGroup = stackTotalGroup = | 
|                     renderer.g('stack-labels') | 
|                         .attr({ | 
|                             visibility: VISIBLE, | 
|                             zIndex: 6 | 
|                         }) | 
|                         .add(); | 
|             } | 
|   | 
|             // plotLeft/Top will change when y axis gets wider so we need to translate the | 
|             // stackTotalGroup at every render call. See bug #506 and #516 | 
|             stackTotalGroup.translate(chart.plotLeft, chart.plotTop); | 
|   | 
|             // Render each stack total | 
|             for (stackKey in stacks) { | 
|                 oneStack = stacks[stackKey]; | 
|                 for (stackCategory in oneStack) { | 
|                     oneStack[stackCategory].render(stackTotalGroup); | 
|                 } | 
|             } | 
|         } | 
|         // End stacked totals | 
|   | 
|         axis.isDirty = false; | 
|     }, | 
|   | 
|     /** | 
|      * Remove a plot band or plot line from the chart by id | 
|      * @param {Object} id | 
|      */ | 
|     removePlotBandOrLine: function (id) { | 
|         var plotLinesAndBands = this.plotLinesAndBands, | 
|             options = this.options, | 
|             userOptions = this.userOptions, | 
|             i = plotLinesAndBands.length; | 
|         while (i--) { | 
|             if (plotLinesAndBands[i].id === id) { | 
|                 plotLinesAndBands[i].destroy(); | 
|             } | 
|         } | 
|         each([options.plotLines || [], userOptions.plotLines || [], options.plotBands || [], userOptions.plotBands || []], function (arr) { | 
|             i = arr.length; | 
|             while (i--) { | 
|                 if (arr[i].id === id) { | 
|                     erase(arr, arr[i]); | 
|                 } | 
|             } | 
|         }); | 
|   | 
|     }, | 
|   | 
|     /** | 
|      * Update the axis title by options | 
|      */ | 
|     setTitle: function (newTitleOptions, redraw) { | 
|         this.update({ title: newTitleOptions }, redraw); | 
|     }, | 
|   | 
|     /** | 
|      * Redraw the axis to reflect changes in the data or axis extremes | 
|      */ | 
|     redraw: function () { | 
|         var axis = this, | 
|             chart = axis.chart, | 
|             pointer = chart.pointer; | 
|   | 
|         // hide tooltip and hover states | 
|         if (pointer.reset) { | 
|             pointer.reset(true); | 
|         } | 
|   | 
|         // render the axis | 
|         axis.render(); | 
|   | 
|         // move plot lines and bands | 
|         each(axis.plotLinesAndBands, function (plotLine) { | 
|             plotLine.render(); | 
|         }); | 
|   | 
|         // mark associated series as dirty and ready for redraw | 
|         each(axis.series, function (series) { | 
|             series.isDirty = true; | 
|         }); | 
|   | 
|     }, | 
|   | 
|     /** | 
|      * Build the stacks from top down | 
|      */ | 
|     buildStacks: function () { | 
|         var series = this.series, | 
|             i = series.length; | 
|         if (!this.isXAxis) { | 
|             while (i--) { | 
|                 series[i].setStackedPoints(); | 
|             } | 
|             // Loop up again to compute percent stack | 
|             if (this.usePercentage) { | 
|                 for (i = 0; i < series.length; i++) { | 
|                     series[i].setPercentStacks(); | 
|                 } | 
|             } | 
|         } | 
|     }, | 
|   | 
|     /** | 
|      * Set new axis categories and optionally redraw | 
|      * @param {Array} categories | 
|      * @param {Boolean} redraw | 
|      */ | 
|     setCategories: function (categories, redraw) { | 
|         this.update({ categories: categories }, redraw); | 
|     }, | 
|   | 
|     /** | 
|      * Destroys an Axis instance. | 
|      */ | 
|     destroy: function (keepEvents) { | 
|         var axis = this, | 
|             stacks = axis.stacks, | 
|             stackKey, | 
|             plotLinesAndBands = axis.plotLinesAndBands, | 
|             i; | 
|   | 
|         // Remove the events | 
|         if (!keepEvents) { | 
|             removeEvent(axis); | 
|         } | 
|   | 
|         // Destroy each stack total | 
|         for (stackKey in stacks) { | 
|             destroyObjectProperties(stacks[stackKey]); | 
|   | 
|             stacks[stackKey] = null; | 
|         } | 
|   | 
|         // Destroy collections | 
|         each([axis.ticks, axis.minorTicks, axis.alternateBands], function (coll) { | 
|             destroyObjectProperties(coll); | 
|         }); | 
|         i = plotLinesAndBands.length; | 
|         while (i--) { // #1975 | 
|             plotLinesAndBands[i].destroy(); | 
|         } | 
|   | 
|         // Destroy local variables | 
|         each(['stackTotalGroup', 'axisLine', 'axisGroup', 'gridGroup', 'labelGroup', 'axisTitle'], function (prop) { | 
|             if (axis[prop]) { | 
|                 axis[prop] = axis[prop].destroy(); | 
|             } | 
|         }); | 
|     } | 
|   | 
|      | 
| }; // end Axis | 
|   | 
| /** | 
|  * The tooltip object | 
|  * @param {Object} chart The chart instance | 
|  * @param {Object} options Tooltip options | 
|  */ | 
| function Tooltip() { | 
|     this.init.apply(this, arguments); | 
| } | 
|   | 
| Tooltip.prototype = { | 
|   | 
|     init: function (chart, options) { | 
|   | 
|         var borderWidth = options.borderWidth, | 
|             style = options.style, | 
|             padding = pInt(style.padding); | 
|   | 
|         // Save the chart and options | 
|         this.chart = chart; | 
|         this.options = options; | 
|   | 
|         // Keep track of the current series | 
|         //this.currentSeries = UNDEFINED; | 
|   | 
|         // List of crosshairs | 
|         this.crosshairs = []; | 
|   | 
|         // Current values of x and y when animating | 
|         this.now = { x: 0, y: 0 }; | 
|   | 
|         // The tooltip is initially hidden | 
|         this.isHidden = true; | 
|   | 
|   | 
|         // create the label | 
|         this.label = chart.renderer.label('', 0, 0, options.shape, null, null, options.useHTML, null, 'tooltip') | 
|             .attr({ | 
|                 padding: padding, | 
|                 fill: options.backgroundColor, | 
|                 'stroke-width': borderWidth, | 
|                 r: options.borderRadius, | 
|                 zIndex: 8 | 
|             }) | 
|             .css(style) | 
|             .css({ padding: 0 }) // Remove it from VML, the padding is applied as an attribute instead (#1117) | 
|             .add() | 
|             .attr({ y: -999 }); // #2301 | 
|   | 
|         // When using canVG the shadow shows up as a gray circle | 
|         // even if the tooltip is hidden. | 
|         if (!useCanVG) { | 
|             this.label.shadow(options.shadow); | 
|         } | 
|   | 
|         // Public property for getting the shared state. | 
|         this.shared = options.shared; | 
|     }, | 
|   | 
|     /** | 
|      * Destroy the tooltip and its elements. | 
|      */ | 
|     destroy: function () { | 
|         each(this.crosshairs, function (crosshair) { | 
|             if (crosshair) { | 
|                 crosshair.destroy(); | 
|             } | 
|         }); | 
|   | 
|         // Destroy and clear local variables | 
|         if (this.label) { | 
|             this.label = this.label.destroy(); | 
|         } | 
|         clearTimeout(this.hideTimer); | 
|         clearTimeout(this.tooltipTimeout); | 
|     }, | 
|   | 
|     /** | 
|      * Provide a soft movement for the tooltip | 
|      * | 
|      * @param {Number} x | 
|      * @param {Number} y | 
|      * @private | 
|      */ | 
|     move: function (x, y, anchorX, anchorY) { | 
|         var tooltip = this, | 
|             now = tooltip.now, | 
|             animate = tooltip.options.animation !== false && !tooltip.isHidden; | 
|   | 
|         // get intermediate values for animation | 
|         extend(now, { | 
|             x: animate ? (2 * now.x + x) / 3 : x, | 
|             y: animate ? (now.y + y) / 2 : y, | 
|             anchorX: animate ? (2 * now.anchorX + anchorX) / 3 : anchorX, | 
|             anchorY: animate ? (now.anchorY + anchorY) / 2 : anchorY | 
|         }); | 
|   | 
|         // move to the intermediate value | 
|         tooltip.label.attr(now); | 
|   | 
|          | 
|         // run on next tick of the mouse tracker | 
|         if (animate && (mathAbs(x - now.x) > 1 || mathAbs(y - now.y) > 1)) { | 
|          | 
|             // never allow two timeouts | 
|             clearTimeout(this.tooltipTimeout); | 
|              | 
|             // set the fixed interval ticking for the smooth tooltip | 
|             this.tooltipTimeout = setTimeout(function () { | 
|                 // The interval function may still be running during destroy, so check that the chart is really there before calling. | 
|                 if (tooltip) { | 
|                     tooltip.move(x, y, anchorX, anchorY); | 
|                 } | 
|             }, 32); | 
|              | 
|         } | 
|     }, | 
|   | 
|     /** | 
|      * Hide the tooltip | 
|      */ | 
|     hide: function () { | 
|         var tooltip = this, | 
|             hoverPoints; | 
|          | 
|         clearTimeout(this.hideTimer); // disallow duplicate timers (#1728, #1766) | 
|         if (!this.isHidden) { | 
|             hoverPoints = this.chart.hoverPoints; | 
|   | 
|             this.hideTimer = setTimeout(function () { | 
|                 tooltip.label.fadeOut(); | 
|                 tooltip.isHidden = true; | 
|             }, pick(this.options.hideDelay, 500)); | 
|   | 
|             // hide previous hoverPoints and set new | 
|             if (hoverPoints) { | 
|                 each(hoverPoints, function (point) { | 
|                     point.setState(); | 
|                 }); | 
|             } | 
|   | 
|             this.chart.hoverPoints = null; | 
|         } | 
|     }, | 
|   | 
|     /** | 
|      * Hide the crosshairs | 
|      */ | 
|     hideCrosshairs: function () { | 
|         each(this.crosshairs, function (crosshair) { | 
|             if (crosshair) { | 
|                 crosshair.hide(); | 
|             } | 
|         }); | 
|     }, | 
|      | 
|     /**  | 
|      * Extendable method to get the anchor position of the tooltip | 
|      * from a point or set of points | 
|      */ | 
|     getAnchor: function (points, mouseEvent) { | 
|         var ret, | 
|             chart = this.chart, | 
|             inverted = chart.inverted, | 
|             plotTop = chart.plotTop, | 
|             plotX = 0, | 
|             plotY = 0, | 
|             yAxis; | 
|          | 
|         points = splat(points); | 
|          | 
|         // Pie uses a special tooltipPos | 
|         ret = points[0].tooltipPos; | 
|          | 
|         // When tooltip follows mouse, relate the position to the mouse | 
|         if (this.followPointer && mouseEvent) { | 
|             if (mouseEvent.chartX === UNDEFINED) { | 
|                 mouseEvent = chart.pointer.normalize(mouseEvent); | 
|             } | 
|             ret = [ | 
|                 mouseEvent.chartX - chart.plotLeft, | 
|                 mouseEvent.chartY - plotTop | 
|             ]; | 
|         } | 
|         // When shared, use the average position | 
|         if (!ret) { | 
|             each(points, function (point) { | 
|                 yAxis = point.series.yAxis; | 
|                 plotX += point.plotX; | 
|                 plotY += (point.plotLow ? (point.plotLow + point.plotHigh) / 2 : point.plotY) + | 
|                     (!inverted && yAxis ? yAxis.top - plotTop : 0); // #1151 | 
|             }); | 
|              | 
|             plotX /= points.length; | 
|             plotY /= points.length; | 
|              | 
|             ret = [ | 
|                 inverted ? chart.plotWidth - plotY : plotX, | 
|                 this.shared && !inverted && points.length > 1 && mouseEvent ?  | 
|                     mouseEvent.chartY - plotTop : // place shared tooltip next to the mouse (#424) | 
|                     inverted ? chart.plotHeight - plotX : plotY | 
|             ]; | 
|         } | 
|   | 
|         return map(ret, mathRound); | 
|     }, | 
|      | 
|     /** | 
|      * Place the tooltip in a chart without spilling over | 
|      * and not covering the point it self. | 
|      */ | 
|     getPosition: function (boxWidth, boxHeight, point) { | 
|          | 
|         // Set up the variables | 
|         var chart = this.chart, | 
|             plotLeft = chart.plotLeft, | 
|             plotTop = chart.plotTop, | 
|             plotWidth = chart.plotWidth, | 
|             plotHeight = chart.plotHeight, | 
|             distance = pick(this.options.distance, 12), | 
|             pointX = point.plotX, | 
|             pointY = point.plotY, | 
|             x = pointX + plotLeft + (chart.inverted ? distance : -boxWidth - distance), | 
|             y = pointY - boxHeight + plotTop + 15, // 15 means the point is 15 pixels up from the bottom of the tooltip | 
|             alignedRight; | 
|      | 
|         // It is too far to the left, adjust it | 
|         if (x < 7) { | 
|             x = plotLeft + mathMax(pointX, 0) + distance; | 
|         } | 
|      | 
|         // Test to see if the tooltip is too far to the right, | 
|         // if it is, move it back to be inside and then up to not cover the point. | 
|         if ((x + boxWidth) > (plotLeft + plotWidth)) { | 
|             x -= (x + boxWidth) - (plotLeft + plotWidth); | 
|             y = pointY - boxHeight + plotTop - distance; | 
|             alignedRight = true; | 
|         } | 
|      | 
|         // If it is now above the plot area, align it to the top of the plot area | 
|         if (y < plotTop + 5) { | 
|             y = plotTop + 5; | 
|      | 
|             // If the tooltip is still covering the point, move it below instead | 
|             if (alignedRight && pointY >= y && pointY <= (y + boxHeight)) { | 
|                 y = pointY + plotTop + distance; // below | 
|             } | 
|         }  | 
|      | 
|         // Now if the tooltip is below the chart, move it up. It's better to cover the | 
|         // point than to disappear outside the chart. #834. | 
|         if (y + boxHeight > plotTop + plotHeight) { | 
|             y = mathMax(plotTop, plotTop + plotHeight - boxHeight - distance); // below | 
|         } | 
|      | 
|         return {x: x, y: y}; | 
|     }, | 
|   | 
|     /** | 
|      * In case no user defined formatter is given, this will be used. Note that the context | 
|      * here is an object holding point, series, x, y etc. | 
|      */ | 
|     defaultFormatter: function (tooltip) { | 
|         var items = this.points || splat(this), | 
|             series = items[0].series, | 
|             s; | 
|   | 
|         // build the header | 
|         s = [series.tooltipHeaderFormatter(items[0])]; | 
|   | 
|         // build the values | 
|         each(items, function (item) { | 
|             series = item.series; | 
|             s.push((series.tooltipFormatter && series.tooltipFormatter(item)) || | 
|                 item.point.tooltipFormatter(series.tooltipOptions.pointFormat)); | 
|         }); | 
|   | 
|         // footer | 
|         s.push(tooltip.options.footerFormat || ''); | 
|   | 
|         return s.join(''); | 
|     }, | 
|   | 
|     /** | 
|      * Refresh the tooltip's text and position. | 
|      * @param {Object} point | 
|      */ | 
|     refresh: function (point, mouseEvent) { | 
|         var tooltip = this, | 
|             chart = tooltip.chart, | 
|             label = tooltip.label, | 
|             options = tooltip.options, | 
|             x, | 
|             y, | 
|             anchor, | 
|             textConfig = {}, | 
|             text, | 
|             pointConfig = [], | 
|             formatter = options.formatter || tooltip.defaultFormatter, | 
|             hoverPoints = chart.hoverPoints, | 
|             borderColor, | 
|             crosshairsOptions = options.crosshairs, | 
|             shared = tooltip.shared, | 
|             currentSeries; | 
|              | 
|         clearTimeout(this.hideTimer); | 
|          | 
|         // get the reference point coordinates (pie charts use tooltipPos) | 
|         tooltip.followPointer = splat(point)[0].series.tooltipOptions.followPointer; | 
|         anchor = tooltip.getAnchor(point, mouseEvent); | 
|         x = anchor[0]; | 
|         y = anchor[1]; | 
|   | 
|         // shared tooltip, array is sent over | 
|         if (shared && !(point.series && point.series.noSharedTooltip)) { | 
|              | 
|             // hide previous hoverPoints and set new | 
|              | 
|             chart.hoverPoints = point; | 
|             if (hoverPoints) { | 
|                 each(hoverPoints, function (point) { | 
|                     point.setState(); | 
|                 }); | 
|             } | 
|   | 
|             each(point, function (item) { | 
|                 item.setState(HOVER_STATE); | 
|   | 
|                 pointConfig.push(item.getLabelConfig()); | 
|             }); | 
|   | 
|             textConfig = { | 
|                 x: point[0].category, | 
|                 y: point[0].y | 
|             }; | 
|             textConfig.points = pointConfig; | 
|             point = point[0]; | 
|   | 
|         // single point tooltip | 
|         } else { | 
|             textConfig = point.getLabelConfig(); | 
|         } | 
|         text = formatter.call(textConfig, tooltip); | 
|   | 
|         // register the current series | 
|         currentSeries = point.series; | 
|   | 
|         // update the inner HTML | 
|         if (text === false) { | 
|             this.hide(); | 
|         } else { | 
|   | 
|             // show it | 
|             if (tooltip.isHidden) { | 
|                 stop(label); | 
|                 label.attr('opacity', 1).show(); | 
|             } | 
|   | 
|             // update text | 
|             label.attr({ | 
|                 text: text | 
|             }); | 
|   | 
|             // set the stroke color of the box | 
|             borderColor = options.borderColor || point.color || currentSeries.color || '#606060'; | 
|             label.attr({ | 
|                 stroke: borderColor | 
|             }); | 
|              | 
|             tooltip.updatePosition({ plotX: x, plotY: y }); | 
|          | 
|             this.isHidden = false; | 
|         } | 
|   | 
|         // crosshairs | 
|         if (crosshairsOptions) { | 
|             crosshairsOptions = splat(crosshairsOptions); // [x, y] | 
|   | 
|             var path, | 
|                 i = crosshairsOptions.length, | 
|                 attribs, | 
|                 axis, | 
|                 val, | 
|                 series; | 
|   | 
|             while (i--) { | 
|                 series = point.series; | 
|                 axis = series[i ? 'yAxis' : 'xAxis']; | 
|                 if (crosshairsOptions[i] && axis) { | 
|                     val = i ? pick(point.stackY, point.y) : point.x; // #814 | 
|                     if (axis.isLog) { // #1671 | 
|                         val = log2lin(val); | 
|                     } | 
|                     if (i === 1 && series.modifyValue) { // #1205, #2316 | 
|                         val = series.modifyValue(val); | 
|                     } | 
|   | 
|                     path = axis.getPlotLinePath( | 
|                         val, | 
|                         1 | 
|                     ); | 
|   | 
|                     if (tooltip.crosshairs[i]) { | 
|                         tooltip.crosshairs[i].attr({ d: path, visibility: VISIBLE }); | 
|                     } else { | 
|                         attribs = { | 
|                             'stroke-width': crosshairsOptions[i].width || 1, | 
|                             stroke: crosshairsOptions[i].color || '#C0C0C0', | 
|                             zIndex: crosshairsOptions[i].zIndex || 2 | 
|                         }; | 
|                         if (crosshairsOptions[i].dashStyle) { | 
|                             attribs.dashstyle = crosshairsOptions[i].dashStyle; | 
|                         } | 
|                         tooltip.crosshairs[i] = chart.renderer.path(path) | 
|                             .attr(attribs) | 
|                             .add(); | 
|                     } | 
|                 } | 
|             } | 
|         } | 
|         fireEvent(chart, 'tooltipRefresh', { | 
|                 text: text, | 
|                 x: x + chart.plotLeft, | 
|                 y: y + chart.plotTop, | 
|                 borderColor: borderColor | 
|             }); | 
|     }, | 
|      | 
|     /** | 
|      * Find the new position and perform the move | 
|      */ | 
|     updatePosition: function (point) { | 
|         var chart = this.chart, | 
|             label = this.label,  | 
|             pos = (this.options.positioner || this.getPosition).call( | 
|                 this, | 
|                 label.width, | 
|                 label.height, | 
|                 point | 
|             ); | 
|   | 
|         // do the move | 
|         this.move( | 
|             mathRound(pos.x),  | 
|             mathRound(pos.y),  | 
|             point.plotX + chart.plotLeft,  | 
|             point.plotY + chart.plotTop | 
|         ); | 
|     } | 
| }; | 
| /** | 
|  * The mouse tracker object. All methods starting with "on" are primary DOM event handlers.  | 
|  * Subsequent methods should be named differently from what they are doing. | 
|  * @param {Object} chart The Chart instance | 
|  * @param {Object} options The root options object | 
|  */ | 
| function Pointer(chart, options) { | 
|     this.init(chart, options); | 
| } | 
|   | 
| Pointer.prototype = { | 
|     /** | 
|      * Initialize Pointer | 
|      */ | 
|     init: function (chart, options) { | 
|          | 
|         var chartOptions = options.chart, | 
|             chartEvents = chartOptions.events, | 
|             zoomType = useCanVG ? '' : chartOptions.zoomType, | 
|             inverted = chart.inverted, | 
|             zoomX, | 
|             zoomY; | 
|   | 
|         // Store references | 
|         this.options = options; | 
|         this.chart = chart; | 
|          | 
|         // Zoom status | 
|         this.zoomX = zoomX = /x/.test(zoomType); | 
|         this.zoomY = zoomY = /y/.test(zoomType); | 
|         this.zoomHor = (zoomX && !inverted) || (zoomY && inverted); | 
|         this.zoomVert = (zoomY && !inverted) || (zoomX && inverted); | 
|   | 
|         // Do we need to handle click on a touch device? | 
|         this.runChartClick = chartEvents && !!chartEvents.click; | 
|   | 
|         this.pinchDown = []; | 
|         this.lastValidTouch = {}; | 
|   | 
|         if (options.tooltip.enabled) { | 
|             chart.tooltip = new Tooltip(chart, options.tooltip); | 
|         } | 
|   | 
|         this.setDOMEvents(); | 
|     },  | 
|   | 
|     /** | 
|      * Add crossbrowser support for chartX and chartY | 
|      * @param {Object} e The event object in standard browsers | 
|      */ | 
|     normalize: function (e, chartPosition) { | 
|         var chartX, | 
|             chartY, | 
|             ePos; | 
|   | 
|         // common IE normalizing | 
|         e = e || win.event; | 
|         if (!e.target) { | 
|             e.target = e.srcElement; | 
|         } | 
|   | 
|         // Framework specific normalizing (#1165) | 
|         e = washMouseEvent(e); | 
|          | 
|         // iOS | 
|         ePos = e.touches ? e.touches.item(0) : e; | 
|   | 
|         // Get mouse position | 
|         if (!chartPosition) { | 
|             this.chartPosition = chartPosition = offset(this.chart.container); | 
|         } | 
|   | 
|         // chartX and chartY | 
|         if (ePos.pageX === UNDEFINED) { // IE < 9. #886. | 
|             chartX = mathMax(e.x, e.clientX - chartPosition.left); // #2005, #2129: the second case is  | 
|                 // for IE10 quirks mode within framesets | 
|             chartY = e.y; | 
|         } else { | 
|             chartX = ePos.pageX - chartPosition.left; | 
|             chartY = ePos.pageY - chartPosition.top; | 
|         } | 
|   | 
|         return extend(e, { | 
|             chartX: mathRound(chartX), | 
|             chartY: mathRound(chartY) | 
|         }); | 
|     }, | 
|   | 
|     /** | 
|      * Get the click position in terms of axis values. | 
|      * | 
|      * @param {Object} e A pointer event | 
|      */ | 
|     getCoordinates: function (e) { | 
|         var coordinates = { | 
|                 xAxis: [], | 
|                 yAxis: [] | 
|             }; | 
|   | 
|         each(this.chart.axes, function (axis) { | 
|             coordinates[axis.isXAxis ? 'xAxis' : 'yAxis'].push({ | 
|                 axis: axis, | 
|                 value: axis.toValue(e[axis.horiz ? 'chartX' : 'chartY']) | 
|             }); | 
|         }); | 
|         return coordinates; | 
|     }, | 
|      | 
|     /** | 
|      * Return the index in the tooltipPoints array, corresponding to pixel position in  | 
|      * the plot area. | 
|      */ | 
|     getIndex: function (e) { | 
|         var chart = this.chart; | 
|         return chart.inverted ?  | 
|             chart.plotHeight + chart.plotTop - e.chartY :  | 
|             e.chartX - chart.plotLeft; | 
|     }, | 
|   | 
|     /** | 
|      * With line type charts with a single tracker, get the point closest to the mouse. | 
|      * Run Point.onMouseOver and display tooltip for the point or points. | 
|      */ | 
|     runPointActions: function (e) { | 
|         var pointer = this, | 
|             chart = pointer.chart, | 
|             series = chart.series, | 
|             tooltip = chart.tooltip, | 
|             point, | 
|             points, | 
|             hoverPoint = chart.hoverPoint, | 
|             hoverSeries = chart.hoverSeries, | 
|             i, | 
|             j, | 
|             distance = chart.chartWidth, | 
|             index = pointer.getIndex(e), | 
|             anchor; | 
|   | 
|         // shared tooltip | 
|         if (tooltip && pointer.options.tooltip.shared && !(hoverSeries && hoverSeries.noSharedTooltip)) { | 
|             points = []; | 
|   | 
|             // loop over all series and find the ones with points closest to the mouse | 
|             i = series.length; | 
|             for (j = 0; j < i; j++) { | 
|                 if (series[j].visible && | 
|                         series[j].options.enableMouseTracking !== false && | 
|                         !series[j].noSharedTooltip && series[j].tooltipPoints.length) { | 
|                     point = series[j].tooltipPoints[index]; | 
|                     if (point && point.series) { // not a dummy point, #1544 | 
|                         point._dist = mathAbs(index - point.clientX); | 
|                         distance = mathMin(distance, point._dist); | 
|                         points.push(point); | 
|                     } | 
|                 } | 
|             } | 
|             // remove furthest points | 
|             i = points.length; | 
|             while (i--) { | 
|                 if (points[i]._dist > distance) { | 
|                     points.splice(i, 1); | 
|                 } | 
|             } | 
|             // refresh the tooltip if necessary | 
|             if (points.length && (points[0].clientX !== pointer.hoverX)) { | 
|                 tooltip.refresh(points, e); | 
|                 pointer.hoverX = points[0].clientX; | 
|             } | 
|         } | 
|   | 
|         // separate tooltip and general mouse events | 
|         if (hoverSeries && hoverSeries.tracker) { // only use for line-type series with common tracker | 
|   | 
|             // get the point | 
|             point = hoverSeries.tooltipPoints[index]; | 
|   | 
|             // a new point is hovered, refresh the tooltip | 
|             if (point && point !== hoverPoint) { | 
|   | 
|                 // trigger the events | 
|                 point.onMouseOver(e); | 
|   | 
|             } | 
|              | 
|         } else if (tooltip && tooltip.followPointer && !tooltip.isHidden) { | 
|             anchor = tooltip.getAnchor([{}], e); | 
|             tooltip.updatePosition({ plotX: anchor[0], plotY: anchor[1] }); | 
|         } | 
|     }, | 
|   | 
|   | 
|   | 
|     /** | 
|      * Reset the tracking by hiding the tooltip, the hover series state and the hover point | 
|      *  | 
|      * @param allowMove {Boolean} Instead of destroying the tooltip altogether, allow moving it if possible | 
|      */ | 
|     reset: function (allowMove) { | 
|         var pointer = this, | 
|             chart = pointer.chart, | 
|             hoverSeries = chart.hoverSeries, | 
|             hoverPoint = chart.hoverPoint, | 
|             tooltip = chart.tooltip, | 
|             tooltipPoints = tooltip && tooltip.shared ? chart.hoverPoints : hoverPoint; | 
|              | 
|         // Narrow in allowMove | 
|         allowMove = allowMove && tooltip && tooltipPoints; | 
|              | 
|         // Check if the points have moved outside the plot area, #1003 | 
|         if (allowMove && splat(tooltipPoints)[0].plotX === UNDEFINED) { | 
|             allowMove = false; | 
|         }     | 
|   | 
|         // Just move the tooltip, #349 | 
|         if (allowMove) { | 
|             tooltip.refresh(tooltipPoints); | 
|   | 
|         // Full reset | 
|         } else { | 
|   | 
|             if (hoverPoint) { | 
|                 hoverPoint.onMouseOut(); | 
|             } | 
|   | 
|             if (hoverSeries) { | 
|                 hoverSeries.onMouseOut(); | 
|             } | 
|   | 
|             if (tooltip) { | 
|                 tooltip.hide(); | 
|                 tooltip.hideCrosshairs(); | 
|             } | 
|   | 
|             pointer.hoverX = null; | 
|   | 
|         } | 
|     }, | 
|   | 
|     /** | 
|      * Scale series groups to a certain scale and translation | 
|      */ | 
|     scaleGroups: function (attribs, clip) { | 
|   | 
|         var chart = this.chart, | 
|             seriesAttribs; | 
|   | 
|         // Scale each series | 
|         each(chart.series, function (series) { | 
|             seriesAttribs = attribs || series.getPlotBox(); // #1701 | 
|             if (series.xAxis && series.xAxis.zoomEnabled) { | 
|                 series.group.attr(seriesAttribs); | 
|                 if (series.markerGroup) { | 
|                     series.markerGroup.attr(seriesAttribs); | 
|                     series.markerGroup.clip(clip ? chart.clipRect : null); | 
|                 } | 
|                 if (series.dataLabelsGroup) { | 
|                     series.dataLabelsGroup.attr(seriesAttribs); | 
|                 } | 
|             } | 
|         }); | 
|          | 
|         // Clip | 
|         chart.clipRect.attr(clip || chart.clipBox); | 
|     }, | 
|   | 
|     /** | 
|      * Run translation operations for each direction (horizontal and vertical) independently | 
|      */ | 
|     pinchTranslateDirection: function (horiz, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch) { | 
|         var chart = this.chart, | 
|             xy = horiz ? 'x' : 'y', | 
|             XY = horiz ? 'X' : 'Y', | 
|             sChartXY = 'chart' + XY, | 
|             wh = horiz ? 'width' : 'height', | 
|             plotLeftTop = chart['plot' + (horiz ? 'Left' : 'Top')], | 
|             selectionWH, | 
|             selectionXY, | 
|             clipXY, | 
|             scale = 1, | 
|             inverted = chart.inverted, | 
|             bounds = chart.bounds[horiz ? 'h' : 'v'], | 
|             singleTouch = pinchDown.length === 1, | 
|             touch0Start = pinchDown[0][sChartXY], | 
|             touch0Now = touches[0][sChartXY], | 
|             touch1Start = !singleTouch && pinchDown[1][sChartXY], | 
|             touch1Now = !singleTouch && touches[1][sChartXY], | 
|             outOfBounds, | 
|             transformScale, | 
|             scaleKey, | 
|             setScale = function () { | 
|                 if (!singleTouch && mathAbs(touch0Start - touch1Start) > 20) { // Don't zoom if fingers are too close on this axis | 
|                     scale = mathAbs(touch0Now - touch1Now) / mathAbs(touch0Start - touch1Start);     | 
|                 } | 
|                  | 
|                 clipXY = ((plotLeftTop - touch0Now) / scale) + touch0Start; | 
|                 selectionWH = chart['plot' + (horiz ? 'Width' : 'Height')] / scale; | 
|             }; | 
|   | 
|         // Set the scale, first pass | 
|         setScale(); | 
|   | 
|         selectionXY = clipXY; // the clip position (x or y) is altered if out of bounds, the selection position is not | 
|   | 
|         // Out of bounds | 
|         if (selectionXY < bounds.min) { | 
|             selectionXY = bounds.min; | 
|             outOfBounds = true; | 
|         } else if (selectionXY + selectionWH > bounds.max) { | 
|             selectionXY = bounds.max - selectionWH; | 
|             outOfBounds = true; | 
|         } | 
|          | 
|         // Is the chart dragged off its bounds, determined by dataMin and dataMax? | 
|         if (outOfBounds) { | 
|   | 
|             // Modify the touchNow position in order to create an elastic drag movement. This indicates | 
|             // to the user that the chart is responsive but can't be dragged further. | 
|             touch0Now -= 0.8 * (touch0Now - lastValidTouch[xy][0]); | 
|             if (!singleTouch) { | 
|                 touch1Now -= 0.8 * (touch1Now - lastValidTouch[xy][1]); | 
|             } | 
|   | 
|             // Set the scale, second pass to adapt to the modified touchNow positions | 
|             setScale(); | 
|   | 
|         } else { | 
|             lastValidTouch[xy] = [touch0Now, touch1Now]; | 
|         } | 
|   | 
|          | 
|         // Set geometry for clipping, selection and transformation | 
|         if (!inverted) { // TODO: implement clipping for inverted charts | 
|             clip[xy] = clipXY - plotLeftTop; | 
|             clip[wh] = selectionWH; | 
|         } | 
|         scaleKey = inverted ? (horiz ? 'scaleY' : 'scaleX') : 'scale' + XY; | 
|         transformScale = inverted ? 1 / scale : scale; | 
|   | 
|         selectionMarker[wh] = selectionWH; | 
|         selectionMarker[xy] = selectionXY; | 
|         transform[scaleKey] = scale; | 
|         transform['translate' + XY] = (transformScale * plotLeftTop) + (touch0Now - (transformScale * touch0Start)); | 
|     }, | 
|      | 
|     /** | 
|      * Handle touch events with two touches | 
|      */ | 
|     pinch: function (e) { | 
|   | 
|         var self = this, | 
|             chart = self.chart, | 
|             pinchDown = self.pinchDown, | 
|             followTouchMove = chart.tooltip && chart.tooltip.options.followTouchMove, | 
|             touches = e.touches, | 
|             touchesLength = touches.length, | 
|             lastValidTouch = self.lastValidTouch, | 
|             zoomHor = self.zoomHor || self.pinchHor, | 
|             zoomVert = self.zoomVert || self.pinchVert, | 
|             hasZoom = zoomHor || zoomVert, | 
|             selectionMarker = self.selectionMarker, | 
|             transform = {}, | 
|             fireClickEvent = touchesLength === 1 && ((self.inClass(e.target, PREFIX + 'tracker') &&  | 
|                 chart.runTrackerClick) || chart.runChartClick), | 
|             clip = {}; | 
|   | 
|         // On touch devices, only proceed to trigger click if a handler is defined | 
|         if ((hasZoom || followTouchMove) && !fireClickEvent) { | 
|             e.preventDefault(); | 
|         } | 
|          | 
|         // Normalize each touch | 
|         map(touches, function (e) { | 
|             return self.normalize(e); | 
|         }); | 
|              | 
|         // Register the touch start position | 
|         if (e.type === 'touchstart') { | 
|             each(touches, function (e, i) { | 
|                 pinchDown[i] = { chartX: e.chartX, chartY: e.chartY }; | 
|             }); | 
|             lastValidTouch.x = [pinchDown[0].chartX, pinchDown[1] && pinchDown[1].chartX]; | 
|             lastValidTouch.y = [pinchDown[0].chartY, pinchDown[1] && pinchDown[1].chartY]; | 
|   | 
|             // Identify the data bounds in pixels | 
|             each(chart.axes, function (axis) { | 
|                 if (axis.zoomEnabled) { | 
|                     var bounds = chart.bounds[axis.horiz ? 'h' : 'v'], | 
|                         minPixelPadding = axis.minPixelPadding, | 
|                         min = axis.toPixels(axis.dataMin), | 
|                         max = axis.toPixels(axis.dataMax), | 
|                         absMin = mathMin(min, max), | 
|                         absMax = mathMax(min, max); | 
|   | 
|                     // Store the bounds for use in the touchmove handler | 
|                     bounds.min = mathMin(axis.pos, absMin - minPixelPadding); | 
|                     bounds.max = mathMax(axis.pos + axis.len, absMax + minPixelPadding); | 
|                 } | 
|             }); | 
|          | 
|         // Event type is touchmove, handle panning and pinching | 
|         } else if (pinchDown.length) { // can be 0 when releasing, if touchend fires first | 
|              | 
|   | 
|             // Set the marker | 
|             if (!selectionMarker) { | 
|                 self.selectionMarker = selectionMarker = extend({ | 
|                     destroy: noop | 
|                 }, chart.plotBox); | 
|             } | 
|   | 
|              | 
|   | 
|             if (zoomHor) { | 
|                 self.pinchTranslateDirection(true, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch); | 
|             } | 
|             if (zoomVert) { | 
|                 self.pinchTranslateDirection(false, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch); | 
|             } | 
|   | 
|             self.hasPinched = hasZoom; | 
|   | 
|             // Scale and translate the groups to provide visual feedback during pinching | 
|             self.scaleGroups(transform, clip); | 
|              | 
|             // Optionally move the tooltip on touchmove | 
|             if (!hasZoom && followTouchMove && touchesLength === 1) { | 
|                 this.runPointActions(self.normalize(e)); | 
|             } | 
|         } | 
|     }, | 
|   | 
|     /** | 
|      * Start a drag operation | 
|      */ | 
|     dragStart: function (e) { | 
|         var chart = this.chart; | 
|   | 
|         // Record the start position | 
|         chart.mouseIsDown = e.type; | 
|         chart.cancelClick = false; | 
|         chart.mouseDownX = this.mouseDownX = e.chartX; | 
|         chart.mouseDownY = this.mouseDownY = e.chartY; | 
|     }, | 
|   | 
|     /** | 
|      * Perform a drag operation in response to a mousemove event while the mouse is down | 
|      */ | 
|     drag: function (e) { | 
|   | 
|         var chart = this.chart, | 
|             chartOptions = chart.options.chart, | 
|             chartX = e.chartX, | 
|             chartY = e.chartY, | 
|             zoomHor = this.zoomHor, | 
|             zoomVert = this.zoomVert, | 
|             plotLeft = chart.plotLeft, | 
|             plotTop = chart.plotTop, | 
|             plotWidth = chart.plotWidth, | 
|             plotHeight = chart.plotHeight, | 
|             clickedInside, | 
|             size, | 
|             mouseDownX = this.mouseDownX, | 
|             mouseDownY = this.mouseDownY; | 
|   | 
|         // If the mouse is outside the plot area, adjust to cooordinates | 
|         // inside to prevent the selection marker from going outside | 
|         if (chartX < plotLeft) { | 
|             chartX = plotLeft; | 
|         } else if (chartX > plotLeft + plotWidth) { | 
|             chartX = plotLeft + plotWidth; | 
|         } | 
|   | 
|         if (chartY < plotTop) { | 
|             chartY = plotTop; | 
|         } else if (chartY > plotTop + plotHeight) { | 
|             chartY = plotTop + plotHeight; | 
|         } | 
|          | 
|         // determine if the mouse has moved more than 10px | 
|         this.hasDragged = Math.sqrt( | 
|             Math.pow(mouseDownX - chartX, 2) + | 
|             Math.pow(mouseDownY - chartY, 2) | 
|         ); | 
|         if (this.hasDragged > 10) { | 
|             clickedInside = chart.isInsidePlot(mouseDownX - plotLeft, mouseDownY - plotTop); | 
|   | 
|             // make a selection | 
|             if (chart.hasCartesianSeries && (this.zoomX || this.zoomY) && clickedInside) { | 
|                 if (!this.selectionMarker) { | 
|                     this.selectionMarker = chart.renderer.rect( | 
|                         plotLeft, | 
|                         plotTop, | 
|                         zoomHor ? 1 : plotWidth, | 
|                         zoomVert ? 1 : plotHeight, | 
|                         0 | 
|                     ) | 
|                     .attr({ | 
|                         fill: chartOptions.selectionMarkerFill || 'rgba(69,114,167,0.25)', | 
|                         zIndex: 7 | 
|                     }) | 
|                     .add(); | 
|                 } | 
|             } | 
|   | 
|             // adjust the width of the selection marker | 
|             if (this.selectionMarker && zoomHor) { | 
|                 size = chartX - mouseDownX; | 
|                 this.selectionMarker.attr({ | 
|                     width: mathAbs(size), | 
|                     x: (size > 0 ? 0 : size) + mouseDownX | 
|                 }); | 
|             } | 
|             // adjust the height of the selection marker | 
|             if (this.selectionMarker && zoomVert) { | 
|                 size = chartY - mouseDownY; | 
|                 this.selectionMarker.attr({ | 
|                     height: mathAbs(size), | 
|                     y: (size > 0 ? 0 : size) + mouseDownY | 
|                 }); | 
|             } | 
|   | 
|             // panning | 
|             if (clickedInside && !this.selectionMarker && chartOptions.panning) { | 
|                 chart.pan(e, chartOptions.panning); | 
|             } | 
|         } | 
|     }, | 
|   | 
|     /** | 
|      * On mouse up or touch end across the entire document, drop the selection. | 
|      */ | 
|     drop: function (e) { | 
|         var chart = this.chart, | 
|             hasPinched = this.hasPinched; | 
|   | 
|         if (this.selectionMarker) { | 
|             var selectionData = { | 
|                     xAxis: [], | 
|                     yAxis: [], | 
|                     originalEvent: e.originalEvent || e | 
|                 }, | 
|                 selectionBox = this.selectionMarker, | 
|                 selectionLeft = selectionBox.x, | 
|                 selectionTop = selectionBox.y, | 
|                 runZoom; | 
|             // a selection has been made | 
|             if (this.hasDragged || hasPinched) { | 
|   | 
|                 // record each axis' min and max | 
|                 each(chart.axes, function (axis) { | 
|                     if (axis.zoomEnabled) { | 
|                         var horiz = axis.horiz, | 
|                             selectionMin = axis.toValue((horiz ? selectionLeft : selectionTop)), | 
|                             selectionMax = axis.toValue((horiz ? selectionLeft + selectionBox.width : selectionTop + selectionBox.height)); | 
|   | 
|                         if (!isNaN(selectionMin) && !isNaN(selectionMax)) { // #859 | 
|                             selectionData[axis.xOrY + 'Axis'].push({ | 
|                                 axis: axis, | 
|                                 min: mathMin(selectionMin, selectionMax), // for reversed axes, | 
|                                 max: mathMax(selectionMin, selectionMax) | 
|                             }); | 
|                             runZoom = true; | 
|                         } | 
|                     } | 
|                 }); | 
|                 if (runZoom) { | 
|                     fireEvent(chart, 'selection', selectionData, function (args) {  | 
|                         chart.zoom(extend(args, hasPinched ? { animation: false } : null));  | 
|                     }); | 
|                 } | 
|   | 
|             } | 
|             this.selectionMarker = this.selectionMarker.destroy(); | 
|   | 
|             // Reset scaling preview | 
|             if (hasPinched) { | 
|                 this.scaleGroups(); | 
|             } | 
|         } | 
|   | 
|         // Reset all | 
|         if (chart) { // it may be destroyed on mouse up - #877 | 
|             css(chart.container, { cursor: chart._cursor }); | 
|             chart.cancelClick = this.hasDragged > 10; // #370 | 
|             chart.mouseIsDown = this.hasDragged = this.hasPinched = false; | 
|             this.pinchDown = []; | 
|         } | 
|     }, | 
|   | 
|     onContainerMouseDown: function (e) { | 
|   | 
|         e = this.normalize(e); | 
|   | 
|         // issue #295, dragging not always working in Firefox | 
|         if (e.preventDefault) { | 
|             e.preventDefault(); | 
|         } | 
|          | 
|         this.dragStart(e); | 
|     }, | 
|   | 
|      | 
|   | 
|     onDocumentMouseUp: function (e) { | 
|         this.drop(e); | 
|     }, | 
|   | 
|     /** | 
|      * Special handler for mouse move that will hide the tooltip when the mouse leaves the plotarea. | 
|      * Issue #149 workaround. The mouseleave event does not always fire.  | 
|      */ | 
|     onDocumentMouseMove: function (e) { | 
|         var chart = this.chart, | 
|             chartPosition = this.chartPosition, | 
|             hoverSeries = chart.hoverSeries; | 
|   | 
|         e = this.normalize(e, chartPosition); | 
|   | 
|         // If we're outside, hide the tooltip | 
|         if (chartPosition && hoverSeries && !this.inClass(e.target, 'highcharts-tracker') && | 
|                 !chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) { | 
|             this.reset(); | 
|         } | 
|     }, | 
|   | 
|     /** | 
|      * When mouse leaves the container, hide the tooltip. | 
|      */ | 
|     onContainerMouseLeave: function () { | 
|         this.reset(); | 
|         this.chartPosition = null; // also reset the chart position, used in #149 fix | 
|     }, | 
|   | 
|     // The mousemove, touchmove and touchstart event handler | 
|     onContainerMouseMove: function (e) { | 
|   | 
|         var chart = this.chart; | 
|   | 
|         // normalize | 
|         e = this.normalize(e); | 
|   | 
|         // #295 | 
|         e.returnValue = false; | 
|          | 
|          | 
|         if (chart.mouseIsDown === 'mousedown') { | 
|             this.drag(e); | 
|         }  | 
|          | 
|         // Show the tooltip and run mouse over events (#977) | 
|         if ((this.inClass(e.target, 'highcharts-tracker') ||  | 
|                 chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) && !chart.openMenu) { | 
|             this.runPointActions(e); | 
|         } | 
|     }, | 
|   | 
|     /** | 
|      * Utility to detect whether an element has, or has a parent with, a specific | 
|      * class name. Used on detection of tracker objects and on deciding whether | 
|      * hovering the tooltip should cause the active series to mouse out. | 
|      */ | 
|     inClass: function (element, className) { | 
|         var elemClassName; | 
|         while (element) { | 
|             elemClassName = attr(element, 'class'); | 
|             if (elemClassName) { | 
|                 if (elemClassName.indexOf(className) !== -1) { | 
|                     return true; | 
|                 } else if (elemClassName.indexOf(PREFIX + 'container') !== -1) { | 
|                     return false; | 
|                 } | 
|             } | 
|             element = element.parentNode; | 
|         }         | 
|     }, | 
|   | 
|     onTrackerMouseOut: function (e) { | 
|         var series = this.chart.hoverSeries; | 
|         if (series && !series.options.stickyTracking && !this.inClass(e.toElement || e.relatedTarget, PREFIX + 'tooltip')) { | 
|             series.onMouseOut(); | 
|         } | 
|     }, | 
|   | 
|     onContainerClick: function (e) { | 
|         var chart = this.chart, | 
|             hoverPoint = chart.hoverPoint,  | 
|             plotLeft = chart.plotLeft, | 
|             plotTop = chart.plotTop, | 
|             inverted = chart.inverted, | 
|             chartPosition, | 
|             plotX, | 
|             plotY; | 
|          | 
|         e = this.normalize(e); | 
|         e.cancelBubble = true; // IE specific | 
|   | 
|         if (!chart.cancelClick) { | 
|              | 
|             // On tracker click, fire the series and point events. #783, #1583 | 
|             if (hoverPoint && this.inClass(e.target, PREFIX + 'tracker')) { | 
|                 chartPosition = this.chartPosition; | 
|                 plotX = hoverPoint.plotX; | 
|                 plotY = hoverPoint.plotY; | 
|   | 
|                 // add page position info | 
|                 extend(hoverPoint, { | 
|                     pageX: chartPosition.left + plotLeft + | 
|                         (inverted ? chart.plotWidth - plotY : plotX), | 
|                     pageY: chartPosition.top + plotTop + | 
|                         (inverted ? chart.plotHeight - plotX : plotY) | 
|                 }); | 
|              | 
|                 // the series click event | 
|                 fireEvent(hoverPoint.series, 'click', extend(e, { | 
|                     point: hoverPoint | 
|                 })); | 
|   | 
|                 // the point click event | 
|                 if (chart.hoverPoint) { // it may be destroyed (#1844) | 
|                     hoverPoint.firePointEvent('click', e); | 
|                 } | 
|   | 
|             // When clicking outside a tracker, fire a chart event | 
|             } else { | 
|                 extend(e, this.getCoordinates(e)); | 
|   | 
|                 // fire a click event in the chart | 
|                 if (chart.isInsidePlot(e.chartX - plotLeft, e.chartY - plotTop)) { | 
|                     fireEvent(chart, 'click', e); | 
|                 } | 
|             } | 
|   | 
|   | 
|         } | 
|     }, | 
|   | 
|     onContainerTouchStart: function (e) { | 
|         var chart = this.chart; | 
|   | 
|         if (e.touches.length === 1) { | 
|   | 
|             e = this.normalize(e); | 
|   | 
|             if (chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) { | 
|   | 
|                 // Prevent the click pseudo event from firing unless it is set in the options | 
|                 /*if (!chart.runChartClick) { | 
|                     e.preventDefault(); | 
|                 }*/ | 
|              | 
|                 // Run mouse events and display tooltip etc | 
|                 this.runPointActions(e); | 
|   | 
|                 this.pinch(e); | 
|   | 
|             } else { | 
|                 // Hide the tooltip on touching outside the plot area (#1203) | 
|                 this.reset(); | 
|             } | 
|   | 
|         } else if (e.touches.length === 2) { | 
|             this.pinch(e); | 
|         }         | 
|     }, | 
|   | 
|     onContainerTouchMove: function (e) { | 
|         if (e.touches.length === 1 || e.touches.length === 2) { | 
|             this.pinch(e); | 
|         } | 
|     }, | 
|   | 
|     onDocumentTouchEnd: function (e) { | 
|         this.drop(e); | 
|     }, | 
|   | 
|     /** | 
|      * Set the JS DOM events on the container and document. This method should contain | 
|      * a one-to-one assignment between methods and their handlers. Any advanced logic should | 
|      * be moved to the handler reflecting the event's name. | 
|      */ | 
|     setDOMEvents: function () { | 
|   | 
|         var pointer = this, | 
|             container = pointer.chart.container, | 
|             events; | 
|   | 
|         this._events = events = [ | 
|             [container, 'onmousedown', 'onContainerMouseDown'], | 
|             [container, 'onmousemove', 'onContainerMouseMove'], | 
|             [container, 'onclick', 'onContainerClick'], | 
|             [container, 'mouseleave', 'onContainerMouseLeave'], | 
|             [doc, 'mousemove', 'onDocumentMouseMove'], | 
|             [doc, 'mouseup', 'onDocumentMouseUp'] | 
|         ]; | 
|   | 
|         if (hasTouch) { | 
|             events.push( | 
|                 [container, 'ontouchstart', 'onContainerTouchStart'], | 
|                 [container, 'ontouchmove', 'onContainerTouchMove'], | 
|                 [doc, 'touchend', 'onDocumentTouchEnd'] | 
|             ); | 
|         } | 
|   | 
|         each(events, function (eventConfig) { | 
|   | 
|             // First, create the callback function that in turn calls the method on Pointer | 
|             pointer['_' + eventConfig[2]] = function (e) { | 
|                 pointer[eventConfig[2]](e); | 
|             }; | 
|   | 
|             // Now attach the function, either as a direct property or through addEvent | 
|             if (eventConfig[1].indexOf('on') === 0) { | 
|                 eventConfig[0][eventConfig[1]] = pointer['_' + eventConfig[2]]; | 
|             } else { | 
|                 addEvent(eventConfig[0], eventConfig[1], pointer['_' + eventConfig[2]]); | 
|             } | 
|         }); | 
|   | 
|          | 
|     }, | 
|   | 
|     /** | 
|      * Destroys the Pointer object and disconnects DOM events. | 
|      */ | 
|     destroy: function () { | 
|         var pointer = this; | 
|   | 
|         // Release all DOM events | 
|         each(pointer._events, function (eventConfig) {     | 
|             if (eventConfig[1].indexOf('on') === 0) { | 
|                 eventConfig[0][eventConfig[1]] = null; // delete breaks oldIE | 
|             } else {         | 
|                 removeEvent(eventConfig[0], eventConfig[1], pointer['_' + eventConfig[2]]); | 
|             } | 
|         }); | 
|         delete pointer._events; | 
|   | 
|         // memory and CPU leak | 
|         clearInterval(pointer.tooltipTimeout); | 
|     } | 
| }; | 
| /** | 
|  * The overview of the chart's series | 
|  */ | 
| function Legend(chart, options) { | 
|     this.init(chart, options); | 
| } | 
|   | 
| Legend.prototype = { | 
|      | 
|     /** | 
|      * Initialize the legend | 
|      */ | 
|     init: function (chart, options) { | 
|          | 
|         var legend = this, | 
|             itemStyle = options.itemStyle, | 
|             padding = pick(options.padding, 8), | 
|             itemMarginTop = options.itemMarginTop || 0; | 
|      | 
|         this.options = options; | 
|   | 
|         if (!options.enabled) { | 
|             return; | 
|         } | 
|      | 
|         legend.baseline = pInt(itemStyle.fontSize) + 3 + itemMarginTop; // used in Series prototype | 
|         legend.itemStyle = itemStyle; | 
|         legend.itemHiddenStyle = merge(itemStyle, options.itemHiddenStyle); | 
|         legend.itemMarginTop = itemMarginTop; | 
|         legend.padding = padding; | 
|         legend.initialItemX = padding; | 
|         legend.initialItemY = padding - 5; // 5 is the number of pixels above the text | 
|         legend.maxItemWidth = 0; | 
|         legend.chart = chart; | 
|         legend.itemHeight = 0; | 
|         legend.lastLineHeight = 0; | 
|   | 
|         // Render it | 
|         legend.render(); | 
|   | 
|         // move checkboxes | 
|         addEvent(legend.chart, 'endResize', function () {  | 
|             legend.positionCheckboxes(); | 
|         }); | 
|   | 
|     }, | 
|   | 
|     /** | 
|      * Set the colors for the legend item | 
|      * @param {Object} item A Series or Point instance | 
|      * @param {Object} visible Dimmed or colored | 
|      */ | 
|     colorizeItem: function (item, visible) { | 
|         var legend = this, | 
|             options = legend.options, | 
|             legendItem = item.legendItem, | 
|             legendLine = item.legendLine, | 
|             legendSymbol = item.legendSymbol, | 
|             hiddenColor = legend.itemHiddenStyle.color, | 
|             textColor = visible ? options.itemStyle.color : hiddenColor, | 
|             symbolColor = visible ? item.color : hiddenColor, | 
|             markerOptions = item.options && item.options.marker, | 
|             symbolAttr = { | 
|                 stroke: symbolColor, | 
|                 fill: symbolColor | 
|             }, | 
|             key, | 
|             val; | 
|          | 
|         if (legendItem) { | 
|             legendItem.css({ fill: textColor, color: textColor }); // color for #1553, oldIE | 
|         } | 
|         if (legendLine) { | 
|             legendLine.attr({ stroke: symbolColor }); | 
|         } | 
|          | 
|         if (legendSymbol) { | 
|              | 
|             // Apply marker options | 
|             if (markerOptions && legendSymbol.isMarker) { // #585 | 
|                 markerOptions = item.convertAttribs(markerOptions); | 
|                 for (key in markerOptions) { | 
|                     val = markerOptions[key]; | 
|                     if (val !== UNDEFINED) { | 
|                         symbolAttr[key] = val; | 
|                     } | 
|                 } | 
|             } | 
|   | 
|             legendSymbol.attr(symbolAttr); | 
|         } | 
|     }, | 
|   | 
|     /** | 
|      * Position the legend item | 
|      * @param {Object} item A Series or Point instance | 
|      */ | 
|     positionItem: function (item) { | 
|         var legend = this, | 
|             options = legend.options, | 
|             symbolPadding = options.symbolPadding, | 
|             ltr = !options.rtl, | 
|             legendItemPos = item._legendItemPos, | 
|             itemX = legendItemPos[0], | 
|             itemY = legendItemPos[1], | 
|             checkbox = item.checkbox; | 
|   | 
|         if (item.legendGroup) { | 
|             item.legendGroup.translate( | 
|                 ltr ? itemX : legend.legendWidth - itemX - 2 * symbolPadding - 4, | 
|                 itemY | 
|             ); | 
|         } | 
|   | 
|         if (checkbox) { | 
|             checkbox.x = itemX; | 
|             checkbox.y = itemY; | 
|         } | 
|     }, | 
|   | 
|     /** | 
|      * Destroy a single legend item | 
|      * @param {Object} item The series or point | 
|      */ | 
|     destroyItem: function (item) { | 
|         var checkbox = item.checkbox; | 
|   | 
|         // destroy SVG elements | 
|         each(['legendItem', 'legendLine', 'legendSymbol', 'legendGroup'], function (key) { | 
|             if (item[key]) { | 
|                 item[key] = item[key].destroy(); | 
|             } | 
|         }); | 
|   | 
|         if (checkbox) { | 
|             discardElement(item.checkbox); | 
|         } | 
|     }, | 
|   | 
|     /** | 
|      * Destroys the legend. | 
|      */ | 
|     destroy: function () { | 
|         var legend = this, | 
|             legendGroup = legend.group, | 
|             box = legend.box; | 
|   | 
|         if (box) { | 
|             legend.box = box.destroy(); | 
|         } | 
|   | 
|         if (legendGroup) { | 
|             legend.group = legendGroup.destroy(); | 
|         } | 
|     }, | 
|   | 
|     /** | 
|      * Position the checkboxes after the width is determined | 
|      */ | 
|     positionCheckboxes: function (scrollOffset) { | 
|         var alignAttr = this.group.alignAttr, | 
|             translateY, | 
|             clipHeight = this.clipHeight || this.legendHeight; | 
|   | 
|         if (alignAttr) { | 
|             translateY = alignAttr.translateY; | 
|             each(this.allItems, function (item) { | 
|                 var checkbox = item.checkbox, | 
|                     top; | 
|                  | 
|                 if (checkbox) { | 
|                     top = (translateY + checkbox.y + (scrollOffset || 0) + 3); | 
|                     css(checkbox, { | 
|                         left: (alignAttr.translateX + item.legendItemWidth + checkbox.x - 20) + PX, | 
|                         top: top + PX, | 
|                         display: top > translateY - 6 && top < translateY + clipHeight - 6 ? '' : NONE | 
|                     }); | 
|                 } | 
|             }); | 
|         } | 
|     }, | 
|      | 
|     /** | 
|      * Render the legend title on top of the legend | 
|      */ | 
|     renderTitle: function () { | 
|         var options = this.options, | 
|             padding = this.padding, | 
|             titleOptions = options.title, | 
|             titleHeight = 0, | 
|             bBox; | 
|          | 
|         if (titleOptions.text) { | 
|             if (!this.title) { | 
|                 this.title = this.chart.renderer.label(titleOptions.text, padding - 3, padding - 4, null, null, null, null, null, 'legend-title') | 
|                     .attr({ zIndex: 1 }) | 
|                     .css(titleOptions.style) | 
|                     .add(this.group); | 
|             } | 
|             bBox = this.title.getBBox(); | 
|             titleHeight = bBox.height; | 
|             this.offsetWidth = bBox.width; // #1717 | 
|             this.contentGroup.attr({ translateY: titleHeight }); | 
|         } | 
|         this.titleHeight = titleHeight; | 
|     }, | 
|   | 
|     /** | 
|      * Render a single specific legend item | 
|      * @param {Object} item A series or point | 
|      */ | 
|     renderItem: function (item) { | 
|         var legend = this, | 
|             chart = legend.chart, | 
|             renderer = chart.renderer, | 
|             options = legend.options, | 
|             horizontal = options.layout === 'horizontal', | 
|             symbolWidth = options.symbolWidth, | 
|             symbolPadding = options.symbolPadding, | 
|             itemStyle = legend.itemStyle, | 
|             itemHiddenStyle = legend.itemHiddenStyle, | 
|             padding = legend.padding, | 
|             itemDistance = horizontal ? pick(options.itemDistance, 8) : 0, | 
|             ltr = !options.rtl, | 
|             itemHeight, | 
|             widthOption = options.width, | 
|             itemMarginBottom = options.itemMarginBottom || 0, | 
|             itemMarginTop = legend.itemMarginTop, | 
|             initialItemX = legend.initialItemX, | 
|             bBox, | 
|             itemWidth, | 
|             li = item.legendItem, | 
|             series = item.series || item, | 
|             itemOptions = series.options, | 
|             showCheckbox = itemOptions.showCheckbox, | 
|             useHTML = options.useHTML; | 
|   | 
|         if (!li) { // generate it once, later move it | 
|   | 
|             // Generate the group box | 
|             // A group to hold the symbol and text. Text is to be appended in Legend class. | 
|             item.legendGroup = renderer.g('legend-item') | 
|                 .attr({ zIndex: 1 }) | 
|                 .add(legend.scrollGroup); | 
|   | 
|             // Draw the legend symbol inside the group box | 
|             series.drawLegendSymbol(legend, item); | 
|   | 
|             // Generate the list item text and add it to the group | 
|             item.legendItem = li = renderer.text( | 
|                     options.labelFormat ? format(options.labelFormat, item) : options.labelFormatter.call(item), | 
|                     ltr ? symbolWidth + symbolPadding : -symbolPadding, | 
|                     legend.baseline, | 
|                     useHTML | 
|                 ) | 
|                 .css(merge(item.visible ? itemStyle : itemHiddenStyle)) // merge to prevent modifying original (#1021) | 
|                 .attr({ | 
|                     align: ltr ? 'left' : 'right', | 
|                     zIndex: 2 | 
|                 }) | 
|                 .add(item.legendGroup); | 
|   | 
|             // Set the events on the item group, or in case of useHTML, the item itself (#1249) | 
|             (useHTML ? li : item.legendGroup).on('mouseover', function () { | 
|                     item.setState(HOVER_STATE); | 
|                     li.css(legend.options.itemHoverStyle); | 
|                 }) | 
|                 .on('mouseout', function () { | 
|                     li.css(item.visible ? itemStyle : itemHiddenStyle); | 
|                     item.setState(); | 
|                 }) | 
|                 .on('click', function (event) { | 
|                     var strLegendItemClick = 'legendItemClick', | 
|                         fnLegendItemClick = function () { | 
|                             item.setVisible(); | 
|                         }; | 
|                          | 
|                     // Pass over the click/touch event. #4. | 
|                     event = { | 
|                         browserEvent: event | 
|                     }; | 
|   | 
|                     // click the name or symbol | 
|                     if (item.firePointEvent) { // point | 
|                         item.firePointEvent(strLegendItemClick, event, fnLegendItemClick); | 
|                     } else { | 
|                         fireEvent(item, strLegendItemClick, event, fnLegendItemClick); | 
|                     } | 
|                 }); | 
|   | 
|             // Colorize the items | 
|             legend.colorizeItem(item, item.visible); | 
|   | 
|             // add the HTML checkbox on top | 
|             if (itemOptions && showCheckbox) { | 
|                 item.checkbox = createElement('input', { | 
|                     type: 'checkbox', | 
|                     checked: item.selected, | 
|                     defaultChecked: item.selected // required by IE7 | 
|                 }, options.itemCheckboxStyle, chart.container); | 
|   | 
|                 addEvent(item.checkbox, 'click', function (event) { | 
|                     var target = event.target; | 
|                     fireEvent(item, 'checkboxClick', { | 
|                             checked: target.checked | 
|                         }, | 
|                         function () { | 
|                             item.select(); | 
|                         } | 
|                     ); | 
|                 }); | 
|             } | 
|         } | 
|   | 
|         // calculate the positions for the next line | 
|         bBox = li.getBBox(); | 
|   | 
|         itemWidth = item.legendItemWidth = | 
|             options.itemWidth || symbolWidth + symbolPadding + bBox.width + itemDistance + | 
|             (showCheckbox ? 20 : 0); | 
|         legend.itemHeight = itemHeight = bBox.height; | 
|   | 
|         // if the item exceeds the width, start a new line | 
|         if (horizontal && legend.itemX - initialItemX + itemWidth > | 
|                 (widthOption || (chart.chartWidth - 2 * padding - initialItemX))) { | 
|             legend.itemX = initialItemX; | 
|             legend.itemY += itemMarginTop + legend.lastLineHeight + itemMarginBottom; | 
|             legend.lastLineHeight = 0; // reset for next line | 
|         } | 
|   | 
|         // If the item exceeds the height, start a new column | 
|         /*if (!horizontal && legend.itemY + options.y + itemHeight > chart.chartHeight - spacingTop - spacingBottom) { | 
|             legend.itemY = legend.initialItemY; | 
|             legend.itemX += legend.maxItemWidth; | 
|             legend.maxItemWidth = 0; | 
|         }*/ | 
|   | 
|         // Set the edge positions | 
|         legend.maxItemWidth = mathMax(legend.maxItemWidth, itemWidth); | 
|         legend.lastItemY = itemMarginTop + legend.itemY + itemMarginBottom; | 
|         legend.lastLineHeight = mathMax(itemHeight, legend.lastLineHeight); // #915 | 
|   | 
|         // cache the position of the newly generated or reordered items | 
|         item._legendItemPos = [legend.itemX, legend.itemY]; | 
|   | 
|         // advance | 
|         if (horizontal) { | 
|             legend.itemX += itemWidth; | 
|   | 
|         } else { | 
|             legend.itemY += itemMarginTop + itemHeight + itemMarginBottom; | 
|             legend.lastLineHeight = itemHeight; | 
|         } | 
|   | 
|         // the width of the widest item | 
|         legend.offsetWidth = widthOption || mathMax( | 
|             (horizontal ? legend.itemX - initialItemX - itemDistance : itemWidth) + padding, | 
|             legend.offsetWidth | 
|         ); | 
|     }, | 
|   | 
|     /** | 
|      * Render the legend. This method can be called both before and after | 
|      * chart.render. If called after, it will only rearrange items instead | 
|      * of creating new ones. | 
|      */ | 
|     render: function () { | 
|         var legend = this, | 
|             chart = legend.chart, | 
|             renderer = chart.renderer, | 
|             legendGroup = legend.group, | 
|             allItems, | 
|             display, | 
|             legendWidth, | 
|             legendHeight, | 
|             box = legend.box, | 
|             options = legend.options, | 
|             padding = legend.padding, | 
|             legendBorderWidth = options.borderWidth, | 
|             legendBackgroundColor = options.backgroundColor; | 
|   | 
|         legend.itemX = legend.initialItemX; | 
|         legend.itemY = legend.initialItemY; | 
|         legend.offsetWidth = 0; | 
|         legend.lastItemY = 0; | 
|   | 
|         if (!legendGroup) { | 
|             legend.group = legendGroup = renderer.g('legend') | 
|                 .attr({ zIndex: 7 })  | 
|                 .add(); | 
|             legend.contentGroup = renderer.g() | 
|                 .attr({ zIndex: 1 }) // above background | 
|                 .add(legendGroup); | 
|             legend.scrollGroup = renderer.g() | 
|                 .add(legend.contentGroup); | 
|         } | 
|          | 
|         legend.renderTitle(); | 
|   | 
|         // add each series or point | 
|         allItems = []; | 
|         each(chart.series, function (serie) { | 
|             var seriesOptions = serie.options; | 
|   | 
|             if (!seriesOptions.showInLegend || defined(seriesOptions.linkedTo)) { | 
|                 return; | 
|             } | 
|   | 
|             // use points or series for the legend item depending on legendType | 
|             allItems = allItems.concat( | 
|                     serie.legendItems || | 
|                     (seriesOptions.legendType === 'point' ? | 
|                             serie.data : | 
|                             serie) | 
|             ); | 
|         }); | 
|   | 
|         // sort by legendIndex | 
|         stableSort(allItems, function (a, b) { | 
|             return ((a.options && a.options.legendIndex) || 0) - ((b.options && b.options.legendIndex) || 0); | 
|         }); | 
|   | 
|         // reversed legend | 
|         if (options.reversed) { | 
|             allItems.reverse(); | 
|         } | 
|   | 
|         legend.allItems = allItems; | 
|         legend.display = display = !!allItems.length; | 
|   | 
|         // render the items | 
|         each(allItems, function (item) { | 
|             legend.renderItem(item);  | 
|         }); | 
|   | 
|         // Draw the border | 
|         legendWidth = options.width || legend.offsetWidth; | 
|         legendHeight = legend.lastItemY + legend.lastLineHeight + legend.titleHeight; | 
|          | 
|          | 
|         legendHeight = legend.handleOverflow(legendHeight); | 
|   | 
|         if (legendBorderWidth || legendBackgroundColor) { | 
|             legendWidth += padding; | 
|             legendHeight += padding; | 
|   | 
|             if (!box) { | 
|                 legend.box = box = renderer.rect( | 
|                     0, | 
|                     0, | 
|                     legendWidth, | 
|                     legendHeight, | 
|                     options.borderRadius, | 
|                     legendBorderWidth || 0 | 
|                 ).attr({ | 
|                     stroke: options.borderColor, | 
|                     'stroke-width': legendBorderWidth || 0, | 
|                     fill: legendBackgroundColor || NONE | 
|                 }) | 
|                 .add(legendGroup) | 
|                 .shadow(options.shadow); | 
|                 box.isNew = true; | 
|   | 
|             } else if (legendWidth > 0 && legendHeight > 0) { | 
|                 box[box.isNew ? 'attr' : 'animate']( | 
|                     box.crisp(null, null, null, legendWidth, legendHeight) | 
|                 ); | 
|                 box.isNew = false; | 
|             } | 
|   | 
|             // hide the border if no items | 
|             box[display ? 'show' : 'hide'](); | 
|         } | 
|          | 
|         legend.legendWidth = legendWidth; | 
|         legend.legendHeight = legendHeight; | 
|   | 
|         // Now that the legend width and height are established, put the items in the  | 
|         // final position | 
|         each(allItems, function (item) { | 
|             legend.positionItem(item); | 
|         }); | 
|   | 
|         // 1.x compatibility: positioning based on style | 
|         /*var props = ['left', 'right', 'top', 'bottom'], | 
|             prop, | 
|             i = 4; | 
|         while (i--) { | 
|             prop = props[i]; | 
|             if (options.style[prop] && options.style[prop] !== 'auto') { | 
|                 options[i < 2 ? 'align' : 'verticalAlign'] = prop; | 
|                 options[i < 2 ? 'x' : 'y'] = pInt(options.style[prop]) * (i % 2 ? -1 : 1); | 
|             } | 
|         }*/ | 
|   | 
|         if (display) { | 
|             legendGroup.align(extend({ | 
|                 width: legendWidth, | 
|                 height: legendHeight | 
|             }, options), true, 'spacingBox'); | 
|         } | 
|   | 
|         if (!chart.isResizing) { | 
|             this.positionCheckboxes(); | 
|         } | 
|     }, | 
|      | 
|     /** | 
|      * Set up the overflow handling by adding navigation with up and down arrows below the | 
|      * legend. | 
|      */ | 
|     handleOverflow: function (legendHeight) { | 
|         var legend = this, | 
|             chart = this.chart, | 
|             renderer = chart.renderer, | 
|             pageCount, | 
|             options = this.options, | 
|             optionsY = options.y, | 
|             alignTop = options.verticalAlign === 'top', | 
|             spaceHeight = chart.spacingBox.height + (alignTop ? -optionsY : optionsY) - this.padding, | 
|             maxHeight = options.maxHeight, | 
|             clipHeight, | 
|             clipRect = this.clipRect, | 
|             navOptions = options.navigation, | 
|             animation = pick(navOptions.animation, true), | 
|             arrowSize = navOptions.arrowSize || 12, | 
|             nav = this.nav; | 
|              | 
|         // Adjust the height | 
|         if (options.layout === 'horizontal') { | 
|             spaceHeight /= 2; | 
|         } | 
|         if (maxHeight) { | 
|             spaceHeight = mathMin(spaceHeight, maxHeight); | 
|         } | 
|          | 
|         // Reset the legend height and adjust the clipping rectangle | 
|         if (legendHeight > spaceHeight && !options.useHTML) { | 
|   | 
|             this.clipHeight = clipHeight = spaceHeight - 20 - this.titleHeight; | 
|             this.pageCount = pageCount = mathCeil(legendHeight / clipHeight); | 
|             this.currentPage = pick(this.currentPage, 1); | 
|             this.fullHeight = legendHeight; | 
|              | 
|             // Only apply clipping if needed. Clipping causes blurred legend in PDF export (#1787) | 
|             if (!clipRect) { | 
|                 clipRect = legend.clipRect = renderer.clipRect(0, 0, 9999, 0); | 
|                 legend.contentGroup.clip(clipRect); | 
|             } | 
|             clipRect.attr({ | 
|                 height: clipHeight | 
|             }); | 
|              | 
|             // Add navigation elements | 
|             if (!nav) { | 
|                 this.nav = nav = renderer.g().attr({ zIndex: 1 }).add(this.group); | 
|                 this.up = renderer.symbol('triangle', 0, 0, arrowSize, arrowSize) | 
|                     .on('click', function () { | 
|                         legend.scroll(-1, animation); | 
|                     }) | 
|                     .add(nav); | 
|                 this.pager = renderer.text('', 15, 10) | 
|                     .css(navOptions.style) | 
|                     .add(nav); | 
|                 this.down = renderer.symbol('triangle-down', 0, 0, arrowSize, arrowSize) | 
|                     .on('click', function () { | 
|                         legend.scroll(1, animation); | 
|                     }) | 
|                     .add(nav); | 
|             } | 
|              | 
|             // Set initial position | 
|             legend.scroll(0); | 
|              | 
|             legendHeight = spaceHeight; | 
|              | 
|         } else if (nav) { | 
|             clipRect.attr({ | 
|                 height: chart.chartHeight | 
|             }); | 
|             nav.hide(); | 
|             this.scrollGroup.attr({ | 
|                 translateY: 1 | 
|             }); | 
|             this.clipHeight = 0; // #1379 | 
|         } | 
|          | 
|         return legendHeight; | 
|     }, | 
|      | 
|     /** | 
|      * Scroll the legend by a number of pages | 
|      * @param {Object} scrollBy | 
|      * @param {Object} animation | 
|      */ | 
|     scroll: function (scrollBy, animation) { | 
|         var pageCount = this.pageCount, | 
|             currentPage = this.currentPage + scrollBy, | 
|             clipHeight = this.clipHeight, | 
|             navOptions = this.options.navigation, | 
|             activeColor = navOptions.activeColor, | 
|             inactiveColor = navOptions.inactiveColor, | 
|             pager = this.pager, | 
|             padding = this.padding, | 
|             scrollOffset; | 
|          | 
|         // When resizing while looking at the last page | 
|         if (currentPage > pageCount) { | 
|             currentPage = pageCount; | 
|         } | 
|          | 
|         if (currentPage > 0) { | 
|              | 
|             if (animation !== UNDEFINED) { | 
|                 setAnimation(animation, this.chart); | 
|             } | 
|              | 
|             this.nav.attr({ | 
|                 translateX: padding, | 
|                 translateY: clipHeight + 7 + this.titleHeight, | 
|                 visibility: VISIBLE | 
|             }); | 
|             this.up.attr({ | 
|                     fill: currentPage === 1 ? inactiveColor : activeColor | 
|                 }) | 
|                 .css({ | 
|                     cursor: currentPage === 1 ? 'default' : 'pointer' | 
|                 }); | 
|             pager.attr({ | 
|                 text: currentPage + '/' + this.pageCount | 
|             }); | 
|             this.down.attr({ | 
|                     x: 18 + this.pager.getBBox().width, // adjust to text width | 
|                     fill: currentPage === pageCount ? inactiveColor : activeColor | 
|                 }) | 
|                 .css({ | 
|                     cursor: currentPage === pageCount ? 'default' : 'pointer' | 
|                 }); | 
|              | 
|             scrollOffset = -mathMin(clipHeight * (currentPage - 1), this.fullHeight - clipHeight + padding) + 1; | 
|             this.scrollGroup.animate({ | 
|                 translateY: scrollOffset | 
|             }); | 
|             pager.attr({ | 
|                 text: currentPage + '/' + pageCount | 
|             }); | 
|              | 
|              | 
|             this.currentPage = currentPage; | 
|             this.positionCheckboxes(scrollOffset); | 
|         } | 
|              | 
|     } | 
|      | 
| }; | 
|   | 
| // Workaround for #2030, horizontal legend items not displaying in IE11 Preview. | 
| // TODO: When IE11 is released, check again for this bug, and remove the fix | 
| // or make a better one. | 
| if (/Trident.*?11\.0/.test(userAgent)) { | 
|     wrap(Legend.prototype, 'positionItem', function (proceed, item) { | 
|         var legend = this; | 
|         setTimeout(function () { | 
|             proceed.call(legend, item); | 
|         }); | 
|     }); | 
| } | 
|   | 
| /** | 
|  * The chart class | 
|  * @param {Object} options | 
|  * @param {Function} callback Function to run when the chart has loaded | 
|  */ | 
| function Chart() { | 
|     this.init.apply(this, arguments); | 
| } | 
|   | 
| Chart.prototype = { | 
|   | 
|     /** | 
|      * Initialize the chart | 
|      */ | 
|     init: function (userOptions, callback) { | 
|   | 
|         // Handle regular options | 
|         var options, | 
|             seriesOptions = userOptions.series; // skip merging data points to increase performance | 
|   | 
|         userOptions.series = null; | 
|         options = merge(defaultOptions, userOptions); // do the merge | 
|         options.series = userOptions.series = seriesOptions; // set back the series data | 
|   | 
|         var optionsChart = options.chart; | 
|          | 
|         // Create margin & spacing array | 
|         this.margin = this.splashArray('margin', optionsChart); | 
|         this.spacing = this.splashArray('spacing', optionsChart); | 
|   | 
|         var chartEvents = optionsChart.events; | 
|   | 
|         //this.runChartClick = chartEvents && !!chartEvents.click; | 
|         this.bounds = { h: {}, v: {} }; // Pixel data bounds for touch zoom | 
|   | 
|         this.callback = callback; | 
|         this.isResizing = 0; | 
|         this.options = options; | 
|         //chartTitleOptions = UNDEFINED; | 
|         //chartSubtitleOptions = UNDEFINED; | 
|   | 
|         this.axes = []; | 
|         this.series = []; | 
|         this.hasCartesianSeries = optionsChart.showAxes; | 
|         //this.axisOffset = UNDEFINED; | 
|         //this.maxTicks = UNDEFINED; // handle the greatest amount of ticks on grouped axes | 
|         //this.inverted = UNDEFINED; | 
|         //this.loadingShown = UNDEFINED; | 
|         //this.container = UNDEFINED; | 
|         //this.chartWidth = UNDEFINED; | 
|         //this.chartHeight = UNDEFINED; | 
|         //this.marginRight = UNDEFINED; | 
|         //this.marginBottom = UNDEFINED; | 
|         //this.containerWidth = UNDEFINED; | 
|         //this.containerHeight = UNDEFINED; | 
|         //this.oldChartWidth = UNDEFINED; | 
|         //this.oldChartHeight = UNDEFINED; | 
|   | 
|         //this.renderTo = UNDEFINED; | 
|         //this.renderToClone = UNDEFINED; | 
|   | 
|         //this.spacingBox = UNDEFINED | 
|   | 
|         //this.legend = UNDEFINED; | 
|   | 
|         // Elements | 
|         //this.chartBackground = UNDEFINED; | 
|         //this.plotBackground = UNDEFINED; | 
|         //this.plotBGImage = UNDEFINED; | 
|         //this.plotBorder = UNDEFINED; | 
|         //this.loadingDiv = UNDEFINED; | 
|         //this.loadingSpan = UNDEFINED; | 
|   | 
|         var chart = this, | 
|             eventType; | 
|   | 
|         // Add the chart to the global lookup | 
|         chart.index = charts.length; | 
|         charts.push(chart); | 
|   | 
|         // Set up auto resize | 
|         if (optionsChart.reflow !== false) { | 
|             addEvent(chart, 'load', function () { | 
|                 chart.initReflow(); | 
|             }); | 
|         } | 
|   | 
|         // Chart event handlers | 
|         if (chartEvents) { | 
|             for (eventType in chartEvents) { | 
|                 addEvent(chart, eventType, chartEvents[eventType]); | 
|             } | 
|         } | 
|   | 
|         chart.xAxis = []; | 
|         chart.yAxis = []; | 
|   | 
|         // Expose methods and variables | 
|         chart.animation = useCanVG ? false : pick(optionsChart.animation, true); | 
|         chart.pointCount = 0; | 
|         chart.counters = new ChartCounters(); | 
|   | 
|         chart.firstRender(); | 
|     }, | 
|   | 
|     /** | 
|      * Initialize an individual series, called internally before render time | 
|      */ | 
|     initSeries: function (options) { | 
|         var chart = this, | 
|             optionsChart = chart.options.chart, | 
|             type = options.type || optionsChart.type || optionsChart.defaultSeriesType, | 
|             series, | 
|             constr = seriesTypes[type]; | 
|   | 
|         // No such series type | 
|         if (!constr) { | 
|             error(17, true); | 
|         } | 
|   | 
|         series = new constr(); | 
|         series.init(this, options); | 
|         return series; | 
|     }, | 
|   | 
|     /** | 
|      * Add a series dynamically after  time | 
|      * | 
|      * @param {Object} options The config options | 
|      * @param {Boolean} redraw Whether to redraw the chart after adding. Defaults to true. | 
|      * @param {Boolean|Object} animation Whether to apply animation, and optionally animation | 
|      *    configuration | 
|      * | 
|      * @return {Object} series The newly created series object | 
|      */ | 
|     addSeries: function (options, redraw, animation) { | 
|         var series, | 
|             chart = this; | 
|   | 
|         if (options) { | 
|             redraw = pick(redraw, true); // defaults to true | 
|   | 
|             fireEvent(chart, 'addSeries', { options: options }, function () { | 
|                 series = chart.initSeries(options); | 
|                  | 
|                 chart.isDirtyLegend = true; // the series array is out of sync with the display | 
|                 chart.linkSeries(); | 
|                 if (redraw) { | 
|                     chart.redraw(animation); | 
|                 } | 
|             }); | 
|         } | 
|   | 
|         return series; | 
|     }, | 
|   | 
|     /** | 
|      * Add an axis to the chart | 
|      * @param {Object} options The axis option | 
|      * @param {Boolean} isX Whether it is an X axis or a value axis | 
|      */ | 
|     addAxis: function (options, isX, redraw, animation) { | 
|         var key = isX ? 'xAxis' : 'yAxis', | 
|             chartOptions = this.options, | 
|             axis; | 
|   | 
|         /*jslint unused: false*/ | 
|         axis = new Axis(this, merge(options, { | 
|             index: this[key].length, | 
|             isX: isX | 
|         })); | 
|         /*jslint unused: true*/ | 
|   | 
|         // Push the new axis options to the chart options | 
|         chartOptions[key] = splat(chartOptions[key] || {}); | 
|         chartOptions[key].push(options); | 
|   | 
|         if (pick(redraw, true)) { | 
|             this.redraw(animation); | 
|         } | 
|     }, | 
|   | 
|     /** | 
|      * Check whether a given point is within the plot area | 
|      * | 
|      * @param {Number} plotX Pixel x relative to the plot area | 
|      * @param {Number} plotY Pixel y relative to the plot area | 
|      * @param {Boolean} inverted Whether the chart is inverted | 
|      */ | 
|     isInsidePlot: function (plotX, plotY, inverted) { | 
|         var x = inverted ? plotY : plotX, | 
|             y = inverted ? plotX : plotY; | 
|              | 
|         return x >= 0 && | 
|             x <= this.plotWidth && | 
|             y >= 0 && | 
|             y <= this.plotHeight; | 
|     }, | 
|   | 
|     /** | 
|      * Adjust all axes tick amounts | 
|      */ | 
|     adjustTickAmounts: function () { | 
|         if (this.options.chart.alignTicks !== false) { | 
|             each(this.axes, function (axis) { | 
|                 axis.adjustTickAmount(); | 
|             }); | 
|         } | 
|         this.maxTicks = null; | 
|     }, | 
|   | 
|     /** | 
|      * Redraw legend, axes or series based on updated data | 
|      * | 
|      * @param {Boolean|Object} animation Whether to apply animation, and optionally animation | 
|      *    configuration | 
|      */ | 
|     redraw: function (animation) { | 
|         var chart = this, | 
|             axes = chart.axes, | 
|             series = chart.series, | 
|             pointer = chart.pointer, | 
|             legend = chart.legend, | 
|             redrawLegend = chart.isDirtyLegend, | 
|             hasStackedSeries, | 
|             hasDirtyStacks, | 
|             isDirtyBox = chart.isDirtyBox, // todo: check if it has actually changed? | 
|             seriesLength = series.length, | 
|             i = seriesLength, | 
|             serie, | 
|             renderer = chart.renderer, | 
|             isHiddenChart = renderer.isHidden(), | 
|             afterRedraw = []; | 
|              | 
|         setAnimation(animation, chart); | 
|          | 
|         if (isHiddenChart) { | 
|             chart.cloneRenderTo(); | 
|         } | 
|   | 
|         // Adjust title layout (reflow multiline text) | 
|         chart.layOutTitles(); | 
|   | 
|         // link stacked series | 
|         while (i--) { | 
|             serie = series[i]; | 
|   | 
|             if (serie.options.stacking) { | 
|                 hasStackedSeries = true; | 
|                  | 
|                 if (serie.isDirty) { | 
|                     hasDirtyStacks = true; | 
|                     break; | 
|                 } | 
|             } | 
|         } | 
|         if (hasDirtyStacks) { // mark others as dirty | 
|             i = seriesLength; | 
|             while (i--) { | 
|                 serie = series[i]; | 
|                 if (serie.options.stacking) { | 
|                     serie.isDirty = true; | 
|                 } | 
|             } | 
|         } | 
|   | 
|         // handle updated data in the series | 
|         each(series, function (serie) { | 
|             if (serie.isDirty) { // prepare the data so axis can read it | 
|                 if (serie.options.legendType === 'point') { | 
|                     redrawLegend = true; | 
|                 } | 
|             } | 
|         }); | 
|   | 
|         // handle added or removed series | 
|         if (redrawLegend && legend.options.enabled) { // series or pie points are added or removed | 
|             // draw legend graphics | 
|             legend.render(); | 
|   | 
|             chart.isDirtyLegend = false; | 
|         } | 
|   | 
|         // reset stacks | 
|         if (hasStackedSeries) { | 
|             chart.getStacks(); | 
|         } | 
|   | 
|   | 
|         if (chart.hasCartesianSeries) { | 
|             if (!chart.isResizing) { | 
|   | 
|                 // reset maxTicks | 
|                 chart.maxTicks = null; | 
|   | 
|                 // set axes scales | 
|                 each(axes, function (axis) { | 
|                     axis.setScale(); | 
|                 }); | 
|             } | 
|   | 
|             chart.adjustTickAmounts(); | 
|             chart.getMargins(); | 
|   | 
|             // If one axis is dirty, all axes must be redrawn (#792, #2169) | 
|             each(axes, function (axis) { | 
|                 if (axis.isDirty) { | 
|                     isDirtyBox = true; | 
|                 } | 
|             }); | 
|   | 
|             // redraw axes | 
|             each(axes, function (axis) { | 
|                  | 
|                 // Fire 'afterSetExtremes' only if extremes are set | 
|                 if (axis.isDirtyExtremes) { // #821 | 
|                     axis.isDirtyExtremes = false; | 
|                     afterRedraw.push(function () { // prevent a recursive call to chart.redraw() (#1119) | 
|                         fireEvent(axis, 'afterSetExtremes', extend(axis.eventArgs, axis.getExtremes())); // #747, #751 | 
|                         delete axis.eventArgs; | 
|                     }); | 
|                 } | 
|                  | 
|                 if (isDirtyBox || hasStackedSeries) { | 
|                     axis.redraw(); | 
|                 } | 
|             }); | 
|   | 
|   | 
|         } | 
|         // the plot areas size has changed | 
|         if (isDirtyBox) { | 
|             chart.drawChartBox(); | 
|         } | 
|   | 
|   | 
|         // redraw affected series | 
|         each(series, function (serie) { | 
|             if (serie.isDirty && serie.visible && | 
|                     (!serie.isCartesian || serie.xAxis)) { // issue #153 | 
|                 serie.redraw(); | 
|             } | 
|         }); | 
|   | 
|         // move tooltip or reset | 
|         if (pointer && pointer.reset) { | 
|             pointer.reset(true); | 
|         } | 
|   | 
|         // redraw if canvas | 
|         renderer.draw(); | 
|   | 
|         // fire the event | 
|         fireEvent(chart, 'redraw'); // jQuery breaks this when calling it from addEvent. Overwrites chart.redraw | 
|          | 
|         if (isHiddenChart) { | 
|             chart.cloneRenderTo(true); | 
|         } | 
|          | 
|         // Fire callbacks that are put on hold until after the redraw | 
|         each(afterRedraw, function (callback) { | 
|             callback.call(); | 
|         }); | 
|     }, | 
|   | 
|   | 
|   | 
|     /** | 
|      * Dim the chart and show a loading text or symbol | 
|      * @param {String} str An optional text to show in the loading label instead of the default one | 
|      */ | 
|     showLoading: function (str) { | 
|         var chart = this, | 
|             options = chart.options, | 
|             loadingDiv = chart.loadingDiv; | 
|   | 
|         var loadingOptions = options.loading; | 
|   | 
|         // create the layer at the first call | 
|         if (!loadingDiv) { | 
|             chart.loadingDiv = loadingDiv = createElement(DIV, { | 
|                 className: PREFIX + 'loading' | 
|             }, extend(loadingOptions.style, { | 
|                 zIndex: 10, | 
|                 display: NONE | 
|             }), chart.container); | 
|   | 
|             chart.loadingSpan = createElement( | 
|                 'span', | 
|                 null, | 
|                 loadingOptions.labelStyle, | 
|                 loadingDiv | 
|             ); | 
|   | 
|         } | 
|   | 
|         // update text | 
|         chart.loadingSpan.innerHTML = str || options.lang.loading; | 
|   | 
|         // show it | 
|         if (!chart.loadingShown) { | 
|             css(loadingDiv, {  | 
|                 opacity: 0,  | 
|                 display: '', | 
|                 left: chart.plotLeft + PX, | 
|                 top: chart.plotTop + PX, | 
|                 width: chart.plotWidth + PX, | 
|                 height: chart.plotHeight + PX | 
|             }); | 
|             animate(loadingDiv, { | 
|                 opacity: loadingOptions.style.opacity | 
|             }, { | 
|                 duration: loadingOptions.showDuration || 0 | 
|             }); | 
|             chart.loadingShown = true; | 
|         } | 
|     }, | 
|   | 
|     /** | 
|      * Hide the loading layer | 
|      */ | 
|     hideLoading: function () { | 
|         var options = this.options, | 
|             loadingDiv = this.loadingDiv; | 
|   | 
|         if (loadingDiv) { | 
|             animate(loadingDiv, { | 
|                 opacity: 0 | 
|             }, { | 
|                 duration: options.loading.hideDuration || 100, | 
|                 complete: function () { | 
|                     css(loadingDiv, { display: NONE }); | 
|                 } | 
|             }); | 
|         } | 
|         this.loadingShown = false; | 
|     }, | 
|   | 
|     /** | 
|      * Get an axis, series or point object by id. | 
|      * @param id {String} The id as given in the configuration options | 
|      */ | 
|     get: function (id) { | 
|         var chart = this, | 
|             axes = chart.axes, | 
|             series = chart.series; | 
|   | 
|         var i, | 
|             j, | 
|             points; | 
|   | 
|         // search axes | 
|         for (i = 0; i < axes.length; i++) { | 
|             if (axes[i].options.id === id) { | 
|                 return axes[i]; | 
|             } | 
|         } | 
|   | 
|         // search series | 
|         for (i = 0; i < series.length; i++) { | 
|             if (series[i].options.id === id) { | 
|                 return series[i]; | 
|             } | 
|         } | 
|   | 
|         // search points | 
|         for (i = 0; i < series.length; i++) { | 
|             points = series[i].points || []; | 
|             for (j = 0; j < points.length; j++) { | 
|                 if (points[j].id === id) { | 
|                     return points[j]; | 
|                 } | 
|             } | 
|         } | 
|         return null; | 
|     }, | 
|   | 
|     /** | 
|      * Create the Axis instances based on the config options | 
|      */ | 
|     getAxes: function () { | 
|         var chart = this, | 
|             options = this.options, | 
|             xAxisOptions = options.xAxis = splat(options.xAxis || {}), | 
|             yAxisOptions = options.yAxis = splat(options.yAxis || {}), | 
|             optionsArray, | 
|             axis; | 
|   | 
|         // make sure the options are arrays and add some members | 
|         each(xAxisOptions, function (axis, i) { | 
|             axis.index = i; | 
|             axis.isX = true; | 
|         }); | 
|   | 
|         each(yAxisOptions, function (axis, i) { | 
|             axis.index = i; | 
|         }); | 
|   | 
|         // concatenate all axis options into one array | 
|         optionsArray = xAxisOptions.concat(yAxisOptions); | 
|   | 
|         each(optionsArray, function (axisOptions) { | 
|             axis = new Axis(chart, axisOptions); | 
|         }); | 
|   | 
|         chart.adjustTickAmounts(); | 
|     }, | 
|   | 
|   | 
|     /** | 
|      * Get the currently selected points from all series | 
|      */ | 
|     getSelectedPoints: function () { | 
|         var points = []; | 
|         each(this.series, function (serie) { | 
|             points = points.concat(grep(serie.points || [], function (point) { | 
|                 return point.selected; | 
|             })); | 
|         }); | 
|         return points; | 
|     }, | 
|   | 
|     /** | 
|      * Get the currently selected series | 
|      */ | 
|     getSelectedSeries: function () { | 
|         return grep(this.series, function (serie) { | 
|             return serie.selected; | 
|         }); | 
|     }, | 
|   | 
|     /** | 
|      * Generate stacks for each series and calculate stacks total values | 
|      */ | 
|     getStacks: function () { | 
|         var chart = this; | 
|   | 
|         // reset stacks for each yAxis | 
|         each(chart.yAxis, function (axis) { | 
|             if (axis.stacks && axis.hasVisibleSeries) { | 
|                 axis.oldStacks = axis.stacks; | 
|             } | 
|         }); | 
|   | 
|         each(chart.series, function (series) { | 
|             if (series.options.stacking && (series.visible === true || chart.options.chart.ignoreHiddenSeries === false)) { | 
|                 series.stackKey = series.type + pick(series.options.stack, ''); | 
|             } | 
|         }); | 
|     }, | 
|   | 
|     /** | 
|      * Display the zoom button | 
|      */ | 
|     showResetZoom: function () { | 
|         var chart = this, | 
|             lang = defaultOptions.lang, | 
|             btnOptions = chart.options.chart.resetZoomButton, | 
|             theme = btnOptions.theme, | 
|             states = theme.states, | 
|             alignTo = btnOptions.relativeTo === 'chart' ? null : 'plotBox'; | 
|              | 
|         this.resetZoomButton = chart.renderer.button(lang.resetZoom, null, null, function () { chart.zoomOut(); }, theme, states && states.hover) | 
|             .attr({ | 
|                 align: btnOptions.position.align, | 
|                 title: lang.resetZoomTitle | 
|             }) | 
|             .add() | 
|             .align(btnOptions.position, false, alignTo); | 
|              | 
|     }, | 
|   | 
|     /** | 
|      * Zoom out to 1:1 | 
|      */ | 
|     zoomOut: function () { | 
|         var chart = this; | 
|         fireEvent(chart, 'selection', { resetSelection: true }, function () {  | 
|             chart.zoom(); | 
|         }); | 
|     }, | 
|   | 
|     /** | 
|      * Zoom into a given portion of the chart given by axis coordinates | 
|      * @param {Object} event | 
|      */ | 
|     zoom: function (event) { | 
|         var chart = this, | 
|             hasZoomed, | 
|             pointer = chart.pointer, | 
|             displayButton = false, | 
|             resetZoomButton; | 
|   | 
|         // If zoom is called with no arguments, reset the axes | 
|         if (!event || event.resetSelection) { | 
|             each(chart.axes, function (axis) { | 
|                 hasZoomed = axis.zoom(); | 
|             }); | 
|         } else { // else, zoom in on all axes | 
|             each(event.xAxis.concat(event.yAxis), function (axisData) { | 
|                 var axis = axisData.axis, | 
|                     isXAxis = axis.isXAxis; | 
|   | 
|                 // don't zoom more than minRange | 
|                 if (pointer[isXAxis ? 'zoomX' : 'zoomY'] || pointer[isXAxis ? 'pinchX' : 'pinchY']) { | 
|                     hasZoomed = axis.zoom(axisData.min, axisData.max); | 
|                     if (axis.displayBtn) { | 
|                         displayButton = true; | 
|                     } | 
|                 } | 
|             }); | 
|         } | 
|          | 
|         // Show or hide the Reset zoom button | 
|         resetZoomButton = chart.resetZoomButton; | 
|         if (displayButton && !resetZoomButton) { | 
|             chart.showResetZoom(); | 
|         } else if (!displayButton && isObject(resetZoomButton)) { | 
|             chart.resetZoomButton = resetZoomButton.destroy(); | 
|         } | 
|          | 
|   | 
|         // Redraw | 
|         if (hasZoomed) { | 
|             chart.redraw( | 
|                 pick(chart.options.chart.animation, event && event.animation, chart.pointCount < 100) // animation | 
|             ); | 
|         } | 
|     }, | 
|   | 
|     /** | 
|      * Pan the chart by dragging the mouse across the pane. This function is called | 
|      * on mouse move, and the distance to pan is computed from chartX compared to | 
|      * the first chartX position in the dragging operation. | 
|      */ | 
|     pan: function (e, panning) { | 
|   | 
|         var chart = this, | 
|             hoverPoints = chart.hoverPoints, | 
|             doRedraw; | 
|   | 
|         // remove active points for shared tooltip | 
|         if (hoverPoints) { | 
|             each(hoverPoints, function (point) { | 
|                 point.setState(); | 
|             }); | 
|         } | 
|   | 
|         each(panning === 'xy' ? [1, 0] : [1], function (isX) { // xy is used in maps | 
|             var mousePos = e[isX ? 'chartX' : 'chartY'], | 
|                 axis = chart[isX ? 'xAxis' : 'yAxis'][0], | 
|                 startPos = chart[isX ? 'mouseDownX' : 'mouseDownY'], | 
|                 halfPointRange = (axis.pointRange || 0) / 2, | 
|                 extremes = axis.getExtremes(), | 
|                 newMin = axis.toValue(startPos - mousePos, true) + halfPointRange, | 
|                 newMax = axis.toValue(startPos + chart[isX ? 'plotWidth' : 'plotHeight'] - mousePos, true) - halfPointRange; | 
|   | 
|             if (axis.series.length && newMin > mathMin(extremes.dataMin, extremes.min) && newMax < mathMax(extremes.dataMax, extremes.max)) { | 
|                 axis.setExtremes(newMin, newMax, false, false, { trigger: 'pan' }); | 
|                 doRedraw = true; | 
|             } | 
|   | 
|             chart[isX ? 'mouseDownX' : 'mouseDownY'] = mousePos; // set new reference for next run | 
|         }); | 
|   | 
|         if (doRedraw) { | 
|             chart.redraw(false); | 
|         } | 
|         css(chart.container, { cursor: 'move' }); | 
|     }, | 
|   | 
|     /** | 
|      * Show the title and subtitle of the chart | 
|      * | 
|      * @param titleOptions {Object} New title options | 
|      * @param subtitleOptions {Object} New subtitle options | 
|      * | 
|      */ | 
|     setTitle: function (titleOptions, subtitleOptions) { | 
|         var chart = this, | 
|             options = chart.options, | 
|             chartTitleOptions, | 
|             chartSubtitleOptions; | 
|   | 
|         chartTitleOptions = options.title = merge(options.title, titleOptions); | 
|         chartSubtitleOptions = options.subtitle = merge(options.subtitle, subtitleOptions); | 
|   | 
|         // add title and subtitle | 
|         each([ | 
|             ['title', titleOptions, chartTitleOptions], | 
|             ['subtitle', subtitleOptions, chartSubtitleOptions] | 
|         ], function (arr) { | 
|             var name = arr[0], | 
|                 title = chart[name], | 
|                 titleOptions = arr[1], | 
|                 chartTitleOptions = arr[2]; | 
|   | 
|             if (title && titleOptions) { | 
|                 chart[name] = title = title.destroy(); // remove old | 
|             } | 
|              | 
|             if (chartTitleOptions && chartTitleOptions.text && !title) { | 
|                 chart[name] = chart.renderer.text( | 
|                     chartTitleOptions.text, | 
|                     0, | 
|                     0, | 
|                     chartTitleOptions.useHTML | 
|                 ) | 
|                 .attr({ | 
|                     align: chartTitleOptions.align, | 
|                     'class': PREFIX + name, | 
|                     zIndex: chartTitleOptions.zIndex || 4 | 
|                 }) | 
|                 .css(chartTitleOptions.style) | 
|                 .add(); | 
|             }     | 
|         }); | 
|         chart.layOutTitles(); | 
|     }, | 
|   | 
|     /** | 
|      * Lay out the chart titles and cache the full offset height for use in getMargins | 
|      */ | 
|     layOutTitles: function () { | 
|         var titleOffset = 0, | 
|             title = this.title, | 
|             subtitle = this.subtitle, | 
|             options = this.options, | 
|             titleOptions = options.title, | 
|             subtitleOptions = options.subtitle, | 
|             autoWidth = this.spacingBox.width - 44; // 44 makes room for default context button | 
|   | 
|         if (title) { | 
|             title | 
|                 .css({ width: (titleOptions.width || autoWidth) + PX }) | 
|                 .align(extend({ y: 15 }, titleOptions), false, 'spacingBox'); | 
|              | 
|             if (!titleOptions.floating && !titleOptions.verticalAlign) { | 
|                 titleOffset = title.getBBox().height; | 
|   | 
|                 // Adjust for browser consistency + backwards compat after #776 fix | 
|                 if (titleOffset >= 18 && titleOffset <= 25) { | 
|                     titleOffset = 15;  | 
|                 } | 
|             } | 
|         } | 
|         if (subtitle) { | 
|             subtitle | 
|                 .css({ width: (subtitleOptions.width || autoWidth) + PX }) | 
|                 .align(extend({ y: titleOffset + titleOptions.margin }, subtitleOptions), false, 'spacingBox'); | 
|              | 
|             if (!subtitleOptions.floating && !subtitleOptions.verticalAlign) { | 
|                 titleOffset = mathCeil(titleOffset + subtitle.getBBox().height); | 
|             } | 
|         } | 
|   | 
|         this.titleOffset = titleOffset; // used in getMargins | 
|     }, | 
|   | 
|     /** | 
|      * Get chart width and height according to options and container size | 
|      */ | 
|     getChartSize: function () { | 
|         var chart = this, | 
|             optionsChart = chart.options.chart, | 
|             renderTo = chart.renderToClone || chart.renderTo; | 
|   | 
|         // get inner width and height from jQuery (#824) | 
|         chart.containerWidth = adapterRun(renderTo, 'width'); | 
|         chart.containerHeight = adapterRun(renderTo, 'height'); | 
|          | 
|         chart.chartWidth = mathMax(0, optionsChart.width || chart.containerWidth || 600); // #1393, 1460 | 
|         chart.chartHeight = mathMax(0, pick(optionsChart.height, | 
|             // the offsetHeight of an empty container is 0 in standard browsers, but 19 in IE7: | 
|             chart.containerHeight > 19 ? chart.containerHeight : 400)); | 
|     }, | 
|   | 
|     /** | 
|      * Create a clone of the chart's renderTo div and place it outside the viewport to allow | 
|      * size computation on chart.render and chart.redraw | 
|      */ | 
|     cloneRenderTo: function (revert) { | 
|         var clone = this.renderToClone, | 
|             container = this.container; | 
|          | 
|         // Destroy the clone and bring the container back to the real renderTo div | 
|         if (revert) { | 
|             if (clone) { | 
|                 this.renderTo.appendChild(container); | 
|                 discardElement(clone); | 
|                 delete this.renderToClone; | 
|             } | 
|          | 
|         // Set up the clone | 
|         } else { | 
|             if (container && container.parentNode === this.renderTo) { | 
|                 this.renderTo.removeChild(container); // do not clone this | 
|             } | 
|             this.renderToClone = clone = this.renderTo.cloneNode(0); | 
|             css(clone, { | 
|                 position: ABSOLUTE, | 
|                 top: '-9999px', | 
|                 display: 'block' // #833 | 
|             }); | 
|             doc.body.appendChild(clone); | 
|             if (container) { | 
|                 clone.appendChild(container); | 
|             } | 
|         } | 
|     }, | 
|   | 
|     /** | 
|      * Get the containing element, determine the size and create the inner container | 
|      * div to hold the chart | 
|      */ | 
|     getContainer: function () { | 
|         var chart = this, | 
|             container, | 
|             optionsChart = chart.options.chart, | 
|             chartWidth, | 
|             chartHeight, | 
|             renderTo, | 
|             indexAttrName = 'data-highcharts-chart', | 
|             oldChartIndex, | 
|             containerId; | 
|   | 
|         chart.renderTo = renderTo = optionsChart.renderTo; | 
|         containerId = PREFIX + idCounter++; | 
|   | 
|         if (isString(renderTo)) { | 
|             chart.renderTo = renderTo = doc.getElementById(renderTo); | 
|         } | 
|          | 
|         // Display an error if the renderTo is wrong | 
|         if (!renderTo) { | 
|             error(13, true); | 
|         } | 
|          | 
|         // If the container already holds a chart, destroy it | 
|         oldChartIndex = pInt(attr(renderTo, indexAttrName)); | 
|         if (!isNaN(oldChartIndex) && charts[oldChartIndex]) { | 
|             charts[oldChartIndex].destroy(); | 
|         }         | 
|          | 
|         // Make a reference to the chart from the div | 
|         attr(renderTo, indexAttrName, chart.index); | 
|   | 
|         // remove previous chart | 
|         renderTo.innerHTML = ''; | 
|   | 
|         // If the container doesn't have an offsetWidth, it has or is a child of a node | 
|         // that has display:none. We need to temporarily move it out to a visible | 
|         // state to determine the size, else the legend and tooltips won't render | 
|         // properly | 
|         if (!renderTo.offsetWidth) { | 
|             chart.cloneRenderTo(); | 
|         } | 
|   | 
|         // get the width and height | 
|         chart.getChartSize(); | 
|         chartWidth = chart.chartWidth; | 
|         chartHeight = chart.chartHeight; | 
|   | 
|         // create the inner container | 
|         chart.container = container = createElement(DIV, { | 
|                 className: PREFIX + 'container' + | 
|                     (optionsChart.className ? ' ' + optionsChart.className : ''), | 
|                 id: containerId | 
|             }, extend({ | 
|                 position: RELATIVE, | 
|                 overflow: HIDDEN, // needed for context menu (avoid scrollbars) and | 
|                     // content overflow in IE | 
|                 width: chartWidth + PX, | 
|                 height: chartHeight + PX, | 
|                 textAlign: 'left', | 
|                 lineHeight: 'normal', // #427 | 
|                 zIndex: 0, // #1072 | 
|                 '-webkit-tap-highlight-color': 'rgba(0,0,0,0)' | 
|             }, optionsChart.style), | 
|             chart.renderToClone || renderTo | 
|         ); | 
|   | 
|         // cache the cursor (#1650) | 
|         chart._cursor = container.style.cursor; | 
|   | 
|         chart.renderer = | 
|             optionsChart.forExport ? // force SVG, used for SVG export | 
|                 new SVGRenderer(container, chartWidth, chartHeight, true) : | 
|                 new Renderer(container, chartWidth, chartHeight); | 
|   | 
|         if (useCanVG) { | 
|             // If we need canvg library, extend and configure the renderer | 
|             // to get the tracker for translating mouse events | 
|             chart.renderer.create(chart, container, chartWidth, chartHeight); | 
|         } | 
|     }, | 
|   | 
|     /** | 
|      * Calculate margins by rendering axis labels in a preliminary position. Title, | 
|      * subtitle and legend have already been rendered at this stage, but will be | 
|      * moved into their final positions | 
|      */ | 
|     getMargins: function () { | 
|         var chart = this, | 
|             spacing = chart.spacing, | 
|             axisOffset, | 
|             legend = chart.legend, | 
|             margin = chart.margin, | 
|             legendOptions = chart.options.legend, | 
|             legendMargin = pick(legendOptions.margin, 10), | 
|             legendX = legendOptions.x, | 
|             legendY = legendOptions.y, | 
|             align = legendOptions.align, | 
|             verticalAlign = legendOptions.verticalAlign, | 
|             titleOffset = chart.titleOffset; | 
|   | 
|         chart.resetMargins(); | 
|         axisOffset = chart.axisOffset; | 
|   | 
|         // Adjust for title and subtitle | 
|         if (titleOffset && !defined(margin[0])) { | 
|             chart.plotTop = mathMax(chart.plotTop, titleOffset + chart.options.title.margin + spacing[0]); | 
|         } | 
|          | 
|         // Adjust for legend | 
|         if (legend.display && !legendOptions.floating) { | 
|             if (align === 'right') { // horizontal alignment handled first | 
|                 if (!defined(margin[1])) { | 
|                     chart.marginRight = mathMax( | 
|                         chart.marginRight, | 
|                         legend.legendWidth - legendX + legendMargin + spacing[1] | 
|                     ); | 
|                 } | 
|             } else if (align === 'left') { | 
|                 if (!defined(margin[3])) { | 
|                     chart.plotLeft = mathMax( | 
|                         chart.plotLeft, | 
|                         legend.legendWidth + legendX + legendMargin + spacing[3] | 
|                     ); | 
|                 } | 
|   | 
|             } else if (verticalAlign === 'top') { | 
|                 if (!defined(margin[0])) { | 
|                     chart.plotTop = mathMax( | 
|                         chart.plotTop, | 
|                         legend.legendHeight + legendY + legendMargin + spacing[0] | 
|                     ); | 
|                 } | 
|   | 
|             } else if (verticalAlign === 'bottom') { | 
|                 if (!defined(margin[2])) { | 
|                     chart.marginBottom = mathMax( | 
|                         chart.marginBottom, | 
|                         legend.legendHeight - legendY + legendMargin + spacing[2] | 
|                     ); | 
|                 } | 
|             } | 
|         } | 
|   | 
|         // adjust for scroller | 
|         if (chart.extraBottomMargin) { | 
|             chart.marginBottom += chart.extraBottomMargin; | 
|         } | 
|         if (chart.extraTopMargin) { | 
|             chart.plotTop += chart.extraTopMargin; | 
|         } | 
|   | 
|         // pre-render axes to get labels offset width | 
|         if (chart.hasCartesianSeries) { | 
|             each(chart.axes, function (axis) { | 
|                 axis.getOffset(); | 
|             }); | 
|         } | 
|          | 
|         if (!defined(margin[3])) { | 
|             chart.plotLeft += axisOffset[3]; | 
|         } | 
|         if (!defined(margin[0])) { | 
|             chart.plotTop += axisOffset[0]; | 
|         } | 
|         if (!defined(margin[2])) { | 
|             chart.marginBottom += axisOffset[2]; | 
|         } | 
|         if (!defined(margin[1])) { | 
|             chart.marginRight += axisOffset[1]; | 
|         } | 
|   | 
|         chart.setChartSize(); | 
|   | 
|     }, | 
|   | 
|     /** | 
|      * Add the event handlers necessary for auto resizing | 
|      * | 
|      */ | 
|     initReflow: function () { | 
|         var chart = this, | 
|             optionsChart = chart.options.chart, | 
|             renderTo = chart.renderTo, | 
|             reflowTimeout; | 
|              | 
|         function reflow(e) { | 
|             var width = optionsChart.width || adapterRun(renderTo, 'width'), | 
|                 height = optionsChart.height || adapterRun(renderTo, 'height'), | 
|                 target = e ? e.target : win; // #805 - MooTools doesn't supply e | 
|                  | 
|             // Width and height checks for display:none. Target is doc in IE8 and Opera, | 
|             // win in Firefox, Chrome and IE9. | 
|             if (!chart.hasUserSize && width && height && (target === win || target === doc)) { | 
|                  | 
|                 if (width !== chart.containerWidth || height !== chart.containerHeight) { | 
|                     clearTimeout(reflowTimeout); | 
|                     chart.reflowTimeout = reflowTimeout = setTimeout(function () { | 
|                         if (chart.container) { // It may have been destroyed in the meantime (#1257) | 
|                             chart.setSize(width, height, false); | 
|                             chart.hasUserSize = null; | 
|                         } | 
|                     }, 100); | 
|                 } | 
|                 chart.containerWidth = width; | 
|                 chart.containerHeight = height; | 
|             } | 
|         } | 
|         chart.reflow = reflow; | 
|         addEvent(win, 'resize', reflow); | 
|         addEvent(chart, 'destroy', function () { | 
|             removeEvent(win, 'resize', reflow); | 
|         }); | 
|     }, | 
|   | 
|     /** | 
|      * Resize the chart to a given width and height | 
|      * @param {Number} width | 
|      * @param {Number} height | 
|      * @param {Object|Boolean} animation | 
|      */ | 
|     setSize: function (width, height, animation) { | 
|         var chart = this, | 
|             chartWidth, | 
|             chartHeight, | 
|             fireEndResize; | 
|   | 
|         // Handle the isResizing counter | 
|         chart.isResizing += 1; | 
|         fireEndResize = function () { | 
|             if (chart) { | 
|                 fireEvent(chart, 'endResize', null, function () { | 
|                     chart.isResizing -= 1; | 
|                 }); | 
|             } | 
|         }; | 
|   | 
|         // set the animation for the current process | 
|         setAnimation(animation, chart); | 
|   | 
|         chart.oldChartHeight = chart.chartHeight; | 
|         chart.oldChartWidth = chart.chartWidth; | 
|         if (defined(width)) { | 
|             chart.chartWidth = chartWidth = mathMax(0, mathRound(width)); | 
|             chart.hasUserSize = !!chartWidth; | 
|         } | 
|         if (defined(height)) { | 
|             chart.chartHeight = chartHeight = mathMax(0, mathRound(height)); | 
|         } | 
|   | 
|         css(chart.container, { | 
|             width: chartWidth + PX, | 
|             height: chartHeight + PX | 
|         }); | 
|         chart.setChartSize(true); | 
|         chart.renderer.setSize(chartWidth, chartHeight, animation); | 
|   | 
|         // handle axes | 
|         chart.maxTicks = null; | 
|         each(chart.axes, function (axis) { | 
|             axis.isDirty = true; | 
|             axis.setScale(); | 
|         }); | 
|   | 
|         // make sure non-cartesian series are also handled | 
|         each(chart.series, function (serie) { | 
|             serie.isDirty = true; | 
|         }); | 
|   | 
|         chart.isDirtyLegend = true; // force legend redraw | 
|         chart.isDirtyBox = true; // force redraw of plot and chart border | 
|   | 
|         chart.getMargins(); | 
|   | 
|         chart.redraw(animation); | 
|   | 
|   | 
|         chart.oldChartHeight = null; | 
|         fireEvent(chart, 'resize'); | 
|   | 
|         // fire endResize and set isResizing back | 
|         // If animation is disabled, fire without delay | 
|         if (globalAnimation === false) { | 
|             fireEndResize(); | 
|         } else { // else set a timeout with the animation duration | 
|             setTimeout(fireEndResize, (globalAnimation && globalAnimation.duration) || 500); | 
|         } | 
|     }, | 
|   | 
|     /** | 
|      * Set the public chart properties. This is done before and after the pre-render | 
|      * to determine margin sizes | 
|      */ | 
|     setChartSize: function (skipAxes) { | 
|         var chart = this, | 
|             inverted = chart.inverted, | 
|             renderer = chart.renderer, | 
|             chartWidth = chart.chartWidth, | 
|             chartHeight = chart.chartHeight, | 
|             optionsChart = chart.options.chart, | 
|             spacing = chart.spacing, | 
|             clipOffset = chart.clipOffset, | 
|             clipX, | 
|             clipY, | 
|             plotLeft, | 
|             plotTop, | 
|             plotWidth, | 
|             plotHeight, | 
|             plotBorderWidth; | 
|   | 
|         chart.plotLeft = plotLeft = mathRound(chart.plotLeft); | 
|         chart.plotTop = plotTop = mathRound(chart.plotTop); | 
|         chart.plotWidth = plotWidth = mathMax(0, mathRound(chartWidth - plotLeft - chart.marginRight)); | 
|         chart.plotHeight = plotHeight = mathMax(0, mathRound(chartHeight - plotTop - chart.marginBottom)); | 
|   | 
|         chart.plotSizeX = inverted ? plotHeight : plotWidth; | 
|         chart.plotSizeY = inverted ? plotWidth : plotHeight; | 
|          | 
|         chart.plotBorderWidth = optionsChart.plotBorderWidth || 0; | 
|   | 
|         // Set boxes used for alignment | 
|         chart.spacingBox = renderer.spacingBox = { | 
|             x: spacing[3], | 
|             y: spacing[0], | 
|             width: chartWidth - spacing[3] - spacing[1], | 
|             height: chartHeight - spacing[0] - spacing[2] | 
|         }; | 
|         chart.plotBox = renderer.plotBox = { | 
|             x: plotLeft, | 
|             y: plotTop, | 
|             width: plotWidth, | 
|             height: plotHeight | 
|         }; | 
|   | 
|         plotBorderWidth = 2 * mathFloor(chart.plotBorderWidth / 2); | 
|         clipX = mathCeil(mathMax(plotBorderWidth, clipOffset[3]) / 2); | 
|         clipY = mathCeil(mathMax(plotBorderWidth, clipOffset[0]) / 2); | 
|         chart.clipBox = { | 
|             x: clipX,  | 
|             y: clipY,  | 
|             width: mathFloor(chart.plotSizeX - mathMax(plotBorderWidth, clipOffset[1]) / 2 - clipX),  | 
|             height: mathFloor(chart.plotSizeY - mathMax(plotBorderWidth, clipOffset[2]) / 2 - clipY) | 
|         }; | 
|   | 
|         if (!skipAxes) { | 
|             each(chart.axes, function (axis) { | 
|                 axis.setAxisSize(); | 
|                 axis.setAxisTranslation(); | 
|             }); | 
|         } | 
|     }, | 
|   | 
|     /** | 
|      * Initial margins before auto size margins are applied | 
|      */ | 
|     resetMargins: function () { | 
|         var chart = this, | 
|             spacing = chart.spacing, | 
|             margin = chart.margin; | 
|   | 
|         chart.plotTop = pick(margin[0], spacing[0]); | 
|         chart.marginRight = pick(margin[1], spacing[1]); | 
|         chart.marginBottom = pick(margin[2], spacing[2]); | 
|         chart.plotLeft = pick(margin[3], spacing[3]); | 
|         chart.axisOffset = [0, 0, 0, 0]; // top, right, bottom, left | 
|         chart.clipOffset = [0, 0, 0, 0]; | 
|     }, | 
|   | 
|     /** | 
|      * Draw the borders and backgrounds for chart and plot area | 
|      */ | 
|     drawChartBox: function () { | 
|         var chart = this, | 
|             optionsChart = chart.options.chart, | 
|             renderer = chart.renderer, | 
|             chartWidth = chart.chartWidth, | 
|             chartHeight = chart.chartHeight, | 
|             chartBackground = chart.chartBackground, | 
|             plotBackground = chart.plotBackground, | 
|             plotBorder = chart.plotBorder, | 
|             plotBGImage = chart.plotBGImage, | 
|             chartBorderWidth = optionsChart.borderWidth || 0, | 
|             chartBackgroundColor = optionsChart.backgroundColor, | 
|             plotBackgroundColor = optionsChart.plotBackgroundColor, | 
|             plotBackgroundImage = optionsChart.plotBackgroundImage, | 
|             plotBorderWidth = optionsChart.plotBorderWidth || 0, | 
|             mgn, | 
|             bgAttr, | 
|             plotLeft = chart.plotLeft, | 
|             plotTop = chart.plotTop, | 
|             plotWidth = chart.plotWidth, | 
|             plotHeight = chart.plotHeight, | 
|             plotBox = chart.plotBox, | 
|             clipRect = chart.clipRect, | 
|             clipBox = chart.clipBox; | 
|   | 
|         // Chart area | 
|         mgn = chartBorderWidth + (optionsChart.shadow ? 8 : 0); | 
|   | 
|         if (chartBorderWidth || chartBackgroundColor) { | 
|             if (!chartBackground) { | 
|                  | 
|                 bgAttr = { | 
|                     fill: chartBackgroundColor || NONE | 
|                 }; | 
|                 if (chartBorderWidth) { // #980 | 
|                     bgAttr.stroke = optionsChart.borderColor; | 
|                     bgAttr['stroke-width'] = chartBorderWidth; | 
|                 } | 
|                 chart.chartBackground = renderer.rect(mgn / 2, mgn / 2, chartWidth - mgn, chartHeight - mgn, | 
|                         optionsChart.borderRadius, chartBorderWidth) | 
|                     .attr(bgAttr) | 
|                     .add() | 
|                     .shadow(optionsChart.shadow); | 
|   | 
|             } else { // resize | 
|                 chartBackground.animate( | 
|                     chartBackground.crisp(null, null, null, chartWidth - mgn, chartHeight - mgn) | 
|                 ); | 
|             } | 
|         } | 
|   | 
|   | 
|         // Plot background | 
|         if (plotBackgroundColor) { | 
|             if (!plotBackground) { | 
|                 chart.plotBackground = renderer.rect(plotLeft, plotTop, plotWidth, plotHeight, 0) | 
|                     .attr({ | 
|                         fill: plotBackgroundColor | 
|                     }) | 
|                     .add() | 
|                     .shadow(optionsChart.plotShadow); | 
|             } else { | 
|                 plotBackground.animate(plotBox); | 
|             } | 
|         } | 
|         if (plotBackgroundImage) { | 
|             if (!plotBGImage) { | 
|                 chart.plotBGImage = renderer.image(plotBackgroundImage, plotLeft, plotTop, plotWidth, plotHeight) | 
|                     .add(); | 
|             } else { | 
|                 plotBGImage.animate(plotBox); | 
|             } | 
|         } | 
|          | 
|         // Plot clip | 
|         if (!clipRect) { | 
|             chart.clipRect = renderer.clipRect(clipBox); | 
|         } else { | 
|             clipRect.animate({ | 
|                 width: clipBox.width, | 
|                 height: clipBox.height | 
|             }); | 
|         } | 
|   | 
|         // Plot area border | 
|         if (plotBorderWidth) { | 
|             if (!plotBorder) { | 
|                 chart.plotBorder = renderer.rect(plotLeft, plotTop, plotWidth, plotHeight, 0, -plotBorderWidth) | 
|                     .attr({ | 
|                         stroke: optionsChart.plotBorderColor, | 
|                         'stroke-width': plotBorderWidth, | 
|                         zIndex: 1 | 
|                     }) | 
|                     .add(); | 
|             } else { | 
|                 plotBorder.animate( | 
|                     plotBorder.crisp(null, plotLeft, plotTop, plotWidth, plotHeight) | 
|                 ); | 
|             } | 
|         } | 
|   | 
|         // reset | 
|         chart.isDirtyBox = false; | 
|     }, | 
|   | 
|     /** | 
|      * Detect whether a certain chart property is needed based on inspecting its options | 
|      * and series. This mainly applies to the chart.invert property, and in extensions to  | 
|      * the chart.angular and chart.polar properties. | 
|      */ | 
|     propFromSeries: function () { | 
|         var chart = this, | 
|             optionsChart = chart.options.chart, | 
|             klass, | 
|             seriesOptions = chart.options.series, | 
|             i, | 
|             value; | 
|              | 
|              | 
|         each(['inverted', 'angular', 'polar'], function (key) { | 
|              | 
|             // The default series type's class | 
|             klass = seriesTypes[optionsChart.type || optionsChart.defaultSeriesType]; | 
|              | 
|             // Get the value from available chart-wide properties | 
|             value = ( | 
|                 chart[key] || // 1. it is set before | 
|                 optionsChart[key] || // 2. it is set in the options | 
|                 (klass && klass.prototype[key]) // 3. it's default series class requires it | 
|             ); | 
|      | 
|             // 4. Check if any the chart's series require it | 
|             i = seriesOptions && seriesOptions.length; | 
|             while (!value && i--) { | 
|                 klass = seriesTypes[seriesOptions[i].type]; | 
|                 if (klass && klass.prototype[key]) { | 
|                     value = true; | 
|                 } | 
|             } | 
|      | 
|             // Set the chart property | 
|             chart[key] = value;     | 
|         }); | 
|          | 
|     }, | 
|   | 
|     /** | 
|      * Link two or more series together. This is done initially from Chart.render, | 
|      * and after Chart.addSeries and Series.remove. | 
|      */ | 
|     linkSeries: function () { | 
|         var chart = this, | 
|             chartSeries = chart.series; | 
|   | 
|         // Reset links | 
|         each(chartSeries, function (series) { | 
|             series.linkedSeries.length = 0; | 
|         }); | 
|   | 
|         // Apply new links | 
|         each(chartSeries, function (series) { | 
|             var linkedTo = series.options.linkedTo; | 
|             if (isString(linkedTo)) { | 
|                 if (linkedTo === ':previous') { | 
|                     linkedTo = chart.series[series.index - 1]; | 
|                 } else { | 
|                     linkedTo = chart.get(linkedTo); | 
|                 } | 
|                 if (linkedTo) { | 
|                     linkedTo.linkedSeries.push(series); | 
|                     series.linkedParent = linkedTo; | 
|                 } | 
|             } | 
|         }); | 
|     }, | 
|   | 
|     /** | 
|      * Render all graphics for the chart | 
|      */ | 
|     render: function () { | 
|         var chart = this, | 
|             axes = chart.axes, | 
|             renderer = chart.renderer, | 
|             options = chart.options; | 
|   | 
|         var labels = options.labels, | 
|             credits = options.credits, | 
|             creditsHref; | 
|   | 
|         // Title | 
|         chart.setTitle(); | 
|   | 
|   | 
|         // Legend | 
|         chart.legend = new Legend(chart, options.legend); | 
|   | 
|         chart.getStacks(); // render stacks | 
|   | 
|         // Get margins by pre-rendering axes | 
|         // set axes scales | 
|         each(axes, function (axis) { | 
|             axis.setScale(); | 
|         }); | 
|   | 
|         chart.getMargins(); | 
|   | 
|         chart.maxTicks = null; // reset for second pass | 
|         each(axes, function (axis) { | 
|             axis.setTickPositions(true); // update to reflect the new margins | 
|             axis.setMaxTicks(); | 
|         }); | 
|         chart.adjustTickAmounts(); | 
|         chart.getMargins(); // second pass to check for new labels | 
|   | 
|   | 
|         // Draw the borders and backgrounds | 
|         chart.drawChartBox();         | 
|   | 
|   | 
|         // Axes | 
|         if (chart.hasCartesianSeries) { | 
|             each(axes, function (axis) { | 
|                 axis.render(); | 
|             }); | 
|         } | 
|   | 
|         // The series | 
|         if (!chart.seriesGroup) { | 
|             chart.seriesGroup = renderer.g('series-group') | 
|                 .attr({ zIndex: 3 }) | 
|                 .add(); | 
|         } | 
|         each(chart.series, function (serie) { | 
|             serie.translate(); | 
|             serie.setTooltipPoints(); | 
|             serie.render(); | 
|         }); | 
|   | 
|         // Labels | 
|         if (labels.items) { | 
|             each(labels.items, function (label) { | 
|                 var style = extend(labels.style, label.style), | 
|                     x = pInt(style.left) + chart.plotLeft, | 
|                     y = pInt(style.top) + chart.plotTop + 12; | 
|   | 
|                 // delete to prevent rewriting in IE | 
|                 delete style.left; | 
|                 delete style.top; | 
|   | 
|                 renderer.text( | 
|                     label.html, | 
|                     x, | 
|                     y | 
|                 ) | 
|                 .attr({ zIndex: 2 }) | 
|                 .css(style) | 
|                 .add(); | 
|   | 
|             }); | 
|         } | 
|   | 
|         // Credits | 
|         if (credits.enabled && !chart.credits) { | 
|             creditsHref = credits.href; | 
|             chart.credits = renderer.text( | 
|                 credits.text, | 
|                 0, | 
|                 0 | 
|             ) | 
|             .on('click', function () { | 
|                 if (creditsHref) { | 
|                     location.href = creditsHref; | 
|                 } | 
|             }) | 
|             .attr({ | 
|                 align: credits.position.align, | 
|                 zIndex: 8 | 
|             }) | 
|             .css(credits.style) | 
|             .add() | 
|             .align(credits.position); | 
|         } | 
|   | 
|         // Set flag | 
|         chart.hasRendered = true; | 
|   | 
|     }, | 
|   | 
|     /** | 
|      * Clean up memory usage | 
|      */ | 
|     destroy: function () { | 
|         var chart = this, | 
|             axes = chart.axes, | 
|             series = chart.series, | 
|             container = chart.container, | 
|             i, | 
|             parentNode = container && container.parentNode; | 
|              | 
|         // fire the chart.destoy event | 
|         fireEvent(chart, 'destroy'); | 
|          | 
|         // Delete the chart from charts lookup array | 
|         charts[chart.index] = UNDEFINED; | 
|         chart.renderTo.removeAttribute('data-highcharts-chart'); | 
|   | 
|         // remove events | 
|         removeEvent(chart); | 
|   | 
|         // ==== Destroy collections: | 
|         // Destroy axes | 
|         i = axes.length; | 
|         while (i--) { | 
|             axes[i] = axes[i].destroy(); | 
|         } | 
|   | 
|         // Destroy each series | 
|         i = series.length; | 
|         while (i--) { | 
|             series[i] = series[i].destroy(); | 
|         } | 
|   | 
|         // ==== Destroy chart properties: | 
|         each(['title', 'subtitle', 'chartBackground', 'plotBackground', 'plotBGImage',  | 
|                 'plotBorder', 'seriesGroup', 'clipRect', 'credits', 'pointer', 'scroller',  | 
|                 'rangeSelector', 'legend', 'resetZoomButton', 'tooltip', 'renderer'], function (name) { | 
|             var prop = chart[name]; | 
|   | 
|             if (prop && prop.destroy) { | 
|                 chart[name] = prop.destroy(); | 
|             } | 
|         }); | 
|   | 
|         // remove container and all SVG | 
|         if (container) { // can break in IE when destroyed before finished loading | 
|             container.innerHTML = ''; | 
|             removeEvent(container); | 
|             if (parentNode) { | 
|                 discardElement(container); | 
|             } | 
|   | 
|         } | 
|   | 
|         // clean it all up | 
|         for (i in chart) { | 
|             delete chart[i]; | 
|         } | 
|   | 
|     }, | 
|   | 
|   | 
|     /** | 
|      * VML namespaces can't be added until after complete. Listening | 
|      * for Perini's doScroll hack is not enough. | 
|      */ | 
|     isReadyToRender: function () { | 
|         var chart = this; | 
|   | 
|         // Note: in spite of JSLint's complaints, win == win.top is required | 
|         /*jslint eqeq: true*/ | 
|         if ((!hasSVG && (win == win.top && doc.readyState !== 'complete')) || (useCanVG && !win.canvg)) { | 
|         /*jslint eqeq: false*/ | 
|             if (useCanVG) { | 
|                 // Delay rendering until canvg library is downloaded and ready | 
|                 CanVGController.push(function () { chart.firstRender(); }, chart.options.global.canvasToolsURL); | 
|             } else { | 
|                 doc.attachEvent('onreadystatechange', function () { | 
|                     doc.detachEvent('onreadystatechange', chart.firstRender); | 
|                     if (doc.readyState === 'complete') { | 
|                         chart.firstRender(); | 
|                     } | 
|                 }); | 
|             } | 
|             return false; | 
|         } | 
|         return true; | 
|     }, | 
|   | 
|     /** | 
|      * Prepare for first rendering after all data are loaded | 
|      */ | 
|     firstRender: function () { | 
|         var chart = this, | 
|             options = chart.options, | 
|             callback = chart.callback; | 
|   | 
|         // Check whether the chart is ready to render | 
|         if (!chart.isReadyToRender()) { | 
|             return; | 
|         } | 
|   | 
|         // Create the container | 
|         chart.getContainer(); | 
|   | 
|         // Run an early event after the container and renderer are established | 
|         fireEvent(chart, 'init'); | 
|   | 
|          | 
|         chart.resetMargins(); | 
|         chart.setChartSize(); | 
|   | 
|         // Set the common chart properties (mainly invert) from the given series | 
|         chart.propFromSeries(); | 
|   | 
|         // get axes | 
|         chart.getAxes(); | 
|   | 
|         // Initialize the series | 
|         each(options.series || [], function (serieOptions) { | 
|             chart.initSeries(serieOptions); | 
|         }); | 
|   | 
|         chart.linkSeries(); | 
|   | 
|         // Run an event after axes and series are initialized, but before render. At this stage, | 
|         // the series data is indexed and cached in the xData and yData arrays, so we can access | 
|         // those before rendering. Used in Highstock.  | 
|         fireEvent(chart, 'beforeRender');  | 
|   | 
|         // depends on inverted and on margins being set | 
|         chart.pointer = new Pointer(chart, options); | 
|   | 
|         chart.render(); | 
|   | 
|         // add canvas | 
|         chart.renderer.draw(); | 
|         // run callbacks | 
|         if (callback) { | 
|             callback.apply(chart, [chart]); | 
|         } | 
|         each(chart.callbacks, function (fn) { | 
|             fn.apply(chart, [chart]); | 
|         }); | 
|          | 
|          | 
|         // If the chart was rendered outside the top container, put it back in | 
|         chart.cloneRenderTo(true); | 
|   | 
|         fireEvent(chart, 'load'); | 
|   | 
|     }, | 
|   | 
|     /** | 
|     * Creates arrays for spacing and margin from given options. | 
|     */ | 
|     splashArray: function (target, options) { | 
|         var oVar = options[target], | 
|             tArray = isObject(oVar) ? oVar : [oVar, oVar, oVar, oVar]; | 
|   | 
|         return [pick(options[target + 'Top'], tArray[0]), | 
|                 pick(options[target + 'Right'], tArray[1]), | 
|                 pick(options[target + 'Bottom'], tArray[2]), | 
|                 pick(options[target + 'Left'], tArray[3])]; | 
|     } | 
| }; // end Chart | 
|   | 
| // Hook for exporting module | 
| Chart.prototype.callbacks = []; | 
| /** | 
|  * The Point object and prototype. Inheritable and used as base for PiePoint | 
|  */ | 
| var Point = function () {}; | 
| Point.prototype = { | 
|   | 
|     /** | 
|      * Initialize the point | 
|      * @param {Object} series The series object containing this point | 
|      * @param {Object} options The data in either number, array or object format | 
|      */ | 
|     init: function (series, options, x) { | 
|   | 
|         var point = this, | 
|             colors; | 
|         point.series = series; | 
|         point.applyOptions(options, x); | 
|         point.pointAttr = {}; | 
|   | 
|         if (series.options.colorByPoint) { | 
|             colors = series.options.colors || series.chart.options.colors; | 
|             point.color = point.color || colors[series.colorCounter++]; | 
|             // loop back to zero | 
|             if (series.colorCounter === colors.length) { | 
|                 series.colorCounter = 0; | 
|             } | 
|         } | 
|   | 
|         series.chart.pointCount++; | 
|         return point; | 
|     }, | 
|     /** | 
|      * Apply the options containing the x and y data and possible some extra properties. | 
|      * This is called on point init or from point.update. | 
|      * | 
|      * @param {Object} options | 
|      */ | 
|     applyOptions: function (options, x) { | 
|         var point = this, | 
|             series = point.series, | 
|             pointValKey = series.pointValKey; | 
|   | 
|         options = Point.prototype.optionsToObject.call(this, options); | 
|   | 
|         // copy options directly to point | 
|         extend(point, options); | 
|         point.options = point.options ? extend(point.options, options) : options; | 
|              | 
|         // For higher dimension series types. For instance, for ranges, point.y is mapped to point.low. | 
|         if (pointValKey) { | 
|             point.y = point[pointValKey]; | 
|         } | 
|          | 
|         // If no x is set by now, get auto incremented value. All points must have an | 
|         // x value, however the y value can be null to create a gap in the series | 
|         if (point.x === UNDEFINED && series) { | 
|             point.x = x === UNDEFINED ? series.autoIncrement() : x; | 
|         } | 
|          | 
|         return point; | 
|     }, | 
|   | 
|     /** | 
|      * Transform number or array configs into objects | 
|      */ | 
|     optionsToObject: function (options) { | 
|         var ret, | 
|             series = this.series, | 
|             pointArrayMap = series.pointArrayMap || ['y'], | 
|             valueCount = pointArrayMap.length, | 
|             firstItemType, | 
|             i = 0, | 
|             j = 0; | 
|   | 
|         if (typeof options === 'number' || options === null) { | 
|             ret = { y: options }; | 
|   | 
|         } else if (isArray(options)) { | 
|             ret = {}; | 
|             // with leading x value | 
|             if (options.length > valueCount) { | 
|                 firstItemType = typeof options[0]; | 
|                 if (firstItemType === 'string') { | 
|                     ret.name = options[0]; | 
|                 } else if (firstItemType === 'number') { | 
|                     ret.x = options[0]; | 
|                 } | 
|                 i++; | 
|             } | 
|             while (j < valueCount) { | 
|                 ret[pointArrayMap[j++]] = options[i++]; | 
|             }             | 
|         } else if (typeof options === 'object') { | 
|             ret = options; | 
|   | 
|             // This is the fastest way to detect if there are individual point dataLabels that need  | 
|             // to be considered in drawDataLabels. These can only occur in object configs. | 
|             if (options.dataLabels) { | 
|                 series._hasPointLabels = true; | 
|             } | 
|   | 
|             // Same approach as above for markers | 
|             if (options.marker) { | 
|                 series._hasPointMarkers = true; | 
|             } | 
|         } | 
|         return ret; | 
|     }, | 
|   | 
|     /** | 
|      * Destroy a point to clear memory. Its reference still stays in series.data. | 
|      */ | 
|     destroy: function () { | 
|         var point = this, | 
|             series = point.series, | 
|             chart = series.chart, | 
|             hoverPoints = chart.hoverPoints, | 
|             prop; | 
|   | 
|         chart.pointCount--; | 
|   | 
|         if (hoverPoints) { | 
|             point.setState(); | 
|             erase(hoverPoints, point); | 
|             if (!hoverPoints.length) { | 
|                 chart.hoverPoints = null; | 
|             } | 
|   | 
|         } | 
|         if (point === chart.hoverPoint) { | 
|             point.onMouseOut(); | 
|         } | 
|          | 
|         // remove all events | 
|         if (point.graphic || point.dataLabel) { // removeEvent and destroyElements are performance expensive | 
|             removeEvent(point); | 
|             point.destroyElements(); | 
|         } | 
|   | 
|         if (point.legendItem) { // pies have legend items | 
|             chart.legend.destroyItem(point); | 
|         } | 
|   | 
|         for (prop in point) { | 
|             point[prop] = null; | 
|         } | 
|   | 
|   | 
|     }, | 
|   | 
|     /** | 
|      * Destroy SVG elements associated with the point | 
|      */ | 
|     destroyElements: function () { | 
|         var point = this, | 
|             props = ['graphic', 'dataLabel', 'dataLabelUpper', 'group', 'connector', 'shadowGroup'], | 
|             prop, | 
|             i = 6; | 
|         while (i--) { | 
|             prop = props[i]; | 
|             if (point[prop]) { | 
|                 point[prop] = point[prop].destroy(); | 
|             } | 
|         } | 
|     }, | 
|   | 
|     /** | 
|      * Return the configuration hash needed for the data label and tooltip formatters | 
|      */ | 
|     getLabelConfig: function () { | 
|         var point = this; | 
|         return { | 
|             x: point.category, | 
|             y: point.y, | 
|             key: point.name || point.category, | 
|             series: point.series, | 
|             point: point, | 
|             percentage: point.percentage, | 
|             total: point.total || point.stackTotal | 
|         }; | 
|     }, | 
|   | 
|     /** | 
|      * Toggle the selection status of a point | 
|      * @param {Boolean} selected Whether to select or unselect the point. | 
|      * @param {Boolean} accumulate Whether to add to the previous selection. By default, | 
|      *     this happens if the control key (Cmd on Mac) was pressed during clicking. | 
|      */ | 
|     select: function (selected, accumulate) { | 
|         var point = this, | 
|             series = point.series, | 
|             chart = series.chart; | 
|   | 
|         selected = pick(selected, !point.selected); | 
|   | 
|         // fire the event with the defalut handler | 
|         point.firePointEvent(selected ? 'select' : 'unselect', { accumulate: accumulate }, function () { | 
|             point.selected = point.options.selected = selected; | 
|             series.options.data[inArray(point, series.data)] = point.options; | 
|              | 
|             point.setState(selected && SELECT_STATE); | 
|   | 
|             // unselect all other points unless Ctrl or Cmd + click | 
|             if (!accumulate) { | 
|                 each(chart.getSelectedPoints(), function (loopPoint) { | 
|                     if (loopPoint.selected && loopPoint !== point) { | 
|                         loopPoint.selected = loopPoint.options.selected = false; | 
|                         series.options.data[inArray(loopPoint, series.data)] = loopPoint.options; | 
|                         loopPoint.setState(NORMAL_STATE); | 
|                         loopPoint.firePointEvent('unselect'); | 
|                     } | 
|                 }); | 
|             } | 
|         }); | 
|     }, | 
|   | 
|     /** | 
|      * Runs on mouse over the point | 
|      */ | 
|     onMouseOver: function (e) { | 
|         var point = this, | 
|             series = point.series, | 
|             chart = series.chart, | 
|             tooltip = chart.tooltip, | 
|             hoverPoint = chart.hoverPoint; | 
|   | 
|         // set normal state to previous series | 
|         if (hoverPoint && hoverPoint !== point) { | 
|             hoverPoint.onMouseOut(); | 
|         } | 
|   | 
|         // trigger the event | 
|         point.firePointEvent('mouseOver'); | 
|   | 
|         // update the tooltip | 
|         if (tooltip && (!tooltip.shared || series.noSharedTooltip)) { | 
|             tooltip.refresh(point, e); | 
|         } | 
|   | 
|         // hover this | 
|         point.setState(HOVER_STATE); | 
|         chart.hoverPoint = point; | 
|     }, | 
|      | 
|     /** | 
|      * Runs on mouse out from the point | 
|      */ | 
|     onMouseOut: function () { | 
|         var chart = this.series.chart, | 
|             hoverPoints = chart.hoverPoints; | 
|          | 
|         if (!hoverPoints || inArray(this, hoverPoints) === -1) { // #887 | 
|             this.firePointEvent('mouseOut'); | 
|      | 
|             this.setState(); | 
|             chart.hoverPoint = null; | 
|         } | 
|     }, | 
|   | 
|     /** | 
|      * Extendable method for formatting each point's tooltip line | 
|      * | 
|      * @return {String} A string to be concatenated in to the common tooltip text | 
|      */ | 
|     tooltipFormatter: function (pointFormat) { | 
|          | 
|         // Insert options for valueDecimals, valuePrefix, and valueSuffix | 
|         var series = this.series, | 
|             seriesTooltipOptions = series.tooltipOptions, | 
|             valueDecimals = pick(seriesTooltipOptions.valueDecimals, ''), | 
|             valuePrefix = seriesTooltipOptions.valuePrefix || '', | 
|             valueSuffix = seriesTooltipOptions.valueSuffix || ''; | 
|              | 
|         // Loop over the point array map and replace unformatted values with sprintf formatting markup | 
|         each(series.pointArrayMap || ['y'], function (key) { | 
|             key = '{point.' + key; // without the closing bracket | 
|             if (valuePrefix || valueSuffix) { | 
|                 pointFormat = pointFormat.replace(key + '}', valuePrefix + key + '}' + valueSuffix); | 
|             } | 
|             pointFormat = pointFormat.replace(key + '}', key + ':,.' + valueDecimals + 'f}'); | 
|         }); | 
|          | 
|         return format(pointFormat, { | 
|             point: this, | 
|             series: this.series | 
|         }); | 
|     }, | 
|   | 
|     /** | 
|      * Update the point with new options (typically x/y data) and optionally redraw the series. | 
|      * | 
|      * @param {Object} options Point options as defined in the series.data array | 
|      * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call | 
|      * @param {Boolean|Object} animation Whether to apply animation, and optionally animation | 
|      *    configuration | 
|      * | 
|      */ | 
|     update: function (options, redraw, animation) { | 
|         var point = this, | 
|             series = point.series, | 
|             graphic = point.graphic, | 
|             i, | 
|             data = series.data, | 
|             chart = series.chart, | 
|             seriesOptions = series.options; | 
|   | 
|         redraw = pick(redraw, true); | 
|   | 
|         // fire the event with a default handler of doing the update | 
|         point.firePointEvent('update', { options: options }, function () { | 
|   | 
|             point.applyOptions(options); | 
|   | 
|             // update visuals | 
|             if (isObject(options)) { | 
|                 series.getAttribs(); | 
|                 if (graphic) { | 
|                     if (options.marker && options.marker.symbol) { | 
|                         point.graphic = graphic.destroy(); | 
|                     } else { | 
|                         graphic.attr(point.pointAttr[point.state || '']); | 
|                     } | 
|                 } | 
|             } | 
|   | 
|             // record changes in the parallel arrays | 
|             i = inArray(point, data); | 
|             series.xData[i] = point.x; | 
|             series.yData[i] = series.toYData ? series.toYData(point) : point.y; | 
|             series.zData[i] = point.z; | 
|             seriesOptions.data[i] = point.options; | 
|   | 
|             // redraw | 
|             series.isDirty = series.isDirtyData = true; | 
|             if (!series.fixedBox && series.hasCartesianSeries) { // #1906, #2320 | 
|                 chart.isDirtyBox = true; | 
|             } | 
|              | 
|             if (seriesOptions.legendType === 'point') { // #1831, #1885 | 
|                 chart.legend.destroyItem(point); | 
|             } | 
|             if (redraw) { | 
|                 chart.redraw(animation); | 
|             } | 
|         }); | 
|     }, | 
|   | 
|     /** | 
|      * Remove a point and optionally redraw the series and if necessary the axes | 
|      * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call | 
|      * @param {Boolean|Object} animation Whether to apply animation, and optionally animation | 
|      *    configuration | 
|      */ | 
|     remove: function (redraw, animation) { | 
|         var point = this, | 
|             series = point.series, | 
|             points = series.points, | 
|             chart = series.chart, | 
|             i, | 
|             data = series.data; | 
|   | 
|         setAnimation(animation, chart); | 
|         redraw = pick(redraw, true); | 
|   | 
|         // fire the event with a default handler of removing the point | 
|         point.firePointEvent('remove', null, function () { | 
|   | 
|             // splice all the parallel arrays | 
|             i = inArray(point, data); | 
|             if (data.length === points.length) { | 
|                 points.splice(i, 1);             | 
|             } | 
|             data.splice(i, 1); | 
|             series.options.data.splice(i, 1); | 
|             series.xData.splice(i, 1); | 
|             series.yData.splice(i, 1); | 
|             series.zData.splice(i, 1); | 
|   | 
|             point.destroy(); | 
|   | 
|   | 
|             // redraw | 
|             series.isDirty = true; | 
|             series.isDirtyData = true; | 
|             if (redraw) { | 
|                 chart.redraw(); | 
|             } | 
|         }); | 
|   | 
|   | 
|     }, | 
|   | 
|     /** | 
|      * Fire an event on the Point object. Must not be renamed to fireEvent, as this | 
|      * causes a name clash in MooTools | 
|      * @param {String} eventType | 
|      * @param {Object} eventArgs Additional event arguments | 
|      * @param {Function} defaultFunction Default event handler | 
|      */ | 
|     firePointEvent: function (eventType, eventArgs, defaultFunction) { | 
|         var point = this, | 
|             series = this.series, | 
|             seriesOptions = series.options; | 
|   | 
|         // load event handlers on demand to save time on mouseover/out | 
|         if (seriesOptions.point.events[eventType] || (point.options && point.options.events && point.options.events[eventType])) { | 
|             this.importEvents(); | 
|         } | 
|   | 
|         // add default handler if in selection mode | 
|         if (eventType === 'click' && seriesOptions.allowPointSelect) { | 
|             defaultFunction = function (event) { | 
|                 // Control key is for Windows, meta (= Cmd key) for Mac, Shift for Opera | 
|                 point.select(null, event.ctrlKey || event.metaKey || event.shiftKey); | 
|             }; | 
|         } | 
|   | 
|         fireEvent(this, eventType, eventArgs, defaultFunction); | 
|     }, | 
|     /** | 
|      * Import events from the series' and point's options. Only do it on | 
|      * demand, to save processing time on hovering. | 
|      */ | 
|     importEvents: function () { | 
|         if (!this.hasImportedEvents) { | 
|             var point = this, | 
|                 options = merge(point.series.options.point, point.options), | 
|                 events = options.events, | 
|                 eventType; | 
|   | 
|             point.events = events; | 
|   | 
|             for (eventType in events) { | 
|                 addEvent(point, eventType, events[eventType]); | 
|             } | 
|             this.hasImportedEvents = true; | 
|   | 
|         } | 
|     }, | 
|   | 
|     /** | 
|      * Set the point's state | 
|      * @param {String} state | 
|      */ | 
|     setState: function (state) { | 
|         var point = this, | 
|             plotX = point.plotX, | 
|             plotY = point.plotY, | 
|             series = point.series, | 
|             stateOptions = series.options.states, | 
|             markerOptions = defaultPlotOptions[series.type].marker && series.options.marker, | 
|             normalDisabled = markerOptions && !markerOptions.enabled, | 
|             markerStateOptions = markerOptions && markerOptions.states[state], | 
|             stateDisabled = markerStateOptions && markerStateOptions.enabled === false, | 
|             stateMarkerGraphic = series.stateMarkerGraphic, | 
|             pointMarker = point.marker || {}, | 
|             chart = series.chart, | 
|             radius, | 
|             newSymbol, | 
|             pointAttr = point.pointAttr; | 
|   | 
|         state = state || NORMAL_STATE; // empty string | 
|   | 
|         if ( | 
|                 // already has this state | 
|                 state === point.state || | 
|                 // selected points don't respond to hover | 
|                 (point.selected && state !== SELECT_STATE) || | 
|                 // series' state options is disabled | 
|                 (stateOptions[state] && stateOptions[state].enabled === false) || | 
|                 // point marker's state options is disabled | 
|                 (state && (stateDisabled || (normalDisabled && !markerStateOptions.enabled))) | 
|   | 
|             ) { | 
|             return; | 
|         } | 
|   | 
|         // apply hover styles to the existing point | 
|         if (point.graphic) { | 
|             radius = markerOptions && point.graphic.symbolName && pointAttr[state].r; | 
|             point.graphic.attr(merge( | 
|                 pointAttr[state], | 
|                 radius ? { // new symbol attributes (#507, #612) | 
|                     x: plotX - radius, | 
|                     y: plotY - radius, | 
|                     width: 2 * radius, | 
|                     height: 2 * radius | 
|                 } : {} | 
|             )); | 
|         } else { | 
|             // if a graphic is not applied to each point in the normal state, create a shared | 
|             // graphic for the hover state | 
|             if (state && markerStateOptions) { | 
|                 radius = markerStateOptions.radius; | 
|                 newSymbol = pointMarker.symbol || series.symbol; | 
|   | 
|                 // If the point has another symbol than the previous one, throw away the  | 
|                 // state marker graphic and force a new one (#1459) | 
|                 if (stateMarkerGraphic && stateMarkerGraphic.currentSymbol !== newSymbol) {                 | 
|                     stateMarkerGraphic = stateMarkerGraphic.destroy(); | 
|                 } | 
|   | 
|                 // Add a new state marker graphic | 
|                 if (!stateMarkerGraphic) { | 
|                     series.stateMarkerGraphic = stateMarkerGraphic = chart.renderer.symbol( | 
|                         newSymbol, | 
|                         plotX - radius, | 
|                         plotY - radius, | 
|                         2 * radius, | 
|                         2 * radius | 
|                     ) | 
|                     .attr(pointAttr[state]) | 
|                     .add(series.markerGroup); | 
|                     stateMarkerGraphic.currentSymbol = newSymbol; | 
|                  | 
|                 // Move the existing graphic | 
|                 } else { | 
|                     stateMarkerGraphic.attr({ // #1054 | 
|                         x: plotX - radius, | 
|                         y: plotY - radius | 
|                     }); | 
|                 } | 
|             } | 
|   | 
|             if (stateMarkerGraphic) { | 
|                 stateMarkerGraphic[state && chart.isInsidePlot(plotX, plotY) ? 'show' : 'hide'](); | 
|             } | 
|         } | 
|   | 
|         point.state = state; | 
|     } | 
| }; | 
|   | 
| /** | 
|  * @classDescription The base function which all other series types inherit from. The data in the series is stored | 
|  * in various arrays. | 
|  * | 
|  * - First, series.options.data contains all the original config options for | 
|  * each point whether added by options or methods like series.addPoint. | 
|  * - Next, series.data contains those values converted to points, but in case the series data length | 
|  * exceeds the cropThreshold, or if the data is grouped, series.data doesn't contain all the points. It | 
|  * only contains the points that have been created on demand. | 
|  * - Then there's series.points that contains all currently visible point objects. In case of cropping, | 
|  * the cropped-away points are not part of this array. The series.points array starts at series.cropStart | 
|  * compared to series.data and series.options.data. If however the series data is grouped, these can't | 
|  * be correlated one to one. | 
|  * - series.xData and series.processedXData contain clean x values, equivalent to series.data and series.points. | 
|  * - series.yData and series.processedYData contain clean x values, equivalent to series.data and series.points. | 
|  * | 
|  * @param {Object} chart | 
|  * @param {Object} options | 
|  */ | 
| var Series = function () {}; | 
|   | 
| Series.prototype = { | 
|   | 
|     isCartesian: true, | 
|     type: 'line', | 
|     pointClass: Point, | 
|     sorted: true, // requires the data to be sorted | 
|     requireSorting: true, | 
|     pointAttrToOptions: { // mapping between SVG attributes and the corresponding options | 
|         stroke: 'lineColor', | 
|         'stroke-width': 'lineWidth', | 
|         fill: 'fillColor', | 
|         r: 'radius' | 
|     }, | 
|     colorCounter: 0, | 
|     init: function (chart, options) { | 
|         var series = this, | 
|             eventType, | 
|             events, | 
|             chartSeries = chart.series; | 
|   | 
|         series.chart = chart; | 
|         series.options = options = series.setOptions(options); // merge with plotOptions | 
|         series.linkedSeries = []; | 
|   | 
|         // bind the axes | 
|         series.bindAxes(); | 
|   | 
|         // set some variables | 
|         extend(series, { | 
|             name: options.name, | 
|             state: NORMAL_STATE, | 
|             pointAttr: {}, | 
|             visible: options.visible !== false, // true by default | 
|             selected: options.selected === true // false by default | 
|         }); | 
|          | 
|         // special | 
|         if (useCanVG) { | 
|             options.animation = false; | 
|         } | 
|   | 
|         // register event listeners | 
|         events = options.events; | 
|         for (eventType in events) { | 
|             addEvent(series, eventType, events[eventType]); | 
|         } | 
|         if ( | 
|             (events && events.click) || | 
|             (options.point && options.point.events && options.point.events.click) || | 
|             options.allowPointSelect | 
|         ) { | 
|             chart.runTrackerClick = true; | 
|         } | 
|   | 
|         series.getColor(); | 
|         series.getSymbol(); | 
|   | 
|         // set the data | 
|         series.setData(options.data, false); | 
|          | 
|         // Mark cartesian | 
|         if (series.isCartesian) { | 
|             chart.hasCartesianSeries = true; | 
|         } | 
|   | 
|         // Register it in the chart | 
|         chartSeries.push(series); | 
|         series._i = chartSeries.length - 1; | 
|          | 
|         // Sort series according to index option (#248, #1123) | 
|         stableSort(chartSeries, function (a, b) { | 
|             return pick(a.options.index, a._i) - pick(b.options.index, a._i); | 
|         }); | 
|         each(chartSeries, function (series, i) { | 
|             series.index = i; | 
|             series.name = series.name || 'Series ' + (i + 1); | 
|         }); | 
|   | 
|     }, | 
|      | 
|     /** | 
|      * Set the xAxis and yAxis properties of cartesian series, and register the series | 
|      * in the axis.series array | 
|      */ | 
|     bindAxes: function () { | 
|         var series = this, | 
|             seriesOptions = series.options, | 
|             chart = series.chart, | 
|             axisOptions; | 
|              | 
|         if (series.isCartesian) { | 
|              | 
|             each(['xAxis', 'yAxis'], function (AXIS) { // repeat for xAxis and yAxis | 
|                  | 
|                 each(chart[AXIS], function (axis) { // loop through the chart's axis objects | 
|                      | 
|                     axisOptions = axis.options; | 
|                      | 
|                     // apply if the series xAxis or yAxis option mathches the number of the  | 
|                     // axis, or if undefined, use the first axis | 
|                     if ((seriesOptions[AXIS] === axisOptions.index) || | 
|                             (seriesOptions[AXIS] !== UNDEFINED && seriesOptions[AXIS] === axisOptions.id) || | 
|                             (seriesOptions[AXIS] === UNDEFINED && axisOptions.index === 0)) { | 
|                          | 
|                         // register this series in the axis.series lookup | 
|                         axis.series.push(series); | 
|                          | 
|                         // set this series.xAxis or series.yAxis reference | 
|                         series[AXIS] = axis; | 
|                          | 
|                         // mark dirty for redraw | 
|                         axis.isDirty = true; | 
|                     } | 
|                 }); | 
|   | 
|                 // The series needs an X and an Y axis | 
|                 if (!series[AXIS]) { | 
|                     error(18, true); | 
|                 } | 
|   | 
|             }); | 
|         } | 
|     }, | 
|   | 
|   | 
|     /** | 
|      * Return an auto incremented x value based on the pointStart and pointInterval options. | 
|      * This is only used if an x value is not given for the point that calls autoIncrement. | 
|      */ | 
|     autoIncrement: function () { | 
|         var series = this, | 
|             options = series.options, | 
|             xIncrement = series.xIncrement; | 
|   | 
|         xIncrement = pick(xIncrement, options.pointStart, 0); | 
|   | 
|         series.pointInterval = pick(series.pointInterval, options.pointInterval, 1); | 
|   | 
|         series.xIncrement = xIncrement + series.pointInterval; | 
|         return xIncrement; | 
|     }, | 
|   | 
|     /** | 
|      * Divide the series data into segments divided by null values. | 
|      */ | 
|     getSegments: function () { | 
|         var series = this, | 
|             lastNull = -1, | 
|             segments = [], | 
|             i, | 
|             points = series.points, | 
|             pointsLength = points.length; | 
|   | 
|         if (pointsLength) { // no action required for [] | 
|              | 
|             // if connect nulls, just remove null points | 
|             if (series.options.connectNulls) { | 
|                 i = pointsLength; | 
|                 while (i--) { | 
|                     if (points[i].y === null) { | 
|                         points.splice(i, 1); | 
|                     } | 
|                 } | 
|                 if (points.length) { | 
|                     segments = [points]; | 
|                 } | 
|                  | 
|             // else, split on null points | 
|             } else { | 
|                 each(points, function (point, i) { | 
|                     if (point.y === null) { | 
|                         if (i > lastNull + 1) { | 
|                             segments.push(points.slice(lastNull + 1, i)); | 
|                         } | 
|                         lastNull = i; | 
|                     } else if (i === pointsLength - 1) { // last value | 
|                         segments.push(points.slice(lastNull + 1, i + 1)); | 
|                     } | 
|                 }); | 
|             } | 
|         } | 
|          | 
|         // register it | 
|         series.segments = segments; | 
|     }, | 
|      | 
|     /** | 
|      * Set the series options by merging from the options tree | 
|      * @param {Object} itemOptions | 
|      */ | 
|     setOptions: function (itemOptions) { | 
|         var chart = this.chart, | 
|             chartOptions = chart.options, | 
|             plotOptions = chartOptions.plotOptions, | 
|             typeOptions = plotOptions[this.type], | 
|             options; | 
|   | 
|         this.userOptions = itemOptions; | 
|   | 
|         options = merge( | 
|             typeOptions, | 
|             plotOptions.series, | 
|             itemOptions | 
|         ); | 
|          | 
|         // the tooltip options are merged between global and series specific options | 
|         this.tooltipOptions = merge(chartOptions.tooltip, options.tooltip); | 
|          | 
|         // Delte marker object if not allowed (#1125) | 
|         if (typeOptions.marker === null) { | 
|             delete options.marker; | 
|         } | 
|          | 
|         return options; | 
|   | 
|     }, | 
|     /** | 
|      * Get the series' color | 
|      */ | 
|     getColor: function () { | 
|         var options = this.options, | 
|             userOptions = this.userOptions, | 
|             defaultColors = this.chart.options.colors, | 
|             counters = this.chart.counters, | 
|             color, | 
|             colorIndex; | 
|   | 
|         color = options.color || defaultPlotOptions[this.type].color; | 
|   | 
|         if (!color && !options.colorByPoint) { | 
|             if (defined(userOptions._colorIndex)) { // after Series.update() | 
|                 colorIndex = userOptions._colorIndex; | 
|             } else { | 
|                 userOptions._colorIndex = counters.color; | 
|                 colorIndex = counters.color++; | 
|             } | 
|             color = defaultColors[colorIndex]; | 
|         } | 
|          | 
|         this.color = color; | 
|         counters.wrapColor(defaultColors.length); | 
|     }, | 
|     /** | 
|      * Get the series' symbol | 
|      */ | 
|     getSymbol: function () { | 
|         var series = this, | 
|             userOptions = series.userOptions, | 
|             seriesMarkerOption = series.options.marker, | 
|             chart = series.chart, | 
|             defaultSymbols = chart.options.symbols, | 
|             counters = chart.counters, | 
|             symbolIndex; | 
|   | 
|         series.symbol = seriesMarkerOption.symbol; | 
|         if (!series.symbol) { | 
|             if (defined(userOptions._symbolIndex)) { // after Series.update() | 
|                 symbolIndex = userOptions._symbolIndex; | 
|             } else { | 
|                 userOptions._symbolIndex = counters.symbol; | 
|                 symbolIndex = counters.symbol++; | 
|             } | 
|             series.symbol = defaultSymbols[symbolIndex]; | 
|         } | 
|   | 
|         // don't substract radius in image symbols (#604) | 
|         if (/^url/.test(series.symbol)) { | 
|             seriesMarkerOption.radius = 0; | 
|         } | 
|         counters.wrapSymbol(defaultSymbols.length); | 
|     }, | 
|   | 
|     /** | 
|      * Get the series' symbol in the legend. This method should be overridable to create custom  | 
|      * symbols through Highcharts.seriesTypes[type].prototype.drawLegendSymbols. | 
|      *  | 
|      * @param {Object} legend The legend object | 
|      */ | 
|     drawLegendSymbol: function (legend) { | 
|          | 
|         var options = this.options, | 
|             markerOptions = options.marker, | 
|             radius, | 
|             legendOptions = legend.options, | 
|             legendSymbol, | 
|             symbolWidth = legendOptions.symbolWidth, | 
|             renderer = this.chart.renderer, | 
|             legendItemGroup = this.legendGroup, | 
|             verticalCenter = legend.baseline - mathRound(renderer.fontMetrics(legendOptions.itemStyle.fontSize).b * 0.3), | 
|             attr; | 
|              | 
|         // Draw the line | 
|         if (options.lineWidth) { | 
|             attr = { | 
|                 'stroke-width': options.lineWidth | 
|             }; | 
|             if (options.dashStyle) { | 
|                 attr.dashstyle = options.dashStyle; | 
|             } | 
|             this.legendLine = renderer.path([ | 
|                 M, | 
|                 0, | 
|                 verticalCenter, | 
|                 L, | 
|                 symbolWidth, | 
|                 verticalCenter | 
|             ]) | 
|             .attr(attr) | 
|             .add(legendItemGroup); | 
|         } | 
|          | 
|         // Draw the marker | 
|         if (markerOptions && markerOptions.enabled) { | 
|             radius = markerOptions.radius; | 
|             this.legendSymbol = legendSymbol = renderer.symbol( | 
|                 this.symbol, | 
|                 (symbolWidth / 2) - radius, | 
|                 verticalCenter - radius, | 
|                 2 * radius, | 
|                 2 * radius | 
|             ) | 
|             .add(legendItemGroup); | 
|             legendSymbol.isMarker = true; | 
|         } | 
|     }, | 
|   | 
|     /** | 
|      * Add a point dynamically after chart load time | 
|      * @param {Object} options Point options as given in series.data | 
|      * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call | 
|      * @param {Boolean} shift If shift is true, a point is shifted off the start | 
|      *    of the series as one is appended to the end. | 
|      * @param {Boolean|Object} animation Whether to apply animation, and optionally animation | 
|      *    configuration | 
|      */ | 
|     addPoint: function (options, redraw, shift, animation) { | 
|         var series = this, | 
|             seriesOptions = series.options, | 
|             data = series.data, | 
|             graph = series.graph, | 
|             area = series.area, | 
|             chart = series.chart, | 
|             xData = series.xData, | 
|             yData = series.yData, | 
|             zData = series.zData, | 
|             names = series.names, | 
|             currentShift = (graph && graph.shift) || 0, | 
|             dataOptions = seriesOptions.data, | 
|             point, | 
|             isInTheMiddle, | 
|             x, | 
|             i; | 
|   | 
|         setAnimation(animation, chart); | 
|   | 
|         // Make graph animate sideways | 
|         if (shift) { | 
|             each([graph, area, series.graphNeg, series.areaNeg], function (shape) { | 
|                 if (shape) { | 
|                     shape.shift = currentShift + 1; | 
|                 } | 
|             }); | 
|         } | 
|         if (area) { | 
|             area.isArea = true; // needed in animation, both with and without shift | 
|         } | 
|          | 
|         // Optional redraw, defaults to true | 
|         redraw = pick(redraw, true); | 
|   | 
|         // Get options and push the point to xData, yData and series.options. In series.generatePoints | 
|         // the Point instance will be created on demand and pushed to the series.data array. | 
|         point = { series: series }; | 
|         series.pointClass.prototype.applyOptions.apply(point, [options]); | 
|         x = point.x; | 
|   | 
|         // Get the insertion point | 
|         i = xData.length; | 
|         if (series.requireSorting && x < xData[i - 1]) { | 
|             isInTheMiddle = true; | 
|             while (i && xData[i - 1] > x) { | 
|                 i--; | 
|             } | 
|         } | 
|          | 
|         xData.splice(i, 0, x); | 
|         yData.splice(i, 0, series.toYData ? series.toYData(point) : point.y); | 
|         zData.splice(i, 0, point.z); | 
|         if (names) { | 
|             names[x] = point.name; | 
|         } | 
|         dataOptions.splice(i, 0, options); | 
|   | 
|         if (isInTheMiddle) { | 
|             series.data.splice(i, 0, null); | 
|             series.processData(); | 
|         } | 
|          | 
|         // Generate points to be added to the legend (#1329)  | 
|         if (seriesOptions.legendType === 'point') { | 
|             series.generatePoints(); | 
|         } | 
|   | 
|         // Shift the first point off the parallel arrays | 
|         // todo: consider series.removePoint(i) method | 
|         if (shift) { | 
|             if (data[0] && data[0].remove) { | 
|                 data[0].remove(false); | 
|             } else { | 
|                 data.shift(); | 
|                 xData.shift(); | 
|                 yData.shift(); | 
|                 zData.shift(); | 
|                 dataOptions.shift(); | 
|             } | 
|         } | 
|   | 
|         // redraw | 
|         series.isDirty = true; | 
|         series.isDirtyData = true; | 
|         if (redraw) { | 
|             series.getAttribs(); // #1937 | 
|             chart.redraw(); | 
|         } | 
|     }, | 
|   | 
|     /** | 
|      * Replace the series data with a new set of data | 
|      * @param {Object} data | 
|      * @param {Object} redraw | 
|      */ | 
|     setData: function (data, redraw) { | 
|         var series = this, | 
|             oldData = series.points, | 
|             options = series.options, | 
|             chart = series.chart, | 
|             firstPoint = null, | 
|             xAxis = series.xAxis, | 
|             names = xAxis && xAxis.categories && !xAxis.categories.length ? [] : null, | 
|             i; | 
|   | 
|         // reset properties | 
|         series.xIncrement = null; | 
|         series.pointRange = xAxis && xAxis.categories ? 1 : options.pointRange; | 
|   | 
|         series.colorCounter = 0; // for series with colorByPoint (#1547) | 
|          | 
|         // parallel arrays | 
|         var xData = [], | 
|             yData = [], | 
|             zData = [], | 
|             dataLength = data ? data.length : [], | 
|             turboThreshold = pick(options.turboThreshold, 1000), | 
|             pt, | 
|             pointArrayMap = series.pointArrayMap, | 
|             valueCount = pointArrayMap && pointArrayMap.length, | 
|             hasToYData = !!series.toYData; | 
|   | 
|         // In turbo mode, only one- or twodimensional arrays of numbers are allowed. The | 
|         // first value is tested, and we assume that all the rest are defined the same | 
|         // way. Although the 'for' loops are similar, they are repeated inside each | 
|         // if-else conditional for max performance. | 
|         if (turboThreshold && dataLength > turboThreshold) {  | 
|              | 
|             // find the first non-null point | 
|             i = 0; | 
|             while (firstPoint === null && i < dataLength) { | 
|                 firstPoint = data[i]; | 
|                 i++; | 
|             } | 
|          | 
|          | 
|             if (isNumber(firstPoint)) { // assume all points are numbers | 
|                 var x = pick(options.pointStart, 0), | 
|                     pointInterval = pick(options.pointInterval, 1); | 
|   | 
|                 for (i = 0; i < dataLength; i++) { | 
|                     xData[i] = x; | 
|                     yData[i] = data[i]; | 
|                     x += pointInterval; | 
|                 } | 
|                 series.xIncrement = x; | 
|             } else if (isArray(firstPoint)) { // assume all points are arrays | 
|                 if (valueCount) { // [x, low, high] or [x, o, h, l, c] | 
|                     for (i = 0; i < dataLength; i++) { | 
|                         pt = data[i]; | 
|                         xData[i] = pt[0]; | 
|                         yData[i] = pt.slice(1, valueCount + 1); | 
|                     } | 
|                 } else { // [x, y] | 
|                     for (i = 0; i < dataLength; i++) { | 
|                         pt = data[i]; | 
|                         xData[i] = pt[0]; | 
|                         yData[i] = pt[1]; | 
|                     } | 
|                 } | 
|             } else { | 
|                 error(12); // Highcharts expects configs to be numbers or arrays in turbo mode | 
|             } | 
|         } else { | 
|             for (i = 0; i < dataLength; i++) { | 
|                 if (data[i] !== UNDEFINED) { // stray commas in oldIE | 
|                     pt = { series: series }; | 
|                     series.pointClass.prototype.applyOptions.apply(pt, [data[i]]); | 
|                     xData[i] = pt.x; | 
|                     yData[i] = hasToYData ? series.toYData(pt) : pt.y; | 
|                     zData[i] = pt.z; | 
|                     if (names && pt.name) { | 
|                         names[pt.x] = pt.name; // #2046 | 
|                     } | 
|                 } | 
|             } | 
|         } | 
|          | 
|         // Forgetting to cast strings to numbers is a common caveat when handling CSV or JSON         | 
|         if (isString(yData[0])) { | 
|             error(14, true); | 
|         }  | 
|   | 
|         series.data = []; | 
|         series.options.data = data; | 
|         series.xData = xData; | 
|         series.yData = yData; | 
|         series.zData = zData; | 
|         series.names = names; | 
|   | 
|         // destroy old points | 
|         i = (oldData && oldData.length) || 0; | 
|         while (i--) { | 
|             if (oldData[i] && oldData[i].destroy) { | 
|                 oldData[i].destroy(); | 
|             } | 
|         } | 
|   | 
|         // reset minRange (#878) | 
|         if (xAxis) { | 
|             xAxis.minRange = xAxis.userMinRange; | 
|         } | 
|   | 
|         // redraw | 
|         series.isDirty = series.isDirtyData = chart.isDirtyBox = true; | 
|         if (pick(redraw, true)) { | 
|             chart.redraw(false); | 
|         } | 
|     }, | 
|   | 
|     /** | 
|      * Remove a series and optionally redraw the chart | 
|      * | 
|      * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call | 
|      * @param {Boolean|Object} animation Whether to apply animation, and optionally animation | 
|      *    configuration | 
|      */ | 
|   | 
|     remove: function (redraw, animation) { | 
|         var series = this, | 
|             chart = series.chart; | 
|         redraw = pick(redraw, true); | 
|   | 
|         if (!series.isRemoving) {  /* prevent triggering native event in jQuery | 
|                 (calling the remove function from the remove event) */ | 
|             series.isRemoving = true; | 
|   | 
|             // fire the event with a default handler of removing the point | 
|             fireEvent(series, 'remove', null, function () { | 
|   | 
|   | 
|                 // destroy elements | 
|                 series.destroy(); | 
|   | 
|   | 
|                 // redraw | 
|                 chart.isDirtyLegend = chart.isDirtyBox = true; | 
|                 chart.linkSeries(); | 
|                  | 
|                 if (redraw) { | 
|                     chart.redraw(animation); | 
|                 } | 
|             }); | 
|   | 
|         } | 
|         series.isRemoving = false; | 
|     }, | 
|   | 
|     /** | 
|      * Process the data by cropping away unused data points if the series is longer | 
|      * than the crop threshold. This saves computing time for lage series. | 
|      */ | 
|     processData: function (force) { | 
|         var series = this, | 
|             processedXData = series.xData, // copied during slice operation below | 
|             processedYData = series.yData, | 
|             dataLength = processedXData.length, | 
|             croppedData, | 
|             cropStart = 0, | 
|             cropped, | 
|             distance, | 
|             closestPointRange, | 
|             xAxis = series.xAxis, | 
|             i, // loop variable | 
|             options = series.options, | 
|             cropThreshold = options.cropThreshold, | 
|             isCartesian = series.isCartesian; | 
|   | 
|         // If the series data or axes haven't changed, don't go through this. Return false to pass | 
|         // the message on to override methods like in data grouping.  | 
|         if (isCartesian && !series.isDirty && !xAxis.isDirty && !series.yAxis.isDirty && !force) { | 
|             return false; | 
|         } | 
|          | 
|   | 
|         // optionally filter out points outside the plot area | 
|         if (isCartesian && series.sorted && (!cropThreshold || dataLength > cropThreshold || series.forceCrop)) { | 
|             var min = xAxis.min, | 
|                 max = xAxis.max; | 
|   | 
|             // it's outside current extremes | 
|             if (processedXData[dataLength - 1] < min || processedXData[0] > max) { | 
|                 processedXData = []; | 
|                 processedYData = []; | 
|              | 
|             // only crop if it's actually spilling out | 
|             } else if (processedXData[0] < min || processedXData[dataLength - 1] > max) { | 
|                 croppedData = this.cropData(series.xData, series.yData, min, max); | 
|                 processedXData = croppedData.xData; | 
|                 processedYData = croppedData.yData; | 
|                 cropStart = croppedData.start; | 
|                 cropped = true; | 
|             } | 
|         } | 
|          | 
|          | 
|         // Find the closest distance between processed points | 
|         for (i = processedXData.length - 1; i >= 0; i--) { | 
|             distance = processedXData[i] - processedXData[i - 1]; | 
|             if (distance > 0 && (closestPointRange === UNDEFINED || distance < closestPointRange)) { | 
|                 closestPointRange = distance; | 
|   | 
|             // Unsorted data is not supported by the line tooltip, as well as data grouping and  | 
|             // navigation in Stock charts (#725) and width calculation of columns (#1900) | 
|             } else if (distance < 0 && series.requireSorting) { | 
|                 error(15); | 
|             } | 
|         } | 
|   | 
|         // Record the properties | 
|         series.cropped = cropped; // undefined or true | 
|         series.cropStart = cropStart; | 
|         series.processedXData = processedXData; | 
|         series.processedYData = processedYData; | 
|   | 
|         if (options.pointRange === null) { // null means auto, as for columns, candlesticks and OHLC | 
|             series.pointRange = closestPointRange || 1; | 
|         } | 
|         series.closestPointRange = closestPointRange; | 
|          | 
|     }, | 
|   | 
|     /** | 
|      * Iterate over xData and crop values between min and max. Returns object containing crop start/end | 
|      * cropped xData with corresponding part of yData, dataMin and dataMax within the cropped range | 
|      */ | 
|     cropData: function (xData, yData, min, max) { | 
|         var dataLength = xData.length, | 
|             cropStart = 0, | 
|             cropEnd = dataLength, | 
|             cropShoulder = pick(this.cropShoulder, 1), // line-type series need one point outside | 
|             i; | 
|   | 
|         // iterate up to find slice start | 
|         for (i = 0; i < dataLength; i++) { | 
|             if (xData[i] >= min) { | 
|                 cropStart = mathMax(0, i - cropShoulder); | 
|                 break; | 
|             } | 
|         } | 
|   | 
|         // proceed to find slice end | 
|         for (; i < dataLength; i++) { | 
|             if (xData[i] > max) { | 
|                 cropEnd = i + cropShoulder; | 
|                 break; | 
|             } | 
|         } | 
|   | 
|         return { | 
|             xData: xData.slice(cropStart, cropEnd), | 
|             yData: yData.slice(cropStart, cropEnd), | 
|             start: cropStart, | 
|             end: cropEnd | 
|         }; | 
|     }, | 
|   | 
|   | 
|     /** | 
|      * Generate the data point after the data has been processed by cropping away | 
|      * unused points and optionally grouped in Highcharts Stock. | 
|      */ | 
|     generatePoints: function () { | 
|         var series = this, | 
|             options = series.options, | 
|             dataOptions = options.data, | 
|             data = series.data, | 
|             dataLength, | 
|             processedXData = series.processedXData, | 
|             processedYData = series.processedYData, | 
|             pointClass = series.pointClass, | 
|             processedDataLength = processedXData.length, | 
|             cropStart = series.cropStart || 0, | 
|             cursor, | 
|             hasGroupedData = series.hasGroupedData, | 
|             point, | 
|             points = [], | 
|             i; | 
|   | 
|         if (!data && !hasGroupedData) { | 
|             var arr = []; | 
|             arr.length = dataOptions.length; | 
|             data = series.data = arr; | 
|         } | 
|   | 
|         for (i = 0; i < processedDataLength; i++) { | 
|             cursor = cropStart + i; | 
|             if (!hasGroupedData) { | 
|                 if (data[cursor]) { | 
|                     point = data[cursor]; | 
|                 } else if (dataOptions[cursor] !== UNDEFINED) { // #970 | 
|                     data[cursor] = point = (new pointClass()).init(series, dataOptions[cursor], processedXData[i]); | 
|                 } | 
|                 points[i] = point; | 
|             } else { | 
|                 // splat the y data in case of ohlc data array | 
|                 points[i] = (new pointClass()).init(series, [processedXData[i]].concat(splat(processedYData[i]))); | 
|             } | 
|         } | 
|   | 
|         // Hide cropped-away points - this only runs when the number of points is above cropThreshold, or when | 
|         // swithching view from non-grouped data to grouped data (#637)     | 
|         if (data && (processedDataLength !== (dataLength = data.length) || hasGroupedData)) { | 
|             for (i = 0; i < dataLength; i++) { | 
|                 if (i === cropStart && !hasGroupedData) { // when has grouped data, clear all points | 
|                     i += processedDataLength; | 
|                 } | 
|                 if (data[i]) { | 
|                     data[i].destroyElements(); | 
|                     data[i].plotX = UNDEFINED; // #1003 | 
|                 } | 
|             } | 
|         } | 
|   | 
|         series.data = data; | 
|         series.points = points; | 
|     }, | 
|   | 
|     /** | 
|      * Adds series' points value to corresponding stack | 
|      */ | 
|     setStackedPoints: function () { | 
|         if (!this.options.stacking || (this.visible !== true && this.chart.options.chart.ignoreHiddenSeries !== false)) { | 
|             return; | 
|         } | 
|   | 
|         var series = this, | 
|             xData = series.processedXData, | 
|             yData = series.processedYData, | 
|             stackedYData = [], | 
|             yDataLength = yData.length, | 
|             seriesOptions = series.options, | 
|             threshold = seriesOptions.threshold, | 
|             stackOption = seriesOptions.stack, | 
|             stacking = seriesOptions.stacking, | 
|             stackKey = series.stackKey, | 
|             negKey = '-' + stackKey, | 
|             negStacks = series.negStacks, | 
|             yAxis = series.yAxis, | 
|             stacks = yAxis.stacks, | 
|             oldStacks = yAxis.oldStacks, | 
|             isNegative, | 
|             stack, | 
|             other, | 
|             key, | 
|             i, | 
|             x, | 
|             y; | 
|   | 
|         // loop over the non-null y values and read them into a local array | 
|         for (i = 0; i < yDataLength; i++) { | 
|             x = xData[i]; | 
|             y = yData[i]; | 
|   | 
|             // Read stacked values into a stack based on the x value, | 
|             // the sign of y and the stack key. Stacking is also handled for null values (#739) | 
|             isNegative = negStacks && y < threshold; | 
|             key = isNegative ? negKey : stackKey; | 
|   | 
|             // Create empty object for this stack if it doesn't exist yet | 
|             if (!stacks[key]) { | 
|                 stacks[key] = {}; | 
|             } | 
|   | 
|             // Initialize StackItem for this x | 
|             if (!stacks[key][x]) { | 
|                 if (oldStacks[key] && oldStacks[key][x]) { | 
|                     stacks[key][x] = oldStacks[key][x]; | 
|                     stacks[key][x].total = null; | 
|                 } else { | 
|                     stacks[key][x] = new StackItem(yAxis, yAxis.options.stackLabels, isNegative, x, stackOption, stacking); | 
|                 } | 
|             } | 
|   | 
|             // If the StackItem doesn't exist, create it first | 
|             stack = stacks[key][x]; | 
|             stack.points[series.index] = [stack.cum || 0]; | 
|   | 
|             // Add value to the stack total | 
|             if (stacking === 'percent') { | 
|                  | 
|                 // Percent stacked column, totals are the same for the positive and negative stacks | 
|                 other = isNegative ? stackKey : negKey; | 
|                 if (negStacks && stacks[other] && stacks[other][x]) { | 
|                     other = stacks[other][x]; | 
|                     stack.total = other.total = mathMax(other.total, stack.total) + mathAbs(y) || 0; | 
|   | 
|                 // Percent stacked areas                     | 
|                 } else { | 
|                     stack.total += mathAbs(y) || 0; | 
|                 } | 
|             } else { | 
|                 stack.total += y || 0; | 
|             } | 
|   | 
|             stack.cum = (stack.cum || 0) + (y || 0); | 
|   | 
|             stack.points[series.index].push(stack.cum); | 
|             stackedYData[i] = stack.cum; | 
|   | 
|         } | 
|   | 
|         if (stacking === 'percent') { | 
|             yAxis.usePercentage = true; | 
|         } | 
|   | 
|         this.stackedYData = stackedYData; // To be used in getExtremes | 
|          | 
|         // Reset old stacks | 
|         yAxis.oldStacks = {}; | 
|     }, | 
|   | 
|     /** | 
|      * Iterate over all stacks and compute the absolute values to percent | 
|      */ | 
|     setPercentStacks: function () { | 
|         var series = this, | 
|             stackKey = series.stackKey, | 
|             stacks = series.yAxis.stacks; | 
|          | 
|         each([stackKey, '-' + stackKey], function (key) { | 
|             var i = series.xData.length, | 
|                 x, | 
|                 stack, | 
|                 pointExtremes, | 
|                 totalFactor; | 
|   | 
|             while (i--) { | 
|                 x = series.xData[i]; | 
|                 stack = stacks[key] && stacks[key][x]; | 
|                 pointExtremes = stack && stack.points[series.index]; | 
|                 if (pointExtremes) { | 
|                     totalFactor = stack.total ? 100 / stack.total : 0; | 
|                     pointExtremes[0] = correctFloat(pointExtremes[0] * totalFactor); // Y bottom value | 
|                     pointExtremes[1] = correctFloat(pointExtremes[1] * totalFactor); // Y value | 
|                     series.stackedYData[i] = pointExtremes[1]; | 
|                 } | 
|             } | 
|         }); | 
|     }, | 
|   | 
|     /** | 
|      * Calculate Y extremes for visible data | 
|      */ | 
|     getExtremes: function () { | 
|         var xAxis = this.xAxis, | 
|             yAxis = this.yAxis, | 
|             xData = this.processedXData, | 
|             yData = this.stackedYData || this.processedYData, | 
|             yDataLength = yData.length, | 
|             activeYData = [], | 
|             activeCounter = 0, | 
|             xExtremes = xAxis.getExtremes(), // #2117, need to compensate for log X axis | 
|             xMin = xExtremes.min, | 
|             xMax = xExtremes.max, | 
|             validValue, | 
|             withinRange, | 
|             dataMin, | 
|             dataMax, | 
|             x, | 
|             y, | 
|             i, | 
|             j; | 
|   | 
|         for (i = 0; i < yDataLength; i++) { | 
|              | 
|             x = xData[i]; | 
|             y = yData[i]; | 
|   | 
|             // For points within the visible range, including the first point outside the | 
|             // visible range, consider y extremes | 
|             validValue = y !== null && y !== UNDEFINED && (!yAxis.isLog || (y.length || y > 0)); | 
|             withinRange = this.getExtremesFromAll || this.cropped || ((xData[i + 1] || x) >= xMin &&  | 
|                 (xData[i - 1] || x) <= xMax); | 
|   | 
|             if (validValue && withinRange) { | 
|   | 
|                 j = y.length; | 
|                 if (j) { // array, like ohlc or range data | 
|                     while (j--) { | 
|                         if (y[j] !== null) { | 
|                             activeYData[activeCounter++] = y[j]; | 
|                         } | 
|                     } | 
|                 } else { | 
|                     activeYData[activeCounter++] = y; | 
|                 } | 
|             } | 
|         } | 
|         this.dataMin = pick(dataMin, arrayMin(activeYData)); | 
|         this.dataMax = pick(dataMax, arrayMax(activeYData)); | 
|     }, | 
|   | 
|     /** | 
|      * Translate data points from raw data values to chart specific positioning data | 
|      * needed later in drawPoints, drawGraph and drawTracker. | 
|      */ | 
|     translate: function () { | 
|         if (!this.processedXData) { // hidden series | 
|             this.processData(); | 
|         } | 
|         this.generatePoints(); | 
|         var series = this, | 
|             options = series.options, | 
|             stacking = options.stacking, | 
|             xAxis = series.xAxis, | 
|             categories = xAxis.categories, | 
|             yAxis = series.yAxis, | 
|             points = series.points, | 
|             dataLength = points.length, | 
|             hasModifyValue = !!series.modifyValue, | 
|             i, | 
|             pointPlacement = options.pointPlacement, | 
|             dynamicallyPlaced = pointPlacement === 'between' || isNumber(pointPlacement), | 
|             threshold = options.threshold; | 
|   | 
|          | 
|         // Translate each point | 
|         for (i = 0; i < dataLength; i++) { | 
|             var point = points[i], | 
|                 xValue = point.x, | 
|                 yValue = point.y, | 
|                 yBottom = point.low, | 
|                 stack = yAxis.stacks[(series.negStacks && yValue < threshold ? '-' : '') + series.stackKey], | 
|                 pointStack, | 
|                 stackValues; | 
|   | 
|             // Discard disallowed y values for log axes | 
|             if (yAxis.isLog && yValue <= 0) { | 
|                 point.y = yValue = null; | 
|             } | 
|              | 
|             // Get the plotX translation | 
|             point.plotX = xAxis.translate(xValue, 0, 0, 0, 1, pointPlacement, this.type === 'flags'); // Math.round fixes #591 | 
|              | 
|   | 
|             // Calculate the bottom y value for stacked series | 
|             if (stacking && series.visible && stack && stack[xValue]) { | 
|   | 
|                 pointStack = stack[xValue]; | 
|                 stackValues = pointStack.points[series.index]; | 
|                 yBottom = stackValues[0]; | 
|                 yValue = stackValues[1]; | 
|   | 
|                 if (yBottom === 0) { | 
|                     yBottom = pick(threshold, yAxis.min); | 
|                 } | 
|                 if (yAxis.isLog && yBottom <= 0) { // #1200, #1232 | 
|                     yBottom = null; | 
|                 } | 
|   | 
|                 point.percentage = stacking === 'percent' && yValue; | 
|                 point.total = point.stackTotal = pointStack.total; | 
|                 point.stackY = yValue; | 
|   | 
|                 // Place the stack label | 
|                 pointStack.setOffset(series.pointXOffset || 0, series.barW || 0); | 
|                  | 
|             } | 
|   | 
|             // Set translated yBottom or remove it | 
|             point.yBottom = defined(yBottom) ?  | 
|                 yAxis.translate(yBottom, 0, 1, 0, 1) : | 
|                 null; | 
|                  | 
|             // general hook, used for Highstock compare mode | 
|             if (hasModifyValue) { | 
|                 yValue = series.modifyValue(yValue, point); | 
|             } | 
|   | 
|             // Set the the plotY value, reset it for redraws | 
|             point.plotY = (typeof yValue === 'number' && yValue !== Infinity) ?  | 
|                 //mathRound(yAxis.translate(yValue, 0, 1, 0, 1) * 10) / 10 : // Math.round fixes #591 | 
|                 yAxis.translate(yValue, 0, 1, 0, 1) :  | 
|                 UNDEFINED; | 
|              | 
|             // Set client related positions for mouse tracking | 
|             point.clientX = dynamicallyPlaced ? xAxis.translate(xValue, 0, 0, 0, 1) : point.plotX; // #1514 | 
|                  | 
|             point.negative = point.y < (threshold || 0); | 
|   | 
|             // some API data | 
|             point.category = categories && categories[point.x] !== UNDEFINED ? | 
|                 categories[point.x] : point.x; | 
|   | 
|   | 
|         } | 
|   | 
|         // now that we have the cropped data, build the segments | 
|         series.getSegments(); | 
|     }, | 
|     /** | 
|      * Memoize tooltip texts and positions | 
|      */ | 
|     setTooltipPoints: function (renew) { | 
|         var series = this, | 
|             points = [], | 
|             pointsLength, | 
|             low, | 
|             high, | 
|             xAxis = series.xAxis, | 
|             xExtremes = xAxis && xAxis.getExtremes(), | 
|             axisLength = xAxis ? (xAxis.tooltipLen || xAxis.len) : series.chart.plotSizeX, // tooltipLen and tooltipPosName used in polar | 
|             point, | 
|             pointX, | 
|             nextPoint, | 
|             i, | 
|             tooltipPoints = []; // a lookup array for each pixel in the x dimension | 
|   | 
|         // don't waste resources if tracker is disabled | 
|         if (series.options.enableMouseTracking === false) { | 
|             return; | 
|         } | 
|   | 
|         // renew | 
|         if (renew) { | 
|             series.tooltipPoints = null; | 
|         } | 
|   | 
|         // concat segments to overcome null values | 
|         each(series.segments || series.points, function (segment) { | 
|             points = points.concat(segment); | 
|         }); | 
|   | 
|         // Reverse the points in case the X axis is reversed | 
|         if (xAxis && xAxis.reversed) { | 
|             points = points.reverse(); | 
|         } | 
|   | 
|         // Polar needs additional shaping | 
|         if (series.orderTooltipPoints) { | 
|             series.orderTooltipPoints(points); | 
|         } | 
|   | 
|         // Assign each pixel position to the nearest point | 
|         pointsLength = points.length; | 
|         for (i = 0; i < pointsLength; i++) { | 
|             point = points[i]; | 
|             pointX = point.x; | 
|             if (pointX >= xExtremes.min && pointX <= xExtremes.max) { // #1149 | 
|                 nextPoint = points[i + 1]; | 
|                  | 
|                 // Set this range's low to the last range's high plus one | 
|                 low = high === UNDEFINED ? 0 : high + 1; | 
|                 // Now find the new high | 
|                 high = points[i + 1] ? | 
|                     mathMin(mathMax(0, mathFloor( // #2070 | 
|                         (point.clientX + (nextPoint ? (nextPoint.wrappedClientX || nextPoint.clientX) : axisLength)) / 2 | 
|                     )), axisLength) : | 
|                     axisLength; | 
|   | 
|                 while (low >= 0 && low <= high) { | 
|                     tooltipPoints[low++] = point; | 
|                 } | 
|             } | 
|         } | 
|         series.tooltipPoints = tooltipPoints; | 
|     }, | 
|   | 
|     /** | 
|      * Format the header of the tooltip | 
|      */ | 
|     tooltipHeaderFormatter: function (point) { | 
|         var series = this, | 
|             tooltipOptions = series.tooltipOptions, | 
|             xDateFormat = tooltipOptions.xDateFormat, | 
|             dateTimeLabelFormats = tooltipOptions.dateTimeLabelFormats, | 
|             xAxis = series.xAxis, | 
|             isDateTime = xAxis && xAxis.options.type === 'datetime', | 
|             headerFormat = tooltipOptions.headerFormat, | 
|             closestPointRange = xAxis && xAxis.closestPointRange, | 
|             n; | 
|              | 
|         // Guess the best date format based on the closest point distance (#568) | 
|         if (isDateTime && !xDateFormat) { | 
|             if (closestPointRange) { | 
|                 for (n in timeUnits) { | 
|                     if (timeUnits[n] >= closestPointRange) { | 
|                         xDateFormat = dateTimeLabelFormats[n]; | 
|                         break; | 
|                     } | 
|                 } | 
|             } else { | 
|                 xDateFormat = dateTimeLabelFormats.day; | 
|             } | 
|         } | 
|          | 
|         // Insert the header date format if any | 
|         if (isDateTime && xDateFormat && isNumber(point.key)) { | 
|             headerFormat = headerFormat.replace('{point.key}', '{point.key:' + xDateFormat + '}'); | 
|         } | 
|          | 
|         return format(headerFormat, { | 
|             point: point, | 
|             series: series | 
|         }); | 
|     }, | 
|   | 
|     /** | 
|      * Series mouse over handler | 
|      */ | 
|     onMouseOver: function () { | 
|         var series = this, | 
|             chart = series.chart, | 
|             hoverSeries = chart.hoverSeries; | 
|   | 
|         // set normal state to previous series | 
|         if (hoverSeries && hoverSeries !== series) { | 
|             hoverSeries.onMouseOut(); | 
|         } | 
|   | 
|         // trigger the event, but to save processing time, | 
|         // only if defined | 
|         if (series.options.events.mouseOver) { | 
|             fireEvent(series, 'mouseOver'); | 
|         } | 
|   | 
|         // hover this | 
|         series.setState(HOVER_STATE); | 
|         chart.hoverSeries = series; | 
|     }, | 
|   | 
|     /** | 
|      * Series mouse out handler | 
|      */ | 
|     onMouseOut: function () { | 
|         // trigger the event only if listeners exist | 
|         var series = this, | 
|             options = series.options, | 
|             chart = series.chart, | 
|             tooltip = chart.tooltip, | 
|             hoverPoint = chart.hoverPoint; | 
|   | 
|         // trigger mouse out on the point, which must be in this series | 
|         if (hoverPoint) { | 
|             hoverPoint.onMouseOut(); | 
|         } | 
|   | 
|         // fire the mouse out event | 
|         if (series && options.events.mouseOut) { | 
|             fireEvent(series, 'mouseOut'); | 
|         } | 
|   | 
|   | 
|         // hide the tooltip | 
|         if (tooltip && !options.stickyTracking && (!tooltip.shared || series.noSharedTooltip)) { | 
|             tooltip.hide(); | 
|         } | 
|   | 
|         // set normal state | 
|         series.setState(); | 
|         chart.hoverSeries = null; | 
|     }, | 
|   | 
|     /** | 
|      * Animate in the series | 
|      */ | 
|     animate: function (init) { | 
|         var series = this, | 
|             chart = series.chart, | 
|             renderer = chart.renderer, | 
|             clipRect, | 
|             markerClipRect, | 
|             animation = series.options.animation, | 
|             clipBox = chart.clipBox, | 
|             inverted = chart.inverted, | 
|             sharedClipKey; | 
|   | 
|         // Animation option is set to true | 
|         if (animation && !isObject(animation)) { | 
|             animation = defaultPlotOptions[series.type].animation; | 
|         } | 
|         sharedClipKey = '_sharedClip' + animation.duration + animation.easing; | 
|   | 
|         // Initialize the animation. Set up the clipping rectangle. | 
|         if (init) {  | 
|              | 
|             // If a clipping rectangle with the same properties is currently present in the chart, use that.  | 
|             clipRect = chart[sharedClipKey]; | 
|             markerClipRect = chart[sharedClipKey + 'm']; | 
|             if (!clipRect) { | 
|                 chart[sharedClipKey] = clipRect = renderer.clipRect( | 
|                     extend(clipBox, { width: 0 }) | 
|                 ); | 
|                  | 
|                 chart[sharedClipKey + 'm'] = markerClipRect = renderer.clipRect( | 
|                     -99, // include the width of the first marker | 
|                     inverted ? -chart.plotLeft : -chart.plotTop,  | 
|                     99, | 
|                     inverted ? chart.chartWidth : chart.chartHeight | 
|                 ); | 
|             } | 
|             series.group.clip(clipRect); | 
|             series.markerGroup.clip(markerClipRect); | 
|             series.sharedClipKey = sharedClipKey; | 
|   | 
|         // Run the animation | 
|         } else {  | 
|             clipRect = chart[sharedClipKey]; | 
|             if (clipRect) { | 
|                 clipRect.animate({ | 
|                     width: chart.plotSizeX | 
|                 }, animation); | 
|                 chart[sharedClipKey + 'm'].animate({ | 
|                     width: chart.plotSizeX + 99 | 
|                 }, animation); | 
|             } | 
|   | 
|             // Delete this function to allow it only once | 
|             series.animate = null; | 
|              | 
|             // Call the afterAnimate function on animation complete (but don't overwrite the animation.complete option | 
|             // which should be available to the user). | 
|             series.animationTimeout = setTimeout(function () { | 
|                 series.afterAnimate(); | 
|             }, animation.duration); | 
|         } | 
|     }, | 
|      | 
|     /** | 
|      * This runs after animation to land on the final plot clipping | 
|      */ | 
|     afterAnimate: function () { | 
|         var chart = this.chart, | 
|             sharedClipKey = this.sharedClipKey, | 
|             group = this.group; | 
|              | 
|         if (group && this.options.clip !== false) { | 
|             group.clip(chart.clipRect); | 
|             this.markerGroup.clip(); // no clip | 
|         } | 
|          | 
|         // Remove the shared clipping rectancgle when all series are shown         | 
|         setTimeout(function () { | 
|             if (sharedClipKey && chart[sharedClipKey]) { | 
|                 chart[sharedClipKey] = chart[sharedClipKey].destroy(); | 
|                 chart[sharedClipKey + 'm'] = chart[sharedClipKey + 'm'].destroy(); | 
|             } | 
|         }, 100); | 
|     }, | 
|   | 
|     /** | 
|      * Draw the markers | 
|      */ | 
|     drawPoints: function () { | 
|         var series = this, | 
|             pointAttr, | 
|             points = series.points, | 
|             chart = series.chart, | 
|             plotX, | 
|             plotY, | 
|             i, | 
|             point, | 
|             radius, | 
|             symbol, | 
|             isImage, | 
|             graphic, | 
|             options = series.options, | 
|             seriesMarkerOptions = options.marker, | 
|             pointMarkerOptions, | 
|             enabled, | 
|             isInside, | 
|             markerGroup = series.markerGroup; | 
|   | 
|         if (seriesMarkerOptions.enabled || series._hasPointMarkers) { | 
|              | 
|             i = points.length; | 
|             while (i--) { | 
|                 point = points[i]; | 
|                 plotX = mathFloor(point.plotX); // #1843 | 
|                 plotY = point.plotY; | 
|                 graphic = point.graphic; | 
|                 pointMarkerOptions = point.marker || {}; | 
|                 enabled = (seriesMarkerOptions.enabled && pointMarkerOptions.enabled === UNDEFINED) || pointMarkerOptions.enabled; | 
|                 isInside = chart.isInsidePlot(mathRound(plotX), plotY, chart.inverted); // #1858 | 
|                  | 
|                 // only draw the point if y is defined | 
|                 if (enabled && plotY !== UNDEFINED && !isNaN(plotY) && point.y !== null) { | 
|   | 
|                     // shortcuts | 
|                     pointAttr = point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE]; | 
|                     radius = pointAttr.r; | 
|                     symbol = pick(pointMarkerOptions.symbol, series.symbol); | 
|                     isImage = symbol.indexOf('url') === 0; | 
|   | 
|                     if (graphic) { // update | 
|                         graphic | 
|                             .attr({ // Since the marker group isn't clipped, each individual marker must be toggled | 
|                                 visibility: isInside ? (hasSVG ? 'inherit' : VISIBLE) : HIDDEN | 
|                             }) | 
|                             .animate(extend({ | 
|                                 x: plotX - radius, | 
|                                 y: plotY - radius | 
|                             }, graphic.symbolName ? { // don't apply to image symbols #507 | 
|                                 width: 2 * radius, | 
|                                 height: 2 * radius | 
|                             } : {})); | 
|                     } else if (isInside && (radius > 0 || isImage)) { | 
|                         point.graphic = graphic = chart.renderer.symbol( | 
|                             symbol, | 
|                             plotX - radius, | 
|                             plotY - radius, | 
|                             2 * radius, | 
|                             2 * radius | 
|                         ) | 
|                         .attr(pointAttr) | 
|                         .add(markerGroup); | 
|                     } | 
|                      | 
|                 } else if (graphic) { | 
|                     point.graphic = graphic.destroy(); // #1269 | 
|                 } | 
|             } | 
|         } | 
|   | 
|     }, | 
|   | 
|     /** | 
|      * Convert state properties from API naming conventions to SVG attributes | 
|      * | 
|      * @param {Object} options API options object | 
|      * @param {Object} base1 SVG attribute object to inherit from | 
|      * @param {Object} base2 Second level SVG attribute object to inherit from | 
|      */ | 
|     convertAttribs: function (options, base1, base2, base3) { | 
|         var conversion = this.pointAttrToOptions, | 
|             attr, | 
|             option, | 
|             obj = {}; | 
|   | 
|         options = options || {}; | 
|         base1 = base1 || {}; | 
|         base2 = base2 || {}; | 
|         base3 = base3 || {}; | 
|   | 
|         for (attr in conversion) { | 
|             option = conversion[attr]; | 
|             obj[attr] = pick(options[option], base1[attr], base2[attr], base3[attr]); | 
|         } | 
|         return obj; | 
|     }, | 
|   | 
|     /** | 
|      * Get the state attributes. Each series type has its own set of attributes | 
|      * that are allowed to change on a point's state change. Series wide attributes are stored for | 
|      * all series, and additionally point specific attributes are stored for all | 
|      * points with individual marker options. If such options are not defined for the point, | 
|      * a reference to the series wide attributes is stored in point.pointAttr. | 
|      */ | 
|     getAttribs: function () { | 
|         var series = this, | 
|             seriesOptions = series.options, | 
|             normalOptions = defaultPlotOptions[series.type].marker ? seriesOptions.marker : seriesOptions, | 
|             stateOptions = normalOptions.states, | 
|             stateOptionsHover = stateOptions[HOVER_STATE], | 
|             pointStateOptionsHover, | 
|             seriesColor = series.color, | 
|             normalDefaults = { | 
|                 stroke: seriesColor, | 
|                 fill: seriesColor | 
|             }, | 
|             points = series.points || [], // #927 | 
|             i, | 
|             point, | 
|             seriesPointAttr = [], | 
|             pointAttr, | 
|             pointAttrToOptions = series.pointAttrToOptions, | 
|             hasPointSpecificOptions, | 
|             negativeColor = seriesOptions.negativeColor, | 
|             defaultLineColor = normalOptions.lineColor, | 
|             key; | 
|   | 
|         // series type specific modifications | 
|         if (seriesOptions.marker) { // line, spline, area, areaspline, scatter | 
|   | 
|             // if no hover radius is given, default to normal radius + 2 | 
|             stateOptionsHover.radius = stateOptionsHover.radius || normalOptions.radius + 2; | 
|             stateOptionsHover.lineWidth = stateOptionsHover.lineWidth || normalOptions.lineWidth + 1; | 
|              | 
|         } else { // column, bar, pie | 
|   | 
|             // if no hover color is given, brighten the normal color | 
|             stateOptionsHover.color = stateOptionsHover.color || | 
|                 Color(stateOptionsHover.color || seriesColor) | 
|                     .brighten(stateOptionsHover.brightness).get(); | 
|         } | 
|   | 
|         // general point attributes for the series normal state | 
|         seriesPointAttr[NORMAL_STATE] = series.convertAttribs(normalOptions, normalDefaults); | 
|   | 
|         // HOVER_STATE and SELECT_STATE states inherit from normal state except the default radius | 
|         each([HOVER_STATE, SELECT_STATE], function (state) { | 
|             seriesPointAttr[state] = | 
|                     series.convertAttribs(stateOptions[state], seriesPointAttr[NORMAL_STATE]); | 
|         }); | 
|   | 
|         // set it | 
|         series.pointAttr = seriesPointAttr; | 
|   | 
|   | 
|         // Generate the point-specific attribute collections if specific point | 
|         // options are given. If not, create a referance to the series wide point | 
|         // attributes | 
|         i = points.length; | 
|         while (i--) { | 
|             point = points[i]; | 
|             normalOptions = (point.options && point.options.marker) || point.options; | 
|             if (normalOptions && normalOptions.enabled === false) { | 
|                 normalOptions.radius = 0; | 
|             } | 
|              | 
|             if (point.negative && negativeColor) { | 
|                 point.color = point.fillColor = negativeColor; | 
|             } | 
|              | 
|             hasPointSpecificOptions = seriesOptions.colorByPoint || point.color; // #868 | 
|   | 
|             // check if the point has specific visual options | 
|             if (point.options) { | 
|                 for (key in pointAttrToOptions) { | 
|                     if (defined(normalOptions[pointAttrToOptions[key]])) { | 
|                         hasPointSpecificOptions = true; | 
|                     } | 
|                 } | 
|             } | 
|   | 
|             // a specific marker config object is defined for the individual point: | 
|             // create it's own attribute collection | 
|             if (hasPointSpecificOptions) { | 
|                 normalOptions = normalOptions || {}; | 
|                 pointAttr = []; | 
|                 stateOptions = normalOptions.states || {}; // reassign for individual point | 
|                 pointStateOptionsHover = stateOptions[HOVER_STATE] = stateOptions[HOVER_STATE] || {}; | 
|   | 
|                 // Handle colors for column and pies | 
|                 if (!seriesOptions.marker) { // column, bar, point | 
|                     // if no hover color is given, brighten the normal color | 
|                     pointStateOptionsHover.color = | 
|                         Color(pointStateOptionsHover.color || point.color) | 
|                             .brighten(pointStateOptionsHover.brightness || | 
|                                 stateOptionsHover.brightness).get(); | 
|   | 
|                 } | 
|   | 
|                 // normal point state inherits series wide normal state | 
|                 pointAttr[NORMAL_STATE] = series.convertAttribs(extend({ | 
|                     color: point.color, // #868 | 
|                     fillColor: point.color, // Individual point color or negative color markers (#2219) | 
|                     lineColor: defaultLineColor === null ? point.color : UNDEFINED // Bubbles take point color, line markers use white | 
|                 }, normalOptions), seriesPointAttr[NORMAL_STATE]); | 
|   | 
|                 // inherit from point normal and series hover | 
|                 pointAttr[HOVER_STATE] = series.convertAttribs( | 
|                     stateOptions[HOVER_STATE], | 
|                     seriesPointAttr[HOVER_STATE], | 
|                     pointAttr[NORMAL_STATE] | 
|                 ); | 
|                  | 
|                 // inherit from point normal and series hover | 
|                 pointAttr[SELECT_STATE] = series.convertAttribs( | 
|                     stateOptions[SELECT_STATE], | 
|                     seriesPointAttr[SELECT_STATE], | 
|                     pointAttr[NORMAL_STATE] | 
|                 ); | 
|   | 
|   | 
|             // no marker config object is created: copy a reference to the series-wide | 
|             // attribute collection | 
|             } else { | 
|                 pointAttr = seriesPointAttr; | 
|             } | 
|   | 
|             point.pointAttr = pointAttr; | 
|   | 
|         } | 
|   | 
|     }, | 
|     /** | 
|      * Update the series with a new set of options | 
|      */ | 
|     update: function (newOptions, redraw) { | 
|         var chart = this.chart, | 
|             // must use user options when changing type because this.options is merged | 
|             // in with type specific plotOptions | 
|             oldOptions = this.userOptions, | 
|             oldType = this.type, | 
|             proto = seriesTypes[oldType].prototype, | 
|             n; | 
|   | 
|         // Do the merge, with some forced options | 
|         newOptions = merge(oldOptions, { | 
|             animation: false, | 
|             index: this.index, | 
|             pointStart: this.xData[0] // when updating after addPoint | 
|         }, { data: this.options.data }, newOptions); | 
|   | 
|         // Destroy the series and reinsert methods from the type prototype | 
|         this.remove(false); | 
|         for (n in proto) { // Overwrite series-type specific methods (#2270) | 
|             if (proto.hasOwnProperty(n)) { | 
|                 this[n] = UNDEFINED; | 
|             } | 
|         } | 
|         extend(this, seriesTypes[newOptions.type || oldType].prototype); | 
|          | 
|   | 
|         this.init(chart, newOptions); | 
|         if (pick(redraw, true)) { | 
|             chart.redraw(false); | 
|         } | 
|     }, | 
|   | 
|     /** | 
|      * Clear DOM objects and free up memory | 
|      */ | 
|     destroy: function () { | 
|         var series = this, | 
|             chart = series.chart, | 
|             issue134 = /AppleWebKit\/533/.test(userAgent), | 
|             destroy, | 
|             i, | 
|             data = series.data || [], | 
|             point, | 
|             prop, | 
|             axis; | 
|   | 
|         // add event hook | 
|         fireEvent(series, 'destroy'); | 
|   | 
|         // remove all events | 
|         removeEvent(series); | 
|          | 
|         // erase from axes | 
|         each(['xAxis', 'yAxis'], function (AXIS) { | 
|             axis = series[AXIS]; | 
|             if (axis) { | 
|                 erase(axis.series, series); | 
|                 axis.isDirty = axis.forceRedraw = true; | 
|                 axis.stacks = {}; // Rebuild stacks when updating (#2229) | 
|             } | 
|         }); | 
|   | 
|         // remove legend items | 
|         if (series.legendItem) { | 
|             series.chart.legend.destroyItem(series); | 
|         } | 
|   | 
|         // destroy all points with their elements | 
|         i = data.length; | 
|         while (i--) { | 
|             point = data[i]; | 
|             if (point && point.destroy) { | 
|                 point.destroy(); | 
|             } | 
|         } | 
|         series.points = null; | 
|   | 
|         // Clear the animation timeout if we are destroying the series during initial animation | 
|         clearTimeout(series.animationTimeout); | 
|   | 
|         // destroy all SVGElements associated to the series | 
|         each(['area', 'graph', 'dataLabelsGroup', 'group', 'markerGroup', 'tracker', | 
|                 'graphNeg', 'areaNeg', 'posClip', 'negClip'], function (prop) { | 
|             if (series[prop]) { | 
|   | 
|                 // issue 134 workaround | 
|                 destroy = issue134 && prop === 'group' ? | 
|                     'hide' : | 
|                     'destroy'; | 
|   | 
|                 series[prop][destroy](); | 
|             } | 
|         }); | 
|   | 
|         // remove from hoverSeries | 
|         if (chart.hoverSeries === series) { | 
|             chart.hoverSeries = null; | 
|         } | 
|         erase(chart.series, series); | 
|   | 
|         // clear all members | 
|         for (prop in series) { | 
|             delete series[prop]; | 
|         } | 
|     }, | 
|   | 
|     /** | 
|      * Draw the data labels | 
|      */ | 
|     drawDataLabels: function () { | 
|          | 
|         var series = this, | 
|             seriesOptions = series.options, | 
|             options = seriesOptions.dataLabels, | 
|             points = series.points, | 
|             pointOptions, | 
|             generalOptions, | 
|             str, | 
|             dataLabelsGroup; | 
|          | 
|         if (options.enabled || series._hasPointLabels) { | 
|                          | 
|             // Process default alignment of data labels for columns | 
|             if (series.dlProcessOptions) { | 
|                 series.dlProcessOptions(options); | 
|             } | 
|   | 
|             // Create a separate group for the data labels to avoid rotation | 
|             dataLabelsGroup = series.plotGroup( | 
|                 'dataLabelsGroup',  | 
|                 'data-labels',  | 
|                 series.visible ? VISIBLE : HIDDEN,  | 
|                 options.zIndex || 6 | 
|             ); | 
|              | 
|             // Make the labels for each point | 
|             generalOptions = options; | 
|             each(points, function (point) { | 
|                  | 
|                 var enabled, | 
|                     dataLabel = point.dataLabel, | 
|                     labelConfig, | 
|                     attr, | 
|                     name, | 
|                     rotation, | 
|                     connector = point.connector, | 
|                     isNew = true; | 
|                  | 
|                 // Determine if each data label is enabled | 
|                 pointOptions = point.options && point.options.dataLabels; | 
|                 enabled = pick(pointOptions && pointOptions.enabled, generalOptions.enabled); // #2282 | 
|                  | 
|                  | 
|                 // If the point is outside the plot area, destroy it. #678, #820 | 
|                 if (dataLabel && !enabled) { | 
|                     point.dataLabel = dataLabel.destroy(); | 
|                  | 
|                 // Individual labels are disabled if the are explicitly disabled  | 
|                 // in the point options, or if they fall outside the plot area. | 
|                 } else if (enabled) { | 
|                      | 
|                     // Create individual options structure that can be extended without  | 
|                     // affecting others | 
|                     options = merge(generalOptions, pointOptions); | 
|   | 
|                     rotation = options.rotation; | 
|                      | 
|                     // Get the string | 
|                     labelConfig = point.getLabelConfig(); | 
|                     str = options.format ? | 
|                         format(options.format, labelConfig) :  | 
|                         options.formatter.call(labelConfig, options); | 
|                      | 
|                     // Determine the color | 
|                     options.style.color = pick(options.color, options.style.color, series.color, 'black'); | 
|      | 
|                      | 
|                     // update existing label | 
|                     if (dataLabel) { | 
|                          | 
|                         if (defined(str)) { | 
|                             dataLabel | 
|                                 .attr({ | 
|                                     text: str | 
|                                 }); | 
|                             isNew = false; | 
|                          | 
|                         } else { // #1437 - the label is shown conditionally | 
|                             point.dataLabel = dataLabel = dataLabel.destroy(); | 
|                             if (connector) { | 
|                                 point.connector = connector.destroy(); | 
|                             } | 
|                         } | 
|                          | 
|                     // create new label | 
|                     } else if (defined(str)) { | 
|                         attr = { | 
|                             //align: align, | 
|                             fill: options.backgroundColor, | 
|                             stroke: options.borderColor, | 
|                             'stroke-width': options.borderWidth, | 
|                             r: options.borderRadius || 0, | 
|                             rotation: rotation, | 
|                             padding: options.padding, | 
|                             zIndex: 1 | 
|                         }; | 
|                         // Remove unused attributes (#947) | 
|                         for (name in attr) { | 
|                             if (attr[name] === UNDEFINED) { | 
|                                 delete attr[name]; | 
|                             } | 
|                         } | 
|                          | 
|                         dataLabel = point.dataLabel = series.chart.renderer[rotation ? 'text' : 'label']( // labels don't support rotation | 
|                             str, | 
|                             0, | 
|                             -999, | 
|                             null, | 
|                             null, | 
|                             null, | 
|                             options.useHTML | 
|                         ) | 
|                         .attr(attr) | 
|                         .css(options.style) | 
|                         .add(dataLabelsGroup) | 
|                         .shadow(options.shadow); | 
|                          | 
|                     } | 
|                      | 
|                     if (dataLabel) { | 
|                         // Now the data label is created and placed at 0,0, so we need to align it | 
|                         series.alignDataLabel(point, dataLabel, options, null, isNew); | 
|                     } | 
|                 } | 
|             }); | 
|         } | 
|     }, | 
|      | 
|     /** | 
|      * Align each individual data label | 
|      */ | 
|     alignDataLabel: function (point, dataLabel, options, alignTo, isNew) { | 
|         var chart = this.chart, | 
|             inverted = chart.inverted, | 
|             plotX = pick(point.plotX, -999), | 
|             plotY = pick(point.plotY, -999), | 
|             bBox = dataLabel.getBBox(), | 
|             visible = this.visible && chart.isInsidePlot(point.plotX, point.plotY, inverted), | 
|             alignAttr; // the final position; | 
|                  | 
|         if (visible) { | 
|   | 
|             // The alignment box is a singular point | 
|             alignTo = extend({ | 
|                 x: inverted ? chart.plotWidth - plotY : plotX, | 
|                 y: mathRound(inverted ? chart.plotHeight - plotX : plotY), | 
|                 width: 0, | 
|                 height: 0 | 
|             }, alignTo); | 
|              | 
|             // Add the text size for alignment calculation | 
|             extend(options, { | 
|                 width: bBox.width, | 
|                 height: bBox.height | 
|             }); | 
|   | 
|             // Allow a hook for changing alignment in the last moment, then do the alignment | 
|             if (options.rotation) { // Fancy box alignment isn't supported for rotated text | 
|                 alignAttr = { | 
|                     align: options.align, | 
|                     x: alignTo.x + options.x + alignTo.width / 2, | 
|                     y: alignTo.y + options.y + alignTo.height / 2 | 
|                 }; | 
|                 dataLabel[isNew ? 'attr' : 'animate'](alignAttr); | 
|             } else { | 
|                 dataLabel.align(options, null, alignTo); | 
|                 alignAttr = dataLabel.alignAttr; | 
|   | 
|                 // Handle justify or crop | 
|                 if (pick(options.overflow, 'justify') === 'justify') { // docs: overflow: justify, also crop only applies when not justify | 
|                     this.justifyDataLabel(dataLabel, options, alignAttr, bBox, alignTo, isNew); | 
|                  | 
|                 } else if (pick(options.crop, true)) { | 
|                     // Now check that the data label is within the plot area | 
|                     visible = chart.isInsidePlot(alignAttr.x, alignAttr.y) && chart.isInsidePlot(alignAttr.x + bBox.width, alignAttr.y + bBox.height); | 
|                  | 
|                 } | 
|             }         | 
|         } | 
|   | 
|         // Show or hide based on the final aligned position | 
|         if (!visible) { | 
|             dataLabel.attr({ y: -999 }); | 
|         } | 
|                  | 
|     }, | 
|      | 
|     /** | 
|      * If data labels fall partly outside the plot area, align them back in, in a way that | 
|      * doesn't hide the point. | 
|      */ | 
|     justifyDataLabel: function (dataLabel, options, alignAttr, bBox, alignTo, isNew) { | 
|         var chart = this.chart, | 
|             align = options.align, | 
|             verticalAlign = options.verticalAlign, | 
|             off, | 
|             justified; | 
|   | 
|         // Off left | 
|         off = alignAttr.x; | 
|         if (off < 0) { | 
|             if (align === 'right') { | 
|                 options.align = 'left'; | 
|             } else { | 
|                 options.x = -off; | 
|             } | 
|             justified = true; | 
|         } | 
|   | 
|         // Off right | 
|         off = alignAttr.x + bBox.width; | 
|         if (off > chart.plotWidth) { | 
|             if (align === 'left') { | 
|                 options.align = 'right'; | 
|             } else { | 
|                 options.x = chart.plotWidth - off; | 
|             } | 
|             justified = true; | 
|         } | 
|   | 
|         // Off top | 
|         off = alignAttr.y; | 
|         if (off < 0) { | 
|             if (verticalAlign === 'bottom') { | 
|                 options.verticalAlign = 'top'; | 
|             } else { | 
|                 options.y = -off; | 
|             } | 
|             justified = true; | 
|         } | 
|   | 
|         // Off bottom | 
|         off = alignAttr.y + bBox.height; | 
|         if (off > chart.plotHeight) { | 
|             if (verticalAlign === 'top') { | 
|                 options.verticalAlign = 'bottom'; | 
|             } else { | 
|                 options.y = chart.plotHeight - off; | 
|             } | 
|             justified = true; | 
|         } | 
|          | 
|         if (justified) { | 
|             dataLabel.placed = !isNew; | 
|             dataLabel.align(options, null, alignTo); | 
|         } | 
|     }, | 
|      | 
|     /** | 
|      * Return the graph path of a segment | 
|      */ | 
|     getSegmentPath: function (segment) {         | 
|         var series = this, | 
|             segmentPath = [], | 
|             step = series.options.step; | 
|              | 
|         // build the segment line | 
|         each(segment, function (point, i) { | 
|              | 
|             var plotX = point.plotX, | 
|                 plotY = point.plotY, | 
|                 lastPoint; | 
|   | 
|             if (series.getPointSpline) { // generate the spline as defined in the SplineSeries object | 
|                 segmentPath.push.apply(segmentPath, series.getPointSpline(segment, point, i)); | 
|   | 
|             } else { | 
|   | 
|                 // moveTo or lineTo | 
|                 segmentPath.push(i ? L : M); | 
|   | 
|                 // step line? | 
|                 if (step && i) { | 
|                     lastPoint = segment[i - 1]; | 
|                     if (step === 'right') { | 
|                         segmentPath.push( | 
|                             lastPoint.plotX, | 
|                             plotY | 
|                         ); | 
|                          | 
|                     } else if (step === 'center') { | 
|                         segmentPath.push( | 
|                             (lastPoint.plotX + plotX) / 2, | 
|                             lastPoint.plotY, | 
|                             (lastPoint.plotX + plotX) / 2, | 
|                             plotY | 
|                         ); | 
|                          | 
|                     } else { | 
|                         segmentPath.push( | 
|                             plotX, | 
|                             lastPoint.plotY | 
|                         ); | 
|                     } | 
|                 } | 
|   | 
|                 // normal line to next point | 
|                 segmentPath.push( | 
|                     point.plotX, | 
|                     point.plotY | 
|                 ); | 
|             } | 
|         }); | 
|          | 
|         return segmentPath; | 
|     }, | 
|   | 
|     /** | 
|      * Get the graph path | 
|      */ | 
|     getGraphPath: function () { | 
|         var series = this, | 
|             graphPath = [], | 
|             segmentPath, | 
|             singlePoints = []; // used in drawTracker | 
|   | 
|         // Divide into segments and build graph and area paths | 
|         each(series.segments, function (segment) { | 
|              | 
|             segmentPath = series.getSegmentPath(segment); | 
|              | 
|             // add the segment to the graph, or a single point for tracking | 
|             if (segment.length > 1) { | 
|                 graphPath = graphPath.concat(segmentPath); | 
|             } else { | 
|                 singlePoints.push(segment[0]); | 
|             } | 
|         }); | 
|   | 
|         // Record it for use in drawGraph and drawTracker, and return graphPath | 
|         series.singlePoints = singlePoints; | 
|         series.graphPath = graphPath; | 
|          | 
|         return graphPath; | 
|          | 
|     }, | 
|      | 
|     /** | 
|      * Draw the actual graph | 
|      */ | 
|     drawGraph: function () { | 
|         var series = this, | 
|             options = this.options, | 
|             props = [['graph', options.lineColor || this.color]], | 
|             lineWidth = options.lineWidth, | 
|             dashStyle =  options.dashStyle, | 
|             graphPath = this.getGraphPath(), | 
|             negativeColor = options.negativeColor; | 
|              | 
|         if (negativeColor) { | 
|             props.push(['graphNeg', negativeColor]); | 
|         } | 
|          | 
|         // draw the graph | 
|         each(props, function (prop, i) { | 
|             var graphKey = prop[0], | 
|                 graph = series[graphKey], | 
|                 attribs; | 
|              | 
|             if (graph) { | 
|                 stop(graph); // cancel running animations, #459 | 
|                 graph.animate({ d: graphPath }); | 
|      | 
|             } else if (lineWidth && graphPath.length) { // #1487 | 
|                 attribs = { | 
|                     stroke: prop[1], | 
|                     'stroke-width': lineWidth, | 
|                     zIndex: 1 // #1069 | 
|                 }; | 
|                 if (dashStyle) { | 
|                     attribs.dashstyle = dashStyle; | 
|                 } else { | 
|                     attribs['stroke-linecap'] = attribs['stroke-linejoin'] = 'round'; | 
|                 } | 
|   | 
|                 series[graphKey] = series.chart.renderer.path(graphPath) | 
|                     .attr(attribs) | 
|                     .add(series.group) | 
|                     .shadow(!i && options.shadow); | 
|             } | 
|         }); | 
|     }, | 
|      | 
|     /** | 
|      * Clip the graphs into the positive and negative coloured graphs | 
|      */ | 
|     clipNeg: function () { | 
|         var options = this.options, | 
|             chart = this.chart, | 
|             renderer = chart.renderer, | 
|             negativeColor = options.negativeColor || options.negativeFillColor, | 
|             translatedThreshold, | 
|             posAttr, | 
|             negAttr, | 
|             graph = this.graph, | 
|             area = this.area, | 
|             posClip = this.posClip, | 
|             negClip = this.negClip, | 
|             chartWidth = chart.chartWidth, | 
|             chartHeight = chart.chartHeight, | 
|             chartSizeMax = mathMax(chartWidth, chartHeight), | 
|             yAxis = this.yAxis, | 
|             above, | 
|             below; | 
|          | 
|         if (negativeColor && (graph || area)) { | 
|             translatedThreshold = mathRound(yAxis.toPixels(options.threshold || 0, true)); | 
|             above = { | 
|                 x: 0, | 
|                 y: 0, | 
|                 width: chartSizeMax, | 
|                 height: translatedThreshold | 
|             }; | 
|             below = { | 
|                 x: 0, | 
|                 y: translatedThreshold, | 
|                 width: chartSizeMax, | 
|                 height: chartSizeMax | 
|             }; | 
|              | 
|             if (chart.inverted) { | 
|   | 
|                 above.height = below.y = chart.plotWidth - translatedThreshold; | 
|                 if (renderer.isVML) { | 
|                     above = { | 
|                         x: chart.plotWidth - translatedThreshold - chart.plotLeft, | 
|                         y: 0, | 
|                         width: chartWidth, | 
|                         height: chartHeight | 
|                     }; | 
|                     below = { | 
|                         x: translatedThreshold + chart.plotLeft - chartWidth, | 
|                         y: 0, | 
|                         width: chart.plotLeft + translatedThreshold, | 
|                         height: chartWidth | 
|                     }; | 
|                 } | 
|             } | 
|              | 
|             if (yAxis.reversed) { | 
|                 posAttr = below; | 
|                 negAttr = above; | 
|             } else { | 
|                 posAttr = above; | 
|                 negAttr = below; | 
|             } | 
|          | 
|             if (posClip) { // update | 
|                 posClip.animate(posAttr); | 
|                 negClip.animate(negAttr); | 
|             } else { | 
|                  | 
|                 this.posClip = posClip = renderer.clipRect(posAttr); | 
|                 this.negClip = negClip = renderer.clipRect(negAttr); | 
|                  | 
|                 if (graph && this.graphNeg) { | 
|                     graph.clip(posClip); | 
|                     this.graphNeg.clip(negClip);     | 
|                 } | 
|                  | 
|                 if (area) { | 
|                     area.clip(posClip); | 
|                     this.areaNeg.clip(negClip); | 
|                 }  | 
|             }  | 
|         }     | 
|     }, | 
|   | 
|     /** | 
|      * Initialize and perform group inversion on series.group and series.markerGroup | 
|      */ | 
|     invertGroups: function () { | 
|         var series = this, | 
|             chart = series.chart; | 
|   | 
|         // Pie, go away (#1736) | 
|         if (!series.xAxis) { | 
|             return; | 
|         } | 
|          | 
|         // A fixed size is needed for inversion to work | 
|         function setInvert() {             | 
|             var size = { | 
|                 width: series.yAxis.len, | 
|                 height: series.xAxis.len | 
|             }; | 
|              | 
|             each(['group', 'markerGroup'], function (groupName) { | 
|                 if (series[groupName]) { | 
|                     series[groupName].attr(size).invert(); | 
|                 } | 
|             }); | 
|         } | 
|   | 
|         addEvent(chart, 'resize', setInvert); // do it on resize | 
|         addEvent(series, 'destroy', function () { | 
|             removeEvent(chart, 'resize', setInvert); | 
|         }); | 
|   | 
|         // Do it now | 
|         setInvert(); // do it now | 
|          | 
|         // On subsequent render and redraw, just do setInvert without setting up events again | 
|         series.invertGroups = setInvert; | 
|     }, | 
|      | 
|     /** | 
|      * General abstraction for creating plot groups like series.group, series.dataLabelsGroup and  | 
|      * series.markerGroup. On subsequent calls, the group will only be adjusted to the updated plot size. | 
|      */ | 
|     plotGroup: function (prop, name, visibility, zIndex, parent) { | 
|         var group = this[prop], | 
|             isNew = !group; | 
|          | 
|         // Generate it on first call | 
|         if (isNew) {     | 
|             this[prop] = group = this.chart.renderer.g(name) | 
|                 .attr({ | 
|                     visibility: visibility, | 
|                     zIndex: zIndex || 0.1 // IE8 needs this | 
|                 }) | 
|                 .add(parent); | 
|         } | 
|         // Place it on first and subsequent (redraw) calls | 
|         group[isNew ? 'attr' : 'animate'](this.getPlotBox()); | 
|         return group;         | 
|     }, | 
|   | 
|     /** | 
|      * Get the translation and scale for the plot area of this series | 
|      */ | 
|     getPlotBox: function () { | 
|         return { | 
|             translateX: this.xAxis ? this.xAxis.left : this.chart.plotLeft,  | 
|             translateY: this.yAxis ? this.yAxis.top : this.chart.plotTop, | 
|             scaleX: 1, // #1623 | 
|             scaleY: 1 | 
|         }; | 
|     }, | 
|      | 
|     /** | 
|      * Render the graph and markers | 
|      */ | 
|     render: function () { | 
|         var series = this, | 
|             chart = series.chart, | 
|             group, | 
|             options = series.options, | 
|             animation = options.animation, | 
|             doAnimation = animation && !!series.animate &&  | 
|                 chart.renderer.isSVG, // this animation doesn't work in IE8 quirks when the group div is hidden, | 
|                 // and looks bad in other oldIE | 
|             visibility = series.visible ? VISIBLE : HIDDEN, | 
|             zIndex = options.zIndex, | 
|             hasRendered = series.hasRendered, | 
|             chartSeriesGroup = chart.seriesGroup; | 
|          | 
|         // the group | 
|         group = series.plotGroup( | 
|             'group',  | 
|             'series',  | 
|             visibility,  | 
|             zIndex,  | 
|             chartSeriesGroup | 
|         ); | 
|          | 
|         series.markerGroup = series.plotGroup( | 
|             'markerGroup',  | 
|             'markers',  | 
|             visibility,  | 
|             zIndex,  | 
|             chartSeriesGroup | 
|         ); | 
|          | 
|         // initiate the animation | 
|         if (doAnimation) { | 
|             series.animate(true); | 
|         } | 
|   | 
|         // cache attributes for shapes | 
|         series.getAttribs(); | 
|   | 
|         // SVGRenderer needs to know this before drawing elements (#1089, #1795) | 
|         group.inverted = series.isCartesian ? chart.inverted : false; | 
|          | 
|         // draw the graph if any | 
|         if (series.drawGraph) { | 
|             series.drawGraph(); | 
|             series.clipNeg(); | 
|         } | 
|   | 
|         // draw the data labels (inn pies they go before the points) | 
|         series.drawDataLabels(); | 
|          | 
|         // draw the points | 
|         series.drawPoints(); | 
|   | 
|   | 
|         // draw the mouse tracking area | 
|         if (series.options.enableMouseTracking !== false) { | 
|             series.drawTracker(); | 
|         } | 
|          | 
|         // Handle inverted series and tracker groups | 
|         if (chart.inverted) { | 
|             series.invertGroups(); | 
|         } | 
|          | 
|         // Initial clipping, must be defined after inverting groups for VML | 
|         if (options.clip !== false && !series.sharedClipKey && !hasRendered) { | 
|             group.clip(chart.clipRect); | 
|         } | 
|   | 
|         // Run the animation | 
|         if (doAnimation) { | 
|             series.animate(); | 
|         } else if (!hasRendered) { | 
|             series.afterAnimate(); | 
|         } | 
|   | 
|         series.isDirty = series.isDirtyData = false; // means data is in accordance with what you see | 
|         // (See #322) series.isDirty = series.isDirtyData = false; // means data is in accordance with what you see | 
|         series.hasRendered = true; | 
|     }, | 
|      | 
|     /** | 
|      * Redraw the series after an update in the axes. | 
|      */ | 
|     redraw: function () { | 
|         var series = this, | 
|             chart = series.chart, | 
|             wasDirtyData = series.isDirtyData, // cache it here as it is set to false in render, but used after | 
|             group = series.group, | 
|             xAxis = series.xAxis, | 
|             yAxis = series.yAxis; | 
|   | 
|         // reposition on resize | 
|         if (group) { | 
|             if (chart.inverted) { | 
|                 group.attr({ | 
|                     width: chart.plotWidth, | 
|                     height: chart.plotHeight | 
|                 }); | 
|             } | 
|   | 
|             group.animate({ | 
|                 translateX: pick(xAxis && xAxis.left, chart.plotLeft), | 
|                 translateY: pick(yAxis && yAxis.top, chart.plotTop) | 
|             }); | 
|         } | 
|   | 
|         series.translate(); | 
|         series.setTooltipPoints(true); | 
|   | 
|         series.render(); | 
|         if (wasDirtyData) { | 
|             fireEvent(series, 'updatedData'); | 
|         } | 
|     }, | 
|   | 
|     /** | 
|      * Set the state of the graph | 
|      */ | 
|     setState: function (state) { | 
|         var series = this, | 
|             options = series.options, | 
|             graph = series.graph, | 
|             graphNeg = series.graphNeg, | 
|             stateOptions = options.states, | 
|             lineWidth = options.lineWidth, | 
|             attribs; | 
|   | 
|         state = state || NORMAL_STATE; | 
|   | 
|         if (series.state !== state) { | 
|             series.state = state; | 
|   | 
|             if (stateOptions[state] && stateOptions[state].enabled === false) { | 
|                 return; | 
|             } | 
|   | 
|             if (state) { | 
|                 lineWidth = stateOptions[state].lineWidth || lineWidth + 1; | 
|             } | 
|   | 
|             if (graph && !graph.dashstyle) { // hover is turned off for dashed lines in VML | 
|                 attribs = { | 
|                     'stroke-width': lineWidth | 
|                 }; | 
|                 // use attr because animate will cause any other animation on the graph to stop | 
|                 graph.attr(attribs); | 
|                 if (graphNeg) { | 
|                     graphNeg.attr(attribs); | 
|                 } | 
|             } | 
|         } | 
|     }, | 
|   | 
|     /** | 
|      * Set the visibility of the graph | 
|      * | 
|      * @param vis {Boolean} True to show the series, false to hide. If UNDEFINED, | 
|      *        the visibility is toggled. | 
|      */ | 
|     setVisible: function (vis, redraw) { | 
|         var series = this, | 
|             chart = series.chart, | 
|             legendItem = series.legendItem, | 
|             showOrHide, | 
|             ignoreHiddenSeries = chart.options.chart.ignoreHiddenSeries, | 
|             oldVisibility = series.visible; | 
|   | 
|         // if called without an argument, toggle visibility | 
|         series.visible = vis = series.userOptions.visible = vis === UNDEFINED ? !oldVisibility : vis; | 
|         showOrHide = vis ? 'show' : 'hide'; | 
|   | 
|         // show or hide elements | 
|         each(['group', 'dataLabelsGroup', 'markerGroup', 'tracker'], function (key) { | 
|             if (series[key]) { | 
|                 series[key][showOrHide](); | 
|             } | 
|         }); | 
|   | 
|          | 
|         // hide tooltip (#1361) | 
|         if (chart.hoverSeries === series) { | 
|             series.onMouseOut(); | 
|         } | 
|   | 
|   | 
|         if (legendItem) { | 
|             chart.legend.colorizeItem(series, vis); | 
|         } | 
|   | 
|   | 
|         // rescale or adapt to resized chart | 
|         series.isDirty = true; | 
|         // in a stack, all other series are affected | 
|         if (series.options.stacking) { | 
|             each(chart.series, function (otherSeries) { | 
|                 if (otherSeries.options.stacking && otherSeries.visible) { | 
|                     otherSeries.isDirty = true; | 
|                 } | 
|             }); | 
|         } | 
|   | 
|         // show or hide linked series | 
|         each(series.linkedSeries, function (otherSeries) { | 
|             otherSeries.setVisible(vis, false); | 
|         }); | 
|   | 
|         if (ignoreHiddenSeries) { | 
|             chart.isDirtyBox = true; | 
|         } | 
|         if (redraw !== false) { | 
|             chart.redraw(); | 
|         } | 
|   | 
|         fireEvent(series, showOrHide); | 
|     }, | 
|   | 
|     /** | 
|      * Show the graph | 
|      */ | 
|     show: function () { | 
|         this.setVisible(true); | 
|     }, | 
|   | 
|     /** | 
|      * Hide the graph | 
|      */ | 
|     hide: function () { | 
|         this.setVisible(false); | 
|     }, | 
|   | 
|   | 
|     /** | 
|      * Set the selected state of the graph | 
|      * | 
|      * @param selected {Boolean} True to select the series, false to unselect. If | 
|      *        UNDEFINED, the selection state is toggled. | 
|      */ | 
|     select: function (selected) { | 
|         var series = this; | 
|         // if called without an argument, toggle | 
|         series.selected = selected = (selected === UNDEFINED) ? !series.selected : selected; | 
|   | 
|         if (series.checkbox) { | 
|             series.checkbox.checked = selected; | 
|         } | 
|   | 
|         fireEvent(series, selected ? 'select' : 'unselect'); | 
|     }, | 
|   | 
|     /** | 
|      * Draw the tracker object that sits above all data labels and markers to | 
|      * track mouse events on the graph or points. For the line type charts | 
|      * the tracker uses the same graphPath, but with a greater stroke width | 
|      * for better control. | 
|      */ | 
|     drawTracker: function () { | 
|         var series = this, | 
|             options = series.options, | 
|             trackByArea = options.trackByArea, | 
|             trackerPath = [].concat(trackByArea ? series.areaPath : series.graphPath), | 
|             trackerPathLength = trackerPath.length, | 
|             chart = series.chart, | 
|             pointer = chart.pointer, | 
|             renderer = chart.renderer, | 
|             snap = chart.options.tooltip.snap, | 
|             tracker = series.tracker, | 
|             cursor = options.cursor, | 
|             css = cursor && { cursor: cursor }, | 
|             singlePoints = series.singlePoints, | 
|             singlePoint, | 
|             i, | 
|             onMouseOver = function () { | 
|                 if (chart.hoverSeries !== series) { | 
|                     series.onMouseOver(); | 
|                 } | 
|             }; | 
|   | 
|         // Extend end points. A better way would be to use round linecaps, | 
|         // but those are not clickable in VML. | 
|         if (trackerPathLength && !trackByArea) { | 
|             i = trackerPathLength + 1; | 
|             while (i--) { | 
|                 if (trackerPath[i] === M) { // extend left side | 
|                     trackerPath.splice(i + 1, 0, trackerPath[i + 1] - snap, trackerPath[i + 2], L); | 
|                 } | 
|                 if ((i && trackerPath[i] === M) || i === trackerPathLength) { // extend right side | 
|                     trackerPath.splice(i, 0, L, trackerPath[i - 2] + snap, trackerPath[i - 1]); | 
|                 } | 
|             } | 
|         } | 
|   | 
|         // handle single points | 
|         for (i = 0; i < singlePoints.length; i++) { | 
|             singlePoint = singlePoints[i]; | 
|             trackerPath.push(M, singlePoint.plotX - snap, singlePoint.plotY, | 
|                 L, singlePoint.plotX + snap, singlePoint.plotY); | 
|         } | 
|          | 
|          | 
|   | 
|         // draw the tracker | 
|         if (tracker) { | 
|             tracker.attr({ d: trackerPath }); | 
|   | 
|         } else { // create | 
|                  | 
|             series.tracker = renderer.path(trackerPath) | 
|                 .attr({ | 
|                     'stroke-linejoin': 'round', // #1225 | 
|                     visibility: series.visible ? VISIBLE : HIDDEN, | 
|                     stroke: TRACKER_FILL, | 
|                     fill: trackByArea ? TRACKER_FILL : NONE, | 
|                     'stroke-width' : options.lineWidth + (trackByArea ? 0 : 2 * snap), | 
|                     zIndex: 2 | 
|                 }) | 
|                 .add(series.group); | 
|                  | 
|             // The tracker is added to the series group, which is clipped, but is covered  | 
|             // by the marker group. So the marker group also needs to capture events. | 
|             each([series.tracker, series.markerGroup], function (tracker) { | 
|                 tracker.addClass(PREFIX + 'tracker') | 
|                     .on('mouseover', onMouseOver) | 
|                     .on('mouseout', function (e) { pointer.onTrackerMouseOut(e); }) | 
|                     .css(css); | 
|   | 
|                 if (hasTouch) { | 
|                     tracker.on('touchstart', onMouseOver); | 
|                 }  | 
|             }); | 
|         } | 
|   | 
|     } | 
|   | 
| }; // end Series prototype | 
|   | 
|   | 
| /** | 
|  * LineSeries object | 
|  */ | 
| var LineSeries = extendClass(Series); | 
| seriesTypes.line = LineSeries; | 
|   | 
| /** | 
|  * Set the default options for area | 
|  */ | 
| defaultPlotOptions.area = merge(defaultSeriesOptions, { | 
|     threshold: 0 | 
|     // trackByArea: false, | 
|     // lineColor: null, // overrides color, but lets fillColor be unaltered | 
|     // fillOpacity: 0.75, | 
|     // fillColor: null | 
| }); | 
|   | 
| /** | 
|  * AreaSeries object | 
|  */ | 
| var AreaSeries = extendClass(Series, { | 
|     type: 'area', | 
|      | 
|     /** | 
|      * For stacks, don't split segments on null values. Instead, draw null values with  | 
|      * no marker. Also insert dummy points for any X position that exists in other series | 
|      * in the stack. | 
|      */  | 
|     getSegments: function () { | 
|         var segments = [], | 
|             segment = [], | 
|             keys = [], | 
|             xAxis = this.xAxis, | 
|             yAxis = this.yAxis, | 
|             stack = yAxis.stacks[this.stackKey], | 
|             pointMap = {}, | 
|             plotX, | 
|             plotY, | 
|             points = this.points, | 
|             connectNulls = this.options.connectNulls, | 
|             val, | 
|             i, | 
|             x; | 
|   | 
|         if (this.options.stacking && !this.cropped) { // cropped causes artefacts in Stock, and perf issue | 
|             // Create a map where we can quickly look up the points by their X value. | 
|             for (i = 0; i < points.length; i++) { | 
|                 pointMap[points[i].x] = points[i]; | 
|             } | 
|   | 
|             // Sort the keys (#1651) | 
|             for (x in stack) { | 
|                 keys.push(+x); | 
|             } | 
|             keys.sort(function (a, b) { | 
|                 return a - b; | 
|             }); | 
|   | 
|             each(keys, function (x) { | 
|                 if (connectNulls && (!pointMap[x] || pointMap[x].y === null)) { // #1836 | 
|                     return; | 
|   | 
|                 // The point exists, push it to the segment | 
|                 } else if (pointMap[x]) { | 
|                     segment.push(pointMap[x]); | 
|   | 
|                 // There is no point for this X value in this series, so we  | 
|                 // insert a dummy point in order for the areas to be drawn | 
|                 // correctly. | 
|                 } else { | 
|                     plotX = xAxis.translate(x); | 
|                     val = stack[x].percent ? (stack[x].total ? stack[x].cum * 100 / stack[x].total : 0) : stack[x].cum; // #1991 | 
|                     plotY = yAxis.toPixels(val, true); | 
|                     segment.push({  | 
|                         y: null,  | 
|                         plotX: plotX, | 
|                         clientX: plotX,  | 
|                         plotY: plotY,  | 
|                         yBottom: plotY, | 
|                         onMouseOver: noop | 
|                     }); | 
|                 } | 
|             }); | 
|   | 
|             if (segment.length) { | 
|                 segments.push(segment); | 
|             } | 
|   | 
|         } else { | 
|             Series.prototype.getSegments.call(this); | 
|             segments = this.segments; | 
|         } | 
|   | 
|         this.segments = segments; | 
|     }, | 
|      | 
|     /** | 
|      * Extend the base Series getSegmentPath method by adding the path for the area. | 
|      * This path is pushed to the series.areaPath property. | 
|      */ | 
|     getSegmentPath: function (segment) { | 
|          | 
|         var segmentPath = Series.prototype.getSegmentPath.call(this, segment), // call base method | 
|             areaSegmentPath = [].concat(segmentPath), // work on a copy for the area path | 
|             i, | 
|             options = this.options, | 
|             segLength = segmentPath.length, | 
|             translatedThreshold = this.yAxis.getThreshold(options.threshold), // #2181 | 
|             yBottom; | 
|          | 
|         if (segLength === 3) { // for animation from 1 to two points | 
|             areaSegmentPath.push(L, segmentPath[1], segmentPath[2]); | 
|         } | 
|         if (options.stacking && !this.closedStacks) { | 
|              | 
|             // Follow stack back. Todo: implement areaspline. A general solution could be to  | 
|             // reverse the entire graphPath of the previous series, though may be hard with | 
|             // splines and with series with different extremes | 
|             for (i = segment.length - 1; i >= 0; i--) { | 
|   | 
|                 yBottom = pick(segment[i].yBottom, translatedThreshold); | 
|              | 
|                 // step line? | 
|                 if (i < segment.length - 1 && options.step) { | 
|                     areaSegmentPath.push(segment[i + 1].plotX, yBottom); | 
|                 } | 
|                  | 
|                 areaSegmentPath.push(segment[i].plotX, yBottom); | 
|             } | 
|   | 
|         } else { // follow zero line back | 
|             this.closeSegment(areaSegmentPath, segment, translatedThreshold); | 
|         } | 
|         this.areaPath = this.areaPath.concat(areaSegmentPath); | 
|         return segmentPath; | 
|     }, | 
|      | 
|     /** | 
|      * Extendable method to close the segment path of an area. This is overridden in polar  | 
|      * charts. | 
|      */ | 
|     closeSegment: function (path, segment, translatedThreshold) { | 
|         path.push( | 
|             L, | 
|             segment[segment.length - 1].plotX, | 
|             translatedThreshold, | 
|             L, | 
|             segment[0].plotX, | 
|             translatedThreshold | 
|         ); | 
|     }, | 
|      | 
|     /** | 
|      * Draw the graph and the underlying area. This method calls the Series base | 
|      * function and adds the area. The areaPath is calculated in the getSegmentPath | 
|      * method called from Series.prototype.drawGraph. | 
|      */ | 
|     drawGraph: function () { | 
|          | 
|         // Define or reset areaPath | 
|         this.areaPath = []; | 
|          | 
|         // Call the base method | 
|         Series.prototype.drawGraph.apply(this); | 
|          | 
|         // Define local variables | 
|         var series = this, | 
|             areaPath = this.areaPath, | 
|             options = this.options, | 
|             negativeColor = options.negativeColor, | 
|             negativeFillColor = options.negativeFillColor, | 
|             props = [['area', this.color, options.fillColor]]; // area name, main color, fill color | 
|          | 
|         if (negativeColor || negativeFillColor) { | 
|             props.push(['areaNeg', negativeColor, negativeFillColor]); | 
|         } | 
|          | 
|         each(props, function (prop) { | 
|             var areaKey = prop[0], | 
|                 area = series[areaKey]; | 
|                  | 
|             // Create or update the area | 
|             if (area) { // update | 
|                 area.animate({ d: areaPath }); | 
|      | 
|             } else { // create | 
|                 series[areaKey] = series.chart.renderer.path(areaPath) | 
|                     .attr({ | 
|                         fill: pick( | 
|                             prop[2], | 
|                             Color(prop[1]).setOpacity(pick(options.fillOpacity, 0.75)).get() | 
|                         ), | 
|                         zIndex: 0 // #1069 | 
|                     }).add(series.group); | 
|             } | 
|         }); | 
|     }, | 
|      | 
|     /** | 
|      * Get the series' symbol in the legend | 
|      *  | 
|      * @param {Object} legend The legend object | 
|      * @param {Object} item The series (this) or point | 
|      */ | 
|     drawLegendSymbol: function (legend, item) { | 
|          | 
|         item.legendSymbol = this.chart.renderer.rect( | 
|             0, | 
|             legend.baseline - 11, | 
|             legend.options.symbolWidth, | 
|             12, | 
|             2 | 
|         ).attr({ | 
|             zIndex: 3 | 
|         }).add(item.legendGroup);         | 
|          | 
|     } | 
| }); | 
|   | 
| seriesTypes.area = AreaSeries;/** | 
|  * Set the default options for spline | 
|  */ | 
| defaultPlotOptions.spline = merge(defaultSeriesOptions); | 
|   | 
| /** | 
|  * SplineSeries object | 
|  */ | 
| var SplineSeries = extendClass(Series, { | 
|     type: 'spline', | 
|   | 
|     /** | 
|      * Get the spline segment from a given point's previous neighbour to the given point | 
|      */ | 
|     getPointSpline: function (segment, point, i) { | 
|         var smoothing = 1.5, // 1 means control points midway between points, 2 means 1/3 from the point, 3 is 1/4 etc | 
|             denom = smoothing + 1, | 
|             plotX = point.plotX, | 
|             plotY = point.plotY, | 
|             lastPoint = segment[i - 1], | 
|             nextPoint = segment[i + 1], | 
|             leftContX, | 
|             leftContY, | 
|             rightContX, | 
|             rightContY, | 
|             ret; | 
|   | 
|         // find control points | 
|         if (lastPoint && nextPoint) { | 
|          | 
|             var lastX = lastPoint.plotX, | 
|                 lastY = lastPoint.plotY, | 
|                 nextX = nextPoint.plotX, | 
|                 nextY = nextPoint.plotY, | 
|                 correction; | 
|   | 
|             leftContX = (smoothing * plotX + lastX) / denom; | 
|             leftContY = (smoothing * plotY + lastY) / denom; | 
|             rightContX = (smoothing * plotX + nextX) / denom; | 
|             rightContY = (smoothing * plotY + nextY) / denom; | 
|   | 
|             // have the two control points make a straight line through main point | 
|             correction = ((rightContY - leftContY) * (rightContX - plotX)) / | 
|                 (rightContX - leftContX) + plotY - rightContY; | 
|   | 
|             leftContY += correction; | 
|             rightContY += correction; | 
|   | 
|             // to prevent false extremes, check that control points are between | 
|             // neighbouring points' y values | 
|             if (leftContY > lastY && leftContY > plotY) { | 
|                 leftContY = mathMax(lastY, plotY); | 
|                 rightContY = 2 * plotY - leftContY; // mirror of left control point | 
|             } else if (leftContY < lastY && leftContY < plotY) { | 
|                 leftContY = mathMin(lastY, plotY); | 
|                 rightContY = 2 * plotY - leftContY; | 
|             } | 
|             if (rightContY > nextY && rightContY > plotY) { | 
|                 rightContY = mathMax(nextY, plotY); | 
|                 leftContY = 2 * plotY - rightContY; | 
|             } else if (rightContY < nextY && rightContY < plotY) { | 
|                 rightContY = mathMin(nextY, plotY); | 
|                 leftContY = 2 * plotY - rightContY; | 
|             } | 
|   | 
|             // record for drawing in next point | 
|             point.rightContX = rightContX; | 
|             point.rightContY = rightContY; | 
|   | 
|         } | 
|          | 
|         // Visualize control points for debugging | 
|         /* | 
|         if (leftContX) { | 
|             this.chart.renderer.circle(leftContX + this.chart.plotLeft, leftContY + this.chart.plotTop, 2) | 
|                 .attr({ | 
|                     stroke: 'red', | 
|                     'stroke-width': 1, | 
|                     fill: 'none' | 
|                 }) | 
|                 .add(); | 
|             this.chart.renderer.path(['M', leftContX + this.chart.plotLeft, leftContY + this.chart.plotTop, | 
|                 'L', plotX + this.chart.plotLeft, plotY + this.chart.plotTop]) | 
|                 .attr({ | 
|                     stroke: 'red', | 
|                     'stroke-width': 1 | 
|                 }) | 
|                 .add(); | 
|             this.chart.renderer.circle(rightContX + this.chart.plotLeft, rightContY + this.chart.plotTop, 2) | 
|                 .attr({ | 
|                     stroke: 'green', | 
|                     'stroke-width': 1, | 
|                     fill: 'none' | 
|                 }) | 
|                 .add(); | 
|             this.chart.renderer.path(['M', rightContX + this.chart.plotLeft, rightContY + this.chart.plotTop, | 
|                 'L', plotX + this.chart.plotLeft, plotY + this.chart.plotTop]) | 
|                 .attr({ | 
|                     stroke: 'green', | 
|                     'stroke-width': 1 | 
|                 }) | 
|                 .add(); | 
|         } | 
|         */ | 
|   | 
|         // moveTo or lineTo | 
|         if (!i) { | 
|             ret = [M, plotX, plotY]; | 
|         } else { // curve from last point to this | 
|             ret = [ | 
|                 'C', | 
|                 lastPoint.rightContX || lastPoint.plotX, | 
|                 lastPoint.rightContY || lastPoint.plotY, | 
|                 leftContX || plotX, | 
|                 leftContY || plotY, | 
|                 plotX, | 
|                 plotY | 
|             ]; | 
|             lastPoint.rightContX = lastPoint.rightContY = null; // reset for updating series later | 
|         } | 
|         return ret; | 
|     } | 
| }); | 
| seriesTypes.spline = SplineSeries; | 
|   | 
| /** | 
|  * Set the default options for areaspline | 
|  */ | 
| defaultPlotOptions.areaspline = merge(defaultPlotOptions.area); | 
|   | 
| /** | 
|  * AreaSplineSeries object | 
|  */ | 
| var areaProto = AreaSeries.prototype, | 
|     AreaSplineSeries = extendClass(SplineSeries, { | 
|         type: 'areaspline', | 
|         closedStacks: true, // instead of following the previous graph back, follow the threshold back | 
|          | 
|         // Mix in methods from the area series | 
|         getSegmentPath: areaProto.getSegmentPath, | 
|         closeSegment: areaProto.closeSegment, | 
|         drawGraph: areaProto.drawGraph, | 
|         drawLegendSymbol: areaProto.drawLegendSymbol | 
|     }); | 
| seriesTypes.areaspline = AreaSplineSeries; | 
|   | 
| /** | 
|  * Set the default options for column | 
|  */ | 
| defaultPlotOptions.column = merge(defaultSeriesOptions, { | 
|     borderColor: '#FFFFFF', | 
|     borderWidth: 1, | 
|     borderRadius: 0, | 
|     //colorByPoint: undefined, | 
|     groupPadding: 0.2, | 
|     //grouping: true, | 
|     marker: null, // point options are specified in the base options | 
|     pointPadding: 0.1, | 
|     //pointWidth: null, | 
|     minPointLength: 0, | 
|     cropThreshold: 50, // when there are more points, they will not animate out of the chart on xAxis.setExtremes | 
|     pointRange: null, // null means auto, meaning 1 in a categorized axis and least distance between points if not categories | 
|     states: { | 
|         hover: { | 
|             brightness: 0.1, | 
|             shadow: false | 
|         }, | 
|         select: { | 
|             color: '#C0C0C0', | 
|             borderColor: '#000000', | 
|             shadow: false | 
|         } | 
|     }, | 
|     dataLabels: { | 
|         align: null, // auto | 
|         verticalAlign: null, // auto | 
|         y: null | 
|     }, | 
|     stickyTracking: false, | 
|     threshold: 0 | 
| }); | 
|   | 
| /** | 
|  * ColumnSeries object | 
|  */ | 
| var ColumnSeries = extendClass(Series, { | 
|     type: 'column', | 
|     pointAttrToOptions: { // mapping between SVG attributes and the corresponding options | 
|         stroke: 'borderColor', | 
|         'stroke-width': 'borderWidth', | 
|         fill: 'color', | 
|         r: 'borderRadius' | 
|     }, | 
|     cropShoulder: 0, | 
|     trackerGroups: ['group', 'dataLabelsGroup'], | 
|     negStacks: true, // use separate negative stacks, unlike area stacks where a negative  | 
|         // point is substracted from previous (#1910) | 
|      | 
|     /** | 
|      * Initialize the series | 
|      */ | 
|     init: function () { | 
|         Series.prototype.init.apply(this, arguments); | 
|   | 
|         var series = this, | 
|             chart = series.chart; | 
|   | 
|         // if the series is added dynamically, force redraw of other | 
|         // series affected by a new column | 
|         if (chart.hasRendered) { | 
|             each(chart.series, function (otherSeries) { | 
|                 if (otherSeries.type === series.type) { | 
|                     otherSeries.isDirty = true; | 
|                 } | 
|             }); | 
|         } | 
|     }, | 
|   | 
|     /** | 
|      * Return the width and x offset of the columns adjusted for grouping, groupPadding, pointPadding, | 
|      * pointWidth etc.  | 
|      */ | 
|     getColumnMetrics: function () { | 
|   | 
|         var series = this, | 
|             options = series.options, | 
|             xAxis = series.xAxis, | 
|             yAxis = series.yAxis, | 
|             reversedXAxis = xAxis.reversed, | 
|             stackKey, | 
|             stackGroups = {}, | 
|             columnIndex, | 
|             columnCount = 0; | 
|   | 
|         // Get the total number of column type series. | 
|         // This is called on every series. Consider moving this logic to a | 
|         // chart.orderStacks() function and call it on init, addSeries and removeSeries | 
|         if (options.grouping === false) { | 
|             columnCount = 1; | 
|         } else { | 
|             each(series.chart.series, function (otherSeries) { | 
|                 var otherOptions = otherSeries.options, | 
|                     otherYAxis = otherSeries.yAxis; | 
|                 if (otherSeries.type === series.type && otherSeries.visible && | 
|                         yAxis.len === otherYAxis.len && yAxis.pos === otherYAxis.pos) {  // #642, #2086 | 
|                     if (otherOptions.stacking) { | 
|                         stackKey = otherSeries.stackKey; | 
|                         if (stackGroups[stackKey] === UNDEFINED) { | 
|                             stackGroups[stackKey] = columnCount++; | 
|                         } | 
|                         columnIndex = stackGroups[stackKey]; | 
|                     } else if (otherOptions.grouping !== false) { // #1162 | 
|                         columnIndex = columnCount++; | 
|                     } | 
|                     otherSeries.columnIndex = columnIndex; | 
|                 } | 
|             }); | 
|         } | 
|   | 
|         var categoryWidth = mathMin( | 
|                 mathAbs(xAxis.transA) * (xAxis.ordinalSlope || options.pointRange || xAxis.closestPointRange || 1),  | 
|                 xAxis.len // #1535 | 
|             ), | 
|             groupPadding = categoryWidth * options.groupPadding, | 
|             groupWidth = categoryWidth - 2 * groupPadding, | 
|             pointOffsetWidth = groupWidth / columnCount, | 
|             optionPointWidth = options.pointWidth, | 
|             pointPadding = defined(optionPointWidth) ? (pointOffsetWidth - optionPointWidth) / 2 : | 
|                 pointOffsetWidth * options.pointPadding, | 
|             pointWidth = pick(optionPointWidth, pointOffsetWidth - 2 * pointPadding), // exact point width, used in polar charts | 
|             colIndex = (reversedXAxis ?  | 
|                 columnCount - (series.columnIndex || 0) : // #1251 | 
|                 series.columnIndex) || 0, | 
|             pointXOffset = pointPadding + (groupPadding + colIndex * | 
|                 pointOffsetWidth - (categoryWidth / 2)) * | 
|                 (reversedXAxis ? -1 : 1); | 
|   | 
|         // Save it for reading in linked series (Error bars particularly) | 
|         return (series.columnMetrics = {  | 
|             width: pointWidth,  | 
|             offset: pointXOffset  | 
|         }); | 
|              | 
|     }, | 
|   | 
|     /** | 
|      * Translate each point to the plot area coordinate system and find shape positions | 
|      */ | 
|     translate: function () { | 
|         var series = this, | 
|             chart = series.chart, | 
|             options = series.options, | 
|             borderWidth = options.borderWidth, | 
|             yAxis = series.yAxis, | 
|             threshold = options.threshold, | 
|             translatedThreshold = series.translatedThreshold = yAxis.getThreshold(threshold), | 
|             minPointLength = pick(options.minPointLength, 5), | 
|             metrics = series.getColumnMetrics(), | 
|             pointWidth = metrics.width, | 
|             seriesBarW = series.barW = mathCeil(mathMax(pointWidth, 1 + 2 * borderWidth)), // rounded and postprocessed for border width | 
|             pointXOffset = series.pointXOffset = metrics.offset, | 
|             xCrisp = -(borderWidth % 2 ? 0.5 : 0), | 
|             yCrisp = borderWidth % 2 ? 0.5 : 1; | 
|   | 
|         if (chart.renderer.isVML && chart.inverted) { | 
|             yCrisp += 1; | 
|         } | 
|   | 
|         Series.prototype.translate.apply(series); | 
|   | 
|         // record the new values | 
|         each(series.points, function (point) { | 
|             var yBottom = pick(point.yBottom, translatedThreshold), | 
|                 plotY = mathMin(mathMax(-999 - yBottom, point.plotY), yAxis.len + 999 + yBottom), // Don't draw too far outside plot area (#1303, #2241) | 
|                 barX = point.plotX + pointXOffset, | 
|                 barW = seriesBarW, | 
|                 barY = mathMin(plotY, yBottom), | 
|                 right, | 
|                 bottom, | 
|                 fromTop, | 
|                 fromLeft, | 
|                 barH = mathMax(plotY, yBottom) - barY; | 
|   | 
|             // Handle options.minPointLength | 
|             if (mathAbs(barH) < minPointLength) { | 
|                 if (minPointLength) { | 
|                     barH = minPointLength; | 
|                     barY = | 
|                         mathRound(mathAbs(barY - translatedThreshold) > minPointLength ? // stacked | 
|                             yBottom - minPointLength : // keep position | 
|                             translatedThreshold - (yAxis.translate(point.y, 0, 1, 0, 1) <= translatedThreshold ? minPointLength : 0)); // use exact yAxis.translation (#1485) | 
|                 } | 
|             } | 
|   | 
|             // Cache for access in polar | 
|             point.barX = barX; | 
|             point.pointWidth = pointWidth; | 
|   | 
|   | 
|             // Round off to obtain crisp edges | 
|             fromLeft = mathAbs(barX) < 0.5; | 
|             right = mathRound(barX + barW) + xCrisp; | 
|             barX = mathRound(barX) + xCrisp; | 
|             barW = right - barX; | 
|   | 
|             fromTop = mathAbs(barY) < 0.5; | 
|             bottom = mathRound(barY + barH) + yCrisp; | 
|             barY = mathRound(barY) + yCrisp; | 
|             barH = bottom - barY; | 
|   | 
|             // Top and left edges are exceptions | 
|             if (fromLeft) { | 
|                 barX += 1; | 
|                 barW -= 1; | 
|             } | 
|             if (fromTop) { | 
|                 barY -= 1; | 
|                 barH += 1; | 
|             } | 
|   | 
|             // Register shape type and arguments to be used in drawPoints | 
|             point.shapeType = 'rect'; | 
|             point.shapeArgs = { | 
|                 x: barX, | 
|                 y: barY, | 
|                 width: barW, | 
|                 height: barH | 
|             }; | 
|         }); | 
|   | 
|     }, | 
|   | 
|     getSymbol: noop, | 
|      | 
|     /** | 
|      * Use a solid rectangle like the area series types | 
|      */ | 
|     drawLegendSymbol: AreaSeries.prototype.drawLegendSymbol, | 
|      | 
|      | 
|     /** | 
|      * Columns have no graph | 
|      */ | 
|     drawGraph: noop, | 
|   | 
|     /** | 
|      * Draw the columns. For bars, the series.group is rotated, so the same coordinates | 
|      * apply for columns and bars. This method is inherited by scatter series. | 
|      * | 
|      */ | 
|     drawPoints: function () { | 
|         var series = this, | 
|             options = series.options, | 
|             renderer = series.chart.renderer, | 
|             shapeArgs; | 
|   | 
|   | 
|         // draw the columns | 
|         each(series.points, function (point) { | 
|             var plotY = point.plotY, | 
|                 graphic = point.graphic; | 
|   | 
|             if (plotY !== UNDEFINED && !isNaN(plotY) && point.y !== null) { | 
|                 shapeArgs = point.shapeArgs; | 
|                  | 
|                 if (graphic) { // update | 
|                     stop(graphic); | 
|                     graphic.animate(merge(shapeArgs)); | 
|   | 
|                 } else { | 
|                     point.graphic = graphic = renderer[point.shapeType](shapeArgs) | 
|                         .attr(point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE]) | 
|                         .add(series.group) | 
|                         .shadow(options.shadow, null, options.stacking && !options.borderRadius); | 
|                 } | 
|   | 
|             } else if (graphic) { | 
|                 point.graphic = graphic.destroy(); // #1269 | 
|             } | 
|         }); | 
|     }, | 
|   | 
|     /** | 
|      * Add tracking event listener to the series group, so the point graphics | 
|      * themselves act as trackers | 
|      */ | 
|     drawTracker: function () { | 
|         var series = this, | 
|             chart = series.chart, | 
|             pointer = chart.pointer, | 
|             cursor = series.options.cursor, | 
|             css = cursor && { cursor: cursor }, | 
|             onMouseOver = function (e) { | 
|                 var target = e.target, | 
|                     point; | 
|   | 
|                 if (chart.hoverSeries !== series) { | 
|                     series.onMouseOver(); | 
|                 } | 
|                 while (target && !point) { | 
|                     point = target.point; | 
|                     target = target.parentNode; | 
|                 } | 
|                 if (point !== UNDEFINED && point !== chart.hoverPoint) { // undefined on graph in scatterchart | 
|                     point.onMouseOver(e); | 
|                 } | 
|             }; | 
|   | 
|         // Add reference to the point | 
|         each(series.points, function (point) { | 
|             if (point.graphic) { | 
|                 point.graphic.element.point = point; | 
|             } | 
|             if (point.dataLabel) { | 
|                 point.dataLabel.element.point = point; | 
|             } | 
|         }); | 
|   | 
|         // Add the event listeners, we need to do this only once | 
|         if (!series._hasTracking) { | 
|             each(series.trackerGroups, function (key) { | 
|                 if (series[key]) { // we don't always have dataLabelsGroup | 
|                     series[key] | 
|                         .addClass(PREFIX + 'tracker') | 
|                         .on('mouseover', onMouseOver) | 
|                         .on('mouseout', function (e) { pointer.onTrackerMouseOut(e); }) | 
|                         .css(css); | 
|                     if (hasTouch) { | 
|                         series[key].on('touchstart', onMouseOver); | 
|                     } | 
|                 } | 
|             }); | 
|             series._hasTracking = true; | 
|         } | 
|     }, | 
|      | 
|     /**  | 
|      * Override the basic data label alignment by adjusting for the position of the column | 
|      */ | 
|     alignDataLabel: function (point, dataLabel, options,  alignTo, isNew) { | 
|         var chart = this.chart, | 
|             inverted = chart.inverted, | 
|             dlBox = point.dlBox || point.shapeArgs, // data label box for alignment | 
|             below = point.below || (point.plotY > pick(this.translatedThreshold, chart.plotSizeY)), | 
|             inside = pick(options.inside, !!this.options.stacking); // draw it inside the box? | 
|          | 
|         // Align to the column itself, or the top of it | 
|         if (dlBox) { // Area range uses this method but not alignTo | 
|             alignTo = merge(dlBox); | 
|             if (inverted) { | 
|                 alignTo = { | 
|                     x: chart.plotWidth - alignTo.y - alignTo.height, | 
|                     y: chart.plotHeight - alignTo.x - alignTo.width, | 
|                     width: alignTo.height, | 
|                     height: alignTo.width | 
|                 }; | 
|             } | 
|                  | 
|             // Compute the alignment box | 
|             if (!inside) { | 
|                 if (inverted) { | 
|                     alignTo.x += below ? 0 : alignTo.width; | 
|                     alignTo.width = 0; | 
|                 } else { | 
|                     alignTo.y += below ? alignTo.height : 0; | 
|                     alignTo.height = 0; | 
|                 } | 
|             } | 
|         } | 
|          | 
|         // When alignment is undefined (typically columns and bars), display the individual  | 
|         // point below or above the point depending on the threshold | 
|         options.align = pick( | 
|             options.align,  | 
|             !inverted || inside ? 'center' : below ? 'right' : 'left' | 
|         ); | 
|         options.verticalAlign = pick( | 
|             options.verticalAlign,  | 
|             inverted || inside ? 'middle' : below ? 'top' : 'bottom' | 
|         ); | 
|          | 
|         // Call the parent method | 
|         Series.prototype.alignDataLabel.call(this, point, dataLabel, options, alignTo, isNew); | 
|     }, | 
|   | 
|   | 
|     /** | 
|      * Animate the column heights one by one from zero | 
|      * @param {Boolean} init Whether to initialize the animation or run it | 
|      */ | 
|     animate: function (init) { | 
|         var series = this, | 
|             yAxis = this.yAxis, | 
|             options = series.options, | 
|             inverted = this.chart.inverted, | 
|             attr = {}, | 
|             translatedThreshold; | 
|   | 
|         if (hasSVG) { // VML is too slow anyway | 
|             if (init) { | 
|                 attr.scaleY = 0.001; | 
|                 translatedThreshold = mathMin(yAxis.pos + yAxis.len, mathMax(yAxis.pos, yAxis.toPixels(options.threshold))); | 
|                 if (inverted) { | 
|                     attr.translateX = translatedThreshold - yAxis.len; | 
|                 } else { | 
|                     attr.translateY = translatedThreshold; | 
|                 } | 
|                 series.group.attr(attr); | 
|   | 
|             } else { // run the animation | 
|                  | 
|                 attr.scaleY = 1; | 
|                 attr[inverted ? 'translateX' : 'translateY'] = yAxis.pos; | 
|                 series.group.animate(attr, series.options.animation); | 
|   | 
|                 // delete this function to allow it only once | 
|                 series.animate = null; | 
|             } | 
|         } | 
|     }, | 
|      | 
|     /** | 
|      * Remove this series from the chart | 
|      */ | 
|     remove: function () { | 
|         var series = this, | 
|             chart = series.chart; | 
|   | 
|         // column and bar series affects other series of the same type | 
|         // as they are either stacked or grouped | 
|         if (chart.hasRendered) { | 
|             each(chart.series, function (otherSeries) { | 
|                 if (otherSeries.type === series.type) { | 
|                     otherSeries.isDirty = true; | 
|                 } | 
|             }); | 
|         } | 
|   | 
|         Series.prototype.remove.apply(series, arguments); | 
|     } | 
| }); | 
| seriesTypes.column = ColumnSeries; | 
| /** | 
|  * Set the default options for bar | 
|  */ | 
| defaultPlotOptions.bar = merge(defaultPlotOptions.column); | 
| /** | 
|  * The Bar series class | 
|  */ | 
| var BarSeries = extendClass(ColumnSeries, { | 
|     type: 'bar', | 
|     inverted: true | 
| }); | 
| seriesTypes.bar = BarSeries; | 
|   | 
| /** | 
|  * Set the default options for scatter | 
|  */ | 
| defaultPlotOptions.scatter = merge(defaultSeriesOptions, { | 
|     lineWidth: 0, | 
|     tooltip: { | 
|         headerFormat: '<span style="font-size: 10px; color:{series.color}">{series.name}</span><br/>', | 
|         pointFormat: 'x: <b>{point.x}</b><br/>y: <b>{point.y}</b><br/>', | 
|         followPointer: true | 
|     }, | 
|     stickyTracking: false | 
| }); | 
|   | 
| /** | 
|  * The scatter series class | 
|  */ | 
| var ScatterSeries = extendClass(Series, { | 
|     type: 'scatter', | 
|     sorted: false, | 
|     requireSorting: false, | 
|     noSharedTooltip: true, | 
|     trackerGroups: ['markerGroup'], | 
|   | 
|     drawTracker: ColumnSeries.prototype.drawTracker, | 
|      | 
|     setTooltipPoints: noop | 
| }); | 
| seriesTypes.scatter = ScatterSeries; | 
|   | 
| /** | 
|  * Set the default options for pie | 
|  */ | 
| defaultPlotOptions.pie = merge(defaultSeriesOptions, { | 
|     borderColor: '#FFFFFF', | 
|     borderWidth: 1, | 
|     center: [null, null], | 
|     clip: false, | 
|     colorByPoint: true, // always true for pies | 
|     dataLabels: { | 
|         // align: null, | 
|         // connectorWidth: 1, | 
|         // connectorColor: point.color, | 
|         // connectorPadding: 5, | 
|         distance: 30, | 
|         enabled: true, | 
|         formatter: function () { | 
|             return this.point.name; | 
|         } | 
|         // softConnector: true, | 
|         //y: 0 | 
|     }, | 
|     ignoreHiddenPoint: true, | 
|     //innerSize: 0, | 
|     legendType: 'point', | 
|     marker: null, // point options are specified in the base options | 
|     size: null, | 
|     showInLegend: false, | 
|     slicedOffset: 10, | 
|     states: { | 
|         hover: { | 
|             brightness: 0.1, | 
|             shadow: false | 
|         } | 
|     }, | 
|     stickyTracking: false, | 
|     tooltip: { | 
|         followPointer: true | 
|     } | 
| }); | 
|   | 
| /** | 
|  * Extended point object for pies | 
|  */ | 
| var PiePoint = extendClass(Point, { | 
|     /** | 
|      * Initiate the pie slice | 
|      */ | 
|     init: function () { | 
|   | 
|         Point.prototype.init.apply(this, arguments); | 
|   | 
|         var point = this, | 
|             toggleSlice; | 
|   | 
|         // Disallow negative values (#1530) | 
|         if (point.y < 0) { | 
|             point.y = null; | 
|         } | 
|   | 
|         //visible: options.visible !== false, | 
|         extend(point, { | 
|             visible: point.visible !== false, | 
|             name: pick(point.name, 'Slice') | 
|         }); | 
|   | 
|         // add event listener for select | 
|         toggleSlice = function (e) { | 
|             point.slice(e.type === 'select'); | 
|         }; | 
|         addEvent(point, 'select', toggleSlice); | 
|         addEvent(point, 'unselect', toggleSlice); | 
|   | 
|         return point; | 
|     }, | 
|   | 
|     /** | 
|      * Toggle the visibility of the pie slice | 
|      * @param {Boolean} vis Whether to show the slice or not. If undefined, the | 
|      *    visibility is toggled | 
|      */ | 
|     setVisible: function (vis) { | 
|         var point = this, | 
|             series = point.series, | 
|             chart = series.chart, | 
|             method; | 
|   | 
|         // if called without an argument, toggle visibility | 
|         point.visible = point.options.visible = vis = vis === UNDEFINED ? !point.visible : vis; | 
|         series.options.data[inArray(point, series.data)] = point.options; // update userOptions.data | 
|          | 
|         method = vis ? 'show' : 'hide'; | 
|   | 
|         // Show and hide associated elements | 
|         each(['graphic', 'dataLabel', 'connector', 'shadowGroup'], function (key) { | 
|             if (point[key]) { | 
|                 point[key][method](); | 
|             } | 
|         }); | 
|   | 
|         if (point.legendItem) { | 
|             chart.legend.colorizeItem(point, vis); | 
|         } | 
|          | 
|         // Handle ignore hidden slices | 
|         if (!series.isDirty && series.options.ignoreHiddenPoint) { | 
|             series.isDirty = true; | 
|             chart.redraw(); | 
|         } | 
|     }, | 
|   | 
|     /** | 
|      * Set or toggle whether the slice is cut out from the pie | 
|      * @param {Boolean} sliced When undefined, the slice state is toggled | 
|      * @param {Boolean} redraw Whether to redraw the chart. True by default. | 
|      */ | 
|     slice: function (sliced, redraw, animation) { | 
|         var point = this, | 
|             series = point.series, | 
|             chart = series.chart, | 
|             translation; | 
|   | 
|         setAnimation(animation, chart); | 
|   | 
|         // redraw is true by default | 
|         redraw = pick(redraw, true); | 
|   | 
|         // if called without an argument, toggle | 
|         point.sliced = point.options.sliced = sliced = defined(sliced) ? sliced : !point.sliced; | 
|         series.options.data[inArray(point, series.data)] = point.options; // update userOptions.data | 
|   | 
|         translation = sliced ? point.slicedTranslation : { | 
|             translateX: 0, | 
|             translateY: 0 | 
|         }; | 
|   | 
|         point.graphic.animate(translation); | 
|          | 
|         if (point.shadowGroup) { | 
|             point.shadowGroup.animate(translation); | 
|         } | 
|   | 
|     } | 
| }); | 
|   | 
| /** | 
|  * The Pie series class | 
|  */ | 
| var PieSeries = { | 
|     type: 'pie', | 
|     isCartesian: false, | 
|     pointClass: PiePoint, | 
|     requireSorting: false, | 
|     noSharedTooltip: true, | 
|     trackerGroups: ['group', 'dataLabelsGroup'], | 
|     pointAttrToOptions: { // mapping between SVG attributes and the corresponding options | 
|         stroke: 'borderColor', | 
|         'stroke-width': 'borderWidth', | 
|         fill: 'color' | 
|     }, | 
|   | 
|     /** | 
|      * Pies have one color each point | 
|      */ | 
|     getColor: noop, | 
|   | 
|     /** | 
|      * Animate the pies in | 
|      */ | 
|     animate: function (init) { | 
|         var series = this, | 
|             points = series.points, | 
|             startAngleRad = series.startAngleRad; | 
|   | 
|         if (!init) { | 
|             each(points, function (point) { | 
|                 var graphic = point.graphic, | 
|                     args = point.shapeArgs; | 
|   | 
|                 if (graphic) { | 
|                     // start values | 
|                     graphic.attr({ | 
|                         r: series.center[3] / 2, // animate from inner radius (#779) | 
|                         start: startAngleRad, | 
|                         end: startAngleRad | 
|                     }); | 
|   | 
|                     // animate | 
|                     graphic.animate({ | 
|                         r: args.r, | 
|                         start: args.start, | 
|                         end: args.end | 
|                     }, series.options.animation); | 
|                 } | 
|             }); | 
|   | 
|             // delete this function to allow it only once | 
|             series.animate = null; | 
|         } | 
|     }, | 
|   | 
|     /** | 
|      * Extend the basic setData method by running processData and generatePoints immediately, | 
|      * in order to access the points from the legend. | 
|      */ | 
|     setData: function (data, redraw) { | 
|         Series.prototype.setData.call(this, data, false); | 
|         this.processData(); | 
|         this.generatePoints(); | 
|         if (pick(redraw, true)) { | 
|             this.chart.redraw(); | 
|         }  | 
|     }, | 
|   | 
|     /** | 
|      * Extend the generatePoints method by adding total and percentage properties to each point | 
|      */ | 
|     generatePoints: function () { | 
|         var i, | 
|             total = 0, | 
|             points, | 
|             len, | 
|             point, | 
|             ignoreHiddenPoint = this.options.ignoreHiddenPoint; | 
|   | 
|         Series.prototype.generatePoints.call(this); | 
|   | 
|         // Populate local vars | 
|         points = this.points; | 
|         len = points.length; | 
|          | 
|         // Get the total sum | 
|         for (i = 0; i < len; i++) { | 
|             point = points[i]; | 
|             total += (ignoreHiddenPoint && !point.visible) ? 0 : point.y; | 
|         } | 
|         this.total = total; | 
|   | 
|         // Set each point's properties | 
|         for (i = 0; i < len; i++) { | 
|             point = points[i]; | 
|             point.percentage = total > 0 ? (point.y / total) * 100 : 0; | 
|             point.total = total; | 
|         } | 
|          | 
|     }, | 
|      | 
|     /** | 
|      * Get the center of the pie based on the size and center options relative to the   | 
|      * plot area. Borrowed by the polar and gauge series types. | 
|      */ | 
|     getCenter: function () { | 
|          | 
|         var options = this.options, | 
|             chart = this.chart, | 
|             slicingRoom = 2 * (options.slicedOffset || 0), | 
|             handleSlicingRoom, | 
|             plotWidth = chart.plotWidth - 2 * slicingRoom, | 
|             plotHeight = chart.plotHeight - 2 * slicingRoom, | 
|             centerOption = options.center, | 
|             positions = [pick(centerOption[0], '50%'), pick(centerOption[1], '50%'), options.size || '100%', options.innerSize || 0], | 
|             smallestSize = mathMin(plotWidth, plotHeight), | 
|             isPercent; | 
|          | 
|         return map(positions, function (length, i) { | 
|             isPercent = /%$/.test(length); | 
|             handleSlicingRoom = i < 2 || (i === 2 && isPercent); | 
|             return (isPercent ? | 
|                 // i == 0: centerX, relative to width | 
|                 // i == 1: centerY, relative to height | 
|                 // i == 2: size, relative to smallestSize | 
|                 // i == 4: innerSize, relative to smallestSize | 
|                 [plotWidth, plotHeight, smallestSize, smallestSize][i] * | 
|                     pInt(length) / 100 : | 
|                 length) + (handleSlicingRoom ? slicingRoom : 0); | 
|         }); | 
|     }, | 
|      | 
|     /** | 
|      * Do translation for pie slices | 
|      */ | 
|     translate: function (positions) { | 
|         this.generatePoints(); | 
|          | 
|         var series = this, | 
|             cumulative = 0, | 
|             precision = 1000, // issue #172 | 
|             options = series.options, | 
|             slicedOffset = options.slicedOffset, | 
|             connectorOffset = slicedOffset + options.borderWidth, | 
|             start, | 
|             end, | 
|             angle, | 
|             startAngle = options.startAngle || 0, | 
|             startAngleRad = series.startAngleRad = mathPI / 180 * (startAngle - 90), | 
|             endAngleRad = series.endAngleRad = mathPI / 180 * ((options.endAngle || (startAngle + 360)) - 90), // docs | 
|             circ = endAngleRad - startAngleRad, //2 * mathPI, | 
|             points = series.points, | 
|             radiusX, // the x component of the radius vector for a given point | 
|             radiusY, | 
|             labelDistance = options.dataLabels.distance, | 
|             ignoreHiddenPoint = options.ignoreHiddenPoint, | 
|             i, | 
|             len = points.length, | 
|             point; | 
|   | 
|         // Get positions - either an integer or a percentage string must be given. | 
|         // If positions are passed as a parameter, we're in a recursive loop for adjusting | 
|         // space for data labels. | 
|         if (!positions) { | 
|             series.center = positions = series.getCenter(); | 
|         } | 
|   | 
|         // utility for getting the x value from a given y, used for anticollision logic in data labels | 
|         series.getX = function (y, left) { | 
|   | 
|             angle = math.asin((y - positions[1]) / (positions[2] / 2 + labelDistance)); | 
|   | 
|             return positions[0] + | 
|                 (left ? -1 : 1) * | 
|                 (mathCos(angle) * (positions[2] / 2 + labelDistance)); | 
|         }; | 
|   | 
|         // Calculate the geometry for each point | 
|         for (i = 0; i < len; i++) { | 
|              | 
|             point = points[i]; | 
|              | 
|             // set start and end angle | 
|             start = startAngleRad + (cumulative * circ); | 
|             if (!ignoreHiddenPoint || point.visible) { | 
|                 cumulative += point.percentage / 100; | 
|             } | 
|             end = startAngleRad + (cumulative * circ); | 
|   | 
|             // set the shape | 
|             point.shapeType = 'arc'; | 
|             point.shapeArgs = { | 
|                 x: positions[0], | 
|                 y: positions[1], | 
|                 r: positions[2] / 2, | 
|                 innerR: positions[3] / 2, | 
|                 start: mathRound(start * precision) / precision, | 
|                 end: mathRound(end * precision) / precision | 
|             }; | 
|   | 
|             // center for the sliced out slice | 
|             angle = (end + start) / 2; | 
|             if (angle > 0.75 * circ) { | 
|                 angle -= 2 * mathPI; | 
|             } | 
|             point.slicedTranslation = { | 
|                 translateX: mathRound(mathCos(angle) * slicedOffset), | 
|                 translateY: mathRound(mathSin(angle) * slicedOffset) | 
|             }; | 
|   | 
|             // set the anchor point for tooltips | 
|             radiusX = mathCos(angle) * positions[2] / 2; | 
|             radiusY = mathSin(angle) * positions[2] / 2; | 
|             point.tooltipPos = [ | 
|                 positions[0] + radiusX * 0.7, | 
|                 positions[1] + radiusY * 0.7 | 
|             ]; | 
|              | 
|             point.half = angle < -mathPI / 2 || angle > mathPI / 2 ? 1 : 0; | 
|             point.angle = angle; | 
|   | 
|             // set the anchor point for data labels | 
|             connectorOffset = mathMin(connectorOffset, labelDistance / 2); // #1678 | 
|             point.labelPos = [ | 
|                 positions[0] + radiusX + mathCos(angle) * labelDistance, // first break of connector | 
|                 positions[1] + radiusY + mathSin(angle) * labelDistance, // a/a | 
|                 positions[0] + radiusX + mathCos(angle) * connectorOffset, // second break, right outside pie | 
|                 positions[1] + radiusY + mathSin(angle) * connectorOffset, // a/a | 
|                 positions[0] + radiusX, // landing point for connector | 
|                 positions[1] + radiusY, // a/a | 
|                 labelDistance < 0 ? // alignment | 
|                     'center' : | 
|                     point.half ? 'right' : 'left', // alignment | 
|                 angle // center angle | 
|             ]; | 
|   | 
|         } | 
|     }, | 
|   | 
|     setTooltipPoints: noop, | 
|     drawGraph: null, | 
|   | 
|     /** | 
|      * Draw the data points | 
|      */ | 
|     drawPoints: function () { | 
|         var series = this, | 
|             chart = series.chart, | 
|             renderer = chart.renderer, | 
|             groupTranslation, | 
|             //center, | 
|             graphic, | 
|             //group, | 
|             shadow = series.options.shadow, | 
|             shadowGroup, | 
|             shapeArgs; | 
|   | 
|         if (shadow && !series.shadowGroup) { | 
|             series.shadowGroup = renderer.g('shadow') | 
|                 .add(series.group); | 
|         } | 
|   | 
|         // draw the slices | 
|         each(series.points, function (point) { | 
|             graphic = point.graphic; | 
|             shapeArgs = point.shapeArgs; | 
|             shadowGroup = point.shadowGroup; | 
|   | 
|             // put the shadow behind all points | 
|             if (shadow && !shadowGroup) { | 
|                 shadowGroup = point.shadowGroup = renderer.g('shadow') | 
|                     .add(series.shadowGroup); | 
|             } | 
|   | 
|             // if the point is sliced, use special translation, else use plot area traslation | 
|             groupTranslation = point.sliced ? point.slicedTranslation : { | 
|                 translateX: 0, | 
|                 translateY: 0 | 
|             }; | 
|   | 
|             //group.translate(groupTranslation[0], groupTranslation[1]); | 
|             if (shadowGroup) { | 
|                 shadowGroup.attr(groupTranslation); | 
|             } | 
|   | 
|             // draw the slice | 
|             if (graphic) { | 
|                 graphic.animate(extend(shapeArgs, groupTranslation)); | 
|             } else { | 
|                 point.graphic = graphic = renderer.arc(shapeArgs) | 
|                     .setRadialReference(series.center) | 
|                     .attr( | 
|                         point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE] | 
|                     ) | 
|                     .attr({ 'stroke-linejoin': 'round' }) | 
|                     .attr(groupTranslation) | 
|                     .add(series.group) | 
|                     .shadow(shadow, shadowGroup);     | 
|             } | 
|   | 
|             // detect point specific visibility | 
|             if (point.visible === false) { | 
|                 point.setVisible(false); | 
|             } | 
|   | 
|         }); | 
|   | 
|     }, | 
|   | 
|     /** | 
|      * Utility for sorting data labels | 
|      */ | 
|     sortByAngle: function (points, sign) { | 
|         points.sort(function (a, b) { | 
|             return a.angle !== undefined && (b.angle - a.angle) * sign; | 
|         }); | 
|     }, | 
|   | 
|     /** | 
|      * Override the base drawDataLabels method by pie specific functionality | 
|      */ | 
|     drawDataLabels: function () { | 
|         var series = this, | 
|             data = series.data, | 
|             point, | 
|             chart = series.chart, | 
|             options = series.options.dataLabels, | 
|             connectorPadding = pick(options.connectorPadding, 10), | 
|             connectorWidth = pick(options.connectorWidth, 1), | 
|             plotWidth = chart.plotWidth, | 
|             plotHeight = chart.plotHeight, | 
|             connector, | 
|             connectorPath, | 
|             softConnector = pick(options.softConnector, true), | 
|             distanceOption = options.distance, | 
|             seriesCenter = series.center, | 
|             radius = seriesCenter[2] / 2, | 
|             centerY = seriesCenter[1], | 
|             outside = distanceOption > 0, | 
|             dataLabel, | 
|             dataLabelWidth, | 
|             labelPos, | 
|             labelHeight, | 
|             halves = [// divide the points into right and left halves for anti collision | 
|                 [], // right | 
|                 []  // left | 
|             ], | 
|             x, | 
|             y, | 
|             visibility, | 
|             rankArr, | 
|             i, | 
|             j, | 
|             overflow = [0, 0, 0, 0], // top, right, bottom, left | 
|             sort = function (a, b) { | 
|                 return b.y - a.y; | 
|             }; | 
|   | 
|         // get out if not enabled | 
|         if (!series.visible || (!options.enabled && !series._hasPointLabels)) { | 
|             return; | 
|         } | 
|   | 
|         // run parent method | 
|         Series.prototype.drawDataLabels.apply(series); | 
|   | 
|         // arrange points for detection collision | 
|         each(data, function (point) { | 
|             if (point.dataLabel) { // it may have been cancelled in the base method (#407) | 
|                 halves[point.half].push(point); | 
|             } | 
|         }); | 
|   | 
|         // assume equal label heights | 
|         i = 0; | 
|         while (!labelHeight && data[i]) { // #1569 | 
|             labelHeight = data[i] && data[i].dataLabel && (data[i].dataLabel.getBBox().height || 21); // 21 is for #968 | 
|             i++; | 
|         } | 
|   | 
|         /* Loop over the points in each half, starting from the top and bottom | 
|          * of the pie to detect overlapping labels. | 
|          */ | 
|         i = 2; | 
|         while (i--) { | 
|   | 
|             var slots = [], | 
|                 slotsLength, | 
|                 usedSlots = [], | 
|                 points = halves[i], | 
|                 pos, | 
|                 length = points.length, | 
|                 slotIndex; | 
|                  | 
|             // Sort by angle | 
|             series.sortByAngle(points, i - 0.5); | 
|   | 
|             // Only do anti-collision when we are outside the pie and have connectors (#856) | 
|             if (distanceOption > 0) { | 
|                  | 
|                 // build the slots | 
|                 for (pos = centerY - radius - distanceOption; pos <= centerY + radius + distanceOption; pos += labelHeight) { | 
|                     slots.push(pos); | 
|                      | 
|                     // visualize the slot | 
|                     /* | 
|                     var slotX = series.getX(pos, i) + chart.plotLeft - (i ? 100 : 0), | 
|                         slotY = pos + chart.plotTop; | 
|                     if (!isNaN(slotX)) { | 
|                         chart.renderer.rect(slotX, slotY - 7, 100, labelHeight, 1) | 
|                             .attr({ | 
|                                 'stroke-width': 1, | 
|                                 stroke: 'silver' | 
|                             }) | 
|                             .add(); | 
|                         chart.renderer.text('Slot '+ (slots.length - 1), slotX, slotY + 4) | 
|                             .attr({ | 
|                                 fill: 'silver' | 
|                             }).add(); | 
|                     } | 
|                     */ | 
|                 } | 
|                 slotsLength = slots.length; | 
|      | 
|                 // if there are more values than available slots, remove lowest values | 
|                 if (length > slotsLength) { | 
|                     // create an array for sorting and ranking the points within each quarter | 
|                     rankArr = [].concat(points); | 
|                     rankArr.sort(sort); | 
|                     j = length; | 
|                     while (j--) { | 
|                         rankArr[j].rank = j; | 
|                     } | 
|                     j = length; | 
|                     while (j--) { | 
|                         if (points[j].rank >= slotsLength) { | 
|                             points.splice(j, 1); | 
|                         } | 
|                     } | 
|                     length = points.length; | 
|                 } | 
|      | 
|                 // The label goes to the nearest open slot, but not closer to the edge than | 
|                 // the label's index. | 
|                 for (j = 0; j < length; j++) { | 
|      | 
|                     point = points[j]; | 
|                     labelPos = point.labelPos; | 
|      | 
|                     var closest = 9999, | 
|                         distance, | 
|                         slotI; | 
|      | 
|                     // find the closest slot index | 
|                     for (slotI = 0; slotI < slotsLength; slotI++) { | 
|                         distance = mathAbs(slots[slotI] - labelPos[1]); | 
|                         if (distance < closest) { | 
|                             closest = distance; | 
|                             slotIndex = slotI; | 
|                         } | 
|                     } | 
|      | 
|                     // if that slot index is closer to the edges of the slots, move it | 
|                     // to the closest appropriate slot | 
|                     if (slotIndex < j && slots[j] !== null) { // cluster at the top | 
|                         slotIndex = j; | 
|                     } else if (slotsLength  < length - j + slotIndex && slots[j] !== null) { // cluster at the bottom | 
|                         slotIndex = slotsLength - length + j; | 
|                         while (slots[slotIndex] === null) { // make sure it is not taken | 
|                             slotIndex++; | 
|                         } | 
|                     } else { | 
|                         // Slot is taken, find next free slot below. In the next run, the next slice will find the | 
|                         // slot above these, because it is the closest one | 
|                         while (slots[slotIndex] === null) { // make sure it is not taken | 
|                             slotIndex++; | 
|                         } | 
|                     } | 
|      | 
|                     usedSlots.push({ i: slotIndex, y: slots[slotIndex] }); | 
|                     slots[slotIndex] = null; // mark as taken | 
|                 } | 
|                 // sort them in order to fill in from the top | 
|                 usedSlots.sort(sort); | 
|             } | 
|   | 
|             // now the used slots are sorted, fill them up sequentially | 
|             for (j = 0; j < length; j++) { | 
|                  | 
|                 var slot, naturalY; | 
|   | 
|                 point = points[j]; | 
|                 labelPos = point.labelPos; | 
|                 dataLabel = point.dataLabel; | 
|                 visibility = point.visible === false ? HIDDEN : VISIBLE; | 
|                 naturalY = labelPos[1]; | 
|                  | 
|                 if (distanceOption > 0) { | 
|                     slot = usedSlots.pop(); | 
|                     slotIndex = slot.i; | 
|   | 
|                     // if the slot next to currrent slot is free, the y value is allowed | 
|                     // to fall back to the natural position | 
|                     y = slot.y; | 
|                     if ((naturalY > y && slots[slotIndex + 1] !== null) || | 
|                             (naturalY < y &&  slots[slotIndex - 1] !== null)) { | 
|                         y = naturalY; | 
|                     } | 
|                      | 
|                 } else { | 
|                     y = naturalY; | 
|                 } | 
|   | 
|                 // get the x - use the natural x position for first and last slot, to prevent the top | 
|                 // and botton slice connectors from touching each other on either side | 
|                 x = options.justify ?  | 
|                     seriesCenter[0] + (i ? -1 : 1) * (radius + distanceOption) : | 
|                     series.getX(slotIndex === 0 || slotIndex === slots.length - 1 ? naturalY : y, i); | 
|                  | 
|              | 
|                 // Record the placement and visibility | 
|                 dataLabel._attr = { | 
|                     visibility: visibility, | 
|                     align: labelPos[6] | 
|                 }; | 
|                 dataLabel._pos = { | 
|                     x: x + options.x + | 
|                         ({ left: connectorPadding, right: -connectorPadding }[labelPos[6]] || 0), | 
|                     y: y + options.y - 10 // 10 is for the baseline (label vs text) | 
|                 }; | 
|                 dataLabel.connX = x; | 
|                 dataLabel.connY = y; | 
|                  | 
|                          | 
|                 // Detect overflowing data labels | 
|                 if (this.options.size === null) { | 
|                     dataLabelWidth = dataLabel.width; | 
|                     // Overflow left | 
|                     if (x - dataLabelWidth < connectorPadding) { | 
|                         overflow[3] = mathMax(mathRound(dataLabelWidth - x + connectorPadding), overflow[3]); | 
|                          | 
|                     // Overflow right | 
|                     } else if (x + dataLabelWidth > plotWidth - connectorPadding) { | 
|                         overflow[1] = mathMax(mathRound(x + dataLabelWidth - plotWidth + connectorPadding), overflow[1]); | 
|                     } | 
|                      | 
|                     // Overflow top | 
|                     if (y - labelHeight / 2 < 0) { | 
|                         overflow[0] = mathMax(mathRound(-y + labelHeight / 2), overflow[0]); | 
|                          | 
|                     // Overflow left | 
|                     } else if (y + labelHeight / 2 > plotHeight) { | 
|                         overflow[2] = mathMax(mathRound(y + labelHeight / 2 - plotHeight), overflow[2]); | 
|                     } | 
|                 } | 
|             } // for each point | 
|         } // for each half | 
|          | 
|         // Do not apply the final placement and draw the connectors until we have verified | 
|         // that labels are not spilling over.  | 
|         if (arrayMax(overflow) === 0 || this.verifyDataLabelOverflow(overflow)) { | 
|              | 
|             // Place the labels in the final position | 
|             this.placeDataLabels(); | 
|              | 
|             // Draw the connectors | 
|             if (outside && connectorWidth) { | 
|                 each(this.points, function (point) { | 
|                     connector = point.connector; | 
|                     labelPos = point.labelPos; | 
|                     dataLabel = point.dataLabel; | 
|                      | 
|                     if (dataLabel && dataLabel._pos) { | 
|                         visibility = dataLabel._attr.visibility; | 
|                         x = dataLabel.connX; | 
|                         y = dataLabel.connY; | 
|                         connectorPath = softConnector ? [ | 
|                             M, | 
|                             x + (labelPos[6] === 'left' ? 5 : -5), y, // end of the string at the label | 
|                             'C', | 
|                             x, y, // first break, next to the label | 
|                             2 * labelPos[2] - labelPos[4], 2 * labelPos[3] - labelPos[5], | 
|                             labelPos[2], labelPos[3], // second break | 
|                             L, | 
|                             labelPos[4], labelPos[5] // base | 
|                         ] : [ | 
|                             M, | 
|                             x + (labelPos[6] === 'left' ? 5 : -5), y, // end of the string at the label | 
|                             L, | 
|                             labelPos[2], labelPos[3], // second break | 
|                             L, | 
|                             labelPos[4], labelPos[5] // base | 
|                         ]; | 
|          | 
|                         if (connector) { | 
|                             connector.animate({ d: connectorPath }); | 
|                             connector.attr('visibility', visibility); | 
|          | 
|                         } else { | 
|                             point.connector = connector = series.chart.renderer.path(connectorPath).attr({ | 
|                                 'stroke-width': connectorWidth, | 
|                                 stroke: options.connectorColor || point.color || '#606060', | 
|                                 visibility: visibility | 
|                             }) | 
|                             .add(series.group); | 
|                         } | 
|                     } else if (connector) { | 
|                         point.connector = connector.destroy(); | 
|                     } | 
|                 }); | 
|             }             | 
|         } | 
|     }, | 
|      | 
|     /** | 
|      * Verify whether the data labels are allowed to draw, or we should run more translation and data | 
|      * label positioning to keep them inside the plot area. Returns true when data labels are ready  | 
|      * to draw. | 
|      */ | 
|     verifyDataLabelOverflow: function (overflow) { | 
|          | 
|         var center = this.center, | 
|             options = this.options, | 
|             centerOption = options.center, | 
|             minSize = options.minSize || 80, | 
|             newSize = minSize, | 
|             ret; | 
|              | 
|         // Handle horizontal size and center | 
|         if (centerOption[0] !== null) { // Fixed center | 
|             newSize = mathMax(center[2] - mathMax(overflow[1], overflow[3]), minSize); | 
|              | 
|         } else { // Auto center | 
|             newSize = mathMax( | 
|                 center[2] - overflow[1] - overflow[3], // horizontal overflow                     | 
|                 minSize | 
|             ); | 
|             center[0] += (overflow[3] - overflow[1]) / 2; // horizontal center | 
|         } | 
|          | 
|         // Handle vertical size and center | 
|         if (centerOption[1] !== null) { // Fixed center | 
|             newSize = mathMax(mathMin(newSize, center[2] - mathMax(overflow[0], overflow[2])), minSize); | 
|              | 
|         } else { // Auto center | 
|             newSize = mathMax( | 
|                 mathMin( | 
|                     newSize,         | 
|                     center[2] - overflow[0] - overflow[2] // vertical overflow | 
|                 ), | 
|                 minSize | 
|             ); | 
|             center[1] += (overflow[0] - overflow[2]) / 2; // vertical center | 
|         } | 
|          | 
|         // If the size must be decreased, we need to run translate and drawDataLabels again | 
|         if (newSize < center[2]) { | 
|             center[2] = newSize; | 
|             this.translate(center); | 
|             each(this.points, function (point) { | 
|                 if (point.dataLabel) { | 
|                     point.dataLabel._pos = null; // reset | 
|                 } | 
|             }); | 
|             this.drawDataLabels(); | 
|              | 
|         // Else, return true to indicate that the pie and its labels is within the plot area | 
|         } else { | 
|             ret = true; | 
|         } | 
|         return ret; | 
|     }, | 
|      | 
|     /** | 
|      * Perform the final placement of the data labels after we have verified that they | 
|      * fall within the plot area. | 
|      */ | 
|     placeDataLabels: function () { | 
|         each(this.points, function (point) { | 
|             var dataLabel = point.dataLabel, | 
|                 _pos; | 
|              | 
|             if (dataLabel) { | 
|                 _pos = dataLabel._pos; | 
|                 if (_pos) { | 
|                     dataLabel.attr(dataLabel._attr);             | 
|                     dataLabel[dataLabel.moved ? 'animate' : 'attr'](_pos); | 
|                     dataLabel.moved = true; | 
|                 } else if (dataLabel) { | 
|                     dataLabel.attr({ y: -999 }); | 
|                 } | 
|             } | 
|         }); | 
|     }, | 
|      | 
|     alignDataLabel: noop, | 
|   | 
|     /** | 
|      * Draw point specific tracker objects. Inherit directly from column series. | 
|      */ | 
|     drawTracker: ColumnSeries.prototype.drawTracker, | 
|   | 
|     /** | 
|      * Use a simple symbol from column prototype | 
|      */ | 
|     drawLegendSymbol: AreaSeries.prototype.drawLegendSymbol, | 
|   | 
|     /** | 
|      * Pies don't have point marker symbols | 
|      */ | 
|     getSymbol: noop | 
|   | 
| }; | 
| PieSeries = extendClass(Series, PieSeries); | 
| seriesTypes.pie = PieSeries; | 
|   | 
|   | 
| // global variables | 
| extend(Highcharts, { | 
|      | 
|     // Constructors | 
|     Axis: Axis, | 
|     Chart: Chart, | 
|     Color: Color, | 
|     Legend: Legend, | 
|     Pointer: Pointer, | 
|     Point: Point, | 
|     Tick: Tick, | 
|     Tooltip: Tooltip, | 
|     Renderer: Renderer, | 
|     Series: Series, | 
|     SVGElement: SVGElement, | 
|     SVGRenderer: SVGRenderer, | 
|      | 
|     // Various | 
|     arrayMin: arrayMin, | 
|     arrayMax: arrayMax, | 
|     charts: charts, | 
|     dateFormat: dateFormat, | 
|     format: format, | 
|     pathAnim: pathAnim, | 
|     getOptions: getOptions, | 
|     hasBidiBug: hasBidiBug, | 
|     isTouchDevice: isTouchDevice, | 
|     numberFormat: numberFormat, | 
|     seriesTypes: seriesTypes, | 
|     setOptions: setOptions, | 
|     addEvent: addEvent, | 
|     removeEvent: removeEvent, | 
|     createElement: createElement, | 
|     discardElement: discardElement, | 
|     css: css, | 
|     each: each, | 
|     extend: extend, | 
|     map: map, | 
|     merge: merge, | 
|     pick: pick, | 
|     splat: splat, | 
|     extendClass: extendClass, | 
|     pInt: pInt, | 
|     wrap: wrap, | 
|     svg: hasSVG, | 
|     canvas: useCanVG, | 
|     vml: !hasSVG && !useCanVG, | 
|     product: PRODUCT, | 
|     version: VERSION | 
| }); | 
| }()); |