You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by an...@apache.org on 2013/10/22 01:24:27 UTC

[7/9] first commit

http://git-wip-us.apache.org/repos/asf/cordova-registry-web/blob/f50cc931/attachments/highcharts/highcharts.src.js
----------------------------------------------------------------------
diff --git a/attachments/highcharts/highcharts.src.js b/attachments/highcharts/highcharts.src.js
new file mode 100755
index 0000000..0a661b0
--- /dev/null
+++ b/attachments/highcharts/highcharts.src.js
@@ -0,0 +1,10580 @@
+// ==ClosureCompiler==
+// @compilation_level SIMPLE_OPTIMIZATIONS
+
+/**
+ * @license Highcharts JS v2.1.2 (2011-01-12)
+ * 
+ * (c) 2009-2010 Torstein Hønsi
+ * 
+ * License: www.highcharts.com/license
+ */
+
+// JSLint options:
+/*jslint forin: true */
+/*global document, window, navigator, setInterval, clearInterval, clearTimeout, setTimeout, location, jQuery, $ */
+	
+(function() {
+// encapsulated variables
+var 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,
+	isIE = /msie/i.test(userAgent) && !win.opera,
+	docMode8 = doc.documentMode == 8,
+	isWebKit = /AppleWebKit/.test(userAgent),
+	hasSVG = win.SVGAngle || doc.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1"),
+	SVG_NS = 'http://www.w3.org/2000/svg',
+	hasTouch = 'ontouchstart' in doc.documentElement,
+	colorCounter,
+	symbolCounter,
+	symbolSizes = {},
+	idCounter = 0,
+	timeFactor = 1, // 1 = JavaScript time, 1000 = Unix time
+	garbageBin,
+	defaultOptions,
+	dateFormat, // function
+	globalAnimation,
+	pathAnim,
+	
+	
+	// some constants for frequently used strings
+	UNDEFINED,
+	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)
+	 * FF: 0.00000000001 (unlimited)
+	 * Chrome: 0.000001
+	 * Safari: 0.000001
+	 * Opera: 0.00000000001 (unlimited)
+	 */
+	TRACKER_FILL = 'rgba(192,192,192,'+ (hasSVG ? 0.000001 : 0.002) +')', // invisible but clickable
+	NORMAL_STATE = '',
+	HOVER_STATE = 'hover',
+	SELECT_STATE = 'select',
+	
+	// time methods, changed based on whether or not UTC is used
+	makeTime,
+	getMinutes,
+	getHours,
+	getDay,
+	getDate,
+	getMonth,
+	getFullYear,
+	setMinutes,
+	setHours,
+	setDate,
+	setMonth,
+	setFullYear,
+	
+	// check for a custom HighchartsAdapter defined prior to this file
+	globalAdapter = win.HighchartsAdapter,
+	adapter = globalAdapter || {}, 
+	
+	// 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.
+	each = adapter.each,
+	grep = adapter.grep,
+	map = adapter.map,
+	merge = adapter.merge,
+	hyphenate = adapter.hyphenate,
+	addEvent = adapter.addEvent,
+	removeEvent = adapter.removeEvent,
+	fireEvent = adapter.fireEvent,
+	animate = adapter.animate,
+	stop = adapter.stop,
+	
+	// lookup over the types and the associated classes
+	seriesTypes = {},
+	hoverChart;
+	
+/**
+ * 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) {
+	if (!a) {
+		a = {};
+	}
+	for (var n in b) {
+		a[n] = b[n];
+	}
+	return a;
+}
+
+/**
+ * Shortcut for parseInt
+ * @param {Object} s
+ */
+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 number
+ * @param {Object} n
+ */
+function isNumber(n) {
+	return typeof n == 'number';
+}
+
+/**
+ * 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) {
+	if (!obj || obj.constructor != Array) {
+		obj = [obj];
+	}
+	return 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;
+		}
+	}
+}
+/**
+ * Make a style string from a JS object
+ * @param {Object} style
+ */
+function serializeCSS(style) {
+	var s = '', 
+		key;
+	// serialize the declaration
+	for (key in style) {
+		s += hyphenate(key) +':'+ style[key] + ';';
+	}
+	return s;
+	
+}
+/**
+ * Set CSS on a give element
+ * @param {Object} el
+ * @param {Object} styles
+ */
+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;
+}
+
+/**
+ * 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);
+}
+
+/* 
+ * Define the adapter for frameworks. If an external adapter is not defined, 
+ * Highcharts reverts to the built-in jQuery adapter.
+ */
+if (globalAdapter && globalAdapter.init) {
+	globalAdapter.init();
+} 
+if (!globalAdapter && win.jQuery) {
+	var jQ = jQuery;
+	
+	/**
+	 * Utility for iterating over an array. Parameters are reversed compared to jQuery.
+	 * @param {Array} arr
+	 * @param {Function} fn
+	 */
+	each = function(arr, fn) {
+		for (var i = 0, len = arr.length; i < len; i++) {
+			if (fn.call(arr[i], arr[i], i, arr) === false) {
+				return i;
+			}
+		}
+	};
+	
+	/**
+	 * Filter an array
+	 */
+	grep = jQ.grep;
+	
+	/**
+	 * Map an array
+	 * @param {Array} arr
+	 * @param {Function} fn
+	 */
+	map = function(arr, fn){
+		//return jQuery.map(arr, fn);
+		var results = [];
+		for (var i = 0, len = arr.length; i < len; i++) {
+			results[i] = fn.call(arr[i], arr[i], i, arr);
+		}
+		return results;
+		
+	};
+	
+	/**
+	 * Deep merge two objects and return a third object
+	 */
+	merge = function(){
+		var args = arguments;
+		return jQ.extend(true, null, args[0], args[1], args[2], args[3]);
+	};
+	
+	/**
+	 * Convert a camelCase string to a hyphenated string
+	 * @param {String} str
+	 */
+	hyphenate = function (str) {
+		return str.replace(/([A-Z])/g, function(a, b){ return '-'+ b.toLowerCase(); });
+	};
+	
+	/**
+	 * 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){
+		jQ(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[func]) {
+			el[func] = function() {};
+		}
+		
+		jQ(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 = jQ.Event(type),
+			detachedType = 'detached'+ type;
+		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;	
+		}
+		
+		// trigger it
+		jQ(el).trigger(event);
+		
+		// attach the method
+		if (el[detachedType]) {
+			el[type] = el[detachedType];
+			el[detachedType] = null;
+		}
+		
+		if (defaultFunction && !event.isDefaultPrevented()) {
+			defaultFunction(event);
+		}	
+	};
+
+	/**
+	 * 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 = jQ(el);
+		if (params.d) {
+			el.toD = params.d; // keep the array form for paths, used in jQ.fx.step.d
+			params.d = 1; // because in jQuery, animating to an array has a different meaning
+		}
+		
+		$el.stop();
+		$el.animate(params, options);
+		
+	};
+	/**
+	 * Stop running animation
+	 */
+	stop = function (el) {
+		jQ(el).stop();
+	};
+	
+	
+	// extend jQuery
+	jQ.extend( jQ.easing, {
+		easeOutQuad: function (x, t, b, c, d) {
+			return -c *(t/=d)*(t-2) + b;
+		}
+	});
+					
+	// extend the animate function to allow SVG animations
+	var oldStepDefault = jQuery.fx.step._default, 
+		oldCur = jQuery.fx.prototype.cur;
+	
+	// do the step
+	jQ.fx.step._default = function(fx){
+		var elem = fx.elem;
+		if (elem.attr) { // is SVG element wrapper
+			elem.attr(fx.prop, fx.now);
+		} else {
+			oldStepDefault.apply(this, arguments);
+		}
+	};
+	// animate paths
+	jQ.fx.step.d = function(fx) {
+		var elem = fx.elem;
+			
+		
+		// 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) {
+			var 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));
+	
+	};
+	// get the current value
+	jQ.fx.prototype.cur = function() {
+		var elem = this.elem,
+			r;
+		if (elem.attr) { // is SVG element wrapper
+			r = elem.attr(this.prop);
+		} else {
+			r = oldCur.apply(this, arguments);
+		}
+		return r;
+	};
+}
+
+
+/**
+ * Add a global listener for mousemove events
+ */
+/*addEvent(doc, 'mousemove', function(e) {
+	if (globalMouseMove) {
+		globalMouseMove(e);
+	}
+});*/
+
+/**
+ * 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 = [].concat(end).splice(0, numParams).concat(end);
+			elem.shift = false; // 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;
+	}
+};
+
+/**
+ * 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;
+	
+	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 = useUTC ? 'getUTCMinutes' : 'getMinutes';
+	getHours = useUTC ? 'getUTCHours' : 'getHours';
+	getDay = useUTC ? 'getUTCDay' : 'getDay';
+	getDate = useUTC ? 'getUTCDate' : 'getDate';
+	getMonth = useUTC ? 'getUTCMonth' : 'getMonth';
+	getFullYear = useUTC ? 'getUTCFullYear' : 'getFullYear';
+	setMinutes = useUTC ? 'setUTCMinutes' : 'setMinutes';
+	setHours = useUTC ? 'setUTCHours' : 'setHours';
+	setDate = useUTC ? 'setUTCDate' : 'setDate';
+	setMonth = useUTC ? 'setUTCMonth' : 'setMonth';
+	setFullYear = useUTC ? 'setUTCFullYear' : 'setFullYear';
+		
+}
+
+/**
+ * Merge the default options with custom options and return the new options structure
+ * @param {Object} options The new custom options
+ */
+function setOptions(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;
+}
+
+/**
+ * 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 = '';
+}
+
+/* ****************************************************************************
+ * Handle the options                                                         *
+ *****************************************************************************/
+var 
+
+defaultLabelOptions = {
+	enabled: true,
+	// rotation: 0,
+	align: 'center',
+	x: 0,
+	y: 15,
+	/*formatter: function() {
+		return this.value;
+	},*/
+	style: {
+		color: '#666',
+		fontSize: '11px',
+		lineHeight: '14px'
+	}
+};
+
+defaultOptions = {
+	colors: ['#4572A7', '#AA4643', '#89A54E', '#80699B', '#3D96AE', 
+		'#DB843D', '#92A8CD', '#A47D7C', '#B5CA92'],
+	symbols: ['circle', 'diamond', 'square', 'triangle', 'triangle-down'],
+	lang: {
+		loading: 'Loading...',
+		months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 
+				'August', 'September', 'October', 'November', 'December'],
+		weekdays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
+		decimalPoint: '.',
+		resetZoom: 'Reset zoom',
+		resetZoomTitle: 'Reset zoom level 1:1',
+		thousandsSep: ','
+	},
+	global: {
+		useUTC: true
+	},
+	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,
+		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: ''
+	},
+	title: {
+		text: 'Chart title',
+		align: 'center',
+		// floating: false,
+		// margin: 15,
+		// x: 0,
+		// verticalAlign: 'top',
+		y: 15, // docs
+		style: {
+			color: '#3E576F',
+			fontSize: '16px'
+		}
+
+	},
+	subtitle: {
+		text: '',
+		align: 'center',
+		// floating: false
+		// x: 0,
+		// verticalAlign: 'top',
+		y: 30, // docs
+		style: {
+			color: '#6D869F'
+		}
+	},
+	
+	plotOptions: {
+		line: { // base series options
+			allowPointSelect: false,
+			showCheckbox: false,
+			animation: {
+				duration: 1000
+			},
+			//cursor: 'default',
+			//dashStyle: null,
+			//enableMouseTracking: true,
+			events: {},
+			lineWidth: 2,
+			shadow: true,
+			// stacking: null,
+			marker: { 
+				enabled: true,
+				//symbol: null, 
+				lineWidth: 0,
+				radius: 4,
+				lineColor: '#FFFFFF',
+				//fillColor: null, 
+				states: { // states for a single point
+					hover: {
+						//radius: base + 2
+					},
+					select: {
+						fillColor: '#FFFFFF',
+						lineColor: '#000000',
+						lineWidth: 2
+					}					
+				}
+			},
+			point: {
+				events: {}
+			},
+			dataLabels: merge(defaultLabelOptions, {
+				enabled: false,
+				y: -6,
+				formatter: function() {
+					return this.y;
+				}
+			}),
+			
+			//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
+			//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;
+		},
+		// lineHeight: 16, // docs: deprecated
+		borderWidth: 1,
+		borderColor: '#909090',
+		borderRadius: 5,
+		// margin: 10,
+		// reversed: false,
+		shadow: false,
+		// backgroundColor: null,
+		style: {
+			padding: '5px'
+		},
+		itemStyle: {
+			cursor: 'pointer',
+			color: '#3E576F'
+		},
+		itemHoverStyle: {
+			cursor: 'pointer',
+			color: '#000000'
+		},
+		itemHiddenStyle: {
+			color: '#C0C0C0'
+		},
+		itemCheckboxStyle: {
+			position: ABSOLUTE,
+			width: '13px', // for IE precision
+			height: '13px'
+		},
+		// itemWidth: undefined,
+		symbolWidth: 16,
+		symbolPadding: 5,
+		verticalAlign: 'bottom',
+		// width: undefined,
+		x: 0, // docs
+		y: 0 // docs
+	},
+	
+	loading: {
+		hideDuration: 100,
+		labelStyle: {
+			fontWeight: 'bold',
+			position: RELATIVE,
+			top: '1em'
+		},
+		showDuration: 100,
+		style: {
+			position: ABSOLUTE,
+			backgroundColor: 'white',
+			opacity: 0.5,
+			textAlign: 'center'
+		}
+	},
+	
+	tooltip: {
+		enabled: true,
+		//crosshairs: null,
+		backgroundColor: 'rgba(255, 255, 255, .85)',
+		borderWidth: 2,
+		borderRadius: 5,
+		//formatter: defaultFormatter,
+		shadow: true,
+		//shared: false,
+		snap: hasTouch ? 25 : 10,
+		style: {
+			color: '#333333',
+			fontSize: '12px',
+			padding: '5px',
+			whiteSpace: 'nowrap'
+		}
+	},
+	
+	toolbar: {
+		itemStyle: {
+			color: '#4572A7',
+			cursor: 'pointer'
+		}
+	},
+	
+	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: '10px'
+		}
+	}
+};
+
+// Axis defaults
+var defaultXAxisOptions =  {
+	// allowDecimals: null,
+	// alternateGridColor: null,
+	// categories: [],
+	dateTimeLabelFormats: {
+		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', // docs
+	// gridLineWidth: 0,
+	// reversed: false,
+	
+	labels: defaultLabelOptions,
+		// { step: null },
+	lineColor: '#C0D0E0',
+	lineWidth: 1,
+	//linkedTo: null,
+	max: null,
+	min: null,
+	minPadding: 0.01,
+	maxPadding: 0.01,
+	//maxZoom: 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: false,
+	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: '#6D869F',
+			//font: defaultFont.replace('normal', 'bold')
+			fontWeight: 'bold'
+		}
+		//x: 0,
+		//y: 0
+	},
+	type: 'linear' // linear or datetime
+},
+
+defaultYAxisOptions = merge(defaultXAxisOptions, {
+	endOnTick: true,
+	gridLineWidth: 1,
+	tickPixelInterval: 72,
+	showLastLabel: true,
+	labels: {
+		align: 'right',
+		x: -8,
+		y: 3
+	},
+	lineWidth: 0,
+	maxPadding: 0.05,
+	minPadding: 0.05,
+	startOnTick: true,
+	tickWidth: 0,
+	title: {
+		rotation: 270,
+		text: 'Y-values'
+	}
+}),
+
+defaultLeftAxisOptions = {
+	labels: {
+		align: 'right',
+		x: -8,
+		y: null // docs
+	},
+	title: {
+		rotation: 270
+	}
+},
+defaultRightAxisOptions = {
+	labels: {
+		align: 'left',
+		x: 8,
+		y: null // docs
+	},
+	title: {
+		rotation: 90
+	}
+},
+defaultBottomAxisOptions = { // horizontal axis
+	labels: {
+		align: 'center',
+		x: 0,
+		y: 14
+		// staggerLines: null
+	},
+	title: {
+		rotation: 0
+	}
+},
+defaultTopAxisOptions = merge(defaultBottomAxisOptions, {
+	labels: {
+		y: -5
+		// staggerLines: null
+	}
+});
+
+
+ 
+
+// Series defaults
+var defaultPlotOptions = defaultOptions.plotOptions, 
+	defaultSeriesOptions = defaultPlotOptions.line; 
+//defaultPlotOptions.line = merge(defaultSeriesOptions);
+defaultPlotOptions.spline = merge(defaultSeriesOptions);
+defaultPlotOptions.scatter = merge(defaultSeriesOptions, {
+	lineWidth: 0,
+	states: {
+		hover: {
+			lineWidth: 0
+		}
+	}
+});
+defaultPlotOptions.area = merge(defaultSeriesOptions, {
+	// threshold: 0,
+	// lineColor: null, // overrides color, but lets fillColor be unaltered
+	// fillOpacity: 0.75,
+	// fillColor: null
+
+});
+defaultPlotOptions.areaspline = merge(defaultPlotOptions.area);
+defaultPlotOptions.column = merge(defaultSeriesOptions, {
+	borderColor: '#FFFFFF',
+	borderWidth: 1,
+	borderRadius: 0,
+	//colorByPoint: undefined,
+	groupPadding: 0.2,
+	marker: null, // point options are specified in the base options
+	pointPadding: 0.1,
+	//pointWidth: null,
+	minPointLength: 0, 
+	states: {
+		hover: {
+			brightness: 0.1,
+			shadow: false
+		},
+		select: {
+			color: '#C0C0C0',
+			borderColor: '#000000',
+			shadow: false
+		}
+	}
+});
+defaultPlotOptions.bar = merge(defaultPlotOptions.column, {
+	dataLabels: {
+		align: 'left',
+		x: 5,
+		y: 0
+	}
+});
+defaultPlotOptions.pie = merge(defaultSeriesOptions, {
+	//dragType: '', // n/a
+	borderColor: '#FFFFFF',
+	borderWidth: 1,
+	center: ['50%', '50%'],
+	colorByPoint: true, // always true for pies
+	dataLabels: {
+		// align: null,
+		// connectorWidth: 1,
+		// connectorColor: '#606060',
+		// connectorPadding: 5,
+		distance: 30,
+		enabled: true,
+		formatter: function() {
+			return this.point.name;
+		},
+		y: 5
+	},
+	//innerSize: 0,
+	legendType: 'point',
+	marker: null, // point options are specified in the base options
+	size: '75%',
+	showInLegend: false,
+	slicedOffset: 10,
+	states: {
+		hover: {
+			brightness: 0.1,
+			shadow: false
+		}
+	}
+	
+});
+
+// set the default time methods
+setTimeMethods();
+
+
+/**
+ * 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;
+}
+
+
+/**
+ * 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;
+	
+	/**
+	 * Parse the input color to rgba array
+	 * @param {String} input
+	 */
+	function init(input) {
+		
+		// rgba
+		if((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))) {
+			rgba = [pInt(result[1]), pInt(result[2]), pInt(result[3]), parseFloat(result[4], 10)];
+		}
+
+		// hex
+		else if((result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(input))) {
+			rgba = [pInt(result[1],16), pInt(result[2],16), pInt(result[3],16), 1];
+		}
+	
+	}
+	/**
+	 * Return the color a specified format
+	 * @param {String} format
+	 */
+	function get(format) {
+		var ret;
+		
+		// it's NaN if gradient colors on a column chart
+		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 (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,
+		setOpacity: setOpacity
+	};
+};
+
+
+
+/**
+ * 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, c = isNaN(decimals = mathAbs(decimals)) ? 2 : decimals,
+		d = decPoint === undefined ? lang.decimalPoint : decPoint,
+		t = thousandsSep === undefined ? lang.thousandsSep : thousandsSep, s = n < 0 ? "-" : "",
+		i = pInt(n = mathAbs(+n || 0).toFixed(c)) + "", j = (j = i.length) > 3 ? j % 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) : "");
+}
+
+/**
+ * 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) {
+	function pad (number) {
+		return number.toString().replace(/^([0-9])$/, '0$1');
+	}
+	
+	if (!defined(timestamp) || isNaN(timestamp)) {
+		return 'Invalid date';
+	}
+	format = pick(format, '%Y-%m-%d %H:%M:%S');
+	
+	var date = new Date(timestamp * timeFactor),
+	
+		// 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,
+		langMonths = lang.months,
+		
+		// list all format keys
+		replacements = {
+
+			// 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)
+			
+			// Month
+			'b': langMonths[month].substr(0, 3), // Short month, like 'Jan'
+			'B': langMonths[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
+			
+		};
+
+
+	// do the replaces
+	for (var key in replacements) {
+		format = format.replace('%'+ key, replacements[key]);
+	}
+		
+	// Optionally capitalize the string and return
+	return capitalize ? format.substr(0, 1).toUpperCase() + format.substr(1) : format;
+};
+
+
+
+/**
+ * Loop up the node tree and add offsetWidth and offsetHeight to get the
+ * total page offset for a given element. Used by Opera and iOS on hover and
+ * all browsers on point click.
+ * 
+ * @param {Object} el
+ * 
+ */
+function getPosition (el) {
+	var p = { left: el.offsetLeft, top: el.offsetTop };
+	while ((el = el.offsetParent))	{
+		p.left += el.offsetLeft;
+		p.top += el.offsetTop;
+		if (el != doc.body && el != doc.documentElement) {
+			p.left -= el.scrollLeft;
+			p.top -= el.scrollTop;
+		}
+	}
+	return p;
+}
+
+
+/**
+ * A wrapper object for SVG elements 
+ */
+function SVGElement () {}
+
+SVGElement.prototype = {
+	/**
+	 * Initialize the SVG renderer
+	 * @param {Object} renderer
+	 * @param {String} nodeName
+	 */
+	init: function(renderer, nodeName) {
+		this.element = doc.createElementNS(SVG_NS, nodeName);
+		this.renderer = renderer;
+	},
+	/**
+	 * 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);
+		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 key, 
+			value, 
+			i, 
+			child,
+			element = this.element,
+			nodeName = element.nodeName,
+			renderer = this.renderer,
+			skipAttr,
+			shadows = this.shadows,
+			hasSetSymbolSize,
+			ret = this;
+			
+		// 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) || this[key] || 0;
+			
+			if (key != 'd' && key != 'visibility') { // 'd' is string in animation step
+				ret = parseFloat(ret);
+			}
+			
+		// setter
+		} else {
+		
+			for (key in hash) {
+				skipAttr = false; // reset
+				value = hash[key];
+				
+				// paths
+				if (key == 'd') {
+					if (value && value.join) { // join path
+						value = value.join(' ');
+					}					
+					if (/(NaN| {2}|^$)/.test(value)) {
+						value = 'M 0 0';
+					}
+					this.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);
+						}
+					}
+					
+					if (this.rotation) {
+						attr(element, 'transform', 'rotate('+ this.rotation +' '+ value +' '+
+							pInt(hash.y || attr(element, 'y')) +')');
+					}
+					
+				// 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;
+					
+				// translation and text rotation
+				} else if (key == 'translateX' || key == 'translateY' || key == 'rotation' || key == 'verticalAlign') {
+					this[key] = value;
+					this.updateTransform();
+					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';
+					if (value) {
+						value = value.toLowerCase()
+							.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]) * hash['stroke-width'];
+						}
+						value = value.join(',');
+					}	
+					
+				// special
+				} else if (key == 'isTracker') {
+					this[key] = value;
+				
+				// 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];
+				}
+				
+				
+				
+				// jQuery animate changes case
+				if (key == 'strokeWidth') {
+					key = 'stroke-width';
+				}
+				
+				// Chrome/Win < 6 bug (http://code.google.com/p/chromium/issues/detail?id=15461)				
+				if (isWebKit && key == 'stroke-width' && value === 0) {
+					value = 0.000001;
+				}
+				
+				// symbols
+				if (this.symbolName && /^(x|y|r|start|end|innerR)/.test(key)) {
+					
+					
+					if (!hasSetSymbolSize) {
+						this.symbolAttr(hash);
+						hasSetSymbolSize = true;
+					}
+					skipAttr = true;
+				}
+				
+				// let the shadow follow the main element
+				if (shadows && /^(width|height|visibility|x|y|d)$/.test(key)) {
+					i = shadows.length;
+					while (i--) {
+						attr(shadows[i], key, value);
+					}					
+				}
+				
+				/* trows errors in Chrome
+				if ((key == 'width' || key == 'height') && nodeName == 'rect' && value < 0) {
+					console.log(element);
+				}
+				*/
+				
+					
+				
+				if (key == 'text') {
+					// only one node allowed
+					this.textStr = value;
+					renderer.buildText(this);
+				} else if (!skipAttr) {
+					//element.setAttribute(key, value);
+					attr(element, key, value);
+				}
+				
+			}
+			
+		}
+		return ret;
+	},
+	
+	/**
+	 * 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;
+		
+		wrapper.x = pick(hash.x, wrapper.x);
+		wrapper.y = pick(hash.y, wrapper.y); // mootools animation bug needs parseFloat
+		wrapper.r = pick(hash.r, wrapper.r);
+		wrapper.start = pick(hash.start, wrapper.start);
+		wrapper.end = pick(hash.end, wrapper.end);
+		wrapper.width = pick(hash.width, wrapper.width);
+		wrapper.height = pick(hash.height, wrapper.height);
+		wrapper.innerR = pick(hash.innerR, wrapper.innerR);
+		
+		wrapper.attr({ 
+			d: wrapper.renderer.symbols[wrapper.symbolName](wrapper.x, wrapper.y, wrapper.r, {
+				start: wrapper.start, 
+				end: wrapper.end,
+				width: wrapper.width, 
+				height: wrapper.height,
+				innerR: wrapper.innerR
+			})
+		});
+	},
+	
+	/**
+	 * Apply a clipping path to this object
+	 * @param {String} id
+	 */
+	clip: function(clipRect) {
+		return this.attr('clip-path', 'url('+ this.renderer.url +'#'+ clipRect.id +')');
+	},
+	
+	/**
+	 * Set styles for the element
+	 * @param {Object} styles
+	 */
+	css: function(styles) {
+		var elemWrapper = this,
+			elem = elemWrapper.element;
+		
+		// convert legacy
+		if (styles && styles.color) {
+			styles.fill = styles.color;
+		}
+		
+		// save the styles in an object
+		styles = extend(
+			elemWrapper.styles,
+			styles
+		);
+		
+		// serialize and set style attribute
+		if (isIE && !hasSVG) { // legacy IE doesn't support setting style attribute 
+			css(elemWrapper.element, styles);	
+		} else {
+			elemWrapper.attr({
+				style: serializeCSS(styles)
+			});
+		}
+		
+		
+		// store object
+		elemWrapper.styles = styles;
+		
+		// re-build text
+		if (styles.width && elem.nodeName == 'text' && elemWrapper.added) {
+			elemWrapper.renderer.buildText(elemWrapper);
+		}
+		
+		return elemWrapper;
+	},
+	
+	/**
+	 * Add an event listener
+	 * @param {String} eventType
+	 * @param {Function} handler
+	 */
+	on: function(eventType, handler) {
+		var fn = handler;
+		// touch
+		if (hasTouch && eventType == 'click') {
+			eventType = 'touchstart';
+			fn = function(e) {
+				e.preventDefault();
+				handler();
+			}
+		}
+		// simplest possible event model for internal use
+		this.element['on'+ eventType] = fn;
+		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;
+	},
+	
+	/**
+	 * Private method to update the transform attribute based on internal 
+	 * properties
+	 */
+	updateTransform: function() {
+		var wrapper = this,
+			translateX = wrapper.translateX || 0,
+			translateY = wrapper.translateY || 0,
+			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
+		if (translateX || translateY) {
+			transform.push('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 +' '+ wrapper.y +')');
+		}
+		
+		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 {Object} box The box to align to, needs a width and height
+	 * 
+	 */
+	align: function(alignOptions, alignByTranslate, box) {
+		
+		if (!alignOptions) { // called on resize
+			alignOptions = this.alignOptions;
+			alignByTranslate = this.alignByTranslate;
+		} else { // first call on instanciate
+			this.alignOptions = alignOptions;
+			this.alignByTranslate = alignByTranslate;
+			if (!box) { // boxes other than renderer handle this internally
+				this.renderer.alignedObjects.push(this);
+			}
+		}
+		
+		box = pick(box, this.renderer);
+		
+		var 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
+			attribs = {};
+			
+			
+		// align
+		if (/^(right|center)$/.test(align)) {
+			x += (box.width - (alignOptions.width || 0) ) /
+					{ right: 1, center: 2 }[align];
+		}
+		attribs[alignByTranslate ? 'translateX' : 'x'] = x;
+		
+		
+		// vertical align
+		if (/^(bottom|middle)$/.test(vAlign)) {
+			y += (box.height - (alignOptions.height || 0)) /
+					({ bottom: 1, middle: 2 }[vAlign] || 1);
+			
+		}
+		attribs[alignByTranslate ? 'translateY' : 'y'] = y;
+		
+		// animate only if already placed
+		this[this.placed ? 'animate' : 'attr'](attribs);
+		this.placed = true;
+		
+		return this;
+	},
+	
+	/**
+	 * Get the bounding box (width, height, x and y) for the element
+	 */
+	getBBox: function() {		
+		var	bBox,
+			width,
+			height,
+			rotation = this.rotation,
+			rad = rotation * deg2rad;
+			
+		try { // fails in Firefox if the container has display: none
+			// use extend because IE9 is not allowed to change width and height in case 
+			// of rotation (below)
+			bBox = extend({}, this.element.getBBox());
+		} catch(e) {
+			bBox = { width: 0, height: 0 };
+		}
+		width = bBox.width;
+		height = bBox.height;
+			
+		// 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));
+		}
+		
+		return bBox;
+	},
+	
+	/* *
+	 * Manually compute width and height of rotated text from non-rotated. Shared by SVG and VML
+	 * @param {Object} bBox
+	 * @param {number} rotation
+	 * /
+	rotateBBox: function(bBox, rotation) {
+		var rad = rotation * math.PI * 2 / 360, // radians
+			width = bBox.width,
+			height = bBox.height;
+			
+		
+	},*/
+	
+	/**
+	 * Show the element
+	 */
+	show: function() {
+		return this.attr({ visibility: VISIBLE });
+	},
+	
+	/**
+	 * Hide the element
+	 */
+	hide: function() {
+		return this.attr({ visibility: HIDDEN });
+	},
+	
+	/**
+	 * 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'),
+			textStr = this.textStr,
+			otherElement,
+			otherZIndex,
+			i;
+			
+		// mark as inverted
+		this.parentInverted = parent && parent.inverted;
+		
+		// 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);
+					return this;
+				}
+			}
+		}
+		
+		// operations before adding
+		if (textStr !== undefined) {
+			renderer.buildText(this);
+			this.added = true;
+		}
+		
+		// default: append at the end
+		parentNode.appendChild(element);
+		return this;
+	},
+
+	/**
+	 * Destroy the element and element wrapper
+	 */
+	destroy: function() {
+		var wrapper = this,
+			element = wrapper.element || {},
+			shadows = wrapper.shadows,
+			parentNode = element.parentNode,
+			key;
+		
+		// remove events
+		element.onclick = element.onmouseout = element.onmouseover = element.onmousemove = null;
+		stop(wrapper); // stop running animations
+		
+		// remove element
+		if (parentNode) {
+			parentNode.removeChild(element);
+		}
+		
+		// destroy shadows
+		if (shadows) {
+			each(shadows, function(shadow) {
+				parentNode = shadow.parentNode;
+				if (parentNode) { // the entire chart HTML can be overwritten
+					parentNode.removeChild(shadow);
+				}				
+			});
+		}
+		
+		// remove from alignObjects
+		erase(wrapper.renderer.alignedObjects, wrapper);
+				
+		for (key in wrapper) {
+			delete wrapper[key];
+		}
+		
+		return null;
+	},
+	
+	/**
+	 * Empty a group element
+	 */
+	empty: function() {
+		var element = this.element,
+			childNodes = element.childNodes,
+			i = childNodes.length;
+			
+		while (i--) {
+			element.removeChild(childNodes[i]);
+		}
+	},
+	
+	/**
+	 * Add a shadow to the element. Must be done after the element is added to the DOM
+	 * @param {Boolean} apply
+	 */
+	shadow: function(apply) {
+		var shadows = [],
+			i,
+			shadow,
+			element = this.element,
+			
+			// compensate for inverted plot area
+			transform = this.parentInverted ? '(-1,-1)' : '(1,1)';
+			
+		
+		if (apply) {
+			for (i = 1; i <= 3; i++) {
+				shadow = element.cloneNode(0);
+				attr(shadow, {
+					'isShadow': 'true',
+					'stroke': 'rgb(0, 0, 0)',
+					'stroke-opacity': 0.05 * i,
+					'stroke-width': 7 - 2 * i,
+					'transform': 'translate'+ transform,
+					'fill': NONE
+				});
+				
+				
+				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 = {
+	/**
+	 * Initialize the SVGRenderer
+	 * @param {Object} container
+	 * @param {Number} width
+	 * @param {Number} height
+	 */
+	init: function(container, width, height) {
+		var renderer = this,
+			loc = location,
+			boxWrapper;
+					
+		renderer.Element = SVGElement;
+		boxWrapper = renderer.createElement('svg')
+			.attr({
+				xmlns: SVG_NS,
+				version: '1.1'
+			});
+		container.appendChild(boxWrapper.element);
+		
+		// object properties
+		renderer.box = boxWrapper.element;
+		renderer.boxWrapper = boxWrapper;
+		renderer.alignedObjects = [];
+		renderer.url = isIE ? '' : loc.href.replace(/#.*?$/, ''); // page url used for internal references
+		renderer.defs = this.createElement('defs').add();
+		
+		renderer.setSize(width, height, false);
+		
+	},
+	
+	
+	/**
+	 * Create a wrapper for an SVG element
+	 * @param {Object} nodeName
+	 */
+	createElement: function(nodeName) {
+		var wrapper = new this.Element();
+		wrapper.init(this, nodeName);
+		return wrapper;
+	},
+	
+	
+	/** 
+	 * Parse a simple HTML string into SVG tspans
+	 * 
+	 * @param {Object} textNode The parent text SVG node
+	 */
+	buildText: function(wrapper) {
+		var textNode = wrapper.element,
+			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="([^"]+)"/,
+			parentX = attr(textNode, 'x'),
+			textStyles = wrapper.styles,
+			width = textStyles && pInt(textStyles.width),
+			textLineHeight = textStyles && textStyles.lineHeight,
+			lastLine,
+			i = childNodes.length;
+			
+		// remove old text
+		while (i--) {
+			textNode.removeChild(childNodes[i]);
+		}
+		
+		if (width) {
+			this.box.appendChild(textNode); // attach it to the DOM to read offset width
+		}
+		
+		each(lines, function(line, lineNo) {
+			var spans, spanNo = 0, lineHeight;
+			
+			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');
+					if (styleRegex.test(span)) {
+						attr(
+							tspan, 
+							'style', 
+							span.match(styleRegex)[1].replace(/(;| |^)color([ :])/, '$1fill$2')
+						);
+					}
+					if (hrefRegex.test(span)) {
+						attr(tspan, 'onclick', 'location.href=\"'+ span.match(hrefRegex)[1] +'\"');
+						css(tspan, { cursor: 'pointer' });
+					}
+					
+					span = span.replace(/<(.|\n)*?>/g, '') || ' ';
+					tspan.appendChild(doc.createTextNode(span)); // WebKit needs a string
+					
+					//console.log('"'+tspan.textContent+'"');
+					if (!spanNo) { // first span in a line, align it to the left
+						attributes.x = parentX;
+					} else {
+						// Firefox ignores spaces at the front or end of the tspan
+						attributes.dx = 3; // space
+					}
+					
+					// first span on subsequent line, add the line height
+					if (!spanNo) {						
+						if (lineNo) {
+							// Webkit and opera sometimes return 'normal' as the line height. In that
+							// case, webkit uses offsetHeight, while Opera falls back to 18
+							lineHeight = pInt(window.getComputedStyle(lastLine, null).getPropertyValue('line-height'));
+							if (isNaN(lineHeight)) {
+								lineHeight = textLineHeight || lastLine.offsetHeight || 18;
+							}
+							attr(tspan, 'dy', lineHeight);
+						}
+						lastLine = tspan; // record for use in next line						
+					}
+					
+					// add attributes
+					attr(tspan, attributes);
+					
+					// append it
+					textNode.appendChild(tspan);
+					
+					spanNo++;
+					
+					// check width and apply soft breaks
+					if (width) {
+						var words = span.replace(/-/g, '- ').split(' '),
+							tooLong,
+							actualWidth,
+							rest = [];
+						
+						while (words.length || rest.length) {
+							actualWidth = textNode.getBBox().width;
+							tooLong = actualWidth > width;
+							if (!tooLong || words.length == 1) { // new line needed
+								words = rest;
+								rest = [];
+								tspan = doc.createElementNS(SVG_NS, 'tspan');
+								attr(tspan, {
+									x: parentX,
+									dy: textLineHeight || 16
+								});
+								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());
+							}
+							
+							tspan.appendChild(doc.createTextNode(words.join(' ').replace(/- /g, '-')));
+						}
+						
+					}
+				}
+			});
+		});
+		
+		
+	},
+	
+	/**
+	 * 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]) {
+			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) {
+		return this.createElement('path').attr({ 
+			d: path, 
+			fill: NONE
+		});
+	},
+	
+	/**
+	 * 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) {
+		// arcs are defined as symbols for the ability to set 
+		// attributes in attr and animate
+		
+		if (isObject(x)) {
+			y = x.y;
+			r = x.r;
+			innerR = x.innerR;
+			start = x.start;
+			end = x.end;
+			x = x.x;
+		}
+		
+		return this.symbol('arc', x || 0, y || 0, r || 0, {
+			innerR: innerR || 0,
+			start: start || 0,
+			end: end || 0
+		});
+	},
+	
+	/**
+	 * 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) {
+		
+		if (arguments.length > 1) {
+			var normalizer = (strokeWidth || 0) % 2 / 2;
+
+			// normalize for crisp edges
+			x = mathRound(x || 0) + normalizer;
+			y = mathRound(y || 0) + normalizer;
+			width = mathRound((width || 0) - 2 * normalizer);
+			height = mathRound((height || 0) - 2 * normalizer);
+		}
+		
+		var attr = isObject(x) ? 
+			x : // the attributes can be passed as the first argument
+			{
+				x: x,
+				y: y,
+				width: mathMax(width, 0),
+				height: mathMax(height, 0)
+			};			
+		
+		return this.createElement('rect').attr(extend(attr, {
+			rx: r || attr.r,
+			ry: r || attr.r,
+			fill: NONE
+		}));
+	},
+	
+	/**
+	 * 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) {
+		return this.createElement('g').attr(
+			defined(name) && { 'class': PREFIX + name }
+		);
+	},
+	
+	/**
+	 * 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
+		elemWrapper.element.setAttributeNS('http://www.w3.org/1999/xlink', 
+			'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, radius, 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(
+				x, 
+				y, 
+				radius, 
+				options
+			),
+			
+			imageRegex = /^url\((.*?)\)$/,
+			imageSrc;
+			
+		if (path) {
+		
+			obj = this.path(path);
+			// expando properties for use in animate and attr
+			extend(obj, {
+				symbolName: symbol,
+				x: x,
+				y: y,
+				r: radius
+			});
+			if (options) {
+				extend(obj, options);
+			}
+			
+			
+		// image symbols
+		} else if (imageRegex.test(symbol)) {
+			
+			imageSrc = symbol.match(imageRegex)[1];
+			
+			// create the image synchronously, add attribs async
+			obj = this.image(imageSrc)
+				.attr({
+					x: x,
+					y: y
+				});
+			
+			// create a dummy JavaScript image to get the width and height  
+			createElement('img', {
+				onload: function() {
+					var img = this,
+						size = symbolSizes[img.src] || [img.width, img.height];
+					obj.attr({						
+						width: size[0],
+						height: size[1]
+					}).translate(
+						-mathRound(size[0] / 2),
+						-mathRound(size[1] / 2)
+					);
+				},
+				src: imageSrc
+			});
+				
+		// default circles
+		} else {
+			obj = this.circle(x, y, radius);
+		}
+		
+		return obj;
+	},
+	
+	/**
+	 * An extendable collection of functions for defining symbol paths.
+	 */
+	symbols: {
+		'square': function (x, y, radius) {
+			var len = 0.707 * radius;
+			return [
+				M, x-len, y-len,
+				L, x+len, y-len,
+				x+len, y+len,
+				x-len, y+len,
+				'Z'
+			];
+		},
+			
+		'triangle': function (x, y, radius) {
+			return [
+				M, x, y-1.33 * radius,
+				L, x+radius, y + 0.67 * radius,
+				x-radius, y + 0.67 * radius,
+				'Z'
+			];
+		},
+			
+		'triangle-down': function (x, y, radius) {
+			return [
+				M, x, y + 1.33 * radius,
+				L, x-radius, y-0.67 * radius,
+				x+radius, y-0.67 * radius,
+				'Z'
+			];
+		},
+		'diamond': function (x, y, radius) {
+			return [
+				M, x, y-radius,
+				L, x+radius, y,
+				x, y+radius,
+				x-radius, y,
+				'Z'
+			];
+		},
+		'arc': function (x, y, radius, options) {
+			var start = options.start,
+				end = options.end - 0.000001, // to prevent cos and sin of start and end from becoming equal on 360 arcs
+				innerRadius = options.innerR,
+				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,
+				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,
+				
+				'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;
+		
+		return wrapper;
+	},
+	
+	
+	/**
+	 * Take a color and return it if it's a string, make it a gradient if it's a
+	 * gradient configuration object
+	 * 
+	 * @param {Object} color The color or config object
+	 */
+	color: function(color, elem, prop) {
+		var colorObject,
+			regexRgba = /^rgba/;
+		if (color && color.linearGradient) {
+			var renderer = this,
+				strLinearGradient = 'linearGradient',
+				linearGradient = color[strLinearGradient],
+				id = PREFIX + idCounter++,
+				gradientObject,
+				stopColor,
+				stopOpacity;
+			gradientObject = renderer.createElement(strLinearGradient).attr({
+				id: id,
+				gradientUnits: 'userSpaceOnUse',
+				x1: linearGradient[0],
+				y1: linearGradient[1],
+				x2: linearGradient[2],
+				y2: linearGradient[3]
+			}).add(renderer.defs);
+			
+			each(color.stops, function(stop) {
+				if (regexRgba.test(stop[1])) {
+					colorObject = Color(stop[1]);
+					stopColor = colorObject.get('rgb');
+					stopOpacity = colorObject.get('a');
+				} else {
+					stopColor = stop[1];
+					stopOpacity = 1;
+				}
+				renderer.createElement('stop').attr({
+					offset: stop[0],
+					'stop-color': stopColor,
+					'stop-opacity': stopOpacity
+				}).add(gradientObject);
+			});
+			
+			return 'url('+ this.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 {
+			return color;
+		}
+		
+	},
+	
+		
+	/**
+	 * Add text to the SVG object
+	 * @param {String} str
+	 * @param {Number} x Left position
+	 * @param {Number} y Top position
+	 */
+	text: function(str, x, y) {
+		
+		// declare variables
+		var defaultChartStyle = defaultOptions.chart.style,
+			wrapper;
+	
+		x = mathRound(pick(x, 0));
+		y = mathRound(pick(y, 0));
+		
+		wrapper = this.createElement('text')
+			.attr({
+				x: x,
+				y: y,
+				text: str	
+			})
+			.css({
+				'font-family': defaultChartStyle.fontFamily,
+				'font-size': defaultChartStyle.fontSize
+			});
+			
+		wrapper.x = x;
+		wrapper.y = y;
+		return wrapper;
+	}
+}; // end 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.               *
+ *                                                                            *
+ *****************************************************************************/
+var VMLRenderer;
+if (!hasSVG) {
+
+/**
+ * The VML element wrapper.
+ */
+var VMLElement = extendClass( SVGElement, {
+	
+	/**
+	 * 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 markup =  ['<', nodeName, ' filled="f" stroked="f"'],
+			style = ['position: ', ABSOLUTE, ';'];
+		
+		// divs and shapes need size
+		if (nodeName == 'shape' || nodeName == DIV) {
+			style.push('left:0;top:0;width:10px;height:10px;');
+		}
+		if (docMode8) {
+			style.push('visibility: ', nodeName == DIV ? HIDDEN : VISIBLE);
+		}
+		
+		markup.push(' style="', style.join(''), '"/>');
+		
+		// create element with default attributes and style
+		if (nodeName) {
+			markup = nodeName == DIV || nodeName == 'span' || nodeName == 'img' ? 
+				markup.join('')
+				: renderer.prepVML(markup);
+			this.element = createElement(markup);
+		}
+		
+		this.renderer = renderer;
+	},
+	
+	/**
+	 * 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);			
+		}
+		
+		// issue #140 workaround - related to #61 and #74
+		if (docMode8 && parentNode.gVis == HIDDEN) {
+			css(element, { visibility: HIDDEN });
+		}
+		
+		// append it
+		parentNode.appendChild(element);
+		
+		// align text after adding to be able to read offset
+		wrapper.added = true;
+		if (wrapper.alignOnAdd) {
+			wrapper.updateTransform();
+		}		
+		
+		return wrapper;
+	},
+	
+	/**
+	 * Get or set attributes
+	 */
+	attr: function(hash, val) {
+		var key, 
+			value, 
+			i, 
+			element = this.element || {},
+			elemStyle = element.style,
+			nodeName = element.nodeName,
+			renderer = this.renderer,
+			symbolName = this.symbolName,
+			childNodes,
+			hasSetSymbolSize,
+			shadows = this.shadows,
+			skipAttr,
+			ret = this;
+			
+		// 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 = this.strokeweight;
+			} else {
+				ret = this[key];
+			}
+			
+		// setter
+		} else {		
+			for (key in hash) {
+				value = hash[key];
+				skipAttr = false;
+				
+				// prepare paths
+				// symbols
+				if (symbolName && /^(x|y|r|start|end|width|height|innerR)/.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) {
+							
+						this.symbolAttr(hash);						
+					
+						hasSetSymbolSize = true;
+					} 
+					
+					skipAttr = true;
+					
+				} else if (key == 'd') {
+					value = value || [];
+					this.d = value.join(' '); // used in getter for animation
+					
+					// convert paths 
+					i = value.length;
+					var convertedPath = [];
+					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])) {
+							convertedPath[i] = mathRound(value[i] * 10) - 5;
+						}
+						// close the path
+						else if (value[i] == 'Z') {
+							convertedPath[i] = 'x';
+						} 
+						else {
+							convertedPath[i] = value[i];
+						}
+						
+					}
+					value = convertedPath.join(' ') || 'x';							
+					element.path = value;
+			
+					// update shadows
+					if (shadows) {
+						i = shadows.length;
+						while (i--) {
+							shadows[i].path = value;
+						}
+					}
+					skipAttr = true;
+	
+				// directly mapped to css
+				} else if (key == 'zIndex' || key == 'visibility') {
+					
+					// issue 61 workaround
+					if (docMode8 && key == 'visibility' && nodeName == 'DIV') {
+						element.gVis = value;
+						childNodes = element.childNodes;
+						i = childNodes.length;
+						while (i--) {
+							css(childNodes[i], { visibility: value });
+						}
+						if (value == VISIBLE) { // issue 74
+							value = null;
+						}
+					}
+					
+					if (value) {
+						elemStyle[key] = value;
+					}
+					
+					
+					
+					skipAttr = true;
+				
+				// width and height
+				} else if (/^(width|height)$/.test(key)) {
+					
+										
+					// clipping rectangle special
+					if (this.updateClipping) {
+						this[key] = value;
+						this.updateClipping();
+						
+					} else {
+						// normal
+						elemStyle[key] = value;
+					}
+					
+					skipAttr = true;
+					
+				// x and y 
+				} else if (/^(x|y)$/.test(key)) {
+
+					this[key] = value; // used in getter
+					
+					if (element.tagName == 'SPAN') {
+						this.updateTransform();
+					
+					} else {
+						elemStyle[{ x: 'left', y: 'top' }[key]] = value;
+					}
+					
+				// class name
+				} else if (key == 'class') {
+					// 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';
+					this[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';
+					this.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 {
+						element.filled = value != NONE ? true : false;
+						
+						value = renderer.color(value, element, key);
+						
+						key = 'fillcolor';
+					}
+				
+				// translation for animation
+				} else if (key == 'translateX' || key == 'translateY' || key == 'rotation' || key == 'align') {
+					if (key == 'align') {
+						key = 'textAlign';
+					}
+					this[key] = value;
+					this.updateTransform();
+					
+					skipAttr = true;
+				}
+				
+				// text for rotated and non-rotated elements
+				else if (key == 'text') {
+					element.innerHTML = value;
+					skipAttr = true;
+				} 
+				
+					
+				// let the shadow follow the main element
+				if (shadows && key == 'visibility') {
+					i = shadows.length;
+					while (i--) {
+						shadows[i].style[key] = value;
+					}
+				}
+				
+				
+				
+				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 = clipRect.members;
+			
+		clipMembers.push(wrapper);
+		wrapper.destroyClip = function() {
+			erase(clipMembers, wrapper);
+		};
+		return wrapper.css(clipRect.getCSS(wrapper.inverted));
+	},
+	
+	/**
+	 * Set styles for the element
+	 * @param {Object} styles
+	 */
+	css: function(styles) {
+		var wrapper = this,
+			element = wrapper.element,
+			textWidth = styles && styles.width && element.tagName == 'SPAN';
+		
+		if (textWidth) {
+			extend(styles, {
+				display: 'block',
+				whiteSpace: 'normal'
+			});	
+		}
+		wrapper.styles = extend(wrapper.styles, styles);
+		css(wrapper.element, styles);
+		
+		if (textWidth) {
+			wrapper.updateTransform();	
+		}
+		
+		return wrapper;
+	},
+	
+	/**
+	 * Extend element.destroy by removing it from the clip members array
+	 */
+	destroy: function() {
+		var wrapper = this;
+		
+		if (wrapper.destroyClip) {
+			wrapper.destroyClip();
+		}
+		
+		SVGElement.prototype.destroy.apply(wrapper);
+	},
+	
+	/**
+	 * Remove all child nodes of a group, except the v:group element
+	 */
+	empty: function() {
+		var element = this.element,
+			childNodes = element.childNodes,
+			i = childNodes.length,
+			node;
+			
+		while (i--) {
+			node = childNodes[i];
+			node.parentNode.removeChild(node);
+		}
+	},
+	
+	/**
+	 * VML override for calculating the bounding box based on offsets
+	 * 
+	 * @return {Object} A hash containing values for x, y, width and height
+	 */
+	
+	getBBox: function() {
+		var element = this.element;
+		
+		// faking getBBox in exported SVG in legacy IE
+		if (element.nodeName == 'text') {
+			element.style.position = ABSOLUTE;
+		}
+		
+		return {
+			x: element.offsetLeft,
+			y: element.offsetTop,
+			width: element.offsetWidth,
+			height: element.offsetHeight
+		};
+					
+	},
+	
+	/**
+	 * 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;
+	},
+	
+	
+	/**
+	 * VML override private method to update elements based on internal 
+	 * properties based on SVG transform
+	 */
+	updateTransform: function(hash) { 
+		// aligning non added elements is expensive
+		if (!this.added) {
+			this.alignOnAdd = true;
+			return;
+		}
+		
+		var wrapper = this,
+			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';
+		
+		// apply translate
+		if (translateX || translateY) {
+			wrapper.css({
+				marginLeft: translateX,
+				marginTop: translateY
+			});
+		}
+		
+		// apply inversion
+		if (wrapper.inverted) { // wrapper is a group
+			each(elem.childNodes, function(child) {
+				wrapper.renderer.invertChild(child, elem);
+			});
+		}
+		
+		if (elem.tagName == 'SPAN') {
+			
+			var width, height,
+				rotation = wrapper.rotation,
+				lineHeight,
+				radians = 0,
+				costheta = 1,
+				sintheta = 0,
+				quad,
+				xCorr = wrapper.xCorr || 0,
+				yCorr = wrapper.yCorr || 0,
+				currentTextTransform = [rotation, align, elem.innerHTML, elem.style.width].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);				
+					 
+					// Adjust for alignment and rotation.
+					// Test case: http://highcharts.com/tests/?file=text-rotation
+					css(elem, {
+						filter: rotation ? ['progid:DXImageTransform.Microsoft.Matrix(M11=', costheta, 
+							', M12=', -sintheta, ', M21=', sintheta, ', M22=', costheta, 
+							', sizingMethod=\'auto expand\')'].join('') : NONE
+					});
+				}
+				
+				width = elem.offsetWidth;
+				height = elem.offsetHeight;
+				
+				// correct x and y
+				lineHeight = mathRound(pInt(elem.style.fontSize || 12) * 1.2);
+				xCorr = costheta < 0 && -width;
+				yCorr = sintheta < 0 && -height;
+				
+				// correct for lineHeight and corners spilling out after rotation
+				quad = costheta * sintheta < 0;
+				xCorr += sintheta * lineHeight * (quad ? 1 - alignCorrection : alignCorrection);
+				yCorr -= costheta * lineHeight * (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,
+				top: y + yCorr
+			});
+			
+			// record current text transform
+			wrapper.cTT = currentTextTransform;
+		}
+	},
+	
+	/**
+	 * Apply a drop shadow by copying elements and giving them different strokes 
+	 * @param {Boolean} apply
+	 */
+	shadow: function(apply) {
+		var shadows = [],
+			i,
+			element = this.element,
+			renderer = this.renderer,
+			shadow,
+			elemStyle = element.style,
+			markup,
+			path = element.path;
+			
+		// the path is some mysterious string-like object that can be cast to a string
+		if (''+ element.path === '') {
+			path = 'x';
+		}
+			
+		if (apply) {
+			for (i = 1; i <= 3; i++) {
+				markup = ['<shape isShadow="true" strokeweight="', ( 7 - 2 * i ) ,
+					'" filled="false" path="', path,
+					'" coordsize="100,100" style="', element.style.cssText, '" />'];
+				shadow = createElement(renderer.prepVML(markup),
+					null, {
+						left: pInt(elemStyle.left) + 1,
+						top: pInt(elemStyle.top) + 1
+					}
+				);
+				
+				// apply the opacity
+				markup = ['<stroke color="black" opacity="', (0.05 * i), '"/>'];
+				createElement(renderer.prepVML(markup), null, null, shadow);				
+				
+				
+				// insert it
+				element.parentNode.insertBefore(shadow, element);
+				
+				// record it
+				shadows.push(shadow);				
+				
+			}
+			
+			this.shadows = shadows;
+		}
+		return this;
+	
+	}
+});
+	
+/**
+ * The VML renderer
+ */
+VMLRenderer = function() {
+	this.init.apply(this, arguments);
+};
+VMLRenderer.prototype = merge( SVGRenderer.prototype, { // inherit SVGRenderer
+	
+	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;
+
+		renderer.Element = VMLElement;
+		renderer.alignedObjects = [];
+		
+		boxWrapper = renderer.createElement(DIV);
+		container.appendChild(boxWrapper.element);
+		
+		
+		// generate the containing box
+		renderer.box = boxWrapper.element;
+		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
+			doc.createStyleSheet().cssText = 
+				'hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke'+
+				'{ behavior:url(#default#VML); display: inline-block; } ';
+			
+		}	
+	},
+	
+	/**
+	 * 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();
+		
+		// mimic a rectangle with its style object for automatic updating in attr
+		return extend(clipRect, {
+			members: [],
+			left: x,
+			top: y,
+			width: width,
+			height: height,
+			getCSS: function(inverted) {
+				var rect = this,//clipRect.element.style,
+					top = rect.top,
+					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) {
+					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.inverted));
+				});
+			}
+		});
+		
+	},
+	
+	
+	/**
+	 * 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) {
+		var colorObject,
+			regexRgba = /^rgba/,
+			markup;
+			
+		if (color && color.linearGradient) {
+			
+			var stopColor, 
+				stopOpacity,
+				linearGradient = color.linearGradient,
+				angle,
+				color1,
+				opacity1,
+				color2,
+				opacity2;	
+				
+			each(color.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;
+				}
+				
+				if (!i) { // first
+					color1 = stopColor;
+					opacity1 = stopOpacity;
+				} else {
+					color2 = stopColor;
+					opacity2 = stopOpacity;
+				}
+			});
+			
+			
+			
+			// calculate the angle based on the linear vector
+			angle = 90  - math.atan(
+				(linearGradient[3] - linearGradient[1]) / // y vector
+				(linearGradient[2] - linearGradient[0]) // x vector
+				) * 180 / mathPI;
+			
+			// when colors attribute is used, the meanings of opacity and o:opacity2
+			// are reversed.
+			markup = ['<', prop, ' colors="0% ', color1, ',100% ', color2, '" angle="', angle,
+				'" opacity="', opacity2, '" o:opacity2="', opacity1,
+				'" type="gradient" focus="100%" />'];
+			createElement(this.prepVML(markup), null, null, elem);
+			
+			
+		
+		// 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);
+			
+			return colorObject.get('rgb');
+			
+			
+		} else {
+			return color;
+		}
+		
+	},
+	
+	/**
+	 * 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: function(str, x, y) {
+		
+		var defaultChartStyle = defaultOptions.chart.style; 
+			
+		return this.createElement('span')
+			.attr({
+				text: str,
+				x: mathRound(x),
+				y: mathRound(y)
+			})
+			.css({
+				whiteSpace: 'nowrap',
+				fontFamily: defaultChartStyle.fontFamily,
+				fontSize: defaultChartStyle.fontSize
+			});
+	},
+	
+	/**
+	 * Create and return a path element
+	 * @param {Array} path
+	 */
+	path: function (path) {
+		// create the shape
+		return this.createElement('shape').attr({
+			// subpixel precision down to 0.1 (width and height = 10px)
+			coordsize: '100 100',
+			d: path
+		});
+	},
+	
+	/**
+	 * 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) {
+		return this.path(this.symbols.circle(x, y, r));
+	},
+	
+	/**
+	 * 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.css({
+				left: x,
+				top: 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) {
+		// todo: share this code with SVG
+		if (arguments.length > 1) {
+			var normalizer = (strokeWidth || 0) % 2 / 2;
+
+			// normalize for crisp edges
+			x = mathRound(x || 0) + normalizer;
+			y = mathRound(y || 0) + normalizer;
+			width = mathRound((width || 0) - 2 * normalizer);
+			height = mathRound((height || 0) - 2 * normalizer);
+		}
+		
+		if (isObject(x)) { // the attributes can be passed as the first argument 
+			y = x.y;
+			width = x.width;
+			height = x.height;
+			r = x.r;
+			x = x.x;
+		} 
+		
+		return this.symbol('rect', x || 0, y || 0, r || 0, {
+			width: width || 0,
+			height: 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) - 10,
+			top: pInt(parentStyle.height) - 10,
+			rotation: -90
+		});
+	},
+	
+	/**
+	 * Symbol definitions that override the parent SVG renderer's symbols
+	 * 
+	 */
+	symbols: {
+		// VML specific arc function
+		arc: function (x, y, radius, options) {
+			var start = options.start,
+				end = options.end,
+				cosStart = mathCos(start),
+				sinStart = mathSin(start),
+				cosEnd = mathCos(end),
+				sinEnd = mathSin(end),
+				innerRadius = options.innerR;
+				
+			if (end - start === 0) { // no angle, don't show it. 
+				return ['x'];
+				
+			} else if (end - start == 2 * mathPI) { // full circle
+				// empirical correction found by trying out the limits for different radii
+				cosEnd = -0.07 / radius;
+			}
+								
+			return [
+				'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
+				
+				
+				'at', // anti clockwise arc to
+				x - innerRadius, // left
+				

<TRUNCATED>