text: 
  /**
   * 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
   */
  var extend = Highcharts.extend = function (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. If the first argument is
   * true, the contents of the second object is copied into the first 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,
  		args = arguments,
  		len,
  		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]' &&
  							key !== 'renderTo' && typeof value.nodeType !== 'number') {
  						copy[key] = doCopy(copy[key] || {}, value);
  
  					// Primitives and arrays are copied over directly
  					} else {
  						copy[key] = original[key];
  					}
  				}
  			}
  			return copy;
  		};
  
  	// If first argument is true, copy into the existing object. Used in setOptions.
  	if (args[0] === true) {
  		ret = args[1];
  		args = Array.prototype.slice.call(args, 2);
  	}
  
  	// For each argument, extend the return
  	len = args.length;
  	for (i = 0; i < len; i++) {
  		ret = doCopy(ret, args[i]);
  	}
  
  	return ret;
  }
  
  /**
   * 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 obj && 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,
  		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];
  }
  
  /**
   * Set a timeout if the delay is given, otherwise perform the function synchronously
   * @param   {Function} fn      The function to perform
   * @param   {Number}   delay   Delay in milliseconds
   * @param   {Ojbect}   context The context
   * @returns {Nubmer}           An identifier for the timeout
   */
  function syncTimeout(fn, delay, context) {
  	if (delay) {
  		return setTimeout(fn, delay, context);
  	}
  	fn.call(0, context);
  }
  
  
  /**
   * Return the first value that is defined. Like MooTools' $.pick.
   */
  var pick = Highcharts.pick = function () {
  	var args = arguments,
  		i,
  		arg,
  		length = args.length;
  	for (i = 0; i < length; i++) {
  		arg = args[i];
  		if (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 (isMS && !hasSVG) { // #2686
  		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;
  }
  
  /**
   * Pad a string to a given length by adding 0 to the beginning
   * @param {Number} number
   * @param {Number} length
   */
  function pad(number, length) {
  	return new Array((length || 2) + 1 - String(number).length).join(0) + number;
  }
  
  /**
   * Return a length based on either the integer value, or a percentage of a base.
   */
  function relativeLength(value, base) {
  	return (/%$/).test(value) ? base * parseFloat(value) / 100 : parseFloat(value);
  }
  
  /**
   * 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.
   */
  var wrap = Highcharts.wrap = function (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);
  	};
  };
  
  
  function getTZOffset(timestamp) {
  	return ((getTimezoneOffset && getTimezoneOffset(timestamp)) || timezoneOffset || 0) * 60000;
  }
  
  /**
   * 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 defaultOptions.lang.invalidDate || '';
  	}
  	format = pick(format, '%Y-%m-%d %H:%M:%S');
  
  	var date = new Date(timestamp - getTZOffset(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
  			'w': day,
  
  			// 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
  			'k': hours, // Hours in 24h format, 0 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;
  		if (val !== null) {
  			val = Highcharts.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, allowDecimals, preventExceed) {
  	var normalized,
  		i,
  		retInterval = interval;
  
  	// 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 (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++) {
  		retInterval = multiples[i];
  		if ((preventExceed && retInterval * magnitude >= interval) || // only allow tick amounts smaller than natural
  				(!preventExceed && (normalized <= (multiples[i] + (multiples[i + 1] || multiples[i])) / 2))) {
  			break;
  		}
  	}
  
  	// multiply back to the correct magnitude
  	retInterval *= magnitude;
  
  	return retInterval;
  }
  
  
  /**
   * 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].safeI = i; // stable sort index
  	}
  
  	arr.sort(function (a, b) {
  		sortValue = sortFunction(a, b);
  		return sortValue === 0 ? a.safeI - b.safeI : sortValue;
  	});
  
  	// Remove index from items
  	for (i = 0; i < length; i++) {
  		delete arr[i].safeI; // 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 = '';
  }
  
  /**
   * Fix JS round off float errors
   * @param {Number} num
   */
  function correctFloat(num, prec) {
  	return parseFloat(
  		num.toPrecision(prec || 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) {
  	chart.renderer.globalAnimation = pick(animation, chart.animation);
  }
  
  /**
   * The time unit lookup
   */
  timeUnits = {
  	millisecond: 1,
  	second: 1000,
  	minute: 60000,
  	hour: 3600000,
  	day: 24 * 3600000,
  	week: 7 * 24 * 3600000,
  	month: 28 * 24 * 3600000,
  	year: 364 * 24 * 3600000
  };
  
  
  /**
   * 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
   */
  Highcharts.numberFormat = function (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 ?
  				Math.min((n.toString().split('.')[1] || '').length, 20) : // Preserve decimals. Not huge numbers (#3793).
  				(isNaN(decimals = Math.abs(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) : ''));
  };
  