You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@usergrid.apache.org by sn...@apache.org on 2014/01/28 21:22:02 UTC
[31/79] [abbrv] [partial] updated to latest Angular-based admin portal
http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/508ef2f7/portal/js/libs/Highcharts-2.3.5/js/highcharts.src.js
----------------------------------------------------------------------
diff --git a/portal/js/libs/Highcharts-2.3.5/js/highcharts.src.js b/portal/js/libs/Highcharts-2.3.5/js/highcharts.src.js
new file mode 100644
index 0000000..b36bf1e
--- /dev/null
+++ b/portal/js/libs/Highcharts-2.3.5/js/highcharts.src.js
@@ -0,0 +1,15281 @@
+// ==ClosureCompiler==
+// @compilation_level SIMPLE_OPTIMIZATIONS
+
+/**
+ * @license Highcharts JS v2.3.5 (2012-12-19)
+ *
+ * (c) 2009-2012 Torstein Hønsi
+ *
+ * License: www.highcharts.com/license
+ */
+
+// JSLint options:
+/*global Highcharts, document, window, navigator, setInterval, clearInterval, clearTimeout, setTimeout, location, jQuery, $, console */
+
+(function () {
+// encapsulated variables
+var UNDEFINED,
+ doc = document,
+ win = window,
+ math = Math,
+ mathRound = math.round,
+ mathFloor = math.floor,
+ mathCeil = math.ceil,
+ mathMax = math.max,
+ mathMin = math.min,
+ mathAbs = math.abs,
+ mathCos = math.cos,
+ mathSin = math.sin,
+ mathPI = math.PI,
+ deg2rad = mathPI * 2 / 360,
+
+
+ // some variables
+ userAgent = navigator.userAgent,
+ isOpera = win.opera,
+ isIE = /msie/i.test(userAgent) && !isOpera,
+ docMode8 = doc.documentMode === 8,
+ isWebKit = /AppleWebKit/.test(userAgent),
+ isFirefox = /Firefox/.test(userAgent),
+ isTouchDevice = /(Mobile|Android|Windows Phone)/.test(userAgent),
+ SVG_NS = 'http://www.w3.org/2000/svg',
+ hasSVG = !!doc.createElementNS && !!doc.createElementNS(SVG_NS, 'svg').createSVGRect,
+ hasBidiBug = isFirefox && parseInt(userAgent.split('Firefox/')[1], 10) < 4, // issue #38
+ useCanVG = !hasSVG && !isIE && !!doc.createElement('canvas').getContext,
+ Renderer,
+ hasTouch = doc.documentElement.ontouchstart !== UNDEFINED,
+ symbolSizes = {},
+ idCounter = 0,
+ garbageBin,
+ defaultOptions,
+ dateFormat, // function
+ globalAnimation,
+ pathAnim,
+ timeUnits,
+ noop = function () {},
+ charts = [],
+
+ // some constants for frequently used strings
+ DIV = 'div',
+ ABSOLUTE = 'absolute',
+ RELATIVE = 'relative',
+ HIDDEN = 'hidden',
+ PREFIX = 'highcharts-',
+ VISIBLE = 'visible',
+ PX = 'px',
+ NONE = 'none',
+ M = 'M',
+ L = 'L',
+ /*
+ * Empirical lowest possible opacities for TRACKER_FILL
+ * IE6: 0.002
+ * IE7: 0.002
+ * IE8: 0.002
+ * IE9: 0.00000000001 (unlimited)
+ * IE10: 0.0001 (exporting only)
+ * FF: 0.00000000001 (unlimited)
+ * Chrome: 0.000001
+ * Safari: 0.000001
+ * Opera: 0.00000000001 (unlimited)
+ */
+ TRACKER_FILL = 'rgba(192,192,192,' + (hasSVG ? 0.0001 : 0.002) + ')', // invisible but clickable
+ //TRACKER_FILL = 'rgba(192,192,192,0.5)',
+ NORMAL_STATE = '',
+ HOVER_STATE = 'hover',
+ SELECT_STATE = 'select',
+ MILLISECOND = 'millisecond',
+ SECOND = 'second',
+ MINUTE = 'minute',
+ HOUR = 'hour',
+ DAY = 'day',
+ WEEK = 'week',
+ MONTH = 'month',
+ YEAR = 'year',
+
+ // constants for attributes
+ FILL = 'fill',
+ LINEAR_GRADIENT = 'linearGradient',
+ STOPS = 'stops',
+ STROKE = 'stroke',
+ STROKE_WIDTH = 'stroke-width',
+
+ // time methods, changed based on whether or not UTC is used
+ makeTime,
+ getMinutes,
+ getHours,
+ getDay,
+ getDate,
+ getMonth,
+ getFullYear,
+ setMinutes,
+ setHours,
+ setDate,
+ setMonth,
+ setFullYear,
+
+
+ // lookup over the types and the associated classes
+ seriesTypes = {};
+
+// The Highcharts namespace
+win.Highcharts = {};
+
+/**
+ * Extend an object with the members of another
+ * @param {Object} a The object to be extended
+ * @param {Object} b The object to add to the first one
+ */
+function extend(a, b) {
+ var n;
+ if (!a) {
+ a = {};
+ }
+ for (n in b) {
+ a[n] = b[n];
+ }
+ return a;
+}
+
+/**
+ * Take an array and turn into a hash with even number arguments as keys and odd numbers as
+ * values. Allows creating constants for commonly used style properties, attributes etc.
+ * Avoid it in performance critical situations like looping
+ */
+function hash() {
+ var i = 0,
+ args = arguments,
+ length = args.length,
+ obj = {};
+ for (; i < length; i++) {
+ obj[args[i++]] = args[i];
+ }
+ return obj;
+}
+
+/**
+ * Shortcut for parseInt
+ * @param {Object} s
+ * @param {Number} mag Magnitude
+ */
+function pInt(s, mag) {
+ return parseInt(s, mag || 10);
+}
+
+/**
+ * Check for string
+ * @param {Object} s
+ */
+function isString(s) {
+ return typeof s === 'string';
+}
+
+/**
+ * Check for object
+ * @param {Object} obj
+ */
+function isObject(obj) {
+ return typeof obj === 'object';
+}
+
+/**
+ * Check for array
+ * @param {Object} obj
+ */
+function isArray(obj) {
+ return Object.prototype.toString.call(obj) === '[object Array]';
+}
+
+/**
+ * Check for number
+ * @param {Object} n
+ */
+function isNumber(n) {
+ return typeof n === 'number';
+}
+
+function log2lin(num) {
+ return math.log(num) / math.LN10;
+}
+function lin2log(num) {
+ return math.pow(10, num);
+}
+
+/**
+ * Remove last occurence of an item from an array
+ * @param {Array} arr
+ * @param {Mixed} item
+ */
+function erase(arr, item) {
+ var i = arr.length;
+ while (i--) {
+ if (arr[i] === item) {
+ arr.splice(i, 1);
+ break;
+ }
+ }
+ //return arr;
+}
+
+/**
+ * Returns true if the object is not null or undefined. Like MooTools' $.defined.
+ * @param {Object} obj
+ */
+function defined(obj) {
+ return obj !== UNDEFINED && obj !== null;
+}
+
+/**
+ * Set or get an attribute or an object of attributes. Can't use jQuery attr because
+ * it attempts to set expando properties on the SVG element, which is not allowed.
+ *
+ * @param {Object} elem The DOM element to receive the attribute(s)
+ * @param {String|Object} prop The property or an abject of key-value pairs
+ * @param {String} value The value if a single property is set
+ */
+function attr(elem, prop, value) {
+ var key,
+ setAttribute = 'setAttribute',
+ ret;
+
+ // if the prop is a string
+ if (isString(prop)) {
+ // set the value
+ if (defined(value)) {
+
+ elem[setAttribute](prop, value);
+
+ // get the value
+ } else if (elem && elem.getAttribute) { // elem not defined when printing pie demo...
+ ret = elem.getAttribute(prop);
+ }
+
+ // else if prop is defined, it is a hash of key/value pairs
+ } else if (defined(prop) && isObject(prop)) {
+ for (key in prop) {
+ elem[setAttribute](key, prop[key]);
+ }
+ }
+ return ret;
+}
+/**
+ * Check if an element is an array, and if not, make it into an array. Like
+ * MooTools' $.splat.
+ */
+function splat(obj) {
+ return isArray(obj) ? obj : [obj];
+}
+
+
+/**
+ * Return the first value that is defined. Like MooTools' $.pick.
+ */
+function pick() {
+ var args = arguments,
+ i,
+ arg,
+ length = args.length;
+ for (i = 0; i < length; i++) {
+ arg = args[i];
+ if (typeof arg !== 'undefined' && arg !== null) {
+ return arg;
+ }
+ }
+}
+
+/**
+ * Set CSS on a given element
+ * @param {Object} el
+ * @param {Object} styles Style object with camel case property names
+ */
+function css(el, styles) {
+ if (isIE) {
+ if (styles && styles.opacity !== UNDEFINED) {
+ styles.filter = 'alpha(opacity=' + (styles.opacity * 100) + ')';
+ }
+ }
+ extend(el.style, styles);
+}
+
+/**
+ * Utility function to create element with attributes and styles
+ * @param {Object} tag
+ * @param {Object} attribs
+ * @param {Object} styles
+ * @param {Object} parent
+ * @param {Object} nopad
+ */
+function createElement(tag, attribs, styles, parent, nopad) {
+ var el = doc.createElement(tag);
+ if (attribs) {
+ extend(el, attribs);
+ }
+ if (nopad) {
+ css(el, {padding: 0, border: NONE, margin: 0});
+ }
+ if (styles) {
+ css(el, styles);
+ }
+ if (parent) {
+ parent.appendChild(el);
+ }
+ return el;
+}
+
+/**
+ * Extend a prototyped class by new members
+ * @param {Object} parent
+ * @param {Object} members
+ */
+function extendClass(parent, members) {
+ var object = function () {};
+ object.prototype = new parent();
+ extend(object.prototype, members);
+ return object;
+}
+
+/**
+ * How many decimals are there in a number
+ */
+function getDecimals(number) {
+
+ number = (number || 0).toString();
+
+ return number.indexOf('.') > -1 ?
+ number.split('.')[1].length :
+ 0;
+}
+
+/**
+ * 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 = decimals === -1 ?
+ getDecimals(number) :
+ (isNaN(decimals = mathAbs(decimals)) ? 2 : decimals),
+ d = decPoint === undefined ? lang.decimalPoint : decPoint,
+ t = thousandsSep === undefined ? lang.thousandsSep : thousandsSep,
+ s = n < 0 ? "-" : "",
+ i = String(pInt(n = mathAbs(+n || 0).toFixed(c))),
+ j = i.length > 3 ? i.length % 3 : 0;
+
+ return s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) +
+ (c ? d + mathAbs(n - i).toFixed(c).slice(2) : "");
+}
+
+/**
+ * Pad a string to a given length by adding 0 to the beginning
+ * @param {Number} number
+ * @param {Number} length
+ */
+function pad(number, length) {
+ // Create an array of the remaining length +1 and join it with 0's
+ return new Array((length || 2) + 1 - String(number).length).join(0) + number;
+}
+
+/**
+ * Wrap a method with extended functionality, preserving the original function
+ * @param {Object} obj The context object that the method belongs to
+ * @param {String} method The name of the method to extend
+ * @param {Function} func A wrapper function callback. This function is called with the same arguments
+ * as the original function, except that the original function is unshifted and passed as the first
+ * argument.
+ */
+function wrap(obj, method, func) {
+ var proceed = obj[method];
+ obj[method] = function () {
+ var args = Array.prototype.slice.call(arguments);
+ args.unshift(proceed);
+ return func.apply(this, args);
+ };
+}
+
+/**
+ * Based on http://www.php.net/manual/en/function.strftime.php
+ * @param {String} format
+ * @param {Number} timestamp
+ * @param {Boolean} capitalize
+ */
+dateFormat = function (format, timestamp, capitalize) {
+ if (!defined(timestamp) || isNaN(timestamp)) {
+ return 'Invalid date';
+ }
+ format = pick(format, '%Y-%m-%d %H:%M:%S');
+
+ var date = new Date(timestamp),
+ key, // used in for constuct below
+ // get the basic time values
+ hours = date[getHours](),
+ day = date[getDay](),
+ dayOfMonth = date[getDate](),
+ month = date[getMonth](),
+ fullYear = date[getFullYear](),
+ lang = defaultOptions.lang,
+ langWeekdays = lang.weekdays,
+ /* // uncomment this and the 'W' format key below to enable week numbers
+ weekNumber = function () {
+ var clone = new Date(date.valueOf()),
+ day = clone[getDay]() == 0 ? 7 : clone[getDay](),
+ dayNumber;
+ clone.setDate(clone[getDate]() + 4 - day);
+ dayNumber = mathFloor((clone.getTime() - new Date(clone[getFullYear](), 0, 1, -6)) / 86400000);
+ return 1 + mathFloor(dayNumber / 7);
+ },
+ */
+
+ // 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)
+ //'W': weekNumber(),
+
+ // Month
+ 'b': lang.shortMonths[month], // Short month, like 'Jan'
+ 'B': lang.months[month], // Long month, like 'January'
+ 'm': pad(month + 1), // Two digit month number, 01 through 12
+
+ // Year
+ 'y': fullYear.toString().substr(2, 2), // Two digits year, like 09 for 2009
+ 'Y': fullYear, // Four digits year, like 2009
+
+ // Time
+ 'H': pad(hours), // Two digits hours in 24h format, 00 through 23
+ 'I': pad((hours % 12) || 12), // Two digits hours in 12h format, 00 through 11
+ 'l': (hours % 12) || 12, // Hours in 12h format, 1 through 12
+ 'M': pad(date[getMinutes]()), // Two digits minutes, 00 through 59
+ 'p': hours < 12 ? 'AM' : 'PM', // Upper case AM or PM
+ 'P': hours < 12 ? 'am' : 'pm', // Lower case AM or PM
+ 'S': pad(date.getSeconds()), // Two digits seconds, 00 through 59
+ 'L': pad(mathRound(timestamp % 1000), 3) // Milliseconds (naming from Ruby)
+ };
+
+
+ // do the replaces
+ for (key in replacements) {
+ while (format.indexOf('%' + key) !== -1) { // regex would do it in one line, but this is faster
+ format = format.replace('%' + key, replacements[key]);
+ }
+ }
+
+ // Optionally capitalize the string and return
+ return capitalize ? format.substr(0, 1).toUpperCase() + format.substr(1) : format;
+};
+
+/**
+ * Take an interval and normalize it to multiples of 1, 2, 2.5 and 5
+ * @param {Number} interval
+ * @param {Array} multiples
+ * @param {Number} magnitude
+ * @param {Object} options
+ */
+function normalizeTickInterval(interval, multiples, magnitude, options) {
+ var normalized, i;
+
+ // round to a tenfold of 1, 2, 2.5 or 5
+ magnitude = pick(magnitude, 1);
+ normalized = interval / magnitude;
+
+ // multiples for a linear scale
+ if (!multiples) {
+ multiples = [1, 2, 2.5, 5, 10];
+
+ // the allowDecimals option
+ if (options && options.allowDecimals === false) {
+ if (magnitude === 1) {
+ multiples = [1, 2, 5, 10];
+ } else if (magnitude <= 0.1) {
+ multiples = [1 / magnitude];
+ }
+ }
+ }
+
+ // normalize the interval to the nearest multiple
+ for (i = 0; i < multiples.length; i++) {
+ interval = multiples[i];
+ if (normalized <= (multiples[i] + (multiples[i + 1] || multiples[i])) / 2) {
+ break;
+ }
+ }
+
+ // multiply back to the correct magnitude
+ interval *= magnitude;
+
+ return interval;
+}
+
+/**
+ * Get a normalized tick interval for dates. Returns a configuration object with
+ * unit range (interval), count and name. Used to prepare data for getTimeTicks.
+ * Previously this logic was part of getTimeTicks, but as getTimeTicks now runs
+ * of segments in stock charts, the normalizing logic was extracted in order to
+ * prevent it for running over again for each segment having the same interval.
+ * #662, #697.
+ */
+function normalizeTimeTickInterval(tickInterval, unitsOption) {
+ var units = unitsOption || [[
+ MILLISECOND, // unit name
+ [1, 2, 5, 10, 20, 25, 50, 100, 200, 500] // allowed multiples
+ ], [
+ SECOND,
+ [1, 2, 5, 10, 15, 30]
+ ], [
+ MINUTE,
+ [1, 2, 5, 10, 15, 30]
+ ], [
+ HOUR,
+ [1, 2, 3, 4, 6, 8, 12]
+ ], [
+ DAY,
+ [1, 2]
+ ], [
+ WEEK,
+ [1, 2]
+ ], [
+ MONTH,
+ [1, 2, 3, 4, 6]
+ ], [
+ YEAR,
+ null
+ ]],
+ unit = units[units.length - 1], // default unit is years
+ interval = timeUnits[unit[0]],
+ multiples = unit[1],
+ count,
+ i;
+
+ // loop through the units to find the one that best fits the tickInterval
+ for (i = 0; i < units.length; i++) {
+ unit = units[i];
+ interval = timeUnits[unit[0]];
+ multiples = unit[1];
+
+
+ if (units[i + 1]) {
+ // lessThan is in the middle between the highest multiple and the next unit.
+ var lessThan = (interval * multiples[multiples.length - 1] +
+ timeUnits[units[i + 1][0]]) / 2;
+
+ // break and keep the current unit
+ if (tickInterval <= lessThan) {
+ break;
+ }
+ }
+ }
+
+ // prevent 2.5 years intervals, though 25, 250 etc. are allowed
+ if (interval === timeUnits[YEAR] && tickInterval < 5 * interval) {
+ multiples = [1, 2, 5];
+ }
+
+ // prevent 2.5 years intervals, though 25, 250 etc. are allowed
+ if (interval === timeUnits[YEAR] && tickInterval < 5 * interval) {
+ multiples = [1, 2, 5];
+ }
+
+ // get the count
+ count = normalizeTickInterval(tickInterval / interval, multiples);
+
+ return {
+ unitRange: interval,
+ count: count,
+ unitName: unit[0]
+ };
+}
+
+/**
+ * Set the tick positions to a time unit that makes sense, for example
+ * on the first of each month or on every Monday. Return an array
+ * with the time positions. Used in datetime axes as well as for grouping
+ * data on a datetime axis.
+ *
+ * @param {Object} normalizedInterval The interval in axis values (ms) and the count
+ * @param {Number} min The minimum in axis values
+ * @param {Number} max The maximum in axis values
+ * @param {Number} startOfWeek
+ */
+function getTimeTicks(normalizedInterval, min, max, startOfWeek) {
+ var tickPositions = [],
+ i,
+ higherRanks = {},
+ useUTC = defaultOptions.global.useUTC,
+ minYear, // used in months and years as a basis for Date.UTC()
+ minDate = new Date(min),
+ interval = normalizedInterval.unitRange,
+ count = normalizedInterval.count;
+
+ if (defined(min)) { // #1300
+ if (interval >= timeUnits[SECOND]) { // second
+ minDate.setMilliseconds(0);
+ minDate.setSeconds(interval >= timeUnits[MINUTE] ? 0 :
+ count * mathFloor(minDate.getSeconds() / count));
+ }
+
+ if (interval >= timeUnits[MINUTE]) { // minute
+ minDate[setMinutes](interval >= timeUnits[HOUR] ? 0 :
+ count * mathFloor(minDate[getMinutes]() / count));
+ }
+
+ if (interval >= timeUnits[HOUR]) { // hour
+ minDate[setHours](interval >= timeUnits[DAY] ? 0 :
+ count * mathFloor(minDate[getHours]() / count));
+ }
+
+ if (interval >= timeUnits[DAY]) { // day
+ minDate[setDate](interval >= timeUnits[MONTH] ? 1 :
+ count * mathFloor(minDate[getDate]() / count));
+ }
+
+ if (interval >= timeUnits[MONTH]) { // month
+ minDate[setMonth](interval >= timeUnits[YEAR] ? 0 :
+ count * mathFloor(minDate[getMonth]() / count));
+ minYear = minDate[getFullYear]();
+ }
+
+ if (interval >= timeUnits[YEAR]) { // year
+ minYear -= minYear % count;
+ minDate[setFullYear](minYear);
+ }
+
+ // week is a special case that runs outside the hierarchy
+ if (interval === timeUnits[WEEK]) {
+ // get start of current week, independent of count
+ minDate[setDate](minDate[getDate]() - minDate[getDay]() +
+ pick(startOfWeek, 1));
+ }
+
+
+ // get tick positions
+ i = 1;
+ minYear = minDate[getFullYear]();
+ var time = minDate.getTime(),
+ minMonth = minDate[getMonth](),
+ minDateDate = minDate[getDate](),
+ timezoneOffset = useUTC ?
+ 0 :
+ (24 * 3600 * 1000 + minDate.getTimezoneOffset() * 60 * 1000) % (24 * 3600 * 1000); // #950
+
+ // iterate and add tick positions at appropriate values
+ while (time < max) {
+ tickPositions.push(time);
+
+ // if the interval is years, use Date.UTC to increase years
+ if (interval === timeUnits[YEAR]) {
+ time = makeTime(minYear + i * count, 0);
+
+ // if the interval is months, use Date.UTC to increase months
+ } else if (interval === timeUnits[MONTH]) {
+ time = makeTime(minYear, minMonth + i * count);
+
+ // if we're using global time, the interval is not fixed as it jumps
+ // one hour at the DST crossover
+ } else if (!useUTC && (interval === timeUnits[DAY] || interval === timeUnits[WEEK])) {
+ time = makeTime(minYear, minMonth, minDateDate +
+ i * count * (interval === timeUnits[DAY] ? 1 : 7));
+
+ // else, the interval is fixed and we use simple addition
+ } else {
+ time += interval * count;
+
+ // mark new days if the time is dividable by day
+ if (interval <= timeUnits[HOUR] && time % timeUnits[DAY] === timezoneOffset) {
+ higherRanks[time] = DAY;
+ }
+ }
+
+ i++;
+ }
+
+ // push the last time
+ tickPositions.push(time);
+ }
+
+ // record information on the chosen unit - for dynamic label formatter
+ tickPositions.info = extend(normalizedInterval, {
+ higherRanks: higherRanks,
+ totalRange: interval * count
+ });
+
+ return tickPositions;
+}
+
+/**
+ * Helper class that contains variuos counters that are local to the chart.
+ */
+function ChartCounters() {
+ this.color = 0;
+ this.symbol = 0;
+}
+
+ChartCounters.prototype = {
+ /**
+ * Wraps the color counter if it reaches the specified length.
+ */
+ wrapColor: function (length) {
+ if (this.color >= length) {
+ this.color = 0;
+ }
+ },
+
+ /**
+ * Wraps the symbol counter if it reaches the specified length.
+ */
+ wrapSymbol: function (length) {
+ if (this.symbol >= length) {
+ this.symbol = 0;
+ }
+ }
+};
+
+
+/**
+ * Utility method that sorts an object array and keeping the order of equal items.
+ * ECMA script standard does not specify the behaviour when items are equal.
+ */
+function stableSort(arr, sortFunction) {
+ var length = arr.length,
+ sortValue,
+ i;
+
+ // Add index to each item
+ for (i = 0; i < length; i++) {
+ arr[i].ss_i = i; // stable sort index
+ }
+
+ arr.sort(function (a, b) {
+ sortValue = sortFunction(a, b);
+ return sortValue === 0 ? a.ss_i - b.ss_i : sortValue;
+ });
+
+ // Remove index from items
+ for (i = 0; i < length; i++) {
+ delete arr[i].ss_i; // stable sort index
+ }
+}
+
+/**
+ * Non-recursive method to find the lowest member of an array. Math.min raises a maximum
+ * call stack size exceeded error in Chrome when trying to apply more than 150.000 points. This
+ * method is slightly slower, but safe.
+ */
+function arrayMin(data) {
+ var i = data.length,
+ min = data[0];
+
+ while (i--) {
+ if (data[i] < min) {
+ min = data[i];
+ }
+ }
+ return min;
+}
+
+/**
+ * Non-recursive method to find the lowest member of an array. Math.min raises a maximum
+ * call stack size exceeded error in Chrome when trying to apply more than 150.000 points. This
+ * method is slightly slower, but safe.
+ */
+function arrayMax(data) {
+ var i = data.length,
+ max = data[0];
+
+ while (i--) {
+ if (data[i] > max) {
+ max = data[i];
+ }
+ }
+ return max;
+}
+
+/**
+ * Utility method that destroys any SVGElement or VMLElement that are properties on the given object.
+ * It loops all properties and invokes destroy if there is a destroy method. The property is
+ * then delete'ed.
+ * @param {Object} The object to destroy properties on
+ * @param {Object} Exception, do not destroy this property, only delete it.
+ */
+function destroyObjectProperties(obj, except) {
+ var n;
+ for (n in obj) {
+ // If the object is non-null and destroy is defined
+ if (obj[n] && obj[n] !== except && obj[n].destroy) {
+ // Invoke the destroy
+ obj[n].destroy();
+ }
+
+ // Delete the property from the object.
+ delete obj[n];
+ }
+}
+
+
+/**
+ * Discard an element by moving it to the bin and delete
+ * @param {Object} The HTML node to discard
+ */
+function discardElement(element) {
+ // create a garbage bin element, not part of the DOM
+ if (!garbageBin) {
+ garbageBin = createElement(DIV);
+ }
+
+ // move the node and empty bin
+ if (element) {
+ garbageBin.appendChild(element);
+ }
+ garbageBin.innerHTML = '';
+}
+
+/**
+ * Provide error messages for debugging, with links to online explanation
+ */
+function error(code, stop) {
+ var msg = 'Highcharts error #' + code + ': www.highcharts.com/errors/' + code;
+ if (stop) {
+ throw msg;
+ } else if (win.console) {
+ console.log(msg);
+ }
+}
+
+/**
+ * Fix JS round off float errors
+ * @param {Number} num
+ */
+function correctFloat(num) {
+ return parseFloat(
+ num.toPrecision(14)
+ );
+}
+
+/**
+ * Set the global animation to either a given value, or fall back to the
+ * given chart's animation option
+ * @param {Object} animation
+ * @param {Object} chart
+ */
+function setAnimation(animation, chart) {
+ globalAnimation = pick(animation, chart.animation);
+}
+
+/**
+ * The time unit lookup
+ */
+/*jslint white: true*/
+timeUnits = hash(
+ MILLISECOND, 1,
+ SECOND, 1000,
+ MINUTE, 60000,
+ HOUR, 3600000,
+ DAY, 24 * 3600000,
+ WEEK, 7 * 24 * 3600000,
+ MONTH, 31 * 24 * 3600000,
+ YEAR, 31556952000
+);
+/*jslint white: false*/
+/**
+ * Path interpolation algorithm used across adapters
+ */
+pathAnim = {
+ /**
+ * Prepare start and end values so that the path can be animated one to one
+ */
+ init: function (elem, fromD, toD) {
+ fromD = fromD || '';
+ var shift = elem.shift,
+ bezier = fromD.indexOf('C') > -1,
+ numParams = bezier ? 7 : 3,
+ endLength,
+ slice,
+ i,
+ start = fromD.split(' '),
+ end = [].concat(toD), // copy
+ startBaseLine,
+ endBaseLine,
+ sixify = function (arr) { // in splines make move points have six parameters like bezier curves
+ i = arr.length;
+ while (i--) {
+ if (arr[i] === M) {
+ arr.splice(i + 1, 0, arr[i + 1], arr[i + 2], arr[i + 1], arr[i + 2]);
+ }
+ }
+ };
+
+ if (bezier) {
+ sixify(start);
+ sixify(end);
+ }
+
+ // pull out the base lines before padding
+ if (elem.isArea) {
+ startBaseLine = start.splice(start.length - 6, 6);
+ endBaseLine = end.splice(end.length - 6, 6);
+ }
+
+ // if shifting points, prepend a dummy point to the end path
+ if (shift <= end.length / numParams) {
+ while (shift--) {
+ end = [].concat(end).splice(0, numParams).concat(end);
+ }
+ }
+ elem.shift = 0; // reset for following animations
+
+ // copy and append last point until the length matches the end length
+ if (start.length) {
+ endLength = end.length;
+ while (start.length < endLength) {
+
+ //bezier && sixify(start);
+ slice = [].concat(start).splice(start.length - numParams, numParams);
+ if (bezier) { // disable first control point
+ slice[numParams - 6] = slice[numParams - 2];
+ slice[numParams - 5] = slice[numParams - 1];
+ }
+ start = start.concat(slice);
+ }
+ }
+
+ if (startBaseLine) { // append the base lines for areas
+ start = start.concat(startBaseLine);
+ end = end.concat(endBaseLine);
+ }
+ return [start, end];
+ },
+
+ /**
+ * Interpolate each value of the path and return the array
+ */
+ step: function (start, end, pos, complete) {
+ var ret = [],
+ i = start.length,
+ startVal;
+
+ if (pos === 1) { // land on the final path without adjustment points appended in the ends
+ ret = complete;
+
+ } else if (i === end.length && pos < 1) {
+ while (i--) {
+ startVal = parseFloat(start[i]);
+ ret[i] =
+ isNaN(startVal) ? // a letter instruction like M or L
+ start[i] :
+ pos * (parseFloat(end[i] - startVal)) + startVal;
+
+ }
+ } else { // if animation is finished or length not matching, land on right value
+ ret = end;
+ }
+ return ret;
+ }
+};
+
+(function ($) {
+ /**
+ * The default HighchartsAdapter for jQuery
+ */
+ win.HighchartsAdapter = win.HighchartsAdapter || ($ && {
+
+ /**
+ * Initialize the adapter by applying some extensions to jQuery
+ */
+ init: function (pathAnim) {
+
+ // extend the animate function to allow SVG animations
+ var Fx = $.fx,
+ Step = Fx.step,
+ dSetter,
+ Tween = $.Tween,
+ propHooks = Tween && Tween.propHooks;
+
+ /*jslint unparam: true*//* allow unused param x in this function */
+ $.extend($.easing, {
+ easeOutQuad: function (x, t, b, c, d) {
+ return -c * (t /= d) * (t - 2) + b;
+ }
+ });
+ /*jslint unparam: false*/
+
+
+ // extend some methods to check for elem.attr, which means it is a Highcharts SVG object
+ $.each(['cur', '_default', 'width', 'height'], function (i, fn) {
+ var obj = Step,
+ base,
+ elem;
+
+ // Handle different parent objects
+ if (fn === 'cur') {
+ obj = Fx.prototype; // 'cur', the getter, relates to Fx.prototype
+
+ } else if (fn === '_default' && Tween) { // jQuery 1.8 model
+ obj = propHooks[fn];
+ fn = 'set';
+ }
+
+ // Overwrite the method
+ base = obj[fn];
+ if (base) { // step.width and step.height don't exist in jQuery < 1.7
+
+ // create the extended function replacement
+ obj[fn] = function (fx) {
+
+ // Fx.prototype.cur does not use fx argument
+ fx = i ? fx : this;
+
+ // shortcut
+ elem = fx.elem;
+
+ // Fx.prototype.cur returns the current value. The other ones are setters
+ // and returning a value has no effect.
+ return elem.attr ? // is SVG element wrapper
+ elem.attr(fx.prop, fn === 'cur' ? UNDEFINED : fx.now) : // apply the SVG wrapper's method
+ base.apply(this, arguments); // use jQuery's built-in method
+ };
+ }
+ });
+
+
+ // Define the setter function for d (path definitions)
+ dSetter = function (fx) {
+ var elem = fx.elem,
+ ends;
+
+ // Normally start and end should be set in state == 0, but sometimes,
+ // for reasons unknown, this doesn't happen. Perhaps state == 0 is skipped
+ // in these cases
+ if (!fx.started) {
+ ends = pathAnim.init(elem, elem.d, elem.toD);
+ fx.start = ends[0];
+ fx.end = ends[1];
+ fx.started = true;
+ }
+
+
+ // interpolate each value of the path
+ elem.attr('d', pathAnim.step(fx.start, fx.end, fx.pos, elem.toD));
+ };
+
+ // jQuery 1.8 style
+ if (Tween) {
+ propHooks.d = {
+ set: dSetter
+ };
+ // pre 1.8
+ } else {
+ // animate paths
+ Step.d = dSetter;
+ }
+
+ /**
+ * Utility for iterating over an array. Parameters are reversed compared to jQuery.
+ * @param {Array} arr
+ * @param {Function} fn
+ */
+ this.each = Array.prototype.forEach ?
+ function (arr, fn) { // modern browsers
+ return Array.prototype.forEach.call(arr, fn);
+
+ } :
+ function (arr, fn) { // legacy
+ var i = 0,
+ len = arr.length;
+ for (; i < len; i++) {
+ if (fn.call(arr[i], arr[i], i, arr) === false) {
+ return i;
+ }
+ }
+ };
+
+ // Register Highcharts as a jQuery plugin
+ // TODO: MooTools and prototype as well?
+ // TODO: StockChart
+ /*$.fn.highcharts = function(options, callback) {
+ options.chart = merge(options.chart, { renderTo: this[0] });
+ this.chart = new Chart(options, callback);
+ return this;
+ };*/
+ },
+
+ /**
+ * Downloads a script and executes a callback when done.
+ * @param {String} scriptLocation
+ * @param {Function} callback
+ */
+ getScript: $.getScript,
+
+ /**
+ * Return the index of an item in an array, or -1 if not found
+ */
+ inArray: $.inArray,
+
+ /**
+ * A direct link to jQuery methods. MooTools and Prototype adapters must be implemented for each case of method.
+ * @param {Object} elem The HTML element
+ * @param {String} method Which method to run on the wrapped element
+ */
+ adapterRun: function (elem, method) {
+ return $(elem)[method]();
+ },
+
+ /**
+ * Filter an array
+ */
+ grep: $.grep,
+
+ /**
+ * Map an array
+ * @param {Array} arr
+ * @param {Function} fn
+ */
+ map: function (arr, fn) {
+ //return jQuery.map(arr, fn);
+ var results = [],
+ i = 0,
+ len = arr.length;
+ for (; i < len; i++) {
+ results[i] = fn.call(arr[i], arr[i], i, arr);
+ }
+ return results;
+
+ },
+
+ /**
+ * Deep merge two objects and return a third object
+ */
+ merge: function () {
+ var args = arguments;
+ return $.extend(true, null, args[0], args[1], args[2], args[3]);
+ },
+
+ /**
+ * Get the position of an element relative to the top left of the page
+ */
+ offset: function (el) {
+ return $(el).offset();
+ },
+
+ /**
+ * Add an event listener
+ * @param {Object} el A HTML element or custom object
+ * @param {String} event The event type
+ * @param {Function} fn The event handler
+ */
+ addEvent: function (el, event, fn) {
+ $(el).bind(event, fn);
+ },
+
+ /**
+ * Remove event added with addEvent
+ * @param {Object} el The object
+ * @param {String} eventType The event type. Leave blank to remove all events.
+ * @param {Function} handler The function to remove
+ */
+ removeEvent: function (el, eventType, handler) {
+ // workaround for jQuery issue with unbinding custom events:
+ // http://forum.jQuery.com/topic/javascript-error-when-unbinding-a-custom-event-using-jQuery-1-4-2
+ var func = doc.removeEventListener ? 'removeEventListener' : 'detachEvent';
+ if (doc[func] && !el[func]) {
+ el[func] = function () {};
+ }
+
+ $(el).unbind(eventType, handler);
+ },
+
+ /**
+ * Fire an event on a custom object
+ * @param {Object} el
+ * @param {String} type
+ * @param {Object} eventArguments
+ * @param {Function} defaultFunction
+ */
+ fireEvent: function (el, type, eventArguments, defaultFunction) {
+ var event = $.Event(type),
+ detachedType = 'detached' + type,
+ defaultPrevented;
+
+ // Remove warnings in Chrome when accessing layerX and layerY. Although Highcharts
+ // never uses these properties, Chrome includes them in the default click event and
+ // raises the warning when they are copied over in the extend statement below.
+ //
+ // To avoid problems in IE (see #1010) where we cannot delete the properties and avoid
+ // testing if they are there (warning in chrome) the only option is to test if running IE.
+ if (!isIE && eventArguments) {
+ delete eventArguments.layerX;
+ delete eventArguments.layerY;
+ }
+
+ extend(event, eventArguments);
+
+ // Prevent jQuery from triggering the object method that is named the
+ // same as the event. For example, if the event is 'select', jQuery
+ // attempts calling el.select and it goes into a loop.
+ if (el[type]) {
+ el[detachedType] = el[type];
+ el[type] = null;
+ }
+
+ // Wrap preventDefault and stopPropagation in try/catch blocks in
+ // order to prevent JS errors when cancelling events on non-DOM
+ // objects. #615.
+ /*jslint unparam: true*/
+ $.each(['preventDefault', 'stopPropagation'], function (i, fn) {
+ var base = event[fn];
+ event[fn] = function () {
+ try {
+ base.call(event);
+ } catch (e) {
+ if (fn === 'preventDefault') {
+ defaultPrevented = true;
+ }
+ }
+ };
+ });
+ /*jslint unparam: false*/
+
+ // trigger it
+ $(el).trigger(event);
+
+ // attach the method
+ if (el[detachedType]) {
+ el[type] = el[detachedType];
+ el[detachedType] = null;
+ }
+
+ if (defaultFunction && !event.isDefaultPrevented() && !defaultPrevented) {
+ defaultFunction(event);
+ }
+ },
+
+ /**
+ * Extension method needed for MooTools
+ */
+ washMouseEvent: function (e) {
+ var ret = e.originalEvent || e;
+
+ // computed by jQuery, needed by IE8
+ if (ret.pageX === UNDEFINED) { // #1236
+ ret.pageX = e.pageX;
+ ret.pageY = e.pageY;
+ }
+
+ return ret;
+ },
+
+ /**
+ * Animate a HTML element or SVG element wrapper
+ * @param {Object} el
+ * @param {Object} params
+ * @param {Object} options jQuery-like animation options: duration, easing, callback
+ */
+ animate: function (el, params, options) {
+ var $el = $(el);
+ if (params.d) {
+ el.toD = params.d; // keep the array form for paths, used in $.fx.step.d
+ params.d = 1; // because in jQuery, animating to an array has a different meaning
+ }
+
+ $el.stop();
+ $el.animate(params, options);
+
+ },
+ /**
+ * Stop running animation
+ */
+ stop: function (el) {
+ $(el).stop();
+ }
+ });
+}(win.jQuery));
+
+
+// check for a custom HighchartsAdapter defined prior to this file
+var globalAdapter = win.HighchartsAdapter,
+ adapter = globalAdapter || {};
+
+// Initialize the adapter
+if (globalAdapter) {
+ globalAdapter.init.call(globalAdapter, pathAnim);
+}
+
+
+ // Utility functions. If the HighchartsAdapter is not defined, adapter is an empty object
+ // and all the utility functions will be null. In that case they are populated by the
+ // default adapters below.
+var adapterRun = adapter.adapterRun,
+ getScript = adapter.getScript,
+ inArray = adapter.inArray,
+ each = adapter.each,
+ grep = adapter.grep,
+ offset = adapter.offset,
+ map = adapter.map,
+ merge = adapter.merge,
+ addEvent = adapter.addEvent,
+ removeEvent = adapter.removeEvent,
+ fireEvent = adapter.fireEvent,
+ washMouseEvent = adapter.washMouseEvent,
+ animate = adapter.animate,
+ stop = adapter.stop;
+
+
+
+/* ****************************************************************************
+ * Handle the options *
+ *****************************************************************************/
+var
+
+defaultLabelOptions = {
+ enabled: true,
+ // rotation: 0,
+ align: 'center',
+ x: 0,
+ y: 15,
+ /*formatter: function () {
+ return this.value;
+ },*/
+ style: {
+ color: '#666',
+ 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'],
+ shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
+ weekdays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
+ decimalPoint: '.',
+ numericSymbols: ['k', 'M', 'G', 'T', 'P', 'E'], // SI prefixes used in axis labels
+ resetZoom: 'Reset zoom',
+ resetZoomTitle: 'Reset zoom level 1:1',
+ thousandsSep: ','
+ },
+ global: {
+ useUTC: true,
+ canvasToolsURL: 'http://code.highcharts.com/2.3.5/modules/canvas-tools.js',
+ VMLRadialGradientURL: 'http://code.highcharts.com/2.3.5/gfx/vml-radial-gradient.png'
+ },
+ chart: {
+ //animation: true,
+ //alignTicks: false,
+ //reflow: true,
+ //className: null,
+ //events: { load, selection },
+ //margin: [null],
+ //marginTop: null,
+ //marginRight: null,
+ //marginBottom: null,
+ //marginLeft: null,
+ borderColor: '#4572A7',
+ //borderWidth: 0,
+ borderRadius: 5,
+ defaultSeriesType: 'line',
+ ignoreHiddenSeries: true,
+ //inverted: false,
+ //shadow: false,
+ spacingTop: 10,
+ spacingRight: 10,
+ spacingBottom: 15,
+ spacingLeft: 10,
+ style: {
+ fontFamily: '"Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, Helvetica, sans-serif', // default font
+ fontSize: '12px'
+ },
+ backgroundColor: '#FFFFFF',
+ //plotBackgroundColor: null,
+ plotBorderColor: '#C0C0C0',
+ //plotBorderWidth: 0,
+ //plotShadow: false,
+ //zoomType: ''
+ resetZoomButton: {
+ theme: {
+ zIndex: 20
+ },
+ position: {
+ align: 'right',
+ x: -10,
+ //verticalAlign: 'top',
+ y: 10
+ }
+ // relativeTo: 'plot'
+ }
+ },
+ title: {
+ text: 'Chart title',
+ align: 'center',
+ // floating: false,
+ // margin: 15,
+ // x: 0,
+ // verticalAlign: 'top',
+ y: 15,
+ style: {
+ color: '#3E576F',
+ fontSize: '16px'
+ }
+
+ },
+ subtitle: {
+ text: '',
+ align: 'center',
+ // floating: false
+ // x: 0,
+ // verticalAlign: 'top',
+ y: 30,
+ style: {
+ color: '#6D869F'
+ }
+ },
+
+ plotOptions: {
+ line: { // base series options
+ allowPointSelect: false,
+ showCheckbox: false,
+ animation: {
+ duration: 1000
+ },
+ //connectNulls: false,
+ //cursor: 'default',
+ //clip: true,
+ //dashStyle: null,
+ //enableMouseTracking: true,
+ events: {},
+ //legendIndex: 0,
+ lineWidth: 2,
+ shadow: true,
+ // stacking: null,
+ marker: {
+ enabled: true,
+ //symbol: null,
+ lineWidth: 0,
+ radius: 4,
+ lineColor: '#FFFFFF',
+ //fillColor: null,
+ states: { // states for a single point
+ hover: {
+ enabled: true
+ //radius: base + 2
+ },
+ select: {
+ fillColor: '#FFFFFF',
+ lineColor: '#000000',
+ lineWidth: 2
+ }
+ }
+ },
+ point: {
+ events: {}
+ },
+ dataLabels: merge(defaultLabelOptions, {
+ enabled: false,
+ formatter: function () {
+ return this.y;
+ },
+ verticalAlign: 'bottom', // above singular point
+ y: 0
+ // backgroundColor: undefined,
+ // borderColor: undefined,
+ // borderRadius: undefined,
+ // borderWidth: undefined,
+ // padding: 3,
+ // shadow: false
+ }),
+ cropThreshold: 300, // draw points outside the plot area when the number of points is less than this
+ pointRange: 0,
+ //pointStart: 0,
+ //pointInterval: 1,
+ showInLegend: true,
+ states: { // states for the entire series
+ hover: {
+ //enabled: false,
+ //lineWidth: base + 1,
+ marker: {
+ // lineWidth: base + 1,
+ // radius: base + 1
+ }
+ },
+ select: {
+ marker: {}
+ }
+ },
+ stickyTracking: true
+ //tooltip: {
+ //pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.y}</b>'
+ //valueDecimals: null,
+ //xDateFormat: '%A, %b %e, %Y',
+ //valuePrefix: '',
+ //ySuffix: ''
+ //}
+ // turboThreshold: 1000
+ // zIndex: null
+ }
+ },
+ labels: {
+ //items: [],
+ style: {
+ //font: defaultFont,
+ position: ABSOLUTE,
+ color: '#3E576F'
+ }
+ },
+ legend: {
+ enabled: true,
+ align: 'center',
+ //floating: false,
+ layout: 'horizontal',
+ labelFormatter: function () {
+ return this.name;
+ },
+ borderWidth: 1,
+ borderColor: '#909090',
+ borderRadius: 5,
+ navigation: {
+ // animation: true,
+ activeColor: '#3E576F',
+ // arrowSize: 12
+ inactiveColor: '#CCC'
+ // style: {} // text styles
+ },
+ // margin: 10,
+ // reversed: false,
+ shadow: false,
+ // backgroundColor: null,
+ /*style: {
+ padding: '5px'
+ },*/
+ itemStyle: {
+ cursor: 'pointer',
+ color: '#3E576F',
+ fontSize: '12px'
+ },
+ itemHoverStyle: {
+ //cursor: 'pointer', removed as of #601
+ color: '#000'
+ },
+ itemHiddenStyle: {
+ color: '#CCC'
+ },
+ itemCheckboxStyle: {
+ position: ABSOLUTE,
+ width: '13px', // for IE precision
+ height: '13px'
+ },
+ // itemWidth: undefined,
+ symbolWidth: 16,
+ symbolPadding: 5,
+ verticalAlign: 'bottom',
+ // width: undefined,
+ x: 0,
+ y: 0
+ },
+
+ loading: {
+ // hideDuration: 100,
+ labelStyle: {
+ fontWeight: 'bold',
+ position: RELATIVE,
+ top: '1em'
+ },
+ // showDuration: 0,
+ style: {
+ position: ABSOLUTE,
+ backgroundColor: 'white',
+ opacity: 0.5,
+ textAlign: 'center'
+ }
+ },
+
+ tooltip: {
+ enabled: true,
+ //crosshairs: null,
+ backgroundColor: 'rgba(255, 255, 255, .85)',
+ borderWidth: 2,
+ borderRadius: 5,
+ dateTimeLabelFormats: {
+ millisecond: '%A, %b %e, %H:%M:%S.%L',
+ second: '%A, %b %e, %H:%M:%S',
+ minute: '%A, %b %e, %H:%M',
+ hour: '%A, %b %e, %H:%M',
+ day: '%A, %b %e, %Y',
+ week: 'Week from %A, %b %e, %Y',
+ month: '%B %Y',
+ year: '%Y'
+ },
+ //formatter: defaultFormatter,
+ headerFormat: '<span style="font-size: 10px">{point.key}</span><br/>',
+ pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.y}</b><br/>',
+ shadow: true,
+ shared: useCanVG,
+ snap: isTouchDevice ? 25 : 10,
+ style: {
+ color: '#333333',
+ fontSize: '12px',
+ padding: '5px',
+ whiteSpace: 'nowrap'
+ }
+ //xDateFormat: '%A, %b %e, %Y',
+ //valueDecimals: null,
+ //valuePrefix: '',
+ //valueSuffix: ''
+ },
+
+ credits: {
+ enabled: true,
+ text: 'Highcharts.com',
+ href: 'http://www.highcharts.com',
+ position: {
+ align: 'right',
+ x: -10,
+ verticalAlign: 'bottom',
+ y: -5
+ },
+ style: {
+ cursor: 'pointer',
+ color: '#909090',
+ fontSize: '10px'
+ }
+ }
+};
+
+
+
+
+// Series defaults
+var defaultPlotOptions = defaultOptions.plotOptions,
+ defaultSeriesOptions = defaultPlotOptions.line;
+
+// set the default time methods
+setTimeMethods();
+
+
+
+/**
+ * Set the time methods globally based on the useUTC option. Time method can be either
+ * local time or UTC (default).
+ */
+function setTimeMethods() {
+ var useUTC = defaultOptions.global.useUTC,
+ GET = useUTC ? 'getUTC' : 'get',
+ SET = useUTC ? 'setUTC' : 'set';
+
+ makeTime = useUTC ? Date.UTC : function (year, month, date, hours, minutes, seconds) {
+ return new Date(
+ year,
+ month,
+ pick(date, 1),
+ pick(hours, 0),
+ pick(minutes, 0),
+ pick(seconds, 0)
+ ).getTime();
+ };
+ getMinutes = GET + 'Minutes';
+ getHours = GET + 'Hours';
+ getDay = GET + 'Day';
+ getDate = GET + 'Date';
+ getMonth = GET + 'Month';
+ getFullYear = GET + 'FullYear';
+ setMinutes = SET + 'Minutes';
+ setHours = SET + 'Hours';
+ setDate = SET + 'Date';
+ setMonth = SET + 'Month';
+ setFullYear = SET + 'FullYear';
+
+}
+
+/**
+ * Merge the default options with custom options and return the new options structure
+ * @param {Object} options The new custom options
+ */
+function setOptions(options) {
+
+ // Pull out axis options and apply them to the respective default axis options
+ /*defaultXAxisOptions = merge(defaultXAxisOptions, options.xAxis);
+ defaultYAxisOptions = merge(defaultYAxisOptions, options.yAxis);
+ options.xAxis = options.yAxis = UNDEFINED;*/
+
+ // Merge in the default options
+ defaultOptions = merge(defaultOptions, options);
+
+ // Apply UTC
+ setTimeMethods();
+
+ return defaultOptions;
+}
+
+/**
+ * Get the updated default options. Merely exposing defaultOptions for outside modules
+ * isn't enough because the setOptions method creates a new object.
+ */
+function getOptions() {
+ return defaultOptions;
+}
+
+
+
+/**
+ * Handle color operations. The object methods are chainable.
+ * @param {String} input The input color in either rbga or hex format
+ */
+var Color = function (input) {
+ // declare variables
+ var rgba = [], result;
+
+ /**
+ * Parse the input color to rgba array
+ * @param {String} input
+ */
+ function init(input) {
+
+ // rgba
+ result = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]?(?:\.[0-9]+)?)\s*\)/.exec(input);
+ if (result) {
+ rgba = [pInt(result[1]), pInt(result[2]), pInt(result[3]), parseFloat(result[4], 10)];
+ } else { // hex
+ result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(input);
+ if (result) {
+ rgba = [pInt(result[1], 16), pInt(result[2], 16), pInt(result[3], 16), 1];
+ }
+ }
+
+ }
+ /**
+ * 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
+ };
+};
+
+
+/**
+ * A wrapper object for SVG elements
+ */
+function SVGElement() {}
+
+SVGElement.prototype = {
+ /**
+ * Initialize the SVG renderer
+ * @param {Object} renderer
+ * @param {String} nodeName
+ */
+ init: function (renderer, nodeName) {
+ var wrapper = this;
+ wrapper.element = nodeName === 'span' ?
+ createElement(nodeName) :
+ doc.createElementNS(SVG_NS, nodeName);
+ wrapper.renderer = renderer;
+ /**
+ * A collection of attribute setters. These methods, if defined, are called right before a certain
+ * attribute is set on an element wrapper. Returning false prevents the default attribute
+ * setter to run. Returning a value causes the default setter to set that value. Used in
+ * Renderer.label.
+ */
+ wrapper.attrSetters = {};
+ },
+ /**
+ * Animate a given attribute
+ * @param {Object} params
+ * @param {Number} options The same options as in jQuery animation
+ * @param {Function} complete Function to perform at the end of animation
+ */
+ animate: function (params, options, complete) {
+ var animOptions = pick(options, globalAnimation, true);
+ stop(this); // stop regardless of animation actually running, or reverting to .attr (#607)
+ if (animOptions) {
+ animOptions = merge(animOptions);
+ if (complete) { // allows using a callback with the global animation without overwriting it
+ animOptions.complete = complete;
+ }
+ animate(this, params, animOptions);
+ } else {
+ this.attr(params);
+ if (complete) {
+ complete();
+ }
+ }
+ },
+ /**
+ * Set or get a given attribute
+ * @param {Object|String} hash
+ * @param {Mixed|Undefined} val
+ */
+ attr: function (hash, val) {
+ var wrapper = this,
+ key,
+ value,
+ result,
+ i,
+ child,
+ element = wrapper.element,
+ nodeName = element.nodeName.toLowerCase(), // Android2 requires lower for "text"
+ renderer = wrapper.renderer,
+ skipAttr,
+ titleNode,
+ attrSetters = wrapper.attrSetters,
+ shadows = wrapper.shadows,
+ hasSetSymbolSize,
+ doTransform,
+ ret = wrapper;
+
+ // single key-value pair
+ if (isString(hash) && defined(val)) {
+ key = hash;
+ hash = {};
+ hash[key] = val;
+ }
+
+ // used as a getter: first argument is a string, second is undefined
+ if (isString(hash)) {
+ key = hash;
+ if (nodeName === 'circle') {
+ key = { x: 'cx', y: 'cy' }[key] || key;
+ } else if (key === 'strokeWidth') {
+ key = 'stroke-width';
+ }
+ ret = attr(element, key) || wrapper[key] || 0;
+
+ if (key !== 'd' && key !== 'visibility') { // 'd' is string in animation step
+ ret = parseFloat(ret);
+ }
+
+ // setter
+ } else {
+
+ for (key in hash) {
+ skipAttr = false; // reset
+ value = hash[key];
+
+ // check for a specific attribute setter
+ result = attrSetters[key] && attrSetters[key].call(wrapper, value, key);
+
+ if (result !== false) {
+ if (result !== UNDEFINED) {
+ value = result; // the attribute setter has returned a new value to set
+ }
+
+ // paths
+ if (key === 'd') {
+ if (value && value.join) { // join path
+ value = value.join(' ');
+ }
+ if (/(NaN| {2}|^$)/.test(value)) {
+ value = 'M 0 0';
+ }
+ //wrapper.d = value; // shortcut for animations
+
+ // update child tspans x values
+ } else if (key === 'x' && nodeName === 'text') {
+ for (i = 0; i < element.childNodes.length; i++) {
+ child = element.childNodes[i];
+ // if the x values are equal, the tspan represents a linebreak
+ if (attr(child, 'x') === attr(element, 'x')) {
+ //child.setAttribute('x', value);
+ attr(child, 'x', value);
+ }
+ }
+
+ if (wrapper.rotation) {
+ attr(element, 'transform', 'rotate(' + wrapper.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;
+
+ // rectangle border radius
+ } else if (nodeName === 'rect' && key === 'r') {
+ attr(element, {
+ rx: value,
+ ry: value
+ });
+ skipAttr = true;
+
+ // translation and text rotation
+ } else if (key === 'translateX' || key === 'translateY' || key === 'rotation' || key === 'verticalAlign') {
+ doTransform = true;
+ skipAttr = true;
+
+ // apply opacity as subnode (required by legacy WebKit and Batik)
+ } else if (key === 'stroke') {
+ value = renderer.color(value, element, key);
+
+ // emulate VML's dashstyle implementation
+ } else if (key === 'dashstyle') {
+ key = 'stroke-dasharray';
+ value = value && value.toLowerCase();
+ if (value === 'solid') {
+ value = NONE;
+ } else if (value) {
+ value = value
+ .replace('shortdashdotdot', '3,1,1,1,1,1,')
+ .replace('shortdashdot', '3,1,1,1')
+ .replace('shortdot', '1,1,')
+ .replace('shortdash', '3,1,')
+ .replace('longdash', '8,3,')
+ .replace(/dot/g, '1,3,')
+ .replace('dash', '4,3,')
+ .replace(/,$/, '')
+ .split(','); // ending comma
+
+ i = value.length;
+ while (i--) {
+ value[i] = pInt(value[i]) * hash['stroke-width'];
+ }
+ value = value.join(',');
+ }
+
+ // special
+ } else if (key === 'isTracker') {
+ wrapper[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];
+
+ // Title requires a subnode, #431
+ } else if (key === 'title') {
+ titleNode = element.getElementsByTagName('title')[0];
+ if (!titleNode) {
+ titleNode = doc.createElementNS(SVG_NS, 'title');
+ element.appendChild(titleNode);
+ }
+ titleNode.textContent = value;
+ }
+
+ // jQuery animate changes case
+ if (key === 'strokeWidth') {
+ key = 'stroke-width';
+ }
+
+ // Chrome/Win < 6 bug (http://code.google.com/p/chromium/issues/detail?id=15461), #1369
+ if (key === 'stroke-width' && value === 0 && (isWebKit || renderer.forExport)) {
+ value = 0.000001;
+ }
+
+ // symbols
+ if (wrapper.symbolName && /^(x|y|width|height|r|start|end|innerR|anchorX|anchorY)/.test(key)) {
+
+
+ if (!hasSetSymbolSize) {
+ wrapper.symbolAttr(hash);
+ hasSetSymbolSize = true;
+ }
+ skipAttr = true;
+ }
+
+ // let the shadow follow the main element
+ if (shadows && /^(width|height|visibility|x|y|d|transform)$/.test(key)) {
+ i = shadows.length;
+ while (i--) {
+ attr(
+ shadows[i],
+ key,
+ key === 'height' ?
+ mathMax(value - (shadows[i].cutHeight || 0), 0) :
+ value
+ );
+ }
+ }
+
+ // validate heights
+ if ((key === 'width' || key === 'height') && nodeName === 'rect' && value < 0) {
+ value = 0;
+ }
+
+ // Record for animation and quick access without polling the DOM
+ wrapper[key] = value;
+
+ // Update transform
+ if (doTransform) {
+ wrapper.updateTransform();
+ }
+
+
+ if (key === 'text') {
+ // Delete bBox memo when the text changes
+ if (value !== wrapper.textStr) {
+ delete wrapper.bBox;
+ }
+ wrapper.textStr = value;
+ if (wrapper.added) {
+ renderer.buildText(wrapper);
+ }
+ } else if (!skipAttr) {
+ attr(element, key, value);
+ }
+
+ }
+
+ }
+
+ }
+
+ 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;
+
+ each(['x', 'y', 'r', 'start', 'end', 'width', 'height', 'innerR', 'anchorX', 'anchorY'], function (key) {
+ wrapper[key] = pick(hash[key], wrapper[key]);
+ });
+
+ wrapper.attr({
+ d: wrapper.renderer.symbols[wrapper.symbolName](wrapper.x, wrapper.y, wrapper.width, wrapper.height, wrapper)
+ });
+ },
+
+ /**
+ * Apply a clipping path to this object
+ * @param {String} id
+ */
+ clip: function (clipRect) {
+ return this.attr('clip-path', clipRect ? 'url(' + this.renderer.url + '#' + clipRect.id + ')' : NONE);
+ },
+
+ /**
+ * Calculate the coordinates needed for drawing a rectangle crisply and return the
+ * calculated attributes
+ * @param {Number} strokeWidth
+ * @param {Number} x
+ * @param {Number} y
+ * @param {Number} width
+ * @param {Number} height
+ */
+ crisp: function (strokeWidth, x, y, width, height) {
+
+ var wrapper = this,
+ key,
+ attribs = {},
+ values = {},
+ normalizer;
+
+ strokeWidth = strokeWidth || wrapper.strokeWidth || (wrapper.attr && wrapper.attr('stroke-width')) || 0;
+ normalizer = mathRound(strokeWidth) % 2 / 2; // mathRound because strokeWidth can sometimes have roundoff errors
+
+ // normalize for crisp edges
+ values.x = mathFloor(x || wrapper.x || 0) + normalizer;
+ values.y = mathFloor(y || wrapper.y || 0) + normalizer;
+ values.width = mathFloor((width || wrapper.width || 0) - 2 * normalizer);
+ values.height = mathFloor((height || wrapper.height || 0) - 2 * normalizer);
+ values.strokeWidth = strokeWidth;
+
+ for (key in values) {
+ if (wrapper[key] !== values[key]) { // only set attribute if changed
+ wrapper[key] = attribs[key] = values[key];
+ }
+ }
+
+ return attribs;
+ },
+
+ /**
+ * Set styles for the element
+ * @param {Object} styles
+ */
+ css: function (styles) {
+ /*jslint unparam: true*//* allow unused param a in the regexp function below */
+ var elemWrapper = this,
+ elem = elemWrapper.element,
+ textWidth = styles && styles.width && elem.nodeName.toLowerCase() === 'text',
+ n,
+ serializedCss = '',
+ hyphenate = function (a, b) { return '-' + b.toLowerCase(); };
+ /*jslint unparam: false*/
+
+ // convert legacy
+ if (styles && styles.color) {
+ styles.fill = styles.color;
+ }
+
+ // Merge the new styles with the old ones
+ styles = extend(
+ elemWrapper.styles,
+ styles
+ );
+
+ // store object
+ elemWrapper.styles = styles;
+
+
+ // Don't handle line wrap on canvas
+ if (useCanVG && textWidth) {
+ delete styles.width;
+ }
+
+ // serialize and set style attribute
+ if (isIE && !hasSVG) { // legacy IE doesn't support setting style attribute
+ if (textWidth) {
+ delete styles.width;
+ }
+ css(elemWrapper.element, styles);
+ } else {
+ for (n in styles) {
+ serializedCss += n.replace(/([A-Z])/g, hyphenate) + ':' + styles[n] + ';';
+ }
+ elemWrapper.attr({
+ style: serializedCss
+ });
+ }
+
+
+ // re-build text
+ if (textWidth && elemWrapper.added) {
+ elemWrapper.renderer.buildText(elemWrapper);
+ }
+
+ return elemWrapper;
+ },
+
+ /**
+ * Add an event listener
+ * @param {String} eventType
+ * @param {Function} handler
+ */
+ on: function (eventType, handler) {
+ // touch
+ if (hasTouch && eventType === 'click') {
+ this.element.ontouchstart = function (e) {
+ e.preventDefault();
+ handler();
+ };
+ }
+ // simplest possible event model for internal use
+ this.element['on' + eventType] = handler;
+ return this;
+ },
+
+ /**
+ * Set the coordinates needed to draw a consistent radial gradient across
+ * pie slices regardless of positioning inside the chart. The format is
+ * [centerX, centerY, diameter] in pixels.
+ */
+ setRadialReference: function (coordinates) {
+ this.element.radialReference = coordinates;
+ return this;
+ },
+
+ /**
+ * Move an object and its children by x and y values
+ * @param {Number} x
+ * @param {Number} y
+ */
+ translate: function (x, y) {
+ return this.attr({
+ translateX: x,
+ translateY: y
+ });
+ },
+
+ /**
+ * Invert a group, rotate and flip
+ */
+ invert: function () {
+ var wrapper = this;
+ wrapper.inverted = true;
+ wrapper.updateTransform();
+ return wrapper;
+ },
+
+ /**
+ * Apply CSS to HTML elements. This is used in text within SVG rendering and
+ * by the VML renderer
+ */
+ htmlCss: function (styles) {
+ var wrapper = this,
+ element = wrapper.element,
+ textWidth = styles && element.tagName === 'SPAN' && styles.width;
+
+ if (textWidth) {
+ delete styles.width;
+ wrapper.textWidth = textWidth;
+ wrapper.updateTransform();
+ }
+
+ wrapper.styles = extend(wrapper.styles, styles);
+ css(wrapper.element, styles);
+
+ return wrapper;
+ },
+
+
+
+ /**
+ * VML and useHTML method for calculating the bounding box based on offsets
+ * @param {Boolean} refresh Whether to force a fresh value from the DOM or to
+ * use the cached value
+ *
+ * @return {Object} A hash containing values for x, y, width and height
+ */
+
+ htmlGetBBox: function () {
+ var wrapper = this,
+ element = wrapper.element,
+ bBox = wrapper.bBox;
+
+ // faking getBBox in exported SVG in legacy IE
+ if (!bBox) {
+ // faking getBBox in exported SVG in legacy IE (is this a duplicate of the fix for #1079?)
+ if (element.nodeName === 'text') {
+ element.style.position = ABSOLUTE;
+ }
+
+ bBox = wrapper.bBox = {
+ x: element.offsetLeft,
+ y: element.offsetTop,
+ width: element.offsetWidth,
+ height: element.offsetHeight
+ };
+ }
+
+ return bBox;
+ },
+
+ /**
+ * VML override private method to update elements based on internal
+ * properties based on SVG transform
+ */
+ htmlUpdateTransform: function () {
+ // aligning non added elements is expensive
+ if (!this.added) {
+ this.alignOnAdd = true;
+ return;
+ }
+
+ var wrapper = this,
+ renderer = wrapper.renderer,
+ elem = wrapper.element,
+ translateX = wrapper.translateX || 0,
+ translateY = wrapper.translateY || 0,
+ x = wrapper.x || 0,
+ y = wrapper.y || 0,
+ align = wrapper.textAlign || 'left',
+ alignCorrection = { left: 0, center: 0.5, right: 1 }[align],
+ nonLeft = align && align !== 'left',
+ shadows = wrapper.shadows;
+
+ // apply translate
+ if (translateX || translateY) {
+ css(elem, {
+ marginLeft: translateX,
+ marginTop: translateY
+ });
+ if (shadows) { // used in labels/tooltip
+ each(shadows, function (shadow) {
+ css(shadow, {
+ marginLeft: translateX + 1,
+ marginTop: translateY + 1
+ });
+ });
+ }
+ }
+
+ // apply inversion
+ if (wrapper.inverted) { // wrapper is a group
+ each(elem.childNodes, function (child) {
+ renderer.invertChild(child, elem);
+ });
+ }
+
+ if (elem.tagName === 'SPAN') {
+
+ var width, height,
+ rotation = wrapper.rotation,
+ baseline,
+ radians = 0,
+ costheta = 1,
+ sintheta = 0,
+ quad,
+ textWidth = pInt(wrapper.textWidth),
+ xCorr = wrapper.xCorr || 0,
+ yCorr = wrapper.yCorr || 0,
+ currentTextTransform = [rotation, align, elem.innerHTML, wrapper.textWidth].join(','),
+ rotationStyle = {},
+ cssTransformKey;
+
+ if (currentTextTransform !== wrapper.cTT) { // do the calculations and DOM access only if properties changed
+
+ if (defined(rotation)) {
+
+ if (renderer.isSVG) { // #916
+ cssTransformKey = isIE ? '-ms-transform' : isWebKit ? '-webkit-transform' : isFirefox ? 'MozTransform' : isOpera ? '-o-transform' : '';
+ rotationStyle[cssTransformKey] = rotationStyle.transform = 'rotate(' + rotation + 'deg)';
+
+ } else {
+ radians = rotation * deg2rad; // deg to rad
+ costheta = mathCos(radians);
+ sintheta = mathSin(radians);
+
+ // Adjust for alignment and rotation. Rotation of useHTML content is not yet implemented
+ // but it can probably be implemented for Firefox 3.5+ on user request. FF3.5+
+ // has support for CSS3 transform. The getBBox method also needs to be updated
+ // to compensate for the rotation, like it currently does for SVG.
+ // Test case: http://highcharts.com/tests/?file=text-rotation
+ rotationStyle.filter = rotation ? ['progid:DXImageTransform.Microsoft.Matrix(M11=', costheta,
+ ', M12=', -sintheta, ', M21=', sintheta, ', M22=', costheta,
+ ', sizingMethod=\'auto expand\')'].join('') : NONE;
+ }
+ css(elem, rotationStyle);
+ }
+
+ width = pick(wrapper.elemWidth, elem.offsetWidth);
+ height = pick(wrapper.elemHeight, elem.offsetHeight);
+
+ // update textWidth
+ if (width > textWidth && /[ \-]/.test(elem.textContent || elem.innerText)) { // #983, #1254
+ css(elem, {
+ width: textWidth + PX,
+ display: 'block',
+ whiteSpace: 'normal'
+ });
+ width = textWidth;
+ }
+
+ // correct x and y
+ baseline = renderer.fontMetrics(elem.style.fontSize).b;
+ xCorr = costheta < 0 && -width;
+ yCorr = sintheta < 0 && -height;
+
+ // correct for baseline and corners spilling out after rotation
+ quad = costheta * sintheta < 0;
+ xCorr += sintheta * baseline * (quad ? 1 - alignCorrection : alignCorrection);
+ yCorr -= costheta * baseline * (rotation ? (quad ? alignCorrection : 1 - alignCorrection) : 1);
+
+ // correct for the length/height of the text
+ if (nonLeft) {
+ xCorr -= width * alignCorrection * (costheta < 0 ? -1 : 1);
+ if (rotation) {
+ yCorr -= height * alignCorrection * (sintheta < 0 ? -1 : 1);
+ }
+ css(elem, {
+ textAlign: align
+ });
+ }
+
+ // record correction
+ wrapper.xCorr = xCorr;
+ wrapper.yCorr = yCorr;
+ }
+
+ // apply position with correction
+ css(elem, {
+ left: (x + xCorr) + PX,
+ top: (y + yCorr) + PX
+ });
+
+ // force reflow in webkit to apply the left and top on useHTML element (#1249)
+ if (isWebKit) {
+ height = elem.offsetHeight; // assigned to height for JSLint purpose
+ }
+
+ // record current text transform
+ wrapper.cTT = currentTextTransform;
+ }
+ },
+
+ /**
+ * 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 || 0) + ' ' + (wrapper.y || 0) + ')');
+ }
+
+ 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) {
+ var elemWrapper = this;
+
+ if (!alignOptions) { // called on resize
+ alignOptions = elemWrapper.alignOptions;
+ alignByTranslate = elemWrapper.alignByTranslate;
+ } else { // first call on instanciate
+ elemWrapper.alignOptions = alignOptions;
+ elemWrapper.alignByTranslate = alignByTranslate;
+ if (!box) { // boxes other than renderer handle this internally
+ elemWrapper.renderer.alignedObjects.push(elemWrapper);
+ }
+ }
+
+ box = pick(box, elemWrapper.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 (align === 'right' || align === 'center') {
+ x += (box.width - (alignOptions.width || 0)) /
+ { right: 1, center: 2 }[align];
+ }
+ attribs[alignByTranslate ? 'translateX' : 'x'] = mathRound(x);
+
+
+ // vertical align
+ if (vAlign === 'bottom' || vAlign === 'middle') {
+ y += (box.height - (alignOptions.height || 0)) /
+ ({ bottom: 1, middle: 2 }[vAlign] || 1);
+
+ }
+ attribs[alignByTranslate ? 'translateY' : 'y'] = mathRound(y);
+
+ // animate only if already placed
+ elemWrapper[elemWrapper.placed ? 'animate' : 'attr'](attribs);
+ elemWrapper.placed = true;
+ elemWrapper.alignAttr = attribs;
+
+ return elemWrapper;
+ },
+
+ /**
+ * Get the bounding box (width, height, x and y) for the element
+ */
+ getBBox: function () {
+ var wrapper = this,
+ bBox = wrapper.bBox,
+ renderer = wrapper.renderer,
+ width,
+ height,
+ rotation = wrapper.rotation,
+ element = wrapper.element,
+ styles = wrapper.styles,
+ rad = rotation * deg2rad;
+
+ if (!bBox) {
+ // SVG elements
+ if (element.namespaceURI === SVG_NS || renderer.forExport) {
+ try { // Fails in Firefox if the container has display: none.
+
+ bBox = element.getBBox ?
+ // SVG: use extend because IE9 is not allowed to change width and height in case
+ // of rotation (below)
+ extend({}, element.getBBox()) :
+ // Canvas renderer and legacy IE in export mode
+ {
+ width: element.offsetWidth,
+ height: element.offsetHeight
+ };
+ } catch (e) {}
+
+ // If the bBox is not set, the try-catch block above failed. The other condition
+ // is for Opera that returns a width of -Infinity on hidden elements.
+ if (!bBox || bBox.width < 0) {
+ bBox = { width: 0, height: 0 };
+ }
+
+
+ // VML Renderer or useHTML within SVG
+ } else {
+
+ bBox = wrapper.htmlGetBBox();
+
+ }
+
+ // True SVG elements as well as HTML elements in modern browsers using the .useHTML option
+ // need to compensated for rotation
+ if (renderer.isSVG) {
+ width = bBox.width;
+ height = bBox.height;
+
+ // Workaround for wrong bounding box in IE9 and IE10 (#1101)
+ if (isIE && styles && styles.fontSize === '11px' && height === 22.700000762939453) {
+ bBox.height = height = 14;
+ }
+
+ // Adjust for rotated text
+ if (rotation) {
+ bBox.width = mathAbs(height * mathSin(rad)) + mathAbs(width * mathCos(rad));
+ bBox.height = mathAbs(height * mathCos(rad)) + mathAbs(width * mathSin(rad));
+ }
+ }
+
+ wrapper.bBox = bBox;
+ }
+ return bBox;
+ },
+
+ /**
+ * Show the element
+ */
+ show: function () {
+ return this.attr({ visibility: VISIBLE });
+ },
+
+ /**
+ * Hide the element
+ */
+ hide: function () {
+ return this.attr({ visibility: HIDDEN });
+ },
+
+ /**
+ * Add the element
+ * @param {Object|Undefined} parent Can be an element, an element wrapper or undefined
+ * to append the element to the renderer.box.
+ */
+ add: function (parent) {
+
+ var renderer = this.renderer,
+ parentWrapper = parent || renderer,
+ parentNode = parentWrapper.element || renderer.box,
+ childNodes = parentNode.childNodes,
+ element = this.element,
+ zIndex = attr(element, 'zIndex'),
+ otherElement,
+ otherZIndex,
+ i,
+ inserted;
+
+ if (parent) {
+ this.parentGroup = parent;
+ }
+
+ // mark as inverted
+ this.parentInverted = parent && parent.inverted;
+
+ // build formatted text
+ if (this.textStr !== undefined) {
+ renderer.buildText(this);
+ }
+
+ // mark the container as having z indexed children
+ if (zIndex) {
+ parentWrapper.handleZ = true;
+ zIndex = pInt(zIndex);
+ }
+
+ // insert according to this and other elements' zIndex
+ if (parentWrapper.handleZ) { // this element or any of its siblings has a z index
+ for (i = 0; i < childNodes.length; i++) {
+ otherElement = childNodes[i];
+ otherZIndex = attr(otherElement, 'zIndex');
+ if (otherElement !== element && (
+ // insert before the first element with a higher zIndex
+ pInt(otherZIndex) > zIndex ||
+ // if no zIndex given, insert before the first element with a zIndex
+ (!defined(zIndex) && defined(otherZIndex))
+
+ )) {
+ parentNode.insertBefore(element, otherElement);
+ inserted = true;
+ break;
+ }
+ }
+ }
+
+ // default: append at the end
+ if (!inserted) {
+ parentNode.appendChild(element);
+ }
+
+ // mark as added
+ this.added = true;
+
+ // fire an event for internal hooks
+ fireEvent(this, 'add');
+
+ return this;
+ },
+
+ /**
+ * Removes a child either by removeChild or move to garbageBin.
+ * Issue 490; in VML removeChild results in Orphaned nodes according to sIEve, discardElement does not.
+ */
+ safeRemoveChild: function (element) {
+ var parentNode = element.parentNode;
+ if (parentNode) {
+ parentNode.removeChild(element);
+ }
+ },
+
+ /**
+ * Destroy the element and element wrapper
+ */
+ destroy: function () {
+ var wrapper = this,
+ element = wrapper.element || {},
+ shadows = wrapper.shadows,
+ key,
+ i;
+
+ // remove events
+ element.onclick = element.onmouseout = element.onmouseover = element.onmousemove = null;
+ stop(wrapper); // stop running animations
+
+ if (wrapper.clipPath) {
+ wrapper.clipPath = wrapper.clipPath.destroy();
+ }
+
+ // Destroy stops in case this is a gradient object
+ if (wrapper.stops) {
+ for (i = 0; i < wrapper.stops.length; i++) {
+ wrapper.stops[i] = wrapper.stops[i].destroy();
+ }
+ wrapper.stops = null;
+ }
+
+ // remove element
+ wrapper.safeRemoveChild(element);
+
+ // destroy shadows
+ if (shadows) {
+ each(shadows, function (shadow) {
+ wrapper.safeRemoveChild(shadow);
+ });
+ }
+
+ // 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|Object} shadowOptions
+ */
+ shadow: function (shadowOptions, group, cutOff) {
+ var shadows = [],
+ i,
+ shadow,
+ element = this.element,
+ strokeWidth,
+ shadowWidth,
+ shadowElementOpacity,
+
+ // compensate for inverted plot area
+ transform;
+
+
+ if (shadowOptions) {
+ shadowWidth = pick(shadowOptions.width, 3);
+ shadowElementOpacity = (shadowOptions.opacity || 0.15) / shadowWidth;
+ transform = this.parentInverted ?
+ '(-1,-1)' :
+ '(' + pick(shadowOptions.offsetX, 1) + ', ' + pick(shadowOptions.offsetY, 1) + ')';
+ for (i = 1; i <= shadowWidth; i++) {
+ shadow = element.cloneNode(0);
+ strokeWidth = (shadowWidth * 2) + 1 - (2 * i);
+ attr(shadow, {
+ 'isShadow': 'true',
+ 'stroke': shadowOptions.color || 'black',
+ 'stroke-opacity': shadowElementOpacity * i,
+ 'stroke-width': strokeWidth,
+ 'transform': 'translate' + transform,
+ 'fill': NONE
+ });
+ if (cutOff) {
+ attr(shadow, 'height', mathMax(attr(shadow, 'height') - strokeWidth, 0));
+ shadow.cutHeight = strokeWidth;
+ }
+
+ if (group) {
+ group.element.appendChild(shadow);
+ } else {
+ element.parentNode.insertBefore(shadow, element);
+ }
+
+ shadows.push(shadow);
+ }
+
+ this.shadows = shadows;
+ }
+ return this;
+
+ }
+};
+
+
+/**
+ * The default SVG renderer
+ */
+var SVGRenderer = function () {
+ this.init.apply(this, arguments);
+};
+SVGRenderer.prototype = {
+ Element: SVGElement,
+
+ /**
+ * Initialize the SVGRenderer
+ * @param {Object} container
+ * @param {Number} width
+ * @param {Number} height
+ * @param {Boolean} forExport
+ */
+ init: function (container, width, height, forExport) {
+ var renderer = this,
+ loc = location,
+ boxWrapper;
+
+ boxWrapper = renderer.createElement('svg')
+ .attr({
+ xmlns: SVG_NS,
+ version: '1.1'
+ });
+ container.appendChild(boxWrapper.element);
+
+ // object properties
+ renderer.isSVG = true;
+ renderer.box = boxWrapper.element;
+ renderer.boxWrapper = boxWrapper;
+ renderer.alignedObjects = [];
+
+ // Page url used for internal references. #24, #672, #1070
+ renderer.url = (isFirefox || isWebKit) && doc.getElementsByTagName('base').length ?
+ loc.href
+ .replace(/#.*?$/, '') // remove the hash
+ .replace(/([\('\)])/g, '\\$1') // escape parantheses and quotes
+ .replace(/ /g, '%20') : // replace spaces (needed for Safari only)
+ '';
+
+ renderer.defs = this.createElement('defs').add();
+ renderer.forExport = forExport;
+ renderer.gradients = {}; // Object where gradient SvgElements are stored
+
+ renderer.setSize(width, height, false);
+
+
+
+ // Issue 110 workaround:
+ // In Firefox, if a div is positioned by percentage, its pixel position may land
+ // between pixels. The container itself doesn't display this, but an SVG element
+ // inside this container will be drawn at subpixel precision. In order to draw
+ // sharp lines, this must be compensated for. This doesn't seem to work inside
+ // iframes though (like in jsFiddle).
+ var subPixelFix, rect;
+ if (isFirefox && container.getBoundingClientRect) {
+ renderer.subPixelFix = subPixelFix = function () {
+ css(container, { left: 0, top: 0 });
+ rect = container.getBoundingClientRect();
+ css(container, {
+ left: (mathCeil(rect.left) - rect.left) + PX,
+ top: (mathCeil(rect.top) - rect.top) + PX
+ });
+ };
+
+ // run the fix now
+ subPixelFix();
+
+ // run it on resize
+ addEvent(win, 'resize', subPixelFix);
+ }
+ },
+
+ /**
+ * Detect whether the renderer is hidden. This happens when one of the parent elements
+ * has display: none. #608.
+ */
+ isHidden: function () {
+ return !this.boxWrapper.getBBox().width;
+ },
+
+ /**
+ * Destroys the renderer and its allocated members.
+ */
+ destroy: function () {
+ var renderer = this,
+ rendererDefs = renderer.defs;
+ renderer.box = null;
+ renderer.boxWrapper = renderer.boxWrapper.destroy();
+
+ // Call destroy on all gradient elements
+ destroyObjectProperties(renderer.gradients || {});
+ renderer.gradients = null;
+
+ // Defs are null in VMLRenderer
+ // Otherwise, destroy them here.
+ if (rendererDefs) {
+ renderer.defs = rendererDefs.destroy();
+ }
+
+ // Remove sub pixel fix handler
+ // We need to check that there is a handler, otherwise all functions that are registered for event 'resize' are removed
+ // See issue #982
+ if (renderer.subPixelFix) {
+ removeEvent(win, 'resize', renderer.subPixelFix);
+ }
+
+ renderer.alignedObjects = null;
+
+ return null;
+ },
+
+ /**
+ * Create a wrapper for an SVG element
+ * @param {Object} nodeName
+ */
+ createElement: function (nodeName) {
+ var wrapper = new this.Element();
+ wrapper.init(this, nodeName);
+ return wrapper;
+ },
+
+ /**
+ * Dummy function for use in canvas renderer
+ */
+ draw: function () {},
+
+ /**
+ * Parse a simple HTML string into SVG tspans
+ *
+ * @param {Object} textNode The parent text SVG node
+ */
+ buildText: function (wrapper) {
+ var textNode = wrapper.element,
+ 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 && textStyles.width && pInt(textStyles.width),
+ textLineHeight = textStyles && textStyles.lineHeight,
+ lastLine,
+ GET_COMPUTED_STYLE = 'getComputedStyle',
+ i = childNodes.length,
+ linePositions = [];
+
+ // Needed in IE9 because it doesn't report tspan's offsetHeight (#893)
+ function getLineHeightByBBox(lineNo) {
+ linePositions[lineNo] = textNode.getBBox ?
+ textNode.getBBox().height :
+ wrapper.renderer.fontMetrics(textNode.style.fontSize).h; // #990
+ return mathRound(linePositions[lineNo] - (linePositions[lineNo - 1] || 0));
+ }
+
+ // remove old text
+ while (i--) {
+ textNode.removeChild(childNodes[i]);
+ }
+
+ if (width && !wrapper.added) {
+ this.box.appendChild(textNode); // attach it to the DOM to read offset width
+ }
+
+ // remove empty line at end
+ if (lines[lines.length - 1] === '') {
+ lines.pop();
+ }
+
+ // build the lines
+ each(lines, function (line, lineNo) {
+ var spans, spanNo = 0, 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'),
+ spanStyle; // #390
+ if (styleRegex.test(span)) {
+ spanStyle = span.match(styleRegex)[1].replace(/(;| |^)color([ :])/, '$1fill$2');
+ attr(tspan, 'style', spanStyle);
+ }
+ if (hrefRegex.test(span)) {
+ attr(tspan, 'onclick', 'location.href=\"' + span.match(hrefRegex)[1] + '\"');
+ css(tspan, { cursor: 'pointer' });
+ }
+
+ span = (span.replace(/<(.|\n)*?>/g, '') || ' ')
+ .replace(/</g, '<')
+ .replace(/>/g, '>');
+
+ // issue #38 workaround.
+ /*if (reverse) {
+ arr = [];
+ i = span.length;
+ while (i--) {
+ arr.push(span.charAt(i));
+ }
+ span = arr.join('');
+ }*/
+
+ // add the text node
+ tspan.appendChild(doc.createTextNode(span));
+
+ if (!spanNo) { // first span in a line, align it to the left
+ attributes.x = parentX;
+ } else {
+ // 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) {
+
+ // allow getting the right offset height in exporting in IE
+ if (!hasSVG && wrapper.renderer.forExport) {
+ css(tspan, { display: 'block' });
+ }
+
+ // Webkit and opera sometimes return 'normal' as the line height. In that
+ // case, webkit uses offsetHeight, while Opera falls back to 18
+ lineHeight = win[GET_COMPUTED_STYLE] &&
+ pInt(win[GET_COMPUTED_STYLE](lastLine, null).getPropertyValue('line-height'));
+
+ if (!lineHeight || isNaN(lineHeight)) {
+ lineHeight = textLineHeight || lastLine.offsetHeight || getLineHeightByBBox(lineNo) || 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, '$1- ').split(' '), // #1273
+ tooLong,
+ actualWidth,
+ rest = [];
+
+ while (words.length || rest.length) {
+ delete wrapper.bBox; // delete cache
+ actualWidth = wrapper.getBBox().width;
+ tooLong = actualWidth > width;
+ if (!tooLong || words.length === 1) { // new line needed
+ words = rest;
+ rest = [];
+ if (words.length) {
+ tspan = doc.createElementNS(SVG_NS, 'tspan');
+ attr(tspan, {
+ dy: textLineHeight || 16,
+ x: parentX
+ });
+ if (spanStyle) { // #390
+ attr(tspan, 'style', spanStyle);
+ }
+ textNode.appendChild(tspan);
+
+ if (actualWidth > width) { // a single word is pressing it out
+ width = actualWidth;
+ }
+ }
+ } else { // append to existing line tspan
+ tspan.removeChild(tspan.firstChild);
+ rest.unshift(words.pop());
+ }
+ if (words.length) {
+ tspan.appendChild(doc.createTextNode(words.join(' ').replace(/- /g, '-')));
+ }
+ }
+ }
+ }
+ });
+ });
+ },
+
+ /**
+ * Create a button with preset states
+ * @param {String} text
+ * @param {Number} x
+ * @param {Number} y
+ * @param {Function} callback
+ * @param {Object} normalState
+ * @param {Object} hoverState
+ * @param {Object} pressedState
+ */
+ button: function (text, x, y, callback, normalState, hoverState, pressedState) {
+ var label = this.label(text, x, y),
+ curState = 0,
+ stateOptions,
+ stateStyle,
+ normalStyle,
+ hoverStyle,
+ pressedStyle,
+ STYLE = 'style',
+ verticalGradient = { x1: 0, y1: 0, x2: 0, y2: 1 };
+
+ // prepare the attributes
+ /*jslint white: true*/
+ normalState = merge(hash(
+ STROKE_WIDTH, 1,
+ STROKE, '#999',
+ FILL, hash(
+ LINEAR_GRADIENT, verticalGradient,
+ STOPS, [
+ [0, '#FFF'],
+ [1, '#DDD']
+ ]
+ ),
+ 'r', 3,
+ 'padding', 3,
+ STYLE, hash(
+ 'color', 'black'
+ )
+ ), normalState);
+ /*jslint white: false*/
+ normalStyle = normalState[STYLE];
+ delete normalState[STYLE];
+
+ /*jslint white: true*/
+ hoverState = merge(normalState, hash(
+ STROKE, '#68A',
+ FILL, hash(
+ LINEAR_GRADIENT, verticalGradient,
+ STOPS, [
+ [0, '#FFF'],
+ [1, '#ACF']
+ ]
+ )
+ ), hoverState);
+ /*jslint white: false*/
+ hoverStyle = hoverState[STYLE];
+ delete hoverState[STYLE];
+
+ /*jslint white: true*/
+ pressedState = merge(normalState, hash(
+ STROKE, '#68A',
+ FILL, hash(
+ LINEAR_GRADIENT, verticalGradient,
+ STOPS, [
+ [0, '#9BD'],
+ [1, '#CDF']
+ ]
+ )
+ ), pressedState);
+ /*jslint white: false*/
+ pressedStyle = pressedState[STYLE];
+ delete pressedState[STYLE];
+
+ // add the events
+ addEvent(label.element, 'mouseenter', function () {
+ label.attr(hoverState)
+ .css(hoverStyle);
+ });
+ addEvent(label.element, 'mouseleave', function () {
+ stateOptions = [normalState, hoverState, pressedState][curState];
+ stateStyle = [normalStyle, hoverStyle, pressedStyle][curState];
+ label.attr(stateOptions)
+ .css(stateStyle);
+ });
+
+ label.setState = function (state) {
+ curState = state;
+ if (!state) {
+ label.attr(normalState)
+ .css(normalStyle);
+ } else if (state === 2) {
+ label.attr(pressedState)
+ .css(pressedStyle);
+ }
+ };
+
+ return label
+ .on('click', function () {
+ callback.call(label);
+ })
+ .attr(normalState)
+ .css(extend({ cursor: 'default' }, normalStyle));
+ },
+
+ /**
+ * Make a straight line crisper by not spilling out to neighbour pixels
+ * @param {Array} points
+ * @param {Number} width
+ */
+ crispLine: function (points, width) {
+ // points format: [M, 0, 0, L, 100, 0]
+ // normalize to a crisp line
+ if (points[1] === points[4]) {
+ // Substract due to #1129. Now bottom and left axis gridlines behave the same.
+ points[1] = points[4] = mathRound(points[1]) - (width % 2 / 2);
+ }
+ if (points[2] === points[5]) {
+ points[2] = points[5] = mathRound(points[2]) + (width % 2 / 2);
+ }
+ return points;
+ },
+
+
+ /**
+ * Draw a path
+ * @param {Array} path An SVG path in array form
+ */
+ path: function (path) {
+ var attr = {
+ fill: NONE
+ };
+ if (isArray(path)) {
+ attr.d = path;
+ } else if (isObject(path)) { // attributes
+ extend(attr, path);
+ }
+ return this.createElement('path').attr(attr);
+ },
+
+ /**
+ * Draw and return an SVG circle
+ * @param {Number} x The x position
+ * @param {Number} y The y position
+ * @param {Number} r The radius
+ */
+ circle: function (x, y, r) {
+ var attr = isObject(x) ?
+ x :
+ {
+ x: x,
+ y: y,
+ r: r
+ };
+
+ return this.createElement('circle').attr(attr);
+ },
+
+ /**
+ * Draw and return an arc
+ * @param {Number} x X position
+ * @param {Number} y Y position
+ * @param {Number} r Radius
+ * @param {Number} innerR Inner radius like used in donut charts
+ * @param {Number} start Starting angle
+ * @param {Number} end Ending angle
+ */
+ arc: function (x, y, r, innerR, start, end) {
+ // 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, 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) {
+
+ r = isObject(x) ? x.r : r;
+
+ var wrapper = this.createElement('rect').attr({
+ rx: r,
+ ry: r,
+ fill: NONE
+ });
+ return wrapper.attr(
+ isObject(x) ?
+ x :
+ // do not crispify when an object is passed in (as in column charts)
+ wrapper.crisp(strokeWidth, x, y, mathMax(width, 0), mathMax(height, 0))
+ );
+ },
+
+ /**
+ * Resize the box and re-align all aligned elements
+ * @param {Object} width
+ * @param {Object} height
+ * @param {Boolean} animate
+ *
+ */
+ setSize: function (width, height, animate) {
+ var renderer = this,
+ alignedObjects = renderer.alignedObjects,
+ i = alignedObjects.length;
+
+ renderer.width = width;
+ renderer.height = height;
+
+ renderer.boxWrapper[pick(animate, true) ? 'animate' : 'attr']({
+ width: width,
<TRUNCATED>