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>