app/assets/javascripts/highcharts.js in highcharts-rails-3.0.3 vs app/assets/javascripts/highcharts.js in highcharts-rails-3.0.5
- old
+ new
@@ -1,16676 +1,280 @@
-// ==ClosureCompiler==
-// @compilation_level SIMPLE_OPTIMIZATIONS
+/*
+ Highcharts JS v3.0.5 (2013-08-23)
-/**
- * @license Highcharts JS v3.0.3 (2013-07-31)
- *
- * (c) 2009-2013 Torstein Hønsi
- *
- * License: www.highcharts.com/license
- */
+ (c) 2009-2013 Torstein Hønsi
-// JSLint options:
-/*global Highcharts, document, window, navigator, setInterval, clearInterval, clearTimeout, setTimeout, location, jQuery, $, console, each, grep */
-
-(function () {
-// encapsulated variables
-var UNDEFINED,
- doc = document,
- win = window,
- math = Math,
- mathRound = math.round,
- mathFloor = math.floor,
- mathCeil = math.ceil,
- mathMax = math.max,
- mathMin = math.min,
- mathAbs = math.abs,
- mathCos = math.cos,
- mathSin = math.sin,
- mathPI = math.PI,
- deg2rad = mathPI * 2 / 360,
-
-
- // some variables
- userAgent = navigator.userAgent,
- isOpera = win.opera,
- isIE = /msie/i.test(userAgent) && !isOpera,
- docMode8 = doc.documentMode === 8,
- isWebKit = /AppleWebKit/.test(userAgent),
- isFirefox = /Firefox/.test(userAgent),
- isTouchDevice = /(Mobile|Android|Windows Phone)/.test(userAgent),
- SVG_NS = 'http://www.w3.org/2000/svg',
- hasSVG = !!doc.createElementNS && !!doc.createElementNS(SVG_NS, 'svg').createSVGRect,
- hasBidiBug = isFirefox && parseInt(userAgent.split('Firefox/')[1], 10) < 4, // issue #38
- useCanVG = !hasSVG && !isIE && !!doc.createElement('canvas').getContext,
- Renderer,
- hasTouch = doc.documentElement.ontouchstart !== UNDEFINED,
- symbolSizes = {},
- idCounter = 0,
- garbageBin,
- defaultOptions,
- dateFormat, // function
- globalAnimation,
- pathAnim,
- timeUnits,
- noop = function () {},
- charts = [],
- PRODUCT = 'Highcharts',
- VERSION = '3.0.3',
-
- // some constants for frequently used strings
- DIV = 'div',
- ABSOLUTE = 'absolute',
- RELATIVE = 'relative',
- HIDDEN = 'hidden',
- PREFIX = 'highcharts-',
- VISIBLE = 'visible',
- PX = 'px',
- NONE = 'none',
- M = 'M',
- L = 'L',
- /*
- * Empirical lowest possible opacities for TRACKER_FILL
- * IE6: 0.002
- * IE7: 0.002
- * IE8: 0.002
- * IE9: 0.00000000001 (unlimited)
- * IE10: 0.0001 (exporting only)
- * FF: 0.00000000001 (unlimited)
- * Chrome: 0.000001
- * Safari: 0.000001
- * Opera: 0.00000000001 (unlimited)
- */
- TRACKER_FILL = 'rgba(192,192,192,' + (hasSVG ? 0.0001 : 0.002) + ')', // invisible but clickable
- //TRACKER_FILL = 'rgba(192,192,192,0.5)',
- NORMAL_STATE = '',
- HOVER_STATE = 'hover',
- SELECT_STATE = 'select',
- MILLISECOND = 'millisecond',
- SECOND = 'second',
- MINUTE = 'minute',
- HOUR = 'hour',
- DAY = 'day',
- WEEK = 'week',
- MONTH = 'month',
- YEAR = 'year',
-
- // constants for attributes
- LINEAR_GRADIENT = 'linearGradient',
- STOPS = 'stops',
- STROKE_WIDTH = 'stroke-width',
-
- // time methods, changed based on whether or not UTC is used
- makeTime,
- getMinutes,
- getHours,
- getDay,
- getDate,
- getMonth,
- getFullYear,
- setMinutes,
- setHours,
- setDate,
- setMonth,
- setFullYear,
-
-
- // lookup over the types and the associated classes
- seriesTypes = {};
-
-// The Highcharts namespace
-win.Highcharts = win.Highcharts ? error(16, true) : {};
-
-/**
- * Extend an object with the members of another
- * @param {Object} a The object to be extended
- * @param {Object} b The object to add to the first one
- */
-function extend(a, b) {
- var n;
- if (!a) {
- a = {};
- }
- for (n in b) {
- a[n] = b[n];
- }
- return a;
-}
-
-/**
- * Deep merge two or more objects and return a third object.
- * Previously this function redirected to jQuery.extend(true), but this had two limitations.
- * First, it deep merged arrays, which lead to workarounds in Highcharts. Second,
- * it copied properties from extended prototypes.
- */
-function merge() {
- var i,
- len = arguments.length,
- ret = {},
- doCopy = function (copy, original) {
- var value, key;
-
- // An object is replacing a primitive
- if (typeof copy !== 'object') {
- copy = {};
- }
-
- for (key in original) {
- if (original.hasOwnProperty(key)) {
- value = original[key];
-
- // Copy the contents of objects, but not arrays or DOM nodes
- if (value && typeof value === 'object' && Object.prototype.toString.call(value) !== '[object Array]'
- && typeof value.nodeType !== 'number') {
- copy[key] = doCopy(copy[key] || {}, value);
-
- // Primitives and arrays are copied over directly
- } else {
- copy[key] = original[key];
- }
- }
- }
- return copy;
- };
-
- // For each argument, extend the return
- for (i = 0; i < len; i++) {
- ret = doCopy(ret, arguments[i]);
- }
-
- return ret;
-}
-
-/**
- * Take an array and turn into a hash with even number arguments as keys and odd numbers as
- * values. Allows creating constants for commonly used style properties, attributes etc.
- * Avoid it in performance critical situations like looping
- */
-function hash() {
- var i = 0,
- args = arguments,
- length = args.length,
- obj = {};
- for (; i < length; i++) {
- obj[args[i++]] = args[i];
- }
- return obj;
-}
-
-/**
- * Shortcut for parseInt
- * @param {Object} s
- * @param {Number} mag Magnitude
- */
-function pInt(s, mag) {
- return parseInt(s, mag || 10);
-}
-
-/**
- * Check for string
- * @param {Object} s
- */
-function isString(s) {
- return typeof s === 'string';
-}
-
-/**
- * Check for object
- * @param {Object} obj
- */
-function isObject(obj) {
- return typeof obj === 'object';
-}
-
-/**
- * Check for array
- * @param {Object} obj
- */
-function isArray(obj) {
- return Object.prototype.toString.call(obj) === '[object Array]';
-}
-
-/**
- * Check for number
- * @param {Object} n
- */
-function isNumber(n) {
- return typeof n === 'number';
-}
-
-function log2lin(num) {
- return math.log(num) / math.LN10;
-}
-function lin2log(num) {
- return math.pow(10, num);
-}
-
-/**
- * Remove last occurence of an item from an array
- * @param {Array} arr
- * @param {Mixed} item
- */
-function erase(arr, item) {
- var i = arr.length;
- while (i--) {
- if (arr[i] === item) {
- arr.splice(i, 1);
- break;
- }
- }
- //return arr;
-}
-
-/**
- * Returns true if the object is not null or undefined. Like MooTools' $.defined.
- * @param {Object} obj
- */
-function defined(obj) {
- return obj !== UNDEFINED && obj !== null;
-}
-
-/**
- * Set or get an attribute or an object of attributes. Can't use jQuery attr because
- * it attempts to set expando properties on the SVG element, which is not allowed.
- *
- * @param {Object} elem The DOM element to receive the attribute(s)
- * @param {String|Object} prop The property or an abject of key-value pairs
- * @param {String} value The value if a single property is set
- */
-function attr(elem, prop, value) {
- var key,
- setAttribute = 'setAttribute',
- ret;
-
- // if the prop is a string
- if (isString(prop)) {
- // set the value
- if (defined(value)) {
-
- elem[setAttribute](prop, value);
-
- // get the value
- } else if (elem && elem.getAttribute) { // elem not defined when printing pie demo...
- ret = elem.getAttribute(prop);
- }
-
- // else if prop is defined, it is a hash of key/value pairs
- } else if (defined(prop) && isObject(prop)) {
- for (key in prop) {
- elem[setAttribute](key, prop[key]);
- }
- }
- return ret;
-}
-/**
- * Check if an element is an array, and if not, make it into an array. Like
- * MooTools' $.splat.
- */
-function splat(obj) {
- return isArray(obj) ? obj : [obj];
-}
-
-
-/**
- * Return the first value that is defined. Like MooTools' $.pick.
- */
-function pick() {
- var args = arguments,
- i,
- arg,
- length = args.length;
- for (i = 0; i < length; i++) {
- arg = args[i];
- if (typeof arg !== 'undefined' && arg !== null) {
- return arg;
- }
- }
-}
-
-/**
- * Set CSS on a given element
- * @param {Object} el
- * @param {Object} styles Style object with camel case property names
- */
-function css(el, styles) {
- if (isIE) {
- if (styles && styles.opacity !== UNDEFINED) {
- styles.filter = 'alpha(opacity=' + (styles.opacity * 100) + ')';
- }
- }
- extend(el.style, styles);
-}
-
-/**
- * Utility function to create element with attributes and styles
- * @param {Object} tag
- * @param {Object} attribs
- * @param {Object} styles
- * @param {Object} parent
- * @param {Object} nopad
- */
-function createElement(tag, attribs, styles, parent, nopad) {
- var el = doc.createElement(tag);
- if (attribs) {
- extend(el, attribs);
- }
- if (nopad) {
- css(el, {padding: 0, border: NONE, margin: 0});
- }
- if (styles) {
- css(el, styles);
- }
- if (parent) {
- parent.appendChild(el);
- }
- return el;
-}
-
-/**
- * Extend a prototyped class by new members
- * @param {Object} parent
- * @param {Object} members
- */
-function extendClass(parent, members) {
- var object = function () {};
- object.prototype = new parent();
- extend(object.prototype, members);
- return object;
-}
-
-/**
- * Format a number and return a string based on input settings
- * @param {Number} number The input number to format
- * @param {Number} decimals The amount of decimals
- * @param {String} decPoint The decimal point, defaults to the one given in the lang options
- * @param {String} thousandsSep The thousands separator, defaults to the one given in the lang options
- */
-function numberFormat(number, decimals, decPoint, thousandsSep) {
- var lang = defaultOptions.lang,
- // http://kevin.vanzonneveld.net/techblog/article/javascript_equivalent_for_phps_number_format/
- n = +number || 0,
- c = decimals === -1 ?
- (n.toString().split('.')[1] || '').length : // preserve decimals
- (isNaN(decimals = mathAbs(decimals)) ? 2 : decimals),
- d = decPoint === undefined ? lang.decimalPoint : decPoint,
- t = thousandsSep === undefined ? lang.thousandsSep : thousandsSep,
- s = n < 0 ? "-" : "",
- i = String(pInt(n = mathAbs(n).toFixed(c))),
- j = i.length > 3 ? i.length % 3 : 0;
-
- return s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) +
- (c ? d + mathAbs(n - i).toFixed(c).slice(2) : "");
-}
-
-/**
- * Pad a string to a given length by adding 0 to the beginning
- * @param {Number} number
- * @param {Number} length
- */
-function pad(number, length) {
- // Create an array of the remaining length +1 and join it with 0's
- return new Array((length || 2) + 1 - String(number).length).join(0) + number;
-}
-
-/**
- * Wrap a method with extended functionality, preserving the original function
- * @param {Object} obj The context object that the method belongs to
- * @param {String} method The name of the method to extend
- * @param {Function} func A wrapper function callback. This function is called with the same arguments
- * as the original function, except that the original function is unshifted and passed as the first
- * argument.
- */
-function wrap(obj, method, func) {
- var proceed = obj[method];
- obj[method] = function () {
- var args = Array.prototype.slice.call(arguments);
- args.unshift(proceed);
- return func.apply(this, args);
- };
-}
-
-/**
- * Based on http://www.php.net/manual/en/function.strftime.php
- * @param {String} format
- * @param {Number} timestamp
- * @param {Boolean} capitalize
- */
-dateFormat = function (format, timestamp, capitalize) {
- if (!defined(timestamp) || isNaN(timestamp)) {
- return 'Invalid date';
- }
- format = pick(format, '%Y-%m-%d %H:%M:%S');
-
- var date = new Date(timestamp),
- key, // used in for constuct below
- // get the basic time values
- hours = date[getHours](),
- day = date[getDay](),
- dayOfMonth = date[getDate](),
- month = date[getMonth](),
- fullYear = date[getFullYear](),
- lang = defaultOptions.lang,
- langWeekdays = lang.weekdays,
-
- // List all format keys. Custom formats can be added from the outside.
- replacements = extend({
-
- // Day
- 'a': langWeekdays[day].substr(0, 3), // Short weekday, like 'Mon'
- 'A': langWeekdays[day], // Long weekday, like 'Monday'
- 'd': pad(dayOfMonth), // Two digit day of the month, 01 to 31
- 'e': dayOfMonth, // Day of the month, 1 through 31
-
- // Week (none implemented)
- //'W': weekNumber(),
-
- // Month
- 'b': lang.shortMonths[month], // Short month, like 'Jan'
- 'B': lang.months[month], // Long month, like 'January'
- 'm': pad(month + 1), // Two digit month number, 01 through 12
-
- // Year
- 'y': fullYear.toString().substr(2, 2), // Two digits year, like 09 for 2009
- 'Y': fullYear, // Four digits year, like 2009
-
- // Time
- 'H': pad(hours), // Two digits hours in 24h format, 00 through 23
- 'I': pad((hours % 12) || 12), // Two digits hours in 12h format, 00 through 11
- 'l': (hours % 12) || 12, // Hours in 12h format, 1 through 12
- 'M': pad(date[getMinutes]()), // Two digits minutes, 00 through 59
- 'p': hours < 12 ? 'AM' : 'PM', // Upper case AM or PM
- 'P': hours < 12 ? 'am' : 'pm', // Lower case AM or PM
- 'S': pad(date.getSeconds()), // Two digits seconds, 00 through 59
- 'L': pad(mathRound(timestamp % 1000), 3) // Milliseconds (naming from Ruby)
- }, Highcharts.dateFormats);
-
-
- // do the replaces
- for (key in replacements) {
- while (format.indexOf('%' + key) !== -1) { // regex would do it in one line, but this is faster
- format = format.replace('%' + key, typeof replacements[key] === 'function' ? replacements[key](timestamp) : replacements[key]);
- }
- }
-
- // Optionally capitalize the string and return
- return capitalize ? format.substr(0, 1).toUpperCase() + format.substr(1) : format;
-};
-
-/**
- * Format a single variable. Similar to sprintf, without the % prefix.
- */
-function formatSingle(format, val) {
- var floatRegex = /f$/,
- decRegex = /\.([0-9])/,
- lang = defaultOptions.lang,
- decimals;
-
- if (floatRegex.test(format)) { // float
- decimals = format.match(decRegex);
- decimals = decimals ? decimals[1] : -1;
- val = numberFormat(
- val,
- decimals,
- lang.decimalPoint,
- format.indexOf(',') > -1 ? lang.thousandsSep : ''
- );
- } else {
- val = dateFormat(format, val);
- }
- return val;
-}
-
-/**
- * Format a string according to a subset of the rules of Python's String.format method.
- */
-function format(str, ctx) {
- var splitter = '{',
- isInside = false,
- segment,
- valueAndFormat,
- path,
- i,
- len,
- ret = [],
- val,
- index;
-
- while ((index = str.indexOf(splitter)) !== -1) {
-
- segment = str.slice(0, index);
- if (isInside) { // we're on the closing bracket looking back
-
- valueAndFormat = segment.split(':');
- path = valueAndFormat.shift().split('.'); // get first and leave format
- len = path.length;
- val = ctx;
-
- // Assign deeper paths
- for (i = 0; i < len; i++) {
- val = val[path[i]];
- }
-
- // Format the replacement
- if (valueAndFormat.length) {
- val = formatSingle(valueAndFormat.join(':'), val);
- }
-
- // Push the result and advance the cursor
- ret.push(val);
-
- } else {
- ret.push(segment);
-
- }
- str = str.slice(index + 1); // the rest
- isInside = !isInside; // toggle
- splitter = isInside ? '}' : '{'; // now look for next matching bracket
- }
- ret.push(str);
- return ret.join('');
-}
-
-/**
- * Get the magnitude of a number
- */
-function getMagnitude(num) {
- return math.pow(10, mathFloor(math.log(num) / math.LN10));
-}
-
-/**
- * Take an interval and normalize it to multiples of 1, 2, 2.5 and 5
- * @param {Number} interval
- * @param {Array} multiples
- * @param {Number} magnitude
- * @param {Object} options
- */
-function normalizeTickInterval(interval, multiples, magnitude, options) {
- var normalized, i;
-
- // round to a tenfold of 1, 2, 2.5 or 5
- magnitude = pick(magnitude, 1);
- normalized = interval / magnitude;
-
- // multiples for a linear scale
- if (!multiples) {
- multiples = [1, 2, 2.5, 5, 10];
-
- // the allowDecimals option
- if (options && options.allowDecimals === false) {
- if (magnitude === 1) {
- multiples = [1, 2, 5, 10];
- } else if (magnitude <= 0.1) {
- multiples = [1 / magnitude];
- }
- }
- }
-
- // normalize the interval to the nearest multiple
- for (i = 0; i < multiples.length; i++) {
- interval = multiples[i];
- if (normalized <= (multiples[i] + (multiples[i + 1] || multiples[i])) / 2) {
- break;
- }
- }
-
- // multiply back to the correct magnitude
- interval *= magnitude;
-
- return interval;
-}
-
-/**
- * Get a normalized tick interval for dates. Returns a configuration object with
- * unit range (interval), count and name. Used to prepare data for getTimeTicks.
- * Previously this logic was part of getTimeTicks, but as getTimeTicks now runs
- * of segments in stock charts, the normalizing logic was extracted in order to
- * prevent it for running over again for each segment having the same interval.
- * #662, #697.
- */
-function normalizeTimeTickInterval(tickInterval, unitsOption) {
- var units = unitsOption || [[
- MILLISECOND, // unit name
- [1, 2, 5, 10, 20, 25, 50, 100, 200, 500] // allowed multiples
- ], [
- SECOND,
- [1, 2, 5, 10, 15, 30]
- ], [
- MINUTE,
- [1, 2, 5, 10, 15, 30]
- ], [
- HOUR,
- [1, 2, 3, 4, 6, 8, 12]
- ], [
- DAY,
- [1, 2]
- ], [
- WEEK,
- [1, 2]
- ], [
- MONTH,
- [1, 2, 3, 4, 6]
- ], [
- YEAR,
- null
- ]],
- unit = units[units.length - 1], // default unit is years
- interval = timeUnits[unit[0]],
- multiples = unit[1],
- count,
- i;
-
- // loop through the units to find the one that best fits the tickInterval
- for (i = 0; i < units.length; i++) {
- unit = units[i];
- interval = timeUnits[unit[0]];
- multiples = unit[1];
-
-
- if (units[i + 1]) {
- // lessThan is in the middle between the highest multiple and the next unit.
- var lessThan = (interval * multiples[multiples.length - 1] +
- timeUnits[units[i + 1][0]]) / 2;
-
- // break and keep the current unit
- if (tickInterval <= lessThan) {
- break;
- }
- }
- }
-
- // prevent 2.5 years intervals, though 25, 250 etc. are allowed
- if (interval === timeUnits[YEAR] && tickInterval < 5 * interval) {
- multiples = [1, 2, 5];
- }
-
- // prevent 2.5 years intervals, though 25, 250 etc. are allowed
- if (interval === timeUnits[YEAR] && tickInterval < 5 * interval) {
- multiples = [1, 2, 5];
- }
-
- // get the count
- count = normalizeTickInterval(
- tickInterval / interval,
- multiples,
- unit[0] === YEAR ? getMagnitude(tickInterval / interval) : 1 // #1913
- );
-
- return {
- unitRange: interval,
- count: count,
- unitName: unit[0]
- };
-}
-
-/**
- * Set the tick positions to a time unit that makes sense, for example
- * on the first of each month or on every Monday. Return an array
- * with the time positions. Used in datetime axes as well as for grouping
- * data on a datetime axis.
- *
- * @param {Object} normalizedInterval The interval in axis values (ms) and the count
- * @param {Number} min The minimum in axis values
- * @param {Number} max The maximum in axis values
- * @param {Number} startOfWeek
- */
-function getTimeTicks(normalizedInterval, min, max, startOfWeek) {
- var tickPositions = [],
- i,
- higherRanks = {},
- useUTC = defaultOptions.global.useUTC,
- minYear, // used in months and years as a basis for Date.UTC()
- minDate = new Date(min),
- interval = normalizedInterval.unitRange,
- count = normalizedInterval.count;
-
- if (defined(min)) { // #1300
- if (interval >= timeUnits[SECOND]) { // second
- minDate.setMilliseconds(0);
- minDate.setSeconds(interval >= timeUnits[MINUTE] ? 0 :
- count * mathFloor(minDate.getSeconds() / count));
- }
-
- if (interval >= timeUnits[MINUTE]) { // minute
- minDate[setMinutes](interval >= timeUnits[HOUR] ? 0 :
- count * mathFloor(minDate[getMinutes]() / count));
- }
-
- if (interval >= timeUnits[HOUR]) { // hour
- minDate[setHours](interval >= timeUnits[DAY] ? 0 :
- count * mathFloor(minDate[getHours]() / count));
- }
-
- if (interval >= timeUnits[DAY]) { // day
- minDate[setDate](interval >= timeUnits[MONTH] ? 1 :
- count * mathFloor(minDate[getDate]() / count));
- }
-
- if (interval >= timeUnits[MONTH]) { // month
- minDate[setMonth](interval >= timeUnits[YEAR] ? 0 :
- count * mathFloor(minDate[getMonth]() / count));
- minYear = minDate[getFullYear]();
- }
-
- if (interval >= timeUnits[YEAR]) { // year
- minYear -= minYear % count;
- minDate[setFullYear](minYear);
- }
-
- // week is a special case that runs outside the hierarchy
- if (interval === timeUnits[WEEK]) {
- // get start of current week, independent of count
- minDate[setDate](minDate[getDate]() - minDate[getDay]() +
- pick(startOfWeek, 1));
- }
-
-
- // get tick positions
- i = 1;
- minYear = minDate[getFullYear]();
- var time = minDate.getTime(),
- minMonth = minDate[getMonth](),
- minDateDate = minDate[getDate](),
- timezoneOffset = useUTC ?
- 0 :
- (24 * 3600 * 1000 + minDate.getTimezoneOffset() * 60 * 1000) % (24 * 3600 * 1000); // #950
-
- // iterate and add tick positions at appropriate values
- while (time < max) {
- tickPositions.push(time);
-
- // if the interval is years, use Date.UTC to increase years
- if (interval === timeUnits[YEAR]) {
- time = makeTime(minYear + i * count, 0);
-
- // if the interval is months, use Date.UTC to increase months
- } else if (interval === timeUnits[MONTH]) {
- time = makeTime(minYear, minMonth + i * count);
-
- // if we're using global time, the interval is not fixed as it jumps
- // one hour at the DST crossover
- } else if (!useUTC && (interval === timeUnits[DAY] || interval === timeUnits[WEEK])) {
- time = makeTime(minYear, minMonth, minDateDate +
- i * count * (interval === timeUnits[DAY] ? 1 : 7));
-
- // else, the interval is fixed and we use simple addition
- } else {
- time += interval * count;
- }
-
- i++;
- }
-
- // push the last time
- tickPositions.push(time);
-
-
- // mark new days if the time is dividible by day (#1649, #1760)
- each(grep(tickPositions, function (time) {
- return interval <= timeUnits[HOUR] && time % timeUnits[DAY] === timezoneOffset;
- }), function (time) {
- higherRanks[time] = DAY;
- });
- }
-
-
- // record information on the chosen unit - for dynamic label formatter
- tickPositions.info = extend(normalizedInterval, {
- higherRanks: higherRanks,
- totalRange: interval * count
- });
-
- return tickPositions;
-}
-
-/**
- * Helper class that contains variuos counters that are local to the chart.
- */
-function ChartCounters() {
- this.color = 0;
- this.symbol = 0;
-}
-
-ChartCounters.prototype = {
- /**
- * Wraps the color counter if it reaches the specified length.
- */
- wrapColor: function (length) {
- if (this.color >= length) {
- this.color = 0;
- }
- },
-
- /**
- * Wraps the symbol counter if it reaches the specified length.
- */
- wrapSymbol: function (length) {
- if (this.symbol >= length) {
- this.symbol = 0;
- }
- }
-};
-
-
-/**
- * Utility method that sorts an object array and keeping the order of equal items.
- * ECMA script standard does not specify the behaviour when items are equal.
- */
-function stableSort(arr, sortFunction) {
- var length = arr.length,
- sortValue,
- i;
-
- // Add index to each item
- for (i = 0; i < length; i++) {
- arr[i].ss_i = i; // stable sort index
- }
-
- arr.sort(function (a, b) {
- sortValue = sortFunction(a, b);
- return sortValue === 0 ? a.ss_i - b.ss_i : sortValue;
- });
-
- // Remove index from items
- for (i = 0; i < length; i++) {
- delete arr[i].ss_i; // stable sort index
- }
-}
-
-/**
- * Non-recursive method to find the lowest member of an array. Math.min raises a maximum
- * call stack size exceeded error in Chrome when trying to apply more than 150.000 points. This
- * method is slightly slower, but safe.
- */
-function arrayMin(data) {
- var i = data.length,
- min = data[0];
-
- while (i--) {
- if (data[i] < min) {
- min = data[i];
- }
- }
- return min;
-}
-
-/**
- * Non-recursive method to find the lowest member of an array. Math.min raises a maximum
- * call stack size exceeded error in Chrome when trying to apply more than 150.000 points. This
- * method is slightly slower, but safe.
- */
-function arrayMax(data) {
- var i = data.length,
- max = data[0];
-
- while (i--) {
- if (data[i] > max) {
- max = data[i];
- }
- }
- return max;
-}
-
-/**
- * Utility method that destroys any SVGElement or VMLElement that are properties on the given object.
- * It loops all properties and invokes destroy if there is a destroy method. The property is
- * then delete'ed.
- * @param {Object} The object to destroy properties on
- * @param {Object} Exception, do not destroy this property, only delete it.
- */
-function destroyObjectProperties(obj, except) {
- var n;
- for (n in obj) {
- // If the object is non-null and destroy is defined
- if (obj[n] && obj[n] !== except && obj[n].destroy) {
- // Invoke the destroy
- obj[n].destroy();
- }
-
- // Delete the property from the object.
- delete obj[n];
- }
-}
-
-
-/**
- * Discard an element by moving it to the bin and delete
- * @param {Object} The HTML node to discard
- */
-function discardElement(element) {
- // create a garbage bin element, not part of the DOM
- if (!garbageBin) {
- garbageBin = createElement(DIV);
- }
-
- // move the node and empty bin
- if (element) {
- garbageBin.appendChild(element);
- }
- garbageBin.innerHTML = '';
-}
-
-/**
- * Provide error messages for debugging, with links to online explanation
- */
-function error(code, stop) {
- var msg = 'Highcharts error #' + code + ': www.highcharts.com/errors/' + code;
- if (stop) {
- throw msg;
- } else if (win.console) {
- console.log(msg);
- }
-}
-
-/**
- * Fix JS round off float errors
- * @param {Number} num
- */
-function correctFloat(num) {
- return parseFloat(
- num.toPrecision(14)
- );
-}
-
-/**
- * Set the global animation to either a given value, or fall back to the
- * given chart's animation option
- * @param {Object} animation
- * @param {Object} chart
- */
-function setAnimation(animation, chart) {
- globalAnimation = pick(animation, chart.animation);
-}
-
-/**
- * The time unit lookup
- */
-/*jslint white: true*/
-timeUnits = hash(
- MILLISECOND, 1,
- SECOND, 1000,
- MINUTE, 60000,
- HOUR, 3600000,
- DAY, 24 * 3600000,
- WEEK, 7 * 24 * 3600000,
- MONTH, 31 * 24 * 3600000,
- YEAR, 31556952000
-);
-/*jslint white: false*/
-/**
- * Path interpolation algorithm used across adapters
- */
-pathAnim = {
- /**
- * Prepare start and end values so that the path can be animated one to one
- */
- init: function (elem, fromD, toD) {
- fromD = fromD || '';
- var shift = elem.shift,
- bezier = fromD.indexOf('C') > -1,
- numParams = bezier ? 7 : 3,
- endLength,
- slice,
- i,
- start = fromD.split(' '),
- end = [].concat(toD), // copy
- startBaseLine,
- endBaseLine,
- sixify = function (arr) { // in splines make move points have six parameters like bezier curves
- i = arr.length;
- while (i--) {
- if (arr[i] === M) {
- arr.splice(i + 1, 0, arr[i + 1], arr[i + 2], arr[i + 1], arr[i + 2]);
- }
- }
- };
-
- if (bezier) {
- sixify(start);
- sixify(end);
- }
-
- // pull out the base lines before padding
- if (elem.isArea) {
- startBaseLine = start.splice(start.length - 6, 6);
- endBaseLine = end.splice(end.length - 6, 6);
- }
-
- // if shifting points, prepend a dummy point to the end path
- if (shift <= end.length / numParams) {
- while (shift--) {
- end = [].concat(end).splice(0, numParams).concat(end);
- }
- }
- elem.shift = 0; // reset for following animations
-
- // copy and append last point until the length matches the end length
- if (start.length) {
- endLength = end.length;
- while (start.length < endLength) {
-
- //bezier && sixify(start);
- slice = [].concat(start).splice(start.length - numParams, numParams);
- if (bezier) { // disable first control point
- slice[numParams - 6] = slice[numParams - 2];
- slice[numParams - 5] = slice[numParams - 1];
- }
- start = start.concat(slice);
- }
- }
-
- if (startBaseLine) { // append the base lines for areas
- start = start.concat(startBaseLine);
- end = end.concat(endBaseLine);
- }
- return [start, end];
- },
-
- /**
- * Interpolate each value of the path and return the array
- */
- step: function (start, end, pos, complete) {
- var ret = [],
- i = start.length,
- startVal;
-
- if (pos === 1) { // land on the final path without adjustment points appended in the ends
- ret = complete;
-
- } else if (i === end.length && pos < 1) {
- while (i--) {
- startVal = parseFloat(start[i]);
- ret[i] =
- isNaN(startVal) ? // a letter instruction like M or L
- start[i] :
- pos * (parseFloat(end[i] - startVal)) + startVal;
-
- }
- } else { // if animation is finished or length not matching, land on right value
- ret = end;
- }
- return ret;
- }
-};
-
-(function ($) {
- /**
- * The default HighchartsAdapter for jQuery
- */
- win.HighchartsAdapter = win.HighchartsAdapter || ($ && {
-
- /**
- * Initialize the adapter by applying some extensions to jQuery
- */
- init: function (pathAnim) {
-
- // extend the animate function to allow SVG animations
- var Fx = $.fx,
- Step = Fx.step,
- dSetter,
- Tween = $.Tween,
- propHooks = Tween && Tween.propHooks,
- opacityHook = $.cssHooks.opacity;
-
- /*jslint unparam: true*//* allow unused param x in this function */
- $.extend($.easing, {
- easeOutQuad: function (x, t, b, c, d) {
- return -c * (t /= d) * (t - 2) + b;
- }
- });
- /*jslint unparam: false*/
-
- // extend some methods to check for elem.attr, which means it is a Highcharts SVG object
- $.each(['cur', '_default', 'width', 'height', 'opacity'], function (i, fn) {
- var obj = Step,
- base,
- elem;
-
- // Handle different parent objects
- if (fn === 'cur') {
- obj = Fx.prototype; // 'cur', the getter, relates to Fx.prototype
-
- } else if (fn === '_default' && Tween) { // jQuery 1.8 model
- obj = propHooks[fn];
- fn = 'set';
- }
-
- // Overwrite the method
- base = obj[fn];
- if (base) { // step.width and step.height don't exist in jQuery < 1.7
-
- // create the extended function replacement
- obj[fn] = function (fx) {
-
- // Fx.prototype.cur does not use fx argument
- fx = i ? fx : this;
-
- // shortcut
- elem = fx.elem;
-
- // Fx.prototype.cur returns the current value. The other ones are setters
- // and returning a value has no effect.
- return elem.attr ? // is SVG element wrapper
- elem.attr(fx.prop, fn === 'cur' ? UNDEFINED : fx.now) : // apply the SVG wrapper's method
- base.apply(this, arguments); // use jQuery's built-in method
- };
- }
- });
-
- // Extend the opacity getter, needed for fading opacity with IE9 and jQuery 1.10+
- wrap(opacityHook, 'get', function (proceed, elem, computed) {
- return elem.attr ? (elem.opacity || 0) : proceed.call(this, elem, computed);
- });
-
-
- // Define the setter function for d (path definitions)
- dSetter = function (fx) {
- var elem = fx.elem,
- ends;
-
- // Normally start and end should be set in state == 0, but sometimes,
- // for reasons unknown, this doesn't happen. Perhaps state == 0 is skipped
- // in these cases
- if (!fx.started) {
- ends = pathAnim.init(elem, elem.d, elem.toD);
- fx.start = ends[0];
- fx.end = ends[1];
- fx.started = true;
- }
-
-
- // interpolate each value of the path
- elem.attr('d', pathAnim.step(fx.start, fx.end, fx.pos, elem.toD));
- };
-
- // jQuery 1.8 style
- if (Tween) {
- propHooks.d = {
- set: dSetter
- };
- // pre 1.8
- } else {
- // animate paths
- Step.d = dSetter;
- }
-
- /**
- * Utility for iterating over an array. Parameters are reversed compared to jQuery.
- * @param {Array} arr
- * @param {Function} fn
- */
- this.each = Array.prototype.forEach ?
- function (arr, fn) { // modern browsers
- return Array.prototype.forEach.call(arr, fn);
-
- } :
- function (arr, fn) { // legacy
- var i = 0,
- len = arr.length;
- for (; i < len; i++) {
- if (fn.call(arr[i], arr[i], i, arr) === false) {
- return i;
- }
- }
- };
-
- /**
- * Register Highcharts as a plugin in the respective framework
- */
- $.fn.highcharts = function () {
- var constr = 'Chart', // default constructor
- args = arguments,
- options,
- ret,
- chart;
-
- if (isString(args[0])) {
- constr = args[0];
- args = Array.prototype.slice.call(args, 1);
- }
- options = args[0];
-
- // Create the chart
- if (options !== UNDEFINED) {
- /*jslint unused:false*/
- options.chart = options.chart || {};
- options.chart.renderTo = this[0];
- chart = new Highcharts[constr](options, args[1]);
- ret = this;
- /*jslint unused:true*/
- }
-
- // When called without parameters or with the return argument, get a predefined chart
- if (options === UNDEFINED) {
- ret = charts[attr(this[0], 'data-highcharts-chart')];
- }
-
- return ret;
- };
-
- },
-
-
- /**
- * Downloads a script and executes a callback when done.
- * @param {String} scriptLocation
- * @param {Function} callback
- */
- getScript: $.getScript,
-
- /**
- * Return the index of an item in an array, or -1 if not found
- */
- inArray: $.inArray,
-
- /**
- * A direct link to jQuery methods. MooTools and Prototype adapters must be implemented for each case of method.
- * @param {Object} elem The HTML element
- * @param {String} method Which method to run on the wrapped element
- */
- adapterRun: function (elem, method) {
- return $(elem)[method]();
- },
-
- /**
- * Filter an array
- */
- grep: $.grep,
-
- /**
- * Map an array
- * @param {Array} arr
- * @param {Function} fn
- */
- map: function (arr, fn) {
- //return jQuery.map(arr, fn);
- var results = [],
- i = 0,
- len = arr.length;
- for (; i < len; i++) {
- results[i] = fn.call(arr[i], arr[i], i, arr);
- }
- return results;
-
- },
-
- /**
- * Get the position of an element relative to the top left of the page
- */
- offset: function (el) {
- return $(el).offset();
- },
-
- /**
- * Add an event listener
- * @param {Object} el A HTML element or custom object
- * @param {String} event The event type
- * @param {Function} fn The event handler
- */
- addEvent: function (el, event, fn) {
- $(el).bind(event, fn);
- },
-
- /**
- * Remove event added with addEvent
- * @param {Object} el The object
- * @param {String} eventType The event type. Leave blank to remove all events.
- * @param {Function} handler The function to remove
- */
- removeEvent: function (el, eventType, handler) {
- // workaround for jQuery issue with unbinding custom events:
- // http://forum.jQuery.com/topic/javascript-error-when-unbinding-a-custom-event-using-jQuery-1-4-2
- var func = doc.removeEventListener ? 'removeEventListener' : 'detachEvent';
- if (doc[func] && el && !el[func]) {
- el[func] = function () {};
- }
-
- $(el).unbind(eventType, handler);
- },
-
- /**
- * Fire an event on a custom object
- * @param {Object} el
- * @param {String} type
- * @param {Object} eventArguments
- * @param {Function} defaultFunction
- */
- fireEvent: function (el, type, eventArguments, defaultFunction) {
- var event = $.Event(type),
- detachedType = 'detached' + type,
- defaultPrevented;
-
- // Remove warnings in Chrome when accessing layerX and layerY. Although Highcharts
- // never uses these properties, Chrome includes them in the default click event and
- // raises the warning when they are copied over in the extend statement below.
- //
- // To avoid problems in IE (see #1010) where we cannot delete the properties and avoid
- // testing if they are there (warning in chrome) the only option is to test if running IE.
- if (!isIE && eventArguments) {
- delete eventArguments.layerX;
- delete eventArguments.layerY;
- }
-
- extend(event, eventArguments);
-
- // Prevent jQuery from triggering the object method that is named the
- // same as the event. For example, if the event is 'select', jQuery
- // attempts calling el.select and it goes into a loop.
- if (el[type]) {
- el[detachedType] = el[type];
- el[type] = null;
- }
-
- // Wrap preventDefault and stopPropagation in try/catch blocks in
- // order to prevent JS errors when cancelling events on non-DOM
- // objects. #615.
- /*jslint unparam: true*/
- $.each(['preventDefault', 'stopPropagation'], function (i, fn) {
- var base = event[fn];
- event[fn] = function () {
- try {
- base.call(event);
- } catch (e) {
- if (fn === 'preventDefault') {
- defaultPrevented = true;
- }
- }
- };
- });
- /*jslint unparam: false*/
-
- // trigger it
- $(el).trigger(event);
-
- // attach the method
- if (el[detachedType]) {
- el[type] = el[detachedType];
- el[detachedType] = null;
- }
-
- if (defaultFunction && !event.isDefaultPrevented() && !defaultPrevented) {
- defaultFunction(event);
- }
- },
-
- /**
- * Extension method needed for MooTools
- */
- washMouseEvent: function (e) {
- var ret = e.originalEvent || e;
-
- // computed by jQuery, needed by IE8
- if (ret.pageX === UNDEFINED) { // #1236
- ret.pageX = e.pageX;
- ret.pageY = e.pageY;
- }
-
- return ret;
- },
-
- /**
- * Animate a HTML element or SVG element wrapper
- * @param {Object} el
- * @param {Object} params
- * @param {Object} options jQuery-like animation options: duration, easing, callback
- */
- animate: function (el, params, options) {
- var $el = $(el);
- if (!el.style) {
- el.style = {}; // #1881
- }
- if (params.d) {
- el.toD = params.d; // keep the array form for paths, used in $.fx.step.d
- params.d = 1; // because in jQuery, animating to an array has a different meaning
- }
-
- $el.stop();
- $el.animate(params, options);
-
- },
- /**
- * Stop running animation
- */
- stop: function (el) {
- $(el).stop();
- }
- });
-}(win.jQuery));
-
-
-// check for a custom HighchartsAdapter defined prior to this file
-var globalAdapter = win.HighchartsAdapter,
- adapter = globalAdapter || {};
-
-// Initialize the adapter
-if (globalAdapter) {
- globalAdapter.init.call(globalAdapter, pathAnim);
-}
-
-
-// Utility functions. If the HighchartsAdapter is not defined, adapter is an empty object
-// and all the utility functions will be null. In that case they are populated by the
-// default adapters below.
-var adapterRun = adapter.adapterRun,
- getScript = adapter.getScript,
- inArray = adapter.inArray,
- each = adapter.each,
- grep = adapter.grep,
- offset = adapter.offset,
- map = adapter.map,
- addEvent = adapter.addEvent,
- removeEvent = adapter.removeEvent,
- fireEvent = adapter.fireEvent,
- washMouseEvent = adapter.washMouseEvent,
- animate = adapter.animate,
- stop = adapter.stop;
-
-
-
-/* ****************************************************************************
- * Handle the options *
- *****************************************************************************/
-var
-
-defaultLabelOptions = {
- enabled: true,
- // rotation: 0,
- // align: 'center',
- x: 0,
- y: 15,
- /*formatter: function () {
- return this.value;
- },*/
- style: {
- color: '#666',
- cursor: 'default',
- fontSize: '11px',
- lineHeight: '14px'
- }
-};
-
-defaultOptions = {
- colors: ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970',
- '#f28f43', '#77a1e5', '#c42525', '#a6c96a'],
- symbols: ['circle', 'diamond', 'square', 'triangle', 'triangle-down'],
- lang: {
- loading: 'Loading...',
- months: ['January', 'February', 'March', 'April', 'May', 'June', 'July',
- 'August', 'September', 'October', 'November', 'December'],
- shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
- weekdays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
- decimalPoint: '.',
- numericSymbols: ['k', 'M', 'G', 'T', 'P', 'E'], // SI prefixes used in axis labels
- resetZoom: 'Reset zoom',
- resetZoomTitle: 'Reset zoom level 1:1',
- thousandsSep: ','
- },
- global: {
- useUTC: true,
- canvasToolsURL: 'http://code.highcharts.com/3.0.3/modules/canvas-tools.js',
- VMLRadialGradientURL: 'http://code.highcharts.com/3.0.3/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: null,
- style: {
- color: '#274b6d',//#3E576F',
- fontSize: '16px'
- }
-
- },
- subtitle: {
- text: '',
- align: 'center',
- // floating: false
- // x: 0,
- // verticalAlign: 'top',
- // y: null,
- style: {
- color: '#4d759e'
- }
- },
-
- plotOptions: {
- line: { // base series options
- allowPointSelect: false,
- showCheckbox: false,
- animation: {
- duration: 1000
- },
- //connectNulls: false,
- //cursor: 'default',
- //clip: true,
- //dashStyle: null,
- //enableMouseTracking: true,
- events: {},
- //legendIndex: 0,
- lineWidth: 2,
- //shadow: false,
- // stacking: null,
- marker: {
- enabled: true,
- //symbol: null,
- lineWidth: 0,
- radius: 4,
- lineColor: '#FFFFFF',
- //fillColor: null,
- states: { // states for a single point
- hover: {
- enabled: true
- //radius: base + 2
- },
- select: {
- fillColor: '#FFFFFF',
- lineColor: '#000000',
- lineWidth: 2
- }
- }
- },
- point: {
- events: {}
- },
- dataLabels: merge(defaultLabelOptions, {
- align: 'center',
- enabled: false,
- formatter: function () {
- return this.y === null ? '' : numberFormat(this.y, -1);
- },
- verticalAlign: 'bottom', // above singular point
- y: 0
- // backgroundColor: undefined,
- // borderColor: undefined,
- // borderRadius: undefined,
- // borderWidth: undefined,
- // padding: 3,
- // shadow: false
- }),
- cropThreshold: 300, // draw points outside the plot area when the number of points is less than this
- pointRange: 0,
- //pointStart: 0,
- //pointInterval: 1,
- showInLegend: true,
- states: { // states for the entire series
- hover: {
- //enabled: false,
- //lineWidth: base + 1,
- marker: {
- // lineWidth: base + 1,
- // radius: base + 1
- }
- },
- select: {
- marker: {}
- }
- },
- stickyTracking: true
- //tooltip: {
- //pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.y}</b>'
- //valueDecimals: null,
- //xDateFormat: '%A, %b %e, %Y',
- //valuePrefix: '',
- //ySuffix: ''
- //}
- // turboThreshold: 1000
- // zIndex: null
- }
- },
- labels: {
- //items: [],
- style: {
- //font: defaultFont,
- position: ABSOLUTE,
- color: '#3E576F'
- }
- },
- legend: {
- enabled: true,
- align: 'center',
- //floating: false,
- layout: 'horizontal',
- labelFormatter: function () {
- return this.name;
- },
- borderWidth: 1,
- borderColor: '#909090',
- borderRadius: 5,
- navigation: {
- // animation: true,
- activeColor: '#274b6d',
- // arrowSize: 12
- inactiveColor: '#CCC'
- // style: {} // text styles
- },
- // margin: 10,
- // reversed: false,
- shadow: false,
- // backgroundColor: null,
- /*style: {
- padding: '5px'
- },*/
- itemStyle: {
- cursor: 'pointer',
- color: '#274b6d',
- fontSize: '12px'
- },
- itemHoverStyle: {
- //cursor: 'pointer', removed as of #601
- color: '#000'
- },
- itemHiddenStyle: {
- color: '#CCC'
- },
- itemCheckboxStyle: {
- position: ABSOLUTE,
- width: '13px', // for IE precision
- height: '13px'
- },
- // itemWidth: undefined,
- symbolWidth: 16,
- symbolPadding: 5,
- verticalAlign: 'bottom',
- // width: undefined,
- x: 0,
- y: 0,
- title: {
- //text: null,
- style: {
- fontWeight: 'bold'
- }
- }
- },
-
- loading: {
- // hideDuration: 100,
- labelStyle: {
- fontWeight: 'bold',
- position: RELATIVE,
- top: '1em'
- },
- // showDuration: 0,
- style: {
- position: ABSOLUTE,
- backgroundColor: 'white',
- opacity: 0.5,
- textAlign: 'center'
- }
- },
-
- tooltip: {
- enabled: true,
- animation: hasSVG,
- //crosshairs: null,
- backgroundColor: 'rgba(255, 255, 255, .85)',
- borderWidth: 1,
- borderRadius: 3,
- dateTimeLabelFormats: {
- millisecond: '%A, %b %e, %H:%M:%S.%L',
- second: '%A, %b %e, %H:%M:%S',
- minute: '%A, %b %e, %H:%M',
- hour: '%A, %b %e, %H:%M',
- day: '%A, %b %e, %Y',
- week: 'Week from %A, %b %e, %Y',
- month: '%B %Y',
- year: '%Y'
- },
- //formatter: defaultFormatter,
- headerFormat: '<span style="font-size: 10px">{point.key}</span><br/>',
- pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.y}</b><br/>',
- shadow: true,
- //shared: false,
- snap: isTouchDevice ? 25 : 10,
- style: {
- color: '#333333',
- cursor: 'default',
- fontSize: '12px',
- padding: '8px',
- whiteSpace: 'nowrap'
- }
- //xDateFormat: '%A, %b %e, %Y',
- //valueDecimals: null,
- //valuePrefix: '',
- //valueSuffix: ''
- },
-
- credits: {
- enabled: true,
- text: 'Highcharts.com',
- href: 'http://www.highcharts.com',
- position: {
- align: 'right',
- x: -10,
- verticalAlign: 'bottom',
- y: -5
- },
- style: {
- cursor: 'pointer',
- color: '#909090',
- fontSize: '9px'
- }
- }
-};
-
-
-
-
-// Series defaults
-var defaultPlotOptions = defaultOptions.plotOptions,
- defaultSeriesOptions = defaultPlotOptions.line;
-
-// set the default time methods
-setTimeMethods();
-
-
-
-/**
- * Set the time methods globally based on the useUTC option. Time method can be either
- * local time or UTC (default).
- */
-function setTimeMethods() {
- var useUTC = defaultOptions.global.useUTC,
- GET = useUTC ? 'getUTC' : 'get',
- SET = useUTC ? 'setUTC' : 'set';
-
- makeTime = useUTC ? Date.UTC : function (year, month, date, hours, minutes, seconds) {
- return new Date(
- year,
- month,
- pick(date, 1),
- pick(hours, 0),
- pick(minutes, 0),
- pick(seconds, 0)
- ).getTime();
- };
- getMinutes = GET + 'Minutes';
- getHours = GET + 'Hours';
- getDay = GET + 'Day';
- getDate = GET + 'Date';
- getMonth = GET + 'Month';
- getFullYear = GET + 'FullYear';
- setMinutes = SET + 'Minutes';
- setHours = SET + 'Hours';
- setDate = SET + 'Date';
- setMonth = SET + 'Month';
- setFullYear = SET + 'FullYear';
-
-}
-
-/**
- * Merge the default options with custom options and return the new options structure
- * @param {Object} options The new custom options
- */
-function setOptions(options) {
-
- // Pull out axis options and apply them to the respective default axis options
- /*defaultXAxisOptions = merge(defaultXAxisOptions, options.xAxis);
- defaultYAxisOptions = merge(defaultYAxisOptions, options.yAxis);
- options.xAxis = options.yAxis = UNDEFINED;*/
-
- // Merge in the default options
- defaultOptions = merge(defaultOptions, options);
-
- // Apply UTC
- setTimeMethods();
-
- return defaultOptions;
-}
-
-/**
- * Get the updated default options. Merely exposing defaultOptions for outside modules
- * isn't enough because the setOptions method creates a new object.
- */
-function getOptions() {
- return defaultOptions;
-}
-
-
-/**
- * Handle color operations. The object methods are chainable.
- * @param {String} input The input color in either rbga or hex format
- */
-var Color = function (input) {
- // declare variables
- var rgba = [], result, stops;
-
- /**
- * Parse the input color to rgba array
- * @param {String} input
- */
- function init(input) {
-
- // Gradients
- if (input && input.stops) {
- stops = map(input.stops, function (stop) {
- return Color(stop[1]);
- });
-
- // Solid colors
- } else {
- // rgba
- result = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]?(?:\.[0-9]+)?)\s*\)/.exec(input);
- if (result) {
- rgba = [pInt(result[1]), pInt(result[2]), pInt(result[3]), parseFloat(result[4], 10)];
- } else {
- // hex
- result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(input);
- if (result) {
- rgba = [pInt(result[1], 16), pInt(result[2], 16), pInt(result[3], 16), 1];
- } else {
- // rgb
- result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(input);
- if (result) {
- rgba = [pInt(result[1]), pInt(result[2]), pInt(result[3]), 1];
- }
- }
- }
- }
-
- }
- /**
- * Return the color a specified format
- * @param {String} format
- */
- function get(format) {
- var ret;
-
- if (stops) {
- ret = merge(input);
- ret.stops = [].concat(ret.stops);
- each(stops, function (stop, i) {
- ret.stops[i] = [ret.stops[i][0], stop.get(format)];
- });
-
- // it's NaN if gradient colors on a column chart
- } else if (rgba && !isNaN(rgba[0])) {
- if (format === 'rgb') {
- ret = 'rgb(' + rgba[0] + ',' + rgba[1] + ',' + rgba[2] + ')';
- } else if (format === 'a') {
- ret = rgba[3];
- } else {
- ret = 'rgba(' + rgba.join(',') + ')';
- }
- } else {
- ret = input;
- }
- return ret;
- }
-
- /**
- * Brighten the color
- * @param {Number} alpha
- */
- function brighten(alpha) {
- if (stops) {
- each(stops, function (stop) {
- stop.brighten(alpha);
- });
-
- } else if (isNumber(alpha) && alpha !== 0) {
- var i;
- for (i = 0; i < 3; i++) {
- rgba[i] += pInt(alpha * 255);
-
- if (rgba[i] < 0) {
- rgba[i] = 0;
- }
- if (rgba[i] > 255) {
- rgba[i] = 255;
- }
- }
- }
- return this;
- }
- /**
- * Set the color's opacity to a given alpha value
- * @param {Number} alpha
- */
- function setOpacity(alpha) {
- rgba[3] = alpha;
- return this;
- }
-
- // initialize: parse the input
- init(input);
-
- // public methods
- return {
- get: get,
- brighten: brighten,
- rgba: rgba,
- setOpacity: setOpacity
- };
-};
-
-
-/**
- * A wrapper object for SVG elements
- */
-function SVGElement() {}
-
-SVGElement.prototype = {
- /**
- * Initialize the SVG renderer
- * @param {Object} renderer
- * @param {String} nodeName
- */
- init: function (renderer, nodeName) {
- var wrapper = this;
- wrapper.element = nodeName === 'span' ?
- createElement(nodeName) :
- doc.createElementNS(SVG_NS, nodeName);
- wrapper.renderer = renderer;
- /**
- * A collection of attribute setters. These methods, if defined, are called right before a certain
- * attribute is set on an element wrapper. Returning false prevents the default attribute
- * setter to run. Returning a value causes the default setter to set that value. Used in
- * Renderer.label.
- */
- wrapper.attrSetters = {};
- },
- /**
- * Default base for animation
- */
- opacity: 1,
- /**
- * Animate a given attribute
- * @param {Object} params
- * @param {Number} options The same options as in jQuery animation
- * @param {Function} complete Function to perform at the end of animation
- */
- animate: function (params, options, complete) {
- var animOptions = pick(options, globalAnimation, true);
- stop(this); // stop regardless of animation actually running, or reverting to .attr (#607)
- if (animOptions) {
- animOptions = merge(animOptions);
- if (complete) { // allows using a callback with the global animation without overwriting it
- animOptions.complete = complete;
- }
- animate(this, params, animOptions);
- } else {
- this.attr(params);
- if (complete) {
- complete();
- }
- }
- },
- /**
- * Set or get a given attribute
- * @param {Object|String} hash
- * @param {Mixed|Undefined} val
- */
- attr: function (hash, val) {
- var wrapper = this,
- key,
- value,
- result,
- i,
- child,
- element = wrapper.element,
- nodeName = element.nodeName.toLowerCase(), // Android2 requires lower for "text"
- renderer = wrapper.renderer,
- skipAttr,
- titleNode,
- attrSetters = wrapper.attrSetters,
- shadows = wrapper.shadows,
- hasSetSymbolSize,
- doTransform,
- ret = wrapper;
-
- // single key-value pair
- if (isString(hash) && defined(val)) {
- key = hash;
- hash = {};
- hash[key] = val;
- }
-
- // used as a getter: first argument is a string, second is undefined
- if (isString(hash)) {
- key = hash;
- if (nodeName === 'circle') {
- key = { x: 'cx', y: 'cy' }[key] || key;
- } else if (key === 'strokeWidth') {
- key = 'stroke-width';
- }
- ret = attr(element, key) || wrapper[key] || 0;
- if (key !== 'd' && key !== 'visibility') { // 'd' is string in animation step
- ret = parseFloat(ret);
- }
-
- // setter
- } else {
-
- for (key in hash) {
- skipAttr = false; // reset
- value = hash[key];
-
- // check for a specific attribute setter
- result = attrSetters[key] && attrSetters[key].call(wrapper, value, key);
-
- if (result !== false) {
- if (result !== UNDEFINED) {
- value = result; // the attribute setter has returned a new value to set
- }
-
-
- // paths
- if (key === 'd') {
- if (value && value.join) { // join path
- value = value.join(' ');
- }
- if (/(NaN| {2}|^$)/.test(value)) {
- value = 'M 0 0';
- }
- //wrapper.d = value; // shortcut for animations
-
- // update child tspans x values
- } else if (key === 'x' && nodeName === 'text') {
- for (i = 0; i < element.childNodes.length; i++) {
- child = element.childNodes[i];
- // if the x values are equal, the tspan represents a linebreak
- if (attr(child, 'x') === attr(element, 'x')) {
- //child.setAttribute('x', value);
- attr(child, 'x', value);
- }
- }
-
- } else if (wrapper.rotation && (key === 'x' || key === 'y')) {
- doTransform = true;
-
- // apply gradients
- } else if (key === 'fill') {
- value = renderer.color(value, element, key);
-
- // circle x and y
- } else if (nodeName === 'circle' && (key === 'x' || key === 'y')) {
- key = { x: 'cx', y: 'cy' }[key] || key;
-
- // rectangle border radius
- } else if (nodeName === 'rect' && key === 'r') {
- attr(element, {
- rx: value,
- ry: value
- });
- skipAttr = true;
-
- // translation and text rotation
- } else if (key === 'translateX' || key === 'translateY' || key === 'rotation' ||
- key === 'verticalAlign' || key === 'scaleX' || key === 'scaleY') {
- doTransform = true;
- skipAttr = true;
-
- // apply opacity as subnode (required by legacy WebKit and Batik)
- } else if (key === 'stroke') {
- value = renderer.color(value, element, key);
-
- // emulate VML's dashstyle implementation
- } else if (key === 'dashstyle') {
- key = 'stroke-dasharray';
- value = value && value.toLowerCase();
- if (value === 'solid') {
- value = NONE;
- } else if (value) {
- value = value
- .replace('shortdashdotdot', '3,1,1,1,1,1,')
- .replace('shortdashdot', '3,1,1,1')
- .replace('shortdot', '1,1,')
- .replace('shortdash', '3,1,')
- .replace('longdash', '8,3,')
- .replace(/dot/g, '1,3,')
- .replace('dash', '4,3,')
- .replace(/,$/, '')
- .split(','); // ending comma
-
- i = value.length;
- while (i--) {
- value[i] = pInt(value[i]) * pick(hash['stroke-width'], wrapper['stroke-width']);
- }
- value = value.join(',');
- }
-
- // IE9/MooTools combo: MooTools returns objects instead of numbers and IE9 Beta 2
- // is unable to cast them. Test again with final IE9.
- } else if (key === 'width') {
- value = pInt(value);
-
- // Text alignment
- } else if (key === 'align') {
- key = 'text-anchor';
- value = { left: 'start', center: 'middle', right: 'end' }[value];
-
- // Title requires a subnode, #431
- } else if (key === 'title') {
- titleNode = element.getElementsByTagName('title')[0];
- if (!titleNode) {
- titleNode = doc.createElementNS(SVG_NS, 'title');
- element.appendChild(titleNode);
- }
- titleNode.textContent = value;
- }
-
- // jQuery animate changes case
- if (key === 'strokeWidth') {
- key = 'stroke-width';
- }
-
- // In Chrome/Win < 6 as well as Batik, the stroke attribute can't be set when the stroke-
- // width is 0. #1369
- if (key === 'stroke-width' || key === 'stroke') {
- wrapper[key] = value;
- // Only apply the stroke attribute if the stroke width is defined and larger than 0
- if (wrapper.stroke && wrapper['stroke-width']) {
- attr(element, 'stroke', wrapper.stroke);
- attr(element, 'stroke-width', wrapper['stroke-width']);
- wrapper.hasStroke = true;
- } else if (key === 'stroke-width' && value === 0 && wrapper.hasStroke) {
- element.removeAttribute('stroke');
- wrapper.hasStroke = false;
- }
- skipAttr = true;
- }
-
- // symbols
- if (wrapper.symbolName && /^(x|y|width|height|r|start|end|innerR|anchorX|anchorY)/.test(key)) {
-
-
- if (!hasSetSymbolSize) {
- wrapper.symbolAttr(hash);
- hasSetSymbolSize = true;
- }
- skipAttr = true;
- }
-
- // let the shadow follow the main element
- if (shadows && /^(width|height|visibility|x|y|d|transform|cx|cy|r)$/.test(key)) {
- i = shadows.length;
- while (i--) {
- attr(
- shadows[i],
- key,
- key === 'height' ?
- mathMax(value - (shadows[i].cutHeight || 0), 0) :
- value
- );
- }
- }
-
- // validate heights
- if ((key === 'width' || key === 'height') && nodeName === 'rect' && value < 0) {
- value = 0;
- }
-
- // Record for animation and quick access without polling the DOM
- wrapper[key] = value;
-
-
- if (key === 'text') {
- // Delete bBox memo when the text changes
- if (value !== wrapper.textStr) {
- delete wrapper.bBox;
- }
- wrapper.textStr = value;
- if (wrapper.added) {
- renderer.buildText(wrapper);
- }
- } else if (!skipAttr) {
- attr(element, key, value);
- }
-
- }
-
- }
-
- // Update transform. Do this outside the loop to prevent redundant updating for batch setting
- // of attributes.
- if (doTransform) {
- wrapper.updateTransform();
- }
-
- }
-
- return ret;
- },
-
-
- /**
- * Add a class name to an element
- */
- addClass: function (className) {
- var element = this.element,
- currentClassName = attr(element, 'class') || '';
-
- if (currentClassName.indexOf(className) === -1) {
- attr(element, 'class', currentClassName + ' ' + className);
- }
- return this;
- },
- /* hasClass and removeClass are not (yet) needed
- hasClass: function (className) {
- return attr(this.element, 'class').indexOf(className) !== -1;
- },
- removeClass: function (className) {
- attr(this.element, 'class', attr(this.element, 'class').replace(className, ''));
- return this;
- },
- */
-
- /**
- * If one of the symbol size affecting parameters are changed,
- * check all the others only once for each call to an element's
- * .attr() method
- * @param {Object} hash
- */
- symbolAttr: function (hash) {
- var wrapper = this;
-
- each(['x', 'y', 'r', 'start', 'end', 'width', 'height', 'innerR', 'anchorX', 'anchorY'], function (key) {
- wrapper[key] = pick(hash[key], wrapper[key]);
- });
-
- wrapper.attr({
- d: wrapper.renderer.symbols[wrapper.symbolName](
- wrapper.x,
- wrapper.y,
- wrapper.width,
- wrapper.height,
- wrapper
- )
- });
- },
-
- /**
- * Apply a clipping path to this object
- * @param {String} id
- */
- clip: function (clipRect) {
- return this.attr('clip-path', clipRect ? 'url(' + this.renderer.url + '#' + clipRect.id + ')' : NONE);
- },
-
- /**
- * Calculate the coordinates needed for drawing a rectangle crisply and return the
- * calculated attributes
- * @param {Number} strokeWidth
- * @param {Number} x
- * @param {Number} y
- * @param {Number} width
- * @param {Number} height
- */
- crisp: function (strokeWidth, x, y, width, height) {
-
- var wrapper = this,
- key,
- attribs = {},
- values = {},
- normalizer;
-
- strokeWidth = strokeWidth || wrapper.strokeWidth || (wrapper.attr && wrapper.attr('stroke-width')) || 0;
- normalizer = mathRound(strokeWidth) % 2 / 2; // mathRound because strokeWidth can sometimes have roundoff errors
-
- // normalize for crisp edges
- values.x = mathFloor(x || wrapper.x || 0) + normalizer;
- values.y = mathFloor(y || wrapper.y || 0) + normalizer;
- values.width = mathFloor((width || wrapper.width || 0) - 2 * normalizer);
- values.height = mathFloor((height || wrapper.height || 0) - 2 * normalizer);
- values.strokeWidth = strokeWidth;
-
- for (key in values) {
- if (wrapper[key] !== values[key]) { // only set attribute if changed
- wrapper[key] = attribs[key] = values[key];
- }
- }
-
- return attribs;
- },
-
- /**
- * Set styles for the element
- * @param {Object} styles
- */
- css: function (styles) {
- /*jslint unparam: true*//* allow unused param a in the regexp function below */
- var elemWrapper = this,
- elem = elemWrapper.element,
- textWidth = styles && styles.width && elem.nodeName.toLowerCase() === 'text',
- n,
- serializedCss = '',
- hyphenate = function (a, b) { return '-' + b.toLowerCase(); };
- /*jslint unparam: false*/
-
- // convert legacy
- if (styles && styles.color) {
- styles.fill = styles.color;
- }
-
- // Merge the new styles with the old ones
- styles = extend(
- elemWrapper.styles,
- styles
- );
-
- // store object
- elemWrapper.styles = styles;
-
-
- // Don't handle line wrap on canvas
- if (useCanVG && textWidth) {
- delete styles.width;
- }
-
- // serialize and set style attribute
- if (isIE && !hasSVG) { // legacy IE doesn't support setting style attribute
- if (textWidth) {
- delete styles.width;
- }
- css(elemWrapper.element, styles);
- } else {
- for (n in styles) {
- serializedCss += n.replace(/([A-Z])/g, hyphenate) + ':' + styles[n] + ';';
- }
- attr(elem, 'style', serializedCss); // #1881
- }
-
-
- // re-build text
- if (textWidth && elemWrapper.added) {
- elemWrapper.renderer.buildText(elemWrapper);
- }
-
- return elemWrapper;
- },
-
- /**
- * Add an event listener
- * @param {String} eventType
- * @param {Function} handler
- */
- on: function (eventType, handler) {
- var element = this.element;
- // touch
- if (hasTouch && eventType === 'click') {
- element.ontouchstart = function (e) {
- e.preventDefault();
- handler.call(element, e);
- };
- }
- // simplest possible event model for internal use
- element['on' + eventType] = handler;
- return this;
- },
-
- /**
- * Set the coordinates needed to draw a consistent radial gradient across
- * pie slices regardless of positioning inside the chart. The format is
- * [centerX, centerY, diameter] in pixels.
- */
- setRadialReference: function (coordinates) {
- this.element.radialReference = coordinates;
- return this;
- },
-
- /**
- * Move an object and its children by x and y values
- * @param {Number} x
- * @param {Number} y
- */
- translate: function (x, y) {
- return this.attr({
- translateX: x,
- translateY: y
- });
- },
-
- /**
- * Invert a group, rotate and flip
- */
- invert: function () {
- var wrapper = this;
- wrapper.inverted = true;
- wrapper.updateTransform();
- return wrapper;
- },
-
- /**
- * Apply CSS to HTML elements. This is used in text within SVG rendering and
- * by the VML renderer
- */
- htmlCss: function (styles) {
- var wrapper = this,
- element = wrapper.element,
- textWidth = styles && element.tagName === 'SPAN' && styles.width;
-
- if (textWidth) {
- delete styles.width;
- wrapper.textWidth = textWidth;
- wrapper.updateTransform();
- }
-
- wrapper.styles = extend(wrapper.styles, styles);
- css(wrapper.element, styles);
-
- return wrapper;
- },
-
-
-
- /**
- * VML and useHTML method for calculating the bounding box based on offsets
- * @param {Boolean} refresh Whether to force a fresh value from the DOM or to
- * use the cached value
- *
- * @return {Object} A hash containing values for x, y, width and height
- */
-
- htmlGetBBox: function () {
- var wrapper = this,
- element = wrapper.element,
- bBox = wrapper.bBox;
-
- // faking getBBox in exported SVG in legacy IE
- if (!bBox) {
- // faking getBBox in exported SVG in legacy IE (is this a duplicate of the fix for #1079?)
- if (element.nodeName === 'text') {
- element.style.position = ABSOLUTE;
- }
-
- bBox = wrapper.bBox = {
- x: element.offsetLeft,
- y: element.offsetTop,
- width: element.offsetWidth,
- height: element.offsetHeight
- };
- }
-
- return bBox;
- },
-
- /**
- * VML override private method to update elements based on internal
- * properties based on SVG transform
- */
- htmlUpdateTransform: function () {
- // aligning non added elements is expensive
- if (!this.added) {
- this.alignOnAdd = true;
- return;
- }
-
- var wrapper = this,
- renderer = wrapper.renderer,
- elem = wrapper.element,
- translateX = wrapper.translateX || 0,
- translateY = wrapper.translateY || 0,
- x = wrapper.x || 0,
- y = wrapper.y || 0,
- align = wrapper.textAlign || 'left',
- alignCorrection = { left: 0, center: 0.5, right: 1 }[align],
- nonLeft = align && align !== 'left',
- shadows = wrapper.shadows;
-
- // apply translate
- css(elem, {
- marginLeft: translateX,
- marginTop: translateY
- });
- if (shadows) { // used in labels/tooltip
- each(shadows, function (shadow) {
- css(shadow, {
- marginLeft: translateX + 1,
- marginTop: translateY + 1
- });
- });
- }
-
- // apply inversion
- if (wrapper.inverted) { // wrapper is a group
- each(elem.childNodes, function (child) {
- renderer.invertChild(child, elem);
- });
- }
-
- if (elem.tagName === 'SPAN') {
-
- var width, height,
- rotation = wrapper.rotation,
- baseline,
- radians = 0,
- costheta = 1,
- sintheta = 0,
- quad,
- textWidth = pInt(wrapper.textWidth),
- xCorr = wrapper.xCorr || 0,
- yCorr = wrapper.yCorr || 0,
- currentTextTransform = [rotation, align, elem.innerHTML, wrapper.textWidth].join(',');
-
- if (currentTextTransform !== wrapper.cTT) { // do the calculations and DOM access only if properties changed
-
- if (defined(rotation)) {
-
- radians = rotation * deg2rad; // deg to rad
- costheta = mathCos(radians);
- sintheta = mathSin(radians);
-
- wrapper.setSpanRotation(rotation, sintheta, costheta);
-
- }
-
- width = pick(wrapper.elemWidth, elem.offsetWidth);
- height = pick(wrapper.elemHeight, elem.offsetHeight);
-
- // update textWidth
- if (width > textWidth && /[ \-]/.test(elem.textContent || elem.innerText)) { // #983, #1254
- css(elem, {
- width: textWidth + PX,
- display: 'block',
- whiteSpace: 'normal'
- });
- width = textWidth;
- }
-
- // correct x and y
- baseline = renderer.fontMetrics(elem.style.fontSize).b;
- xCorr = costheta < 0 && -width;
- yCorr = sintheta < 0 && -height;
-
- // correct for baseline and corners spilling out after rotation
- quad = costheta * sintheta < 0;
- xCorr += sintheta * baseline * (quad ? 1 - alignCorrection : alignCorrection);
- yCorr -= costheta * baseline * (rotation ? (quad ? alignCorrection : 1 - alignCorrection) : 1);
-
- // correct for the length/height of the text
- if (nonLeft) {
- xCorr -= width * alignCorrection * (costheta < 0 ? -1 : 1);
- if (rotation) {
- yCorr -= height * alignCorrection * (sintheta < 0 ? -1 : 1);
- }
- css(elem, {
- textAlign: align
- });
- }
-
- // record correction
- wrapper.xCorr = xCorr;
- wrapper.yCorr = yCorr;
- }
-
- // apply position with correction
- css(elem, {
- left: (x + xCorr) + PX,
- top: (y + yCorr) + PX
- });
-
- // force reflow in webkit to apply the left and top on useHTML element (#1249)
- if (isWebKit) {
- height = elem.offsetHeight; // assigned to height for JSLint purpose
- }
-
- // record current text transform
- wrapper.cTT = currentTextTransform;
- }
- },
-
- /**
- * Set the rotation of an individual HTML span
- */
- setSpanRotation: function (rotation) {
- var rotationStyle = {},
- cssTransformKey = isIE ? '-ms-transform' : isWebKit ? '-webkit-transform' : isFirefox ? 'MozTransform' : isOpera ? '-o-transform' : '';
-
- rotationStyle[cssTransformKey] = rotationStyle.transform = 'rotate(' + rotation + 'deg)';
- css(this.element, rotationStyle);
- },
-
- /**
- * Private method to update the transform attribute based on internal
- * properties
- */
- updateTransform: function () {
- var wrapper = this,
- translateX = wrapper.translateX || 0,
- translateY = wrapper.translateY || 0,
- scaleX = wrapper.scaleX,
- scaleY = wrapper.scaleY,
- inverted = wrapper.inverted,
- rotation = wrapper.rotation,
- transform;
-
- // flipping affects translate as adjustment for flipping around the group's axis
- if (inverted) {
- translateX += wrapper.attr('width');
- translateY += wrapper.attr('height');
- }
-
- // Apply translate. Nearly all transformed elements have translation, so instead
- // of checking for translate = 0, do it always (#1767, #1846).
- transform = ['translate(' + translateX + ',' + translateY + ')'];
-
- // apply rotation
- if (inverted) {
- transform.push('rotate(90) scale(-1,1)');
- } else if (rotation) { // text rotation
- transform.push('rotate(' + rotation + ' ' + (wrapper.x || 0) + ' ' + (wrapper.y || 0) + ')');
- }
-
- // apply scale
- if (defined(scaleX) || defined(scaleY)) {
- transform.push('scale(' + pick(scaleX, 1) + ' ' + pick(scaleY, 1) + ')');
- }
-
- if (transform.length) {
- attr(wrapper.element, 'transform', transform.join(' '));
- }
- },
- /**
- * Bring the element to the front
- */
- toFront: function () {
- var element = this.element;
- element.parentNode.appendChild(element);
- return this;
- },
-
-
- /**
- * Break down alignment options like align, verticalAlign, x and y
- * to x and y relative to the chart.
- *
- * @param {Object} alignOptions
- * @param {Boolean} alignByTranslate
- * @param {String[Object} box The box to align to, needs a width and height. When the
- * box is a string, it refers to an object in the Renderer. For example, when
- * box is 'spacingBox', it refers to Renderer.spacingBox which holds width, height
- * x and y properties.
- *
- */
- align: function (alignOptions, alignByTranslate, box) {
- var align,
- vAlign,
- x,
- y,
- attribs = {},
- alignTo,
- renderer = this.renderer,
- alignedObjects = renderer.alignedObjects;
-
- // First call on instanciate
- if (alignOptions) {
- this.alignOptions = alignOptions;
- this.alignByTranslate = alignByTranslate;
- if (!box || isString(box)) { // boxes other than renderer handle this internally
- this.alignTo = alignTo = box || 'renderer';
- erase(alignedObjects, this); // prevent duplicates, like legendGroup after resize
- alignedObjects.push(this);
- box = null; // reassign it below
- }
-
- // When called on resize, no arguments are supplied
- } else {
- alignOptions = this.alignOptions;
- alignByTranslate = this.alignByTranslate;
- alignTo = this.alignTo;
- }
-
- box = pick(box, renderer[alignTo], renderer);
-
- // Assign variables
- align = alignOptions.align;
- vAlign = alignOptions.verticalAlign;
- x = (box.x || 0) + (alignOptions.x || 0); // default: left align
- y = (box.y || 0) + (alignOptions.y || 0); // default: top align
-
- // Align
- if (align === 'right' || align === 'center') {
- x += (box.width - (alignOptions.width || 0)) /
- { right: 1, center: 2 }[align];
- }
- attribs[alignByTranslate ? 'translateX' : 'x'] = mathRound(x);
-
-
- // Vertical align
- if (vAlign === 'bottom' || vAlign === 'middle') {
- y += (box.height - (alignOptions.height || 0)) /
- ({ bottom: 1, middle: 2 }[vAlign] || 1);
-
- }
- attribs[alignByTranslate ? 'translateY' : 'y'] = mathRound(y);
-
- // Animate only if already placed
- this[this.placed ? 'animate' : 'attr'](attribs);
- this.placed = true;
- this.alignAttr = attribs;
-
- return this;
- },
-
- /**
- * Get the bounding box (width, height, x and y) for the element
- */
- getBBox: function () {
- var wrapper = this,
- bBox = wrapper.bBox,
- renderer = wrapper.renderer,
- width,
- height,
- rotation = wrapper.rotation,
- element = wrapper.element,
- styles = wrapper.styles,
- rad = rotation * deg2rad;
-
- if (!bBox) {
- // SVG elements
- if (element.namespaceURI === SVG_NS || renderer.forExport) {
- try { // Fails in Firefox if the container has display: none.
-
- bBox = element.getBBox ?
- // SVG: use extend because IE9 is not allowed to change width and height in case
- // of rotation (below)
- extend({}, element.getBBox()) :
- // Canvas renderer and legacy IE in export mode
- {
- width: element.offsetWidth,
- height: element.offsetHeight
- };
- } catch (e) {}
-
- // If the bBox is not set, the try-catch block above failed. The other condition
- // is for Opera that returns a width of -Infinity on hidden elements.
- if (!bBox || bBox.width < 0) {
- bBox = { width: 0, height: 0 };
- }
-
-
- // VML Renderer or useHTML within SVG
- } else {
-
- bBox = wrapper.htmlGetBBox();
-
- }
-
- // True SVG elements as well as HTML elements in modern browsers using the .useHTML option
- // need to compensated for rotation
- if (renderer.isSVG) {
- width = bBox.width;
- height = bBox.height;
-
- // Workaround for wrong bounding box in IE9 and IE10 (#1101, #1505, #1669)
- if (isIE && styles && styles.fontSize === '11px' && height.toPrecision(3) === '22.7') {
- bBox.height = height = 14;
- }
-
- // Adjust for rotated text
- if (rotation) {
- bBox.width = mathAbs(height * mathSin(rad)) + mathAbs(width * mathCos(rad));
- bBox.height = mathAbs(height * mathCos(rad)) + mathAbs(width * mathSin(rad));
- }
- }
-
- wrapper.bBox = bBox;
- }
- return bBox;
- },
-
- /**
- * Show the element
- */
- show: function () {
- return this.attr({ visibility: VISIBLE });
- },
-
- /**
- * Hide the element
- */
- hide: function () {
- return this.attr({ visibility: HIDDEN });
- },
-
- fadeOut: function (duration) {
- var elemWrapper = this;
- elemWrapper.animate({
- opacity: 0
- }, {
- duration: duration || 150,
- complete: function () {
- elemWrapper.hide();
- }
- });
- },
-
- /**
- * Add the element
- * @param {Object|Undefined} parent Can be an element, an element wrapper or undefined
- * to append the element to the renderer.box.
- */
- add: function (parent) {
-
- var renderer = this.renderer,
- parentWrapper = parent || renderer,
- parentNode = parentWrapper.element || renderer.box,
- childNodes = parentNode.childNodes,
- element = this.element,
- zIndex = attr(element, 'zIndex'),
- otherElement,
- otherZIndex,
- i,
- inserted;
-
- if (parent) {
- this.parentGroup = parent;
- }
-
- // mark as inverted
- this.parentInverted = parent && parent.inverted;
-
- // build formatted text
- if (this.textStr !== undefined) {
- renderer.buildText(this);
- }
-
- // mark the container as having z indexed children
- if (zIndex) {
- parentWrapper.handleZ = true;
- zIndex = pInt(zIndex);
- }
-
- // insert according to this and other elements' zIndex
- if (parentWrapper.handleZ) { // this element or any of its siblings has a z index
- for (i = 0; i < childNodes.length; i++) {
- otherElement = childNodes[i];
- otherZIndex = attr(otherElement, 'zIndex');
- if (otherElement !== element && (
- // insert before the first element with a higher zIndex
- pInt(otherZIndex) > zIndex ||
- // if no zIndex given, insert before the first element with a zIndex
- (!defined(zIndex) && defined(otherZIndex))
-
- )) {
- parentNode.insertBefore(element, otherElement);
- inserted = true;
- break;
- }
- }
- }
-
- // default: append at the end
- if (!inserted) {
- parentNode.appendChild(element);
- }
-
- // mark as added
- this.added = true;
-
- // fire an event for internal hooks
- fireEvent(this, 'add');
-
- return this;
- },
-
- /**
- * Removes a child either by removeChild or move to garbageBin.
- * Issue 490; in VML removeChild results in Orphaned nodes according to sIEve, discardElement does not.
- */
- safeRemoveChild: function (element) {
- var parentNode = element.parentNode;
- if (parentNode) {
- parentNode.removeChild(element);
- }
- },
-
- /**
- * Destroy the element and element wrapper
- */
- destroy: function () {
- var wrapper = this,
- element = wrapper.element || {},
- shadows = wrapper.shadows,
- parentToClean = wrapper.renderer.isSVG && element.nodeName === 'SPAN' && element.parentNode,
- grandParent,
- key,
- i;
-
- // remove events
- element.onclick = element.onmouseout = element.onmouseover = element.onmousemove = element.point = null;
- stop(wrapper); // stop running animations
-
- if (wrapper.clipPath) {
- wrapper.clipPath = wrapper.clipPath.destroy();
- }
-
- // Destroy stops in case this is a gradient object
- if (wrapper.stops) {
- for (i = 0; i < wrapper.stops.length; i++) {
- wrapper.stops[i] = wrapper.stops[i].destroy();
- }
- wrapper.stops = null;
- }
-
- // remove element
- wrapper.safeRemoveChild(element);
-
- // destroy shadows
- if (shadows) {
- each(shadows, function (shadow) {
- wrapper.safeRemoveChild(shadow);
- });
- }
-
- // In case of useHTML, clean up empty containers emulating SVG groups (#1960).
- while (parentToClean && parentToClean.childNodes.length === 0) {
- grandParent = parentToClean.parentNode;
- wrapper.safeRemoveChild(parentToClean);
- parentToClean = grandParent;
- }
-
- // remove from alignObjects
- if (wrapper.alignTo) {
- erase(wrapper.renderer.alignedObjects, wrapper);
- }
-
- for (key in wrapper) {
- delete wrapper[key];
- }
-
- return null;
- },
-
- /**
- * Add a shadow to the element. Must be done after the element is added to the DOM
- * @param {Boolean|Object} shadowOptions
- */
- shadow: function (shadowOptions, group, cutOff) {
- var shadows = [],
- i,
- shadow,
- element = this.element,
- strokeWidth,
- shadowWidth,
- shadowElementOpacity,
-
- // compensate for inverted plot area
- transform;
-
-
- if (shadowOptions) {
- shadowWidth = pick(shadowOptions.width, 3);
- shadowElementOpacity = (shadowOptions.opacity || 0.15) / shadowWidth;
- transform = this.parentInverted ?
- '(-1,-1)' :
- '(' + pick(shadowOptions.offsetX, 1) + ', ' + pick(shadowOptions.offsetY, 1) + ')';
- for (i = 1; i <= shadowWidth; i++) {
- shadow = element.cloneNode(0);
- strokeWidth = (shadowWidth * 2) + 1 - (2 * i);
- attr(shadow, {
- 'isShadow': 'true',
- 'stroke': shadowOptions.color || 'black',
- 'stroke-opacity': shadowElementOpacity * i,
- 'stroke-width': strokeWidth,
- 'transform': 'translate' + transform,
- 'fill': NONE
- });
- if (cutOff) {
- attr(shadow, 'height', mathMax(attr(shadow, 'height') - strokeWidth, 0));
- shadow.cutHeight = strokeWidth;
- }
-
- if (group) {
- group.element.appendChild(shadow);
- } else {
- element.parentNode.insertBefore(shadow, element);
- }
-
- shadows.push(shadow);
- }
-
- this.shadows = shadows;
- }
- return this;
-
- }
-};
-
-
-/**
- * The default SVG renderer
- */
-var SVGRenderer = function () {
- this.init.apply(this, arguments);
-};
-SVGRenderer.prototype = {
- Element: SVGElement,
-
- /**
- * Initialize the SVGRenderer
- * @param {Object} container
- * @param {Number} width
- * @param {Number} height
- * @param {Boolean} forExport
- */
- init: function (container, width, height, forExport) {
- var renderer = this,
- loc = location,
- boxWrapper,
- element,
- desc;
-
- boxWrapper = renderer.createElement('svg')
- .attr({
- version: '1.1'
- });
- element = boxWrapper.element;
- container.appendChild(element);
-
- // For browsers other than IE, add the namespace attribute (#1978)
- if (container.innerHTML.indexOf('xmlns') === -1) {
- attr(element, 'xmlns', SVG_NS);
- }
-
- // object properties
- renderer.isSVG = true;
- renderer.box = element;
- renderer.boxWrapper = boxWrapper;
- renderer.alignedObjects = [];
-
- // Page url used for internal references. #24, #672, #1070
- renderer.url = (isFirefox || isWebKit) && doc.getElementsByTagName('base').length ?
- loc.href
- .replace(/#.*?$/, '') // remove the hash
- .replace(/([\('\)])/g, '\\$1') // escape parantheses and quotes
- .replace(/ /g, '%20') : // replace spaces (needed for Safari only)
- '';
-
- // Add description
- desc = this.createElement('desc').add();
- desc.element.appendChild(doc.createTextNode('Created with ' + PRODUCT + ' ' + VERSION));
-
-
- renderer.defs = this.createElement('defs').add();
- renderer.forExport = forExport;
- renderer.gradients = {}; // Object where gradient SvgElements are stored
-
- renderer.setSize(width, height, false);
-
-
-
- // Issue 110 workaround:
- // In Firefox, if a div is positioned by percentage, its pixel position may land
- // between pixels. The container itself doesn't display this, but an SVG element
- // inside this container will be drawn at subpixel precision. In order to draw
- // sharp lines, this must be compensated for. This doesn't seem to work inside
- // iframes though (like in jsFiddle).
- var subPixelFix, rect;
- if (isFirefox && container.getBoundingClientRect) {
- renderer.subPixelFix = subPixelFix = function () {
- css(container, { left: 0, top: 0 });
- rect = container.getBoundingClientRect();
- css(container, {
- left: (mathCeil(rect.left) - rect.left) + PX,
- top: (mathCeil(rect.top) - rect.top) + PX
- });
- };
-
- // run the fix now
- subPixelFix();
-
- // run it on resize
- addEvent(win, 'resize', subPixelFix);
- }
- },
-
- /**
- * Detect whether the renderer is hidden. This happens when one of the parent elements
- * has display: none. #608.
- */
- isHidden: function () {
- return !this.boxWrapper.getBBox().width;
- },
-
- /**
- * Destroys the renderer and its allocated members.
- */
- destroy: function () {
- var renderer = this,
- rendererDefs = renderer.defs;
- renderer.box = null;
- renderer.boxWrapper = renderer.boxWrapper.destroy();
-
- // Call destroy on all gradient elements
- destroyObjectProperties(renderer.gradients || {});
- renderer.gradients = null;
-
- // Defs are null in VMLRenderer
- // Otherwise, destroy them here.
- if (rendererDefs) {
- renderer.defs = rendererDefs.destroy();
- }
-
- // Remove sub pixel fix handler
- // We need to check that there is a handler, otherwise all functions that are registered for event 'resize' are removed
- // See issue #982
- if (renderer.subPixelFix) {
- removeEvent(win, 'resize', renderer.subPixelFix);
- }
-
- renderer.alignedObjects = null;
-
- return null;
- },
-
- /**
- * Create a wrapper for an SVG element
- * @param {Object} nodeName
- */
- createElement: function (nodeName) {
- var wrapper = new this.Element();
- wrapper.init(this, nodeName);
- return wrapper;
- },
-
- /**
- * Dummy function for use in canvas renderer
- */
- draw: function () {},
-
- /**
- * Parse a simple HTML string into SVG tspans
- *
- * @param {Object} textNode The parent text SVG node
- */
- buildText: function (wrapper) {
- var textNode = wrapper.element,
- renderer = this,
- forExport = renderer.forExport,
- lines = pick(wrapper.textStr, '').toString()
- .replace(/<(b|strong)>/g, '<span style="font-weight:bold">')
- .replace(/<(i|em)>/g, '<span style="font-style:italic">')
- .replace(/<a/g, '<span')
- .replace(/<\/(b|strong|i|em|a)>/g, '</span>')
- .split(/<br.*?>/g),
- childNodes = textNode.childNodes,
- styleRegex = /style="([^"]+)"/,
- hrefRegex = /href="(http[^"]+)"/,
- parentX = attr(textNode, 'x'),
- textStyles = wrapper.styles,
- width = textStyles && textStyles.width && pInt(textStyles.width),
- textLineHeight = textStyles && textStyles.lineHeight,
- i = childNodes.length;
-
- /// remove old text
- while (i--) {
- textNode.removeChild(childNodes[i]);
- }
-
- if (width && !wrapper.added) {
- this.box.appendChild(textNode); // attach it to the DOM to read offset width
- }
-
- // remove empty line at end
- if (lines[lines.length - 1] === '') {
- lines.pop();
- }
-
- // build the lines
- each(lines, function (line, lineNo) {
- var spans, spanNo = 0;
-
- line = line.replace(/<span/g, '|||<span').replace(/<\/span>/g, '</span>|||');
- spans = line.split('|||');
-
- each(spans, function (span) {
- if (span !== '' || spans.length === 1) {
- var attributes = {},
- tspan = doc.createElementNS(SVG_NS, 'tspan'),
- spanStyle; // #390
- if (styleRegex.test(span)) {
- spanStyle = span.match(styleRegex)[1].replace(/(;| |^)color([ :])/, '$1fill$2');
- attr(tspan, 'style', spanStyle);
- }
- if (hrefRegex.test(span) && !forExport) { // Not for export - #1529
- attr(tspan, 'onclick', 'location.href=\"' + span.match(hrefRegex)[1] + '\"');
- css(tspan, { cursor: 'pointer' });
- }
-
- span = (span.replace(/<(.|\n)*?>/g, '') || ' ')
- .replace(/</g, '<')
- .replace(/>/g, '>');
-
- // Nested tags aren't supported, and cause crash in Safari (#1596)
- if (span !== ' ') {
-
- // add the text node
- tspan.appendChild(doc.createTextNode(span));
-
- if (!spanNo) { // first span in a line, align it to the left
- attributes.x = parentX;
- } else {
- attributes.dx = 0; // #16
- }
-
- // add attributes
- attr(tspan, attributes);
-
- // first span on subsequent line, add the line height
- if (!spanNo && lineNo) {
-
- // allow getting the right offset height in exporting in IE
- if (!hasSVG && forExport) {
- css(tspan, { display: 'block' });
- }
-
- // Set the line height based on the font size of either
- // the text element or the tspan element
- attr(
- tspan,
- 'dy',
- textLineHeight || renderer.fontMetrics(
- /px$/.test(tspan.style.fontSize) ?
- tspan.style.fontSize :
- textStyles.fontSize
- ).h,
- // Safari 6.0.2 - too optimized for its own good (#1539)
- // TODO: revisit this with future versions of Safari
- isWebKit && tspan.offsetHeight
- );
- }
-
- // Append it
- textNode.appendChild(tspan);
-
- spanNo++;
-
- // check width and apply soft breaks
- if (width) {
- var words = span.replace(/([^\^])-/g, '$1- ').split(' '), // #1273
- tooLong,
- actualWidth,
- 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, null, null, null, null, null, 'button'),
- curState = 0,
- stateOptions,
- stateStyle,
- normalStyle,
- hoverStyle,
- pressedStyle,
- STYLE = 'style',
- verticalGradient = { x1: 0, y1: 0, x2: 0, y2: 1 };
-
- // Normal state - prepare the attributes
- normalState = merge({
- 'stroke-width': 1,
- stroke: '#CCCCCC',
- fill: {
- linearGradient: verticalGradient,
- stops: [
- [0, '#FEFEFE'],
- [1, '#F6F6F6']
- ]
- },
- r: 2,
- padding: 5,
- style: {
- color: 'black'
- }
- }, normalState);
- normalStyle = normalState[STYLE];
- delete normalState[STYLE];
-
- // Hover state
- hoverState = merge(normalState, {
- stroke: '#68A',
- fill: {
- linearGradient: verticalGradient,
- stops: [
- [0, '#FFF'],
- [1, '#ACF']
- ]
- }
- }, hoverState);
- hoverStyle = hoverState[STYLE];
- delete hoverState[STYLE];
-
- // Pressed state
- pressedState = merge(normalState, {
- stroke: '#68A',
- fill: {
- linearGradient: verticalGradient,
- stops: [
- [0, '#9BD'],
- [1, '#CDF']
- ]
- }
- }, pressedState);
- pressedStyle = pressedState[STYLE];
- delete pressedState[STYLE];
-
- // Add the events. IE9 and IE10 need mouseover and mouseout to funciton (#667).
- addEvent(label.element, isIE ? 'mouseover' : 'mouseenter', function () {
- label.attr(hoverState)
- .css(hoverStyle);
- });
- addEvent(label.element, isIE ? 'mouseout' : '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) {
- var arc;
-
- if (isObject(x)) {
- y = x.y;
- r = x.r;
- innerR = x.innerR;
- start = x.start;
- end = x.end;
- x = x.x;
- }
-
- // Arcs are defined as symbols for the ability to set
- // attributes in attr and animate
- arc = this.symbol('arc', x || 0, y || 0, r || 0, r || 0, {
- innerR: innerR || 0,
- start: start || 0,
- end: end || 0
- });
- arc.r = r; // #959
- return arc;
- },
-
- /**
- * Draw and return a rectangle
- * @param {Number} x Left position
- * @param {Number} y Top position
- * @param {Number} width
- * @param {Number} height
- * @param {Number} r Border corner radius
- * @param {Number} strokeWidth A stroke width can be supplied to allow crisp drawing
- */
- rect: function (x, y, width, height, r, strokeWidth) {
-
- r = isObject(x) ? x.r : r;
-
- var wrapper = this.createElement('rect').attr({
- rx: r,
- ry: r,
- fill: NONE
- });
- return wrapper.attr(
- isObject(x) ?
- x :
- // do not crispify when an object is passed in (as in column charts)
- wrapper.crisp(strokeWidth, x, y, mathMax(width, 0), mathMax(height, 0))
- );
- },
-
- /**
- * Resize the box and re-align all aligned elements
- * @param {Object} width
- * @param {Object} height
- * @param {Boolean} animate
- *
- */
- setSize: function (width, height, animate) {
- var renderer = this,
- alignedObjects = renderer.alignedObjects,
- i = alignedObjects.length;
-
- renderer.width = width;
- renderer.height = height;
-
- renderer.boxWrapper[pick(animate, true) ? 'animate' : 'attr']({
- width: width,
- height: height
- });
-
- while (i--) {
- alignedObjects[i].align();
- }
- },
-
- /**
- * Create a group
- * @param {String} name The group will be given a class name of 'highcharts-{name}'.
- * This can be used for styling and scripting.
- */
- g: function (name) {
- var elem = this.createElement('g');
- return defined(name) ? elem.attr({ 'class': PREFIX + name }) : elem;
- },
-
- /**
- * Display an image
- * @param {String} src
- * @param {Number} x
- * @param {Number} y
- * @param {Number} width
- * @param {Number} height
- */
- image: function (src, x, y, width, height) {
- var attribs = {
- preserveAspectRatio: NONE
- },
- elemWrapper;
-
- // optional properties
- if (arguments.length > 1) {
- extend(attribs, {
- x: x,
- y: y,
- width: width,
- height: height
- });
- }
-
- elemWrapper = this.createElement('image').attr(attribs);
-
- // set the href in the xlink namespace
- if (elemWrapper.element.setAttributeNS) {
- elemWrapper.element.setAttributeNS('http://www.w3.org/1999/xlink',
- 'href', src);
- } else {
- // could be exporting in IE
- // using href throws "not supported" in ie7 and under, requries regex shim to fix later
- elemWrapper.element.setAttribute('hc-svg-href', src);
- }
-
- return elemWrapper;
- },
-
- /**
- * Draw a symbol out of pre-defined shape paths from the namespace 'symbol' object.
- *
- * @param {Object} symbol
- * @param {Object} x
- * @param {Object} y
- * @param {Object} radius
- * @param {Object} options
- */
- symbol: function (symbol, x, y, width, height, options) {
-
- var obj,
-
- // get the symbol definition function
- symbolFn = this.symbols[symbol],
-
- // check if there's a path defined for this symbol
- path = symbolFn && symbolFn(
- mathRound(x),
- mathRound(y),
- width,
- height,
- options
- ),
-
- imageElement,
- imageRegex = /^url\((.*?)\)$/,
- imageSrc,
- imageSize,
- centerImage;
-
- if (path) {
-
- obj = this.path(path);
- // expando properties for use in animate and attr
- extend(obj, {
- symbolName: symbol,
- x: x,
- y: y,
- width: width,
- height: height
- });
- if (options) {
- extend(obj, options);
- }
-
-
- // image symbols
- } else if (imageRegex.test(symbol)) {
-
- // On image load, set the size and position
- centerImage = function (img, size) {
- if (img.element) { // it may be destroyed in the meantime (#1390)
- img.attr({
- width: size[0],
- height: size[1]
- });
-
- if (!img.alignByTranslate) { // #185
- img.translate(
- mathRound((width - size[0]) / 2), // #1378
- mathRound((height - size[1]) / 2)
- );
- }
- }
- };
-
- imageSrc = symbol.match(imageRegex)[1];
- imageSize = symbolSizes[imageSrc];
-
- // Ireate the image synchronously, add attribs async
- obj = this.image(imageSrc)
- .attr({
- x: x,
- y: y
- });
- obj.isImg = true;
-
- if (imageSize) {
- centerImage(obj, imageSize);
- } else {
- // Initialize image to be 0 size so export will still function if there's no cached sizes.
- //
- obj.attr({ width: 0, height: 0 });
-
- // Create a dummy JavaScript image to get the width and height. Due to a bug in IE < 8,
- // the created element must be assigned to a variable in order to load (#292).
- imageElement = createElement('img', {
- onload: function () {
- centerImage(obj, symbolSizes[imageSrc] = [this.width, this.height]);
- },
- src: imageSrc
- });
- }
- }
-
- return obj;
- },
-
- /**
- * An extendable collection of functions for defining symbol paths.
- */
- symbols: {
- 'circle': function (x, y, w, h) {
- var cpw = 0.166 * w;
- return [
- M, x + w / 2, y,
- 'C', x + w + cpw, y, x + w + cpw, y + h, x + w / 2, y + h,
- 'C', x - cpw, y + h, x - cpw, y, x + w / 2, y,
- 'Z'
- ];
- },
-
- 'square': function (x, y, w, h) {
- return [
- M, x, y,
- L, x + w, y,
- x + w, y + h,
- x, y + h,
- 'Z'
- ];
- },
-
- 'triangle': function (x, y, w, h) {
- return [
- M, x + w / 2, y,
- L, x + w, y + h,
- x, y + h,
- 'Z'
- ];
- },
-
- 'triangle-down': function (x, y, w, h) {
- return [
- M, x, y,
- L, x + w, y,
- x + w / 2, y + h,
- 'Z'
- ];
- },
- 'diamond': function (x, y, w, h) {
- return [
- M, x + w / 2, y,
- L, x + w, y + h / 2,
- x + w / 2, y + h,
- x, y + h / 2,
- 'Z'
- ];
- },
- 'arc': function (x, y, w, h, options) {
- var start = options.start,
- radius = options.r || w || h,
- end = options.end - 0.001, // to prevent cos and sin of start and end from becoming equal on 360 arcs (related: #1561)
- innerRadius = options.innerR,
- open = options.open,
- cosStart = mathCos(start),
- sinStart = mathSin(start),
- cosEnd = mathCos(end),
- sinEnd = mathSin(end),
- longArc = options.end - start < mathPI ? 0 : 1;
-
- return [
- M,
- x + radius * cosStart,
- y + radius * sinStart,
- 'A', // arcTo
- radius, // x radius
- radius, // y radius
- 0, // slanting
- longArc, // long or short arc
- 1, // clockwise
- x + radius * cosEnd,
- y + radius * sinEnd,
- open ? M : L,
- x + innerRadius * cosEnd,
- y + innerRadius * sinEnd,
- 'A', // arcTo
- innerRadius, // x radius
- innerRadius, // y radius
- 0, // slanting
- longArc, // long or short arc
- 0, // clockwise
- x + innerRadius * cosStart,
- y + innerRadius * sinStart,
-
- open ? '' : 'Z' // close
- ];
- }
- },
-
- /**
- * Define a clipping rectangle
- * @param {String} id
- * @param {Number} x
- * @param {Number} y
- * @param {Number} width
- * @param {Number} height
- */
- clipRect: function (x, y, width, height) {
- var wrapper,
- id = PREFIX + idCounter++,
-
- clipPath = this.createElement('clipPath').attr({
- id: id
- }).add(this.defs);
-
- wrapper = this.rect(x, y, width, height, 0).add(clipPath);
- wrapper.id = id;
- wrapper.clipPath = clipPath;
-
- return wrapper;
- },
-
-
- /**
- * Take a color and return it if it's a string, make it a gradient if it's a
- * gradient configuration object. Prior to Highstock, an array was used to define
- * a linear gradient with pixel positions relative to the SVG. In newer versions
- * we change the coordinates to apply relative to the shape, using coordinates
- * 0-1 within the shape. To preserve backwards compatibility, linearGradient
- * in this definition is an object of x1, y1, x2 and y2.
- *
- * @param {Object} color The color or config object
- */
- color: function (color, elem, prop) {
- var renderer = this,
- colorObject,
- regexRgba = /^rgba/,
- gradName,
- gradAttr,
- gradients,
- gradientObject,
- stops,
- stopColor,
- stopOpacity,
- radialReference,
- n,
- id,
- key = [];
-
- // Apply linear or radial gradients
- if (color && color.linearGradient) {
- gradName = 'linearGradient';
- } else if (color && color.radialGradient) {
- gradName = 'radialGradient';
- }
-
- if (gradName) {
- gradAttr = color[gradName];
- gradients = renderer.gradients;
- stops = color.stops;
- radialReference = elem.radialReference;
-
- // Keep < 2.2 kompatibility
- if (isArray(gradAttr)) {
- color[gradName] = gradAttr = {
- x1: gradAttr[0],
- y1: gradAttr[1],
- x2: gradAttr[2],
- y2: gradAttr[3],
- gradientUnits: 'userSpaceOnUse'
- };
- }
-
- // Correct the radial gradient for the radial reference system
- if (gradName === 'radialGradient' && radialReference && !defined(gradAttr.gradientUnits)) {
- gradAttr = merge(gradAttr, {
- cx: (radialReference[0] - radialReference[2] / 2) + gradAttr.cx * radialReference[2],
- cy: (radialReference[1] - radialReference[2] / 2) + gradAttr.cy * radialReference[2],
- r: gradAttr.r * radialReference[2],
- gradientUnits: 'userSpaceOnUse'
- });
- }
-
- // Build the unique key to detect whether we need to create a new element (#1282)
- for (n in gradAttr) {
- if (n !== 'id') {
- key.push(n, gradAttr[n]);
- }
- }
- for (n in stops) {
- key.push(stops[n]);
- }
- key = key.join(',');
-
- // Check if a gradient object with the same config object is created within this renderer
- if (gradients[key]) {
- id = gradients[key].id;
-
- } else {
-
- // Set the id and create the element
- gradAttr.id = id = PREFIX + idCounter++;
- gradients[key] = gradientObject = renderer.createElement(gradName)
- .attr(gradAttr)
- .add(renderer.defs);
-
-
- // The gradient needs to keep a list of stops to be able to destroy them
- gradientObject.stops = [];
- each(stops, function (stop) {
- var stopObject;
- if (regexRgba.test(stop[1])) {
- colorObject = Color(stop[1]);
- stopColor = colorObject.get('rgb');
- stopOpacity = colorObject.get('a');
- } else {
- stopColor = stop[1];
- stopOpacity = 1;
- }
- stopObject = renderer.createElement('stop').attr({
- offset: stop[0],
- 'stop-color': stopColor,
- 'stop-opacity': stopOpacity
- }).add(gradientObject);
-
- // Add the stop element to the gradient
- gradientObject.stops.push(stopObject);
- });
- }
-
- // Return the reference to the gradient object
- return 'url(' + renderer.url + '#' + id + ')';
-
- // Webkit and Batik can't show rgba.
- } else if (regexRgba.test(color)) {
- colorObject = Color(color);
- attr(elem, prop + '-opacity', colorObject.get('a'));
-
- return colorObject.get('rgb');
-
-
- } else {
- // Remove the opacity attribute added above. Does not throw if the attribute is not there.
- elem.removeAttribute(prop + '-opacity');
-
- return color;
- }
-
- },
-
-
- /**
- * Add text to the SVG object
- * @param {String} str
- * @param {Number} x Left position
- * @param {Number} y Top position
- * @param {Boolean} useHTML Use HTML to render the text
- */
- text: function (str, x, y, useHTML) {
-
- // declare variables
- var renderer = this,
- defaultChartStyle = defaultOptions.chart.style,
- fakeSVG = useCanVG || (!hasSVG && renderer.forExport),
- wrapper;
-
- if (useHTML && !renderer.forExport) {
- return renderer.html(str, x, y);
- }
-
- x = mathRound(pick(x, 0));
- y = mathRound(pick(y, 0));
-
- wrapper = renderer.createElement('text')
- .attr({
- x: x,
- y: y,
- text: str
- })
- .css({
- fontFamily: defaultChartStyle.fontFamily,
- fontSize: defaultChartStyle.fontSize
- });
-
- // Prevent wrapping from creating false offsetWidths in export in legacy IE (#1079, #1063)
- if (fakeSVG) {
- wrapper.css({
- position: ABSOLUTE
- });
- }
-
- wrapper.x = x;
- wrapper.y = y;
- return wrapper;
- },
-
-
- /**
- * Create HTML text node. This is used by the VML renderer as well as the SVG
- * renderer through the useHTML option.
- *
- * @param {String} str
- * @param {Number} x
- * @param {Number} y
- */
- html: function (str, x, y) {
- var defaultChartStyle = defaultOptions.chart.style,
- wrapper = this.createElement('span'),
- attrSetters = wrapper.attrSetters,
- element = wrapper.element,
- renderer = wrapper.renderer;
-
- // Text setter
- attrSetters.text = function (value) {
- if (value !== element.innerHTML) {
- delete this.bBox;
- }
- element.innerHTML = value;
- return false;
- };
-
- // Various setters which rely on update transform
- attrSetters.x = attrSetters.y = attrSetters.align = function (value, key) {
- if (key === 'align') {
- key = 'textAlign'; // Do not overwrite the SVGElement.align method. Same as VML.
- }
- wrapper[key] = value;
- wrapper.htmlUpdateTransform();
- return false;
- };
-
- // Set the default attributes
- wrapper.attr({
- text: str,
- x: mathRound(x),
- y: mathRound(y)
- })
- .css({
- position: ABSOLUTE,
- whiteSpace: 'nowrap',
- fontFamily: defaultChartStyle.fontFamily,
- fontSize: defaultChartStyle.fontSize
- });
-
- // Use the HTML specific .css method
- wrapper.css = wrapper.htmlCss;
-
- // This is specific for HTML within SVG
- if (renderer.isSVG) {
- wrapper.add = function (svgGroupWrapper) {
-
- var htmlGroup,
- container = renderer.box.parentNode,
- parentGroup,
- parents = [];
-
- // Create a mock group to hold the HTML elements
- if (svgGroupWrapper) {
- htmlGroup = svgGroupWrapper.div;
- if (!htmlGroup) {
-
- // Read the parent chain into an array and read from top down
- parentGroup = svgGroupWrapper;
- while (parentGroup) {
-
- parents.push(parentGroup);
-
- // Move up to the next parent group
- parentGroup = parentGroup.parentGroup;
- }
-
- // Ensure dynamically updating position when any parent is translated
- each(parents.reverse(), function (parentGroup) {
- var htmlGroupStyle;
-
- // Create a HTML div and append it to the parent div to emulate
- // the SVG group structure
- htmlGroup = parentGroup.div = parentGroup.div || createElement(DIV, {
- className: attr(parentGroup.element, 'class')
- }, {
- position: ABSOLUTE,
- left: (parentGroup.translateX || 0) + PX,
- top: (parentGroup.translateY || 0) + PX
- }, htmlGroup || container); // the top group is appended to container
-
- // Shortcut
- htmlGroupStyle = htmlGroup.style;
-
- // Set listeners to update the HTML div's position whenever the SVG group
- // position is changed
- extend(parentGroup.attrSetters, {
- translateX: function (value) {
- htmlGroupStyle.left = value + PX;
- },
- translateY: function (value) {
- htmlGroupStyle.top = value + PX;
- },
- visibility: function (value, key) {
- htmlGroupStyle[key] = value;
- }
- });
- });
-
- }
- } else {
- htmlGroup = container;
- }
-
- htmlGroup.appendChild(element);
-
- // Shared with VML:
- wrapper.added = true;
- if (wrapper.alignOnAdd) {
- wrapper.htmlUpdateTransform();
- }
-
- return wrapper;
- };
- }
- return wrapper;
- },
-
- /**
- * Utility to return the baseline offset and total line height from the font size
- */
- fontMetrics: function (fontSize) {
- fontSize = pInt(fontSize || 11);
-
- // Empirical values found by comparing font size and bounding box height.
- // Applies to the default font family. http://jsfiddle.net/highcharts/7xvn7/
- var lineHeight = fontSize < 24 ? fontSize + 4 : mathRound(fontSize * 1.2),
- baseline = mathRound(lineHeight * 0.8);
-
- return {
- h: lineHeight,
- b: baseline
- };
- },
-
- /**
- * Add a label, a text item that can hold a colored or gradient background
- * as well as a border and shadow.
- * @param {string} str
- * @param {Number} x
- * @param {Number} y
- * @param {String} shape
- * @param {Number} anchorX In case the shape has a pointer, like a flag, this is the
- * coordinates it should be pinned to
- * @param {Number} anchorY
- * @param {Boolean} baseline Whether to position the label relative to the text baseline,
- * like renderer.text, or to the upper border of the rectangle.
- * @param {String} className Class name for the group
- */
- label: function (str, x, y, shape, anchorX, anchorY, useHTML, baseline, className) {
-
- var renderer = this,
- wrapper = renderer.g(className),
- text = renderer.text('', 0, 0, useHTML)
- .attr({
- zIndex: 1
- }),
- //.add(wrapper),
- box,
- bBox,
- alignFactor = 0,
- padding = 3,
- paddingLeft = 0,
- width,
- height,
- wrapperX,
- wrapperY,
- crispAdjust = 0,
- deferredAttr = {},
- baselineOffset,
- attrSetters = wrapper.attrSetters,
- needsBox;
-
- /**
- * This function runs after the label is added to the DOM (when the bounding box is
- * available), and after the text of the label is updated to detect the new bounding
- * box and reflect it in the border box.
- */
- function updateBoxSize() {
- var boxX,
- boxY,
- style = text.element.style;
-
- bBox = (width === undefined || height === undefined || wrapper.styles.textAlign) &&
- text.getBBox();
- wrapper.width = (width || bBox.width || 0) + 2 * padding + paddingLeft;
- wrapper.height = (height || bBox.height || 0) + 2 * padding;
-
- // update the label-scoped y offset
- baselineOffset = padding + renderer.fontMetrics(style && style.fontSize).b;
-
- if (needsBox) {
-
- // create the border box if it is not already present
- if (!box) {
- boxX = mathRound(-alignFactor * padding);
- boxY = baseline ? -baselineOffset : 0;
-
- wrapper.box = box = shape ?
- renderer.symbol(shape, boxX, boxY, wrapper.width, wrapper.height) :
- renderer.rect(boxX, boxY, wrapper.width, wrapper.height, 0, deferredAttr[STROKE_WIDTH]);
- box.add(wrapper);
- }
-
- // apply the box attributes
- if (!box.isImg) { // #1630
- box.attr(merge({
- width: wrapper.width,
- height: wrapper.height
- }, deferredAttr));
- }
- deferredAttr = null;
- }
- }
-
- /**
- * This function runs after setting text or padding, but only if padding is changed
- */
- function updateTextPadding() {
- var styles = wrapper.styles,
- textAlign = styles && styles.textAlign,
- x = paddingLeft + padding * (1 - alignFactor),
- y;
-
- // determin y based on the baseline
- y = baseline ? 0 : baselineOffset;
-
- // compensate for alignment
- if (defined(width) && (textAlign === 'center' || textAlign === 'right')) {
- x += { center: 0.5, right: 1 }[textAlign] * (width - bBox.width);
- }
-
- // update if anything changed
- if (x !== text.x || y !== text.y) {
- text.attr({
- x: x,
- y: y
- });
- }
-
- // record current values
- text.x = x;
- text.y = y;
- }
-
- /**
- * Set a box attribute, or defer it if the box is not yet created
- * @param {Object} key
- * @param {Object} value
- */
- function boxAttr(key, value) {
- if (box) {
- box.attr(key, value);
- } else {
- deferredAttr[key] = value;
- }
- }
-
- function getSizeAfterAdd() {
- text.add(wrapper);
- wrapper.attr({
- text: str, // alignment is available now
- x: x,
- y: y
- });
-
- if (box && defined(anchorX)) {
- wrapper.attr({
- anchorX: anchorX,
- anchorY: anchorY
- });
- }
- }
-
- /**
- * After the text element is added, get the desired size of the border box
- * and add it before the text in the DOM.
- */
- addEvent(wrapper, 'add', getSizeAfterAdd);
-
- /*
- * Add specific attribute setters.
- */
-
- // only change local variables
- attrSetters.width = function (value) {
- width = value;
- return false;
- };
- attrSetters.height = function (value) {
- height = value;
- return false;
- };
- attrSetters.padding = function (value) {
- if (defined(value) && value !== padding) {
- padding = value;
- updateTextPadding();
- }
- return false;
- };
- attrSetters.paddingLeft = function (value) {
- if (defined(value) && value !== paddingLeft) {
- paddingLeft = value;
- updateTextPadding();
- }
- return false;
- };
-
-
- // change local variable and set attribue as well
- attrSetters.align = function (value) {
- alignFactor = { left: 0, center: 0.5, right: 1 }[value];
- return false; // prevent setting text-anchor on the group
- };
-
- // apply these to the box and the text alike
- attrSetters.text = function (value, key) {
- text.attr(key, value);
- updateBoxSize();
- updateTextPadding();
- return false;
- };
-
- // apply these to the box but not to the text
- attrSetters[STROKE_WIDTH] = function (value, key) {
- needsBox = true;
- crispAdjust = value % 2 / 2;
- boxAttr(key, value);
- return false;
- };
- attrSetters.stroke = attrSetters.fill = attrSetters.r = function (value, key) {
- if (key === 'fill') {
- needsBox = true;
- }
- boxAttr(key, value);
- return false;
- };
- attrSetters.anchorX = function (value, key) {
- anchorX = value;
- boxAttr(key, value + crispAdjust - wrapperX);
- return false;
- };
- attrSetters.anchorY = function (value, key) {
- anchorY = value;
- boxAttr(key, value - wrapperY);
- return false;
- };
-
- // rename attributes
- attrSetters.x = function (value) {
- wrapper.x = value; // for animation getter
- value -= alignFactor * ((width || bBox.width) + padding);
- wrapperX = mathRound(value);
-
- wrapper.attr('translateX', wrapperX);
- return false;
- };
- attrSetters.y = function (value) {
- wrapperY = wrapper.y = mathRound(value);
- wrapper.attr('translateY', wrapperY);
- return false;
- };
-
- // Redirect certain methods to either the box or the text
- var baseCss = wrapper.css;
- return extend(wrapper, {
- /**
- * Pick up some properties and apply them to the text instead of the wrapper
- */
- css: function (styles) {
- if (styles) {
- var textStyles = {};
- styles = merge(styles); // create a copy to avoid altering the original object (#537)
- each(['fontSize', 'fontWeight', 'fontFamily', 'color', 'lineHeight', 'width', 'textDecoration'], function (prop) {
- if (styles[prop] !== UNDEFINED) {
- textStyles[prop] = styles[prop];
- delete styles[prop];
- }
- });
- text.css(textStyles);
- }
- return baseCss.call(wrapper, styles);
- },
- /**
- * Return the bounding box of the box, not the group
- */
- getBBox: function () {
- return {
- width: bBox.width + 2 * padding,
- height: bBox.height + 2 * padding,
- x: bBox.x - padding,
- y: bBox.y - padding
- };
- },
- /**
- * Apply the shadow to the box
- */
- shadow: function (b) {
- if (box) {
- box.shadow(b);
- }
- return wrapper;
- },
- /**
- * Destroy and release memory.
- */
- destroy: function () {
- removeEvent(wrapper, 'add', getSizeAfterAdd);
-
- // Added by button implementation
- removeEvent(wrapper.element, 'mouseenter');
- removeEvent(wrapper.element, 'mouseleave');
-
- if (text) {
- text = text.destroy();
- }
- if (box) {
- box = box.destroy();
- }
- // Call base implementation to destroy the rest
- SVGElement.prototype.destroy.call(wrapper);
-
- // Release local pointers (#1298)
- wrapper = renderer = updateBoxSize = updateTextPadding = boxAttr = getSizeAfterAdd = null;
- }
- });
- }
-}; // end SVGRenderer
-
-
-// general renderer
-Renderer = SVGRenderer;
-
-
-/* ****************************************************************************
- * *
- * START OF INTERNET EXPLORER <= 8 SPECIFIC CODE *
- * *
- * For applications and websites that don't need IE support, like platform *
- * targeted mobile apps and web apps, this code can be removed. *
- * *
- *****************************************************************************/
-
-/**
- * @constructor
- */
-var VMLRenderer, VMLElement;
-if (!hasSVG && !useCanVG) {
-
-/**
- * The VML element wrapper.
- */
-Highcharts.VMLElement = VMLElement = {
-
- /**
- * Initialize a new VML element wrapper. It builds the markup as a string
- * to minimize DOM traffic.
- * @param {Object} renderer
- * @param {Object} nodeName
- */
- init: function (renderer, nodeName) {
- var wrapper = this,
- markup = ['<', nodeName, ' filled="f" stroked="f"'],
- style = ['position: ', ABSOLUTE, ';'],
- isDiv = nodeName === DIV;
-
- // divs and shapes need size
- if (nodeName === 'shape' || isDiv) {
- style.push('left:0;top:0;width:1px;height:1px;');
- }
- style.push('visibility: ', isDiv ? HIDDEN : VISIBLE);
-
- markup.push(' style="', style.join(''), '"/>');
-
- // create element with default attributes and style
- if (nodeName) {
- markup = isDiv || nodeName === 'span' || nodeName === 'img' ?
- markup.join('')
- : renderer.prepVML(markup);
- wrapper.element = createElement(markup);
- }
-
- wrapper.renderer = renderer;
- wrapper.attrSetters = {};
- },
-
- /**
- * Add the node to the given parent
- * @param {Object} parent
- */
- add: function (parent) {
- var wrapper = this,
- renderer = wrapper.renderer,
- element = wrapper.element,
- box = renderer.box,
- inverted = parent && parent.inverted,
-
- // get the parent node
- parentNode = parent ?
- parent.element || parent :
- box;
-
-
- // if the parent group is inverted, apply inversion on all children
- if (inverted) { // only on groups
- renderer.invertChild(element, parentNode);
- }
-
- // append it
- parentNode.appendChild(element);
-
- // align text after adding to be able to read offset
- wrapper.added = true;
- if (wrapper.alignOnAdd && !wrapper.deferUpdateTransform) {
- wrapper.updateTransform();
- }
-
- // fire an event for internal hooks
- fireEvent(wrapper, 'add');
-
- return wrapper;
- },
-
- /**
- * VML always uses htmlUpdateTransform
- */
- updateTransform: SVGElement.prototype.htmlUpdateTransform,
-
- /**
- * Set the rotation of a span with oldIE's filter
- */
- setSpanRotation: function (rotation, sintheta, costheta) {
- // Adjust for alignment and rotation. Rotation of useHTML content is not yet implemented
- // but it can probably be implemented for Firefox 3.5+ on user request. FF3.5+
- // has support for CSS3 transform. The getBBox method also needs to be updated
- // to compensate for the rotation, like it currently does for SVG.
- // Test case: http://highcharts.com/tests/?file=text-rotation
- css(this.element, {
- filter: rotation ? ['progid:DXImageTransform.Microsoft.Matrix(M11=', costheta,
- ', M12=', -sintheta, ', M21=', sintheta, ', M22=', costheta,
- ', sizingMethod=\'auto expand\')'].join('') : NONE
- });
- },
-
- /**
- * Get or set attributes
- */
- attr: function (hash, val) {
- var wrapper = this,
- key,
- value,
- i,
- result,
- element = wrapper.element || {},
- elemStyle = element.style,
- nodeName = element.nodeName,
- renderer = wrapper.renderer,
- symbolName = wrapper.symbolName,
- hasSetSymbolSize,
- shadows = wrapper.shadows,
- skipAttr,
- attrSetters = wrapper.attrSetters,
- ret = wrapper;
-
- // single key-value pair
- if (isString(hash) && defined(val)) {
- key = hash;
- hash = {};
- hash[key] = val;
- }
-
- // used as a getter, val is undefined
- if (isString(hash)) {
- key = hash;
- if (key === 'strokeWidth' || key === 'stroke-width') {
- ret = wrapper.strokeweight;
- } else {
- ret = wrapper[key];
- }
-
- // setter
- } else {
- for (key in hash) {
- value = hash[key];
- skipAttr = false;
-
- // check for a specific attribute setter
- result = attrSetters[key] && attrSetters[key].call(wrapper, value, key);
-
- if (result !== false && value !== null) { // #620
-
- if (result !== UNDEFINED) {
- value = result; // the attribute setter has returned a new value to set
- }
-
-
- // prepare paths
- // symbols
- if (symbolName && /^(x|y|r|start|end|width|height|innerR|anchorX|anchorY)/.test(key)) {
- // if one of the symbol size affecting parameters are changed,
- // check all the others only once for each call to an element's
- // .attr() method
- if (!hasSetSymbolSize) {
- wrapper.symbolAttr(hash);
-
- hasSetSymbolSize = true;
- }
- skipAttr = true;
-
- } else if (key === 'd') {
- value = value || [];
- wrapper.d = value.join(' '); // used in getter for animation
-
- // convert paths
- i = value.length;
- var convertedPath = [],
- clockwise;
- while (i--) {
-
- // Multiply by 10 to allow subpixel precision.
- // Substracting half a pixel seems to make the coordinates
- // align with SVG, but this hasn't been tested thoroughly
- if (isNumber(value[i])) {
- convertedPath[i] = mathRound(value[i] * 10) - 5;
- } else if (value[i] === 'Z') { // close the path
- convertedPath[i] = 'x';
- } else {
- convertedPath[i] = value[i];
-
- // When the start X and end X coordinates of an arc are too close,
- // they are rounded to the same value above. In this case, substract 1 from the end X
- // position. #760, #1371.
- if (value.isArc && (value[i] === 'wa' || value[i] === 'at')) {
- clockwise = value[i] === 'wa' ? 1 : -1; // #1642
- if (convertedPath[i + 5] === convertedPath[i + 7]) {
- convertedPath[i + 7] -= clockwise;
- }
- // Start and end Y (#1410)
- if (convertedPath[i + 6] === convertedPath[i + 8]) {
- convertedPath[i + 8] -= clockwise;
- }
- }
- }
-
- }
- value = convertedPath.join(' ') || 'x';
- element.path = value;
-
- // update shadows
- if (shadows) {
- i = shadows.length;
- while (i--) {
- shadows[i].path = shadows[i].cutOff ? this.cutOffPath(value, shadows[i].cutOff) : value;
- }
- }
- skipAttr = true;
-
- // handle visibility
- } else if (key === 'visibility') {
-
- // let the shadow follow the main element
- if (shadows) {
- i = shadows.length;
- while (i--) {
- shadows[i].style[key] = value;
- }
- }
-
- // Instead of toggling the visibility CSS property, move the div out of the viewport.
- // This works around #61 and #586
- if (nodeName === 'DIV') {
- value = value === HIDDEN ? '-999em' : 0;
-
- // In order to redraw, IE7 needs the div to be visible when tucked away
- // outside the viewport. So the visibility is actually opposite of
- // the expected value. This applies to the tooltip only.
- if (!docMode8) {
- elemStyle[key] = value ? VISIBLE : HIDDEN;
- }
- key = 'top';
- }
- elemStyle[key] = value;
- skipAttr = true;
-
- // directly mapped to css
- } else if (key === 'zIndex') {
-
- if (value) {
- elemStyle[key] = value;
- }
- skipAttr = true;
-
- // x, y, width, height
- } else if (inArray(key, ['x', 'y', 'width', 'height']) !== -1) {
-
- wrapper[key] = value; // used in getter
-
- if (key === 'x' || key === 'y') {
- key = { x: 'left', y: 'top' }[key];
- } else {
- value = mathMax(0, value); // don't set width or height below zero (#311)
- }
-
- // clipping rectangle special
- if (wrapper.updateClipping) {
- wrapper[key] = value; // the key is now 'left' or 'top' for 'x' and 'y'
- wrapper.updateClipping();
- } else {
- // normal
- elemStyle[key] = value;
- }
-
- skipAttr = true;
-
- // class name
- } else if (key === 'class' && nodeName === 'DIV') {
- // IE8 Standards mode has problems retrieving the className
- element.className = value;
-
- // stroke
- } else if (key === 'stroke') {
-
- value = renderer.color(value, element, key);
-
- key = 'strokecolor';
-
- // stroke width
- } else if (key === 'stroke-width' || key === 'strokeWidth') {
- element.stroked = value ? true : false;
- key = 'strokeweight';
- wrapper[key] = value; // used in getter, issue #113
- if (isNumber(value)) {
- value += PX;
- }
-
- // dashStyle
- } else if (key === 'dashstyle') {
- var strokeElem = element.getElementsByTagName('stroke')[0] ||
- createElement(renderer.prepVML(['<stroke/>']), null, null, element);
- strokeElem[key] = value || 'solid';
- wrapper.dashstyle = value; /* because changing stroke-width will change the dash length
- and cause an epileptic effect */
- skipAttr = true;
-
- // fill
- } else if (key === 'fill') {
-
- if (nodeName === 'SPAN') { // text color
- elemStyle.color = value;
- } else if (nodeName !== 'IMG') { // #1336
- element.filled = value !== NONE ? true : false;
-
- value = renderer.color(value, element, key, wrapper);
-
- key = 'fillcolor';
- }
-
- // opacity: don't bother - animation is too slow and filters introduce artifacts
- } else if (key === 'opacity') {
- /*css(element, {
- opacity: value
- });*/
- skipAttr = true;
-
- // rotation on VML elements
- } else if (nodeName === 'shape' && key === 'rotation') {
-
- wrapper[key] = element.style[key] = value; // style is for #1873
-
- // Correction for the 1x1 size of the shape container. Used in gauge needles.
- element.style.left = -mathRound(mathSin(value * deg2rad) + 1) + PX;
- element.style.top = mathRound(mathCos(value * deg2rad)) + PX;
-
- // translation for animation
- } else if (key === 'translateX' || key === 'translateY' || key === 'rotation') {
- wrapper[key] = value;
- wrapper.updateTransform();
-
- skipAttr = true;
-
- // text for rotated and non-rotated elements
- } else if (key === 'text') {
- this.bBox = null;
- element.innerHTML = value;
- skipAttr = true;
- }
-
-
- if (!skipAttr) {
- if (docMode8) { // IE8 setAttribute bug
- element[key] = value;
- } else {
- attr(element, key, value);
- }
- }
-
- }
- }
- }
- return ret;
- },
-
- /**
- * Set the element's clipping to a predefined rectangle
- *
- * @param {String} id The id of the clip rectangle
- */
- clip: function (clipRect) {
- var wrapper = this,
- clipMembers,
- cssRet;
-
- if (clipRect) {
- clipMembers = clipRect.members;
- erase(clipMembers, wrapper); // Ensure unique list of elements (#1258)
- clipMembers.push(wrapper);
- wrapper.destroyClip = function () {
- erase(clipMembers, wrapper);
- };
- cssRet = clipRect.getCSS(wrapper);
-
- } else {
- if (wrapper.destroyClip) {
- wrapper.destroyClip();
- }
- cssRet = { clip: docMode8 ? 'inherit' : 'rect(auto)' }; // #1214
- }
-
- return wrapper.css(cssRet);
-
- },
-
- /**
- * Set styles for the element
- * @param {Object} styles
- */
- css: SVGElement.prototype.htmlCss,
-
- /**
- * Removes a child either by removeChild or move to garbageBin.
- * Issue 490; in VML removeChild results in Orphaned nodes according to sIEve, discardElement does not.
- */
- safeRemoveChild: function (element) {
- // discardElement will detach the node from its parent before attaching it
- // to the garbage bin. Therefore it is important that the node is attached and have parent.
- if (element.parentNode) {
- discardElement(element);
- }
- },
-
- /**
- * Extend element.destroy by removing it from the clip members array
- */
- destroy: function () {
- if (this.destroyClip) {
- this.destroyClip();
- }
-
- return SVGElement.prototype.destroy.apply(this);
- },
-
- /**
- * Add an event listener. VML override for normalizing event parameters.
- * @param {String} eventType
- * @param {Function} handler
- */
- on: function (eventType, handler) {
- // simplest possible event model for internal use
- this.element['on' + eventType] = function () {
- var evt = win.event;
- evt.target = evt.srcElement;
- handler(evt);
- };
- return this;
- },
-
- /**
- * In stacked columns, cut off the shadows so that they don't overlap
- */
- cutOffPath: function (path, length) {
-
- var len;
-
- path = path.split(/[ ,]/);
- len = path.length;
-
- if (len === 9 || len === 11) {
- path[len - 4] = path[len - 2] = pInt(path[len - 2]) - 10 * length;
- }
- return path.join(' ');
- },
-
- /**
- * Apply a drop shadow by copying elements and giving them different strokes
- * @param {Boolean|Object} shadowOptions
- */
- shadow: function (shadowOptions, group, cutOff) {
- var shadows = [],
- i,
- element = this.element,
- renderer = this.renderer,
- shadow,
- elemStyle = element.style,
- markup,
- path = element.path,
- strokeWidth,
- modifiedPath,
- shadowWidth,
- shadowElementOpacity;
-
- // some times empty paths are not strings
- if (path && typeof path.value !== 'string') {
- path = 'x';
- }
- modifiedPath = path;
-
- if (shadowOptions) {
- shadowWidth = pick(shadowOptions.width, 3);
- shadowElementOpacity = (shadowOptions.opacity || 0.15) / shadowWidth;
- for (i = 1; i <= 3; i++) {
-
- strokeWidth = (shadowWidth * 2) + 1 - (2 * i);
-
- // Cut off shadows for stacked column items
- if (cutOff) {
- modifiedPath = this.cutOffPath(path.value, strokeWidth + 0.5);
- }
-
- markup = ['<shape isShadow="true" strokeweight="', strokeWidth,
- '" filled="false" path="', modifiedPath,
- '" coordsize="10 10" style="', element.style.cssText, '" />'];
-
- shadow = createElement(renderer.prepVML(markup),
- null, {
- left: pInt(elemStyle.left) + pick(shadowOptions.offsetX, 1),
- top: pInt(elemStyle.top) + pick(shadowOptions.offsetY, 1)
- }
- );
- if (cutOff) {
- shadow.cutOff = strokeWidth + 1;
- }
-
- // apply the opacity
- markup = ['<stroke color="', shadowOptions.color || 'black', '" opacity="', shadowElementOpacity * i, '"/>'];
- createElement(renderer.prepVML(markup), null, null, shadow);
-
-
- // insert it
- if (group) {
- group.element.appendChild(shadow);
- } else {
- element.parentNode.insertBefore(shadow, element);
- }
-
- // record it
- shadows.push(shadow);
-
- }
-
- this.shadows = shadows;
- }
- return this;
-
- }
-};
-VMLElement = extendClass(SVGElement, VMLElement);
-
-/**
- * The VML renderer
- */
-var VMLRendererExtension = { // inherit SVGRenderer
-
- Element: VMLElement,
- isIE8: userAgent.indexOf('MSIE 8.0') > -1,
-
-
- /**
- * Initialize the VMLRenderer
- * @param {Object} container
- * @param {Number} width
- * @param {Number} height
- */
- init: function (container, width, height) {
- var renderer = this,
- boxWrapper,
- box;
-
- renderer.alignedObjects = [];
-
- boxWrapper = renderer.createElement(DIV);
- box = boxWrapper.element;
- box.style.position = RELATIVE; // for freeform drawing using renderer directly
- container.appendChild(boxWrapper.element);
-
-
- // generate the containing box
- renderer.isVML = true;
- renderer.box = box;
- renderer.boxWrapper = boxWrapper;
-
-
- renderer.setSize(width, height, false);
-
- // The only way to make IE6 and IE7 print is to use a global namespace. However,
- // with IE8 the only way to make the dynamic shapes visible in screen and print mode
- // seems to be to add the xmlns attribute and the behaviour style inline.
- if (!doc.namespaces.hcv) {
-
- doc.namespaces.add('hcv', 'urn:schemas-microsoft-com:vml');
-
- // setup default css
- doc.createStyleSheet().cssText =
- 'hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke' +
- '{ behavior:url(#default#VML); display: inline-block; } ';
-
- }
- },
-
-
- /**
- * Detect whether the renderer is hidden. This happens when one of the parent elements
- * has display: none
- */
- isHidden: function () {
- return !this.box.offsetWidth;
- },
-
- /**
- * Define a clipping rectangle. In VML it is accomplished by storing the values
- * for setting the CSS style to all associated members.
- *
- * @param {Number} x
- * @param {Number} y
- * @param {Number} width
- * @param {Number} height
- */
- clipRect: function (x, y, width, height) {
-
- // create a dummy element
- var clipRect = this.createElement(),
- isObj = isObject(x);
-
- // mimic a rectangle with its style object for automatic updating in attr
- return extend(clipRect, {
- members: [],
- left: isObj ? x.x : x,
- top: isObj ? x.y : y,
- width: isObj ? x.width : width,
- height: isObj ? x.height : height,
- getCSS: function (wrapper) {
- var element = wrapper.element,
- nodeName = element.nodeName,
- isShape = nodeName === 'shape',
- inverted = wrapper.inverted,
- rect = this,
- top = rect.top - (isShape ? element.offsetTop : 0),
- left = rect.left,
- right = left + rect.width,
- bottom = top + rect.height,
- ret = {
- clip: 'rect(' +
- mathRound(inverted ? left : top) + 'px,' +
- mathRound(inverted ? bottom : right) + 'px,' +
- mathRound(inverted ? right : bottom) + 'px,' +
- mathRound(inverted ? top : left) + 'px)'
- };
-
- // issue 74 workaround
- if (!inverted && docMode8 && nodeName === 'DIV') {
- extend(ret, {
- width: right + PX,
- height: bottom + PX
- });
- }
- return ret;
- },
-
- // used in attr and animation to update the clipping of all members
- updateClipping: function () {
- each(clipRect.members, function (member) {
- member.css(clipRect.getCSS(member));
- });
- }
- });
-
- },
-
-
- /**
- * Take a color and return it if it's a string, make it a gradient if it's a
- * gradient configuration object, and apply opacity.
- *
- * @param {Object} color The color or config object
- */
- color: function (color, elem, prop, wrapper) {
- var renderer = this,
- colorObject,
- regexRgba = /^rgba/,
- markup,
- fillType,
- ret = NONE;
-
- // Check for linear or radial gradient
- if (color && color.linearGradient) {
- fillType = 'gradient';
- } else if (color && color.radialGradient) {
- fillType = 'pattern';
- }
-
-
- if (fillType) {
-
- var stopColor,
- stopOpacity,
- gradient = color.linearGradient || color.radialGradient,
- x1,
- y1,
- x2,
- y2,
- opacity1,
- opacity2,
- color1,
- color2,
- fillAttr = '',
- stops = color.stops,
- firstStop,
- lastStop,
- colors = [],
- addFillNode = function () {
- // Add the fill subnode. When colors attribute is used, the meanings of opacity and o:opacity2
- // are reversed.
- markup = ['<fill colors="' + colors.join(',') + '" opacity="', opacity2, '" o:opacity2="', opacity1,
- '" type="', fillType, '" ', fillAttr, 'focus="100%" method="any" />'];
- createElement(renderer.prepVML(markup), null, null, elem);
- };
-
- // Extend from 0 to 1
- firstStop = stops[0];
- lastStop = stops[stops.length - 1];
- if (firstStop[0] > 0) {
- stops.unshift([
- 0,
- firstStop[1]
- ]);
- }
- if (lastStop[0] < 1) {
- stops.push([
- 1,
- lastStop[1]
- ]);
- }
-
- // Compute the stops
- each(stops, function (stop, i) {
- if (regexRgba.test(stop[1])) {
- colorObject = Color(stop[1]);
- stopColor = colorObject.get('rgb');
- stopOpacity = colorObject.get('a');
- } else {
- stopColor = stop[1];
- stopOpacity = 1;
- }
-
- // Build the color attribute
- colors.push((stop[0] * 100) + '% ' + stopColor);
-
- // Only start and end opacities are allowed, so we use the first and the last
- if (!i) {
- opacity1 = stopOpacity;
- color2 = stopColor;
- } else {
- opacity2 = stopOpacity;
- color1 = stopColor;
- }
- });
-
- // Apply the gradient to fills only.
- if (prop === 'fill') {
-
- // Handle linear gradient angle
- if (fillType === 'gradient') {
- x1 = gradient.x1 || gradient[0] || 0;
- y1 = gradient.y1 || gradient[1] || 0;
- x2 = gradient.x2 || gradient[2] || 0;
- y2 = gradient.y2 || gradient[3] || 0;
- fillAttr = 'angle="' + (90 - math.atan(
- (y2 - y1) / // y vector
- (x2 - x1) // x vector
- ) * 180 / mathPI) + '"';
-
- addFillNode();
-
- // Radial (circular) gradient
- } else {
-
- var r = gradient.r,
- sizex = r * 2,
- sizey = r * 2,
- cx = gradient.cx,
- cy = gradient.cy,
- radialReference = elem.radialReference,
- bBox,
- applyRadialGradient = function () {
- if (radialReference) {
- bBox = wrapper.getBBox();
- cx += (radialReference[0] - bBox.x) / bBox.width - 0.5;
- cy += (radialReference[1] - bBox.y) / bBox.height - 0.5;
- sizex *= radialReference[2] / bBox.width;
- sizey *= radialReference[2] / bBox.height;
- }
- fillAttr = 'src="' + defaultOptions.global.VMLRadialGradientURL + '" ' +
- 'size="' + sizex + ',' + sizey + '" ' +
- 'origin="0.5,0.5" ' +
- 'position="' + cx + ',' + cy + '" ' +
- 'color2="' + color2 + '" ';
-
- addFillNode();
- };
-
- // Apply radial gradient
- if (wrapper.added) {
- applyRadialGradient();
- } else {
- // We need to know the bounding box to get the size and position right
- addEvent(wrapper, 'add', applyRadialGradient);
- }
-
- // The fill element's color attribute is broken in IE8 standards mode, so we
- // need to set the parent shape's fillcolor attribute instead.
- ret = color1;
- }
-
- // Gradients are not supported for VML stroke, return the first color. #722.
- } else {
- ret = stopColor;
- }
-
- // if the color is an rgba color, split it and add a fill node
- // to hold the opacity component
- } else if (regexRgba.test(color) && elem.tagName !== 'IMG') {
-
- colorObject = Color(color);
-
- markup = ['<', prop, ' opacity="', colorObject.get('a'), '"/>'];
- createElement(this.prepVML(markup), null, null, elem);
-
- ret = colorObject.get('rgb');
-
-
- } else {
- var propNodes = elem.getElementsByTagName(prop); // 'stroke' or 'fill' node
- if (propNodes.length) {
- propNodes[0].opacity = 1;
- propNodes[0].type = 'solid';
- }
- ret = color;
- }
-
- return ret;
- },
-
- /**
- * Take a VML string and prepare it for either IE8 or IE6/IE7.
- * @param {Array} markup A string array of the VML markup to prepare
- */
- prepVML: function (markup) {
- var vmlStyle = 'display:inline-block;behavior:url(#default#VML);',
- isIE8 = this.isIE8;
-
- markup = markup.join('');
-
- if (isIE8) { // add xmlns and style inline
- markup = markup.replace('/>', ' xmlns="urn:schemas-microsoft-com:vml" />');
- if (markup.indexOf('style="') === -1) {
- markup = markup.replace('/>', ' style="' + vmlStyle + '" />');
- } else {
- markup = markup.replace('style="', 'style="' + vmlStyle);
- }
-
- } else { // add namespace
- markup = markup.replace('<', '<hcv:');
- }
-
- return markup;
- },
-
- /**
- * Create rotated and aligned text
- * @param {String} str
- * @param {Number} x
- * @param {Number} y
- */
- text: SVGRenderer.prototype.html,
-
- /**
- * Create and return a path element
- * @param {Array} path
- */
- path: function (path) {
- var attr = {
- // subpixel precision down to 0.1 (width and height = 1px)
- coordsize: '10 10'
- };
- if (isArray(path)) {
- attr.d = path;
- } else if (isObject(path)) { // attributes
- extend(attr, path);
- }
- // create the shape
- return this.createElement('shape').attr(attr);
- },
-
- /**
- * Create and return a circle element. In VML circles are implemented as
- * shapes, which is faster than v:oval
- * @param {Number} x
- * @param {Number} y
- * @param {Number} r
- */
- circle: function (x, y, r) {
- var circle = this.symbol('circle');
- if (isObject(x)) {
- r = x.r;
- y = x.y;
- x = x.x;
- }
- circle.isCircle = true; // Causes x and y to mean center (#1682)
- return circle.attr({ x: x, y: y, width: 2 * r, height: 2 * 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.attr({
- x: x,
- y: y,
- width: width,
- height: height
- });
- }
- return obj;
- },
-
- /**
- * VML uses a shape for rect to overcome bugs and rotation problems
- */
- rect: function (x, y, width, height, r, strokeWidth) {
-
- if (isObject(x)) {
- y = x.y;
- width = x.width;
- height = x.height;
- strokeWidth = x.strokeWidth;
- x = x.x;
- }
- var wrapper = this.symbol('rect');
- wrapper.r = r;
-
- return wrapper.attr(wrapper.crisp(strokeWidth, x, y, mathMax(width, 0), mathMax(height, 0)));
- },
-
- /**
- * In the VML renderer, each child of an inverted div (group) is inverted
- * @param {Object} element
- * @param {Object} parentNode
- */
- invertChild: function (element, parentNode) {
- var parentStyle = parentNode.style;
- css(element, {
- flip: 'x',
- left: pInt(parentStyle.width) - 1,
- top: pInt(parentStyle.height) - 1,
- rotation: -90
- });
- },
-
- /**
- * Symbol definitions that override the parent SVG renderer's symbols
- *
- */
- symbols: {
- // VML specific arc function
- arc: function (x, y, w, h, options) {
- var start = options.start,
- end = options.end,
- radius = options.r || w || h,
- innerRadius = options.innerR,
- cosStart = mathCos(start),
- sinStart = mathSin(start),
- cosEnd = mathCos(end),
- sinEnd = mathSin(end),
- ret;
-
- if (end - start === 0) { // no angle, don't show it.
- return ['x'];
- }
-
- ret = [
- 'wa', // clockwise arc to
- x - radius, // left
- y - radius, // top
- x + radius, // right
- y + radius, // bottom
- x + radius * cosStart, // start x
- y + radius * sinStart, // start y
- x + radius * cosEnd, // end x
- y + radius * sinEnd // end y
- ];
-
- if (options.open && !innerRadius) {
- ret.push(
- 'e',
- M,
- x,// - innerRadius,
- y// - innerRadius
- );
- }
-
- ret.push(
- 'at', // anti clockwise arc to
- x - innerRadius, // left
- y - innerRadius, // top
- x + innerRadius, // right
- y + innerRadius, // bottom
- x + innerRadius * cosEnd, // start x
- y + innerRadius * sinEnd, // start y
- x + innerRadius * cosStart, // end x
- y + innerRadius * sinStart, // end y
- 'x', // finish path
- 'e' // close
- );
-
- ret.isArc = true;
- return ret;
-
- },
- // Add circle symbol path. This performs significantly faster than v:oval.
- circle: function (x, y, w, h, wrapper) {
- // Center correction, #1682
- if (wrapper && wrapper.isCircle) {
- x -= w / 2;
- y -= h / 2;
- }
-
- // Return the path
- return [
- 'wa', // clockwisearcto
- x, // left
- y, // top
- x + w, // right
- y + h, // bottom
- x + w, // start x
- y + h / 2, // start y
- x + w, // end x
- y + h / 2, // end y
- //'x', // finish path
- 'e' // close
- ];
- },
- /**
- * Add rectangle symbol path which eases rotation and omits arcsize problems
- * compared to the built-in VML roundrect shape
- *
- * @param {Number} left Left position
- * @param {Number} top Top position
- * @param {Number} r Border radius
- * @param {Object} options Width and height
- */
-
- rect: function (left, top, width, height, options) {
-
- var right = left + width,
- bottom = top + height,
- ret,
- r;
-
- // No radius, return the more lightweight square
- if (!defined(options) || !options.r) {
- ret = SVGRenderer.prototype.symbols.square.apply(0, arguments);
-
- // Has radius add arcs for the corners
- } else {
-
- r = mathMin(options.r, width, height);
- ret = [
- M,
- left + r, top,
-
- L,
- right - r, top,
- 'wa',
- right - 2 * r, top,
- right, top + 2 * r,
- right - r, top,
- right, top + r,
-
- L,
- right, bottom - r,
- 'wa',
- right - 2 * r, bottom - 2 * r,
- right, bottom,
- right, bottom - r,
- right - r, bottom,
-
- L,
- left + r, bottom,
- 'wa',
- left, bottom - 2 * r,
- left + 2 * r, bottom,
- left + r, bottom,
- left, bottom - r,
-
- L,
- left, top + r,
- 'wa',
- left, top,
- left + 2 * r, top + 2 * r,
- left, top + r,
- left + r, top,
-
-
- 'x',
- 'e'
- ];
- }
- return ret;
- }
- }
-};
-Highcharts.VMLRenderer = VMLRenderer = function () {
- this.init.apply(this, arguments);
-};
-VMLRenderer.prototype = merge(SVGRenderer.prototype, VMLRendererExtension);
-
- // general renderer
- Renderer = VMLRenderer;
-}
-
-/* ****************************************************************************
- * *
- * END OF INTERNET EXPLORER <= 8 SPECIFIC CODE *
- * *
- *****************************************************************************/
-/* ****************************************************************************
- * *
- * START OF ANDROID < 3 SPECIFIC CODE. THIS CAN BE REMOVED IF YOU'RE NOT *
- * TARGETING THAT SYSTEM. *
- * *
- *****************************************************************************/
-var CanVGRenderer,
- CanVGController;
-
-if (useCanVG) {
- /**
- * The CanVGRenderer is empty from start to keep the source footprint small.
- * When requested, the CanVGController downloads the rest of the source packaged
- * together with the canvg library.
- */
- Highcharts.CanVGRenderer = CanVGRenderer = function () {
- // Override the global SVG namespace to fake SVG/HTML that accepts CSS
- SVG_NS = 'http://www.w3.org/1999/xhtml';
- };
-
- /**
- * Start with an empty symbols object. This is needed when exporting is used (exporting.src.js will add a few symbols), but
- * the implementation from SvgRenderer will not be merged in until first render.
- */
- CanVGRenderer.prototype.symbols = {};
-
- /**
- * Handles on demand download of canvg rendering support.
- */
- CanVGController = (function () {
- // List of renderering calls
- var deferredRenderCalls = [];
-
- /**
- * When downloaded, we are ready to draw deferred charts.
- */
- function drawDeferred() {
- var callLength = deferredRenderCalls.length,
- callIndex;
-
- // Draw all pending render calls
- for (callIndex = 0; callIndex < callLength; callIndex++) {
- deferredRenderCalls[callIndex]();
- }
- // Clear the list
- deferredRenderCalls = [];
- }
-
- return {
- push: function (func, scriptLocation) {
- // Only get the script once
- if (deferredRenderCalls.length === 0) {
- getScript(scriptLocation, drawDeferred);
- }
- // Register render call
- deferredRenderCalls.push(func);
- }
- };
- }());
-
- Renderer = CanVGRenderer;
-} // end CanVGRenderer
-
-/* ****************************************************************************
- * *
- * END OF ANDROID < 3 SPECIFIC CODE *
- * *
- *****************************************************************************/
-
-/**
- * The Tick class
- */
-function Tick(axis, pos, type, noLabel) {
- this.axis = axis;
- this.pos = pos;
- this.type = type || '';
- this.isNew = true;
-
- if (!type && !noLabel) {
- this.addLabel();
- }
-}
-
-Tick.prototype = {
- /**
- * Write the tick label
- */
- addLabel: function () {
- var tick = this,
- axis = tick.axis,
- options = axis.options,
- chart = axis.chart,
- horiz = axis.horiz,
- categories = axis.categories,
- names = axis.series[0] && axis.series[0].names,
- pos = tick.pos,
- labelOptions = options.labels,
- str,
- tickPositions = axis.tickPositions,
- width = (horiz && categories &&
- !labelOptions.step && !labelOptions.staggerLines &&
- !labelOptions.rotation &&
- chart.plotWidth / tickPositions.length) ||
- (!horiz && (chart.optionsMarginLeft || chart.chartWidth * 0.33)), // #1580, #1931
- isFirst = pos === tickPositions[0],
- isLast = pos === tickPositions[tickPositions.length - 1],
- css,
- attr,
- value = categories ?
- pick(categories[pos], names && names[pos], pos) :
- pos,
- label = tick.label,
- tickPositionInfo = tickPositions.info,
- dateTimeLabelFormat;
-
- // Set the datetime label format. If a higher rank is set for this position, use that. If not,
- // use the general format.
- if (axis.isDatetimeAxis && tickPositionInfo) {
- dateTimeLabelFormat = options.dateTimeLabelFormats[tickPositionInfo.higherRanks[pos] || tickPositionInfo.unitName];
- }
-
- // set properties for access in render method
- tick.isFirst = isFirst;
- tick.isLast = isLast;
-
- // get the string
- str = axis.labelFormatter.call({
- axis: axis,
- chart: chart,
- isFirst: isFirst,
- isLast: isLast,
- dateTimeLabelFormat: dateTimeLabelFormat,
- value: axis.isLog ? correctFloat(lin2log(value)) : value
- });
-
- // prepare CSS
- css = width && { width: mathMax(1, mathRound(width - 2 * (labelOptions.padding || 10))) + PX };
- css = extend(css, labelOptions.style);
-
- // first call
- if (!defined(label)) {
- attr = {
- align: axis.labelAlign
- };
- if (isNumber(labelOptions.rotation)) {
- attr.rotation = labelOptions.rotation;
- }
- tick.label =
- defined(str) && labelOptions.enabled ?
- chart.renderer.text(
- str,
- 0,
- 0,
- labelOptions.useHTML
- )
- .attr(attr)
- // without position absolute, IE export sometimes is wrong
- .css(css)
- .add(axis.labelGroup) :
- null;
-
- // update
- } else if (label) {
- label.attr({
- text: str
- })
- .css(css);
- }
- },
-
- /**
- * Get the offset height or width of the label
- */
- getLabelSize: function () {
- var label = this.label,
- axis = this.axis;
- return label ?
- ((this.labelBBox = label.getBBox()))[axis.horiz ? 'height' : 'width'] :
- 0;
- },
-
- /**
- * Find how far the labels extend to the right and left of the tick's x position. Used for anti-collision
- * detection with overflow logic.
- */
- getLabelSides: function () {
- var bBox = this.labelBBox, // assume getLabelSize has run at this point
- axis = this.axis,
- options = axis.options,
- labelOptions = options.labels,
- width = bBox.width,
- leftSide = width * { left: 0, center: 0.5, right: 1 }[axis.labelAlign] - labelOptions.x;
-
- return [-leftSide, width - leftSide];
- },
-
- /**
- * Handle the label overflow by adjusting the labels to the left and right edge, or
- * hide them if they collide into the neighbour label.
- */
- handleOverflow: function (index, xy) {
- var show = true,
- axis = this.axis,
- chart = axis.chart,
- isFirst = this.isFirst,
- isLast = this.isLast,
- x = xy.x,
- reversed = axis.reversed,
- tickPositions = axis.tickPositions;
-
- if (isFirst || isLast) {
-
- var sides = this.getLabelSides(),
- leftSide = sides[0],
- rightSide = sides[1],
- plotLeft = chart.plotLeft,
- plotRight = plotLeft + axis.len,
- neighbour = axis.ticks[tickPositions[index + (isFirst ? 1 : -1)]],
- neighbourEdge = neighbour && neighbour.label.xy && neighbour.label.xy.x + neighbour.getLabelSides()[isFirst ? 0 : 1];
-
- if ((isFirst && !reversed) || (isLast && reversed)) {
- // Is the label spilling out to the left of the plot area?
- if (x + leftSide < plotLeft) {
-
- // Align it to plot left
- x = plotLeft - leftSide;
-
- // Hide it if it now overlaps the neighbour label
- if (neighbour && x + rightSide > neighbourEdge) {
- show = false;
- }
- }
-
- } else {
- // Is the label spilling out to the right of the plot area?
- if (x + rightSide > plotRight) {
-
- // Align it to plot right
- x = plotRight - rightSide;
-
- // Hide it if it now overlaps the neighbour label
- if (neighbour && x + leftSide < neighbourEdge) {
- show = false;
- }
-
- }
- }
-
- // Set the modified x position of the label
- xy.x = x;
- }
- return show;
- },
-
- /**
- * Get the x and y position for ticks and labels
- */
- getPosition: function (horiz, pos, tickmarkOffset, old) {
- var axis = this.axis,
- chart = axis.chart,
- cHeight = (old && chart.oldChartHeight) || chart.chartHeight;
-
- return {
- x: horiz ?
- axis.translate(pos + tickmarkOffset, null, null, old) + axis.transB :
- axis.left + axis.offset + (axis.opposite ? ((old && chart.oldChartWidth) || chart.chartWidth) - axis.right - axis.left : 0),
-
- y: horiz ?
- cHeight - axis.bottom + axis.offset - (axis.opposite ? axis.height : 0) :
- cHeight - axis.translate(pos + tickmarkOffset, null, null, old) - axis.transB
- };
-
- },
-
- /**
- * Get the x, y position of the tick label
- */
- getLabelPosition: function (x, y, label, horiz, labelOptions, tickmarkOffset, index, step) {
- var axis = this.axis,
- transA = axis.transA,
- reversed = axis.reversed,
- staggerLines = axis.staggerLines,
- baseline = axis.chart.renderer.fontMetrics(labelOptions.style.fontSize).b,
- rotation = labelOptions.rotation;
-
- x = x + labelOptions.x - (tickmarkOffset && horiz ?
- tickmarkOffset * transA * (reversed ? -1 : 1) : 0);
- y = y + labelOptions.y - (tickmarkOffset && !horiz ?
- tickmarkOffset * transA * (reversed ? 1 : -1) : 0);
-
- // Correct for rotation (#1764)
- if (rotation && axis.side === 2) {
- y -= baseline - baseline * mathCos(rotation * deg2rad);
- }
-
- // Vertically centered
- if (!defined(labelOptions.y) && !rotation) { // #1951
- y += baseline - label.getBBox().height / 2;
- }
-
- // Correct for staggered labels
- if (staggerLines) {
- y += (index / (step || 1) % staggerLines) * (axis.labelOffset / staggerLines);
- }
-
- return {
- x: x,
- y: y
- };
- },
-
- /**
- * Extendible method to return the path of the marker
- */
- getMarkPath: function (x, y, tickLength, tickWidth, horiz, renderer) {
- return renderer.crispLine([
- M,
- x,
- y,
- L,
- x + (horiz ? 0 : -tickLength),
- y + (horiz ? tickLength : 0)
- ], tickWidth);
- },
-
- /**
- * Put everything in place
- *
- * @param index {Number}
- * @param old {Boolean} Use old coordinates to prepare an animation into new position
- */
- render: function (index, old, opacity) {
- var tick = this,
- axis = tick.axis,
- options = axis.options,
- chart = axis.chart,
- renderer = chart.renderer,
- horiz = axis.horiz,
- type = tick.type,
- label = tick.label,
- pos = tick.pos,
- labelOptions = options.labels,
- gridLine = tick.gridLine,
- gridPrefix = type ? type + 'Grid' : 'grid',
- tickPrefix = type ? type + 'Tick' : 'tick',
- gridLineWidth = options[gridPrefix + 'LineWidth'],
- gridLineColor = options[gridPrefix + 'LineColor'],
- dashStyle = options[gridPrefix + 'LineDashStyle'],
- tickLength = options[tickPrefix + 'Length'],
- tickWidth = options[tickPrefix + 'Width'] || 0,
- tickColor = options[tickPrefix + 'Color'],
- tickPosition = options[tickPrefix + 'Position'],
- gridLinePath,
- mark = tick.mark,
- markPath,
- step = labelOptions.step,
- attribs,
- show = true,
- tickmarkOffset = axis.tickmarkOffset,
- xy = tick.getPosition(horiz, pos, tickmarkOffset, old),
- x = xy.x,
- y = xy.y,
- reverseCrisp = ((horiz && x === axis.pos) || (!horiz && y === axis.pos + axis.len)) ? -1 : 1, // #1480
- staggerLines = axis.staggerLines;
-
- this.isActive = true;
-
- // create the grid line
- if (gridLineWidth) {
- gridLinePath = axis.getPlotLinePath(pos + tickmarkOffset, gridLineWidth * reverseCrisp, old, true);
-
- if (gridLine === UNDEFINED) {
- attribs = {
- stroke: gridLineColor,
- 'stroke-width': gridLineWidth
- };
- if (dashStyle) {
- attribs.dashstyle = dashStyle;
- }
- if (!type) {
- attribs.zIndex = 1;
- }
- if (old) {
- attribs.opacity = 0;
- }
- tick.gridLine = gridLine =
- gridLineWidth ?
- renderer.path(gridLinePath)
- .attr(attribs).add(axis.gridGroup) :
- null;
- }
-
- // If the parameter 'old' is set, the current call will be followed
- // by another call, therefore do not do any animations this time
- if (!old && gridLine && gridLinePath) {
- gridLine[tick.isNew ? 'attr' : 'animate']({
- d: gridLinePath,
- opacity: opacity
- });
- }
- }
-
- // create the tick mark
- if (tickWidth && tickLength) {
-
- // negate the length
- if (tickPosition === 'inside') {
- tickLength = -tickLength;
- }
- if (axis.opposite) {
- tickLength = -tickLength;
- }
-
- markPath = tick.getMarkPath(x, y, tickLength, tickWidth * reverseCrisp, horiz, renderer);
-
- if (mark) { // updating
- mark.animate({
- d: markPath,
- opacity: opacity
- });
- } else { // first time
- tick.mark = renderer.path(
- markPath
- ).attr({
- stroke: tickColor,
- 'stroke-width': tickWidth,
- opacity: opacity
- }).add(axis.axisGroup);
- }
- }
-
- // the label is created on init - now move it into place
- if (label && !isNaN(x)) {
- label.xy = xy = tick.getLabelPosition(x, y, label, horiz, labelOptions, tickmarkOffset, index, step);
-
- // apply show first and show last
- if ((tick.isFirst && !pick(options.showFirstLabel, 1)) ||
- (tick.isLast && !pick(options.showLastLabel, 1))) {
- show = false;
-
- // Handle label overflow and show or hide accordingly
- } else if (!staggerLines && horiz && labelOptions.overflow === 'justify' && !tick.handleOverflow(index, xy)) {
- show = false;
- }
-
- // apply step
- if (step && index % step) {
- // show those indices dividable by step
- show = false;
- }
-
- // Set the new position, and show or hide
- if (show && !isNaN(xy.y)) {
- xy.opacity = opacity;
- label[tick.isNew ? 'attr' : 'animate'](xy);
- tick.isNew = false;
- } else {
- label.attr('y', -9999); // #1338
- }
- }
- },
-
- /**
- * Destructor for the tick prototype
- */
- destroy: function () {
- destroyObjectProperties(this, this.axis);
- }
-};
-
-/**
- * The object wrapper for plot lines and plot bands
- * @param {Object} options
- */
-function PlotLineOrBand(axis, options) {
- this.axis = axis;
-
- if (options) {
- this.options = options;
- this.id = options.id;
- }
-}
-
-PlotLineOrBand.prototype = {
-
- /**
- * Render the plot line or plot band. If it is already existing,
- * move it.
- */
- render: function () {
- var plotLine = this,
- axis = plotLine.axis,
- horiz = axis.horiz,
- halfPointRange = (axis.pointRange || 0) / 2,
- options = plotLine.options,
- optionsLabel = options.label,
- label = plotLine.label,
- width = options.width,
- to = options.to,
- from = options.from,
- isBand = defined(from) && defined(to),
- value = options.value,
- dashStyle = options.dashStyle,
- svgElem = plotLine.svgElem,
- path = [],
- addEvent,
- eventType,
- xs,
- ys,
- x,
- y,
- color = options.color,
- zIndex = options.zIndex,
- events = options.events,
- attribs,
- renderer = axis.chart.renderer;
-
- // logarithmic conversion
- if (axis.isLog) {
- from = log2lin(from);
- to = log2lin(to);
- value = log2lin(value);
- }
-
- // plot line
- if (width) {
- path = axis.getPlotLinePath(value, width);
- attribs = {
- stroke: color,
- 'stroke-width': width
- };
- if (dashStyle) {
- attribs.dashstyle = dashStyle;
- }
- } else if (isBand) { // plot band
-
- // keep within plot area
- from = mathMax(from, axis.min - halfPointRange);
- to = mathMin(to, axis.max + halfPointRange);
-
- path = axis.getPlotBandPath(from, to, options);
- attribs = {
- fill: color
- };
- if (options.borderWidth) {
- attribs.stroke = options.borderColor;
- attribs['stroke-width'] = options.borderWidth;
- }
- } else {
- return;
- }
- // zIndex
- if (defined(zIndex)) {
- attribs.zIndex = zIndex;
- }
-
- // common for lines and bands
- if (svgElem) {
- if (path) {
- svgElem.animate({
- d: path
- }, null, svgElem.onGetPath);
- } else {
- svgElem.hide();
- svgElem.onGetPath = function () {
- svgElem.show();
- };
- }
- } else if (path && path.length) {
- plotLine.svgElem = svgElem = renderer.path(path)
- .attr(attribs).add();
-
- // events
- if (events) {
- addEvent = function (eventType) {
- svgElem.on(eventType, function (e) {
- events[eventType].apply(plotLine, [e]);
- });
- };
- for (eventType in events) {
- addEvent(eventType);
- }
- }
- }
-
- // the plot band/line label
- if (optionsLabel && defined(optionsLabel.text) && path && path.length && axis.width > 0 && axis.height > 0) {
- // apply defaults
- optionsLabel = merge({
- align: horiz && isBand && 'center',
- x: horiz ? !isBand && 4 : 10,
- verticalAlign : !horiz && isBand && 'middle',
- y: horiz ? isBand ? 16 : 10 : isBand ? 6 : -4,
- rotation: horiz && !isBand && 90
- }, optionsLabel);
-
- // add the SVG element
- if (!label) {
- plotLine.label = label = renderer.text(
- optionsLabel.text,
- 0,
- 0,
- optionsLabel.useHTML // docs: useHTML for plotLines and plotBands
- )
- .attr({
- align: optionsLabel.textAlign || optionsLabel.align,
- rotation: optionsLabel.rotation,
- zIndex: zIndex
- })
- .css(optionsLabel.style)
- .add();
- }
-
- // get the bounding box and align the label
- xs = [path[1], path[4], pick(path[6], path[1])];
- ys = [path[2], path[5], pick(path[7], path[2])];
- x = arrayMin(xs);
- y = arrayMin(ys);
-
- label.align(optionsLabel, false, {
- x: x,
- y: y,
- width: arrayMax(xs) - x,
- height: arrayMax(ys) - y
- });
- label.show();
-
- } else if (label) { // move out of sight
- label.hide();
- }
-
- // chainable
- return plotLine;
- },
-
- /**
- * Remove the plot line or band
- */
- destroy: function () {
- var plotLine = this,
- axis = plotLine.axis;
-
- // remove it from the lookup
- erase(axis.plotLinesAndBands, plotLine);
-
- destroyObjectProperties(plotLine, this.axis);
- }
-};
-/**
- * The class for stack items
- */
-function StackItem(axis, options, isNegative, x, stackOption, stacking) {
-
- var inverted = axis.chart.inverted;
-
- this.axis = axis;
-
- // Tells if the stack is negative
- this.isNegative = isNegative;
-
- // Save the options to be able to style the label
- this.options = options;
-
- // Save the x value to be able to position the label later
- this.x = x;
-
- // Initialize total value
- this.total = 0;
-
- // This will keep each points' extremes stored by series.index
- this.points = {};
-
- // Save the stack option on the series configuration object, and whether to treat it as percent
- this.stack = stackOption;
- this.percent = stacking === 'percent';
-
- // The align options and text align varies on whether the stack is negative and
- // if the chart is inverted or not.
- // First test the user supplied value, then use the dynamic.
- this.alignOptions = {
- align: options.align || (inverted ? (isNegative ? 'left' : 'right') : 'center'),
- verticalAlign: options.verticalAlign || (inverted ? 'middle' : (isNegative ? 'bottom' : 'top')),
- y: pick(options.y, inverted ? 4 : (isNegative ? 14 : -6)),
- x: pick(options.x, inverted ? (isNegative ? -6 : 6) : 0)
- };
-
- this.textAlign = options.textAlign || (inverted ? (isNegative ? 'right' : 'left') : 'center');
-}
-
-StackItem.prototype = {
- destroy: function () {
- destroyObjectProperties(this, this.axis);
- },
-
- /**
- * Sets the total of this stack. Should be called when a serie is hidden or shown
- * since that will affect the total of other stacks.
- */
- setTotal: function (total) {
- this.total = total;
- this.cum = total;
- },
-
- /**
- * Adds value to stack total, this method takes care of correcting floats
- */
- addValue: function (y) {
- this.setTotal(correctFloat(this.total + y));
- },
-
- /**
- * Renders the stack total label and adds it to the stack label group.
- */
- render: function (group) {
- var options = this.options,
- formatOption = options.format,
- str = formatOption ?
- format(formatOption, this) :
- options.formatter.call(this); // format the text in the label
-
- // Change the text to reflect the new total and set visibility to hidden in case the serie is hidden
- if (this.label) {
- this.label.attr({text: str, visibility: HIDDEN});
- // Create new label
- } else {
- this.label =
- this.axis.chart.renderer.text(str, 0, 0, options.useHTML) // dummy positions, actual position updated with setOffset method in columnseries
- .css(options.style) // apply style
- .attr({
- align: this.textAlign, // fix the text-anchor
- rotation: options.rotation, // rotation
- visibility: HIDDEN // hidden until setOffset is called
- })
- .add(group); // add to the labels-group
- }
- },
-
- cacheExtremes: function (series, extremes) {
- this.points[series.index] = extremes;
- },
-
- /**
- * Sets the offset that the stack has from the x value and repositions the label.
- */
- setOffset: function (xOffset, xWidth) {
- var stackItem = this,
- axis = stackItem.axis,
- chart = axis.chart,
- inverted = chart.inverted,
- neg = this.isNegative, // special treatment is needed for negative stacks
- y = axis.translate(this.percent ? 100 : this.total, 0, 0, 0, 1), // stack value translated mapped to chart coordinates
- yZero = axis.translate(0), // stack origin
- h = mathAbs(y - yZero), // stack height
- x = chart.xAxis[0].translate(this.x) + xOffset, // stack x position
- plotHeight = chart.plotHeight,
- stackBox = { // this is the box for the complete stack
- x: inverted ? (neg ? y : y - h) : x,
- y: inverted ? plotHeight - x - xWidth : (neg ? (plotHeight - y - h) : plotHeight - y),
- width: inverted ? h : xWidth,
- height: inverted ? xWidth : h
- },
- label = this.label,
- alignAttr;
-
- if (label) {
- label.align(this.alignOptions, null, stackBox); // align the label to the box
-
- // Set visibility (#678)
- alignAttr = label.alignAttr;
- label.attr({
- visibility: this.options.crop === false || chart.isInsidePlot(alignAttr.x, alignAttr.y) ?
- (hasSVG ? 'inherit' : VISIBLE) :
- HIDDEN
- });
- }
- }
-};
-/**
- * Create a new axis object
- * @param {Object} chart
- * @param {Object} options
- */
-function Axis() {
- this.init.apply(this, arguments);
-}
-
-Axis.prototype = {
-
- /**
- * Default options for the X axis - the Y axis has extended defaults
- */
- defaultOptions: {
- // allowDecimals: null,
- // alternateGridColor: null,
- // categories: [],
- dateTimeLabelFormats: {
- millisecond: '%H:%M:%S.%L',
- second: '%H:%M:%S',
- minute: '%H:%M',
- hour: '%H:%M',
- day: '%e. %b',
- week: '%e. %b',
- month: '%b \'%y',
- year: '%Y'
- },
- endOnTick: false,
- gridLineColor: '#C0C0C0',
- // gridLineDashStyle: 'solid',
- // gridLineWidth: 0,
- // reversed: false,
-
- labels: defaultLabelOptions,
- // { step: null },
- lineColor: '#C0D0E0',
- lineWidth: 1,
- //linkedTo: null,
- //max: undefined,
- //min: undefined,
- minPadding: 0.01,
- maxPadding: 0.01,
- //minRange: null,
- minorGridLineColor: '#E0E0E0',
- // minorGridLineDashStyle: null,
- minorGridLineWidth: 1,
- minorTickColor: '#A0A0A0',
- //minorTickInterval: null,
- minorTickLength: 2,
- minorTickPosition: 'outside', // inside or outside
- //minorTickWidth: 0,
- //opposite: false,
- //offset: 0,
- //plotBands: [{
- // events: {},
- // zIndex: 1,
- // labels: { align, x, verticalAlign, y, style, rotation, textAlign }
- //}],
- //plotLines: [{
- // events: {}
- // dashStyle: {}
- // zIndex:
- // labels: { align, x, verticalAlign, y, style, rotation, textAlign }
- //}],
- //reversed: false,
- // showFirstLabel: true,
- // showLastLabel: true,
- startOfWeek: 1,
- startOnTick: false,
- tickColor: '#C0D0E0',
- //tickInterval: null,
- tickLength: 5,
- tickmarkPlacement: 'between', // on or between
- tickPixelInterval: 100,
- tickPosition: 'outside',
- tickWidth: 1,
- title: {
- //text: null,
- align: 'middle', // low, middle or high
- //margin: 0 for horizontal, 10 for vertical axes,
- //rotation: 0,
- //side: 'outside',
- style: {
- color: '#4d759e',
- //font: defaultFont.replace('normal', 'bold')
- fontWeight: 'bold'
- }
- //x: 0,
- //y: 0
- },
- type: 'linear' // linear, logarithmic or datetime
- },
-
- /**
- * This options set extends the defaultOptions for Y axes
- */
- defaultYAxisOptions: {
- endOnTick: true,
- gridLineWidth: 1,
- tickPixelInterval: 72,
- showLastLabel: true,
- labels: {
- x: -8,
- y: 3
- },
- lineWidth: 0,
- maxPadding: 0.05,
- minPadding: 0.05,
- startOnTick: true,
- tickWidth: 0,
- title: {
- rotation: 270,
- text: 'Values'
- },
- stackLabels: {
- enabled: false,
- //align: dynamic,
- //y: dynamic,
- //x: dynamic,
- //verticalAlign: dynamic,
- //textAlign: dynamic,
- //rotation: 0,
- formatter: function () {
- return numberFormat(this.total, -1);
- },
- style: defaultLabelOptions.style
- }
- },
-
- /**
- * These options extend the defaultOptions for left axes
- */
- defaultLeftAxisOptions: {
- labels: {
- x: -8,
- y: null
- },
- title: {
- rotation: 270
- }
- },
-
- /**
- * These options extend the defaultOptions for right axes
- */
- defaultRightAxisOptions: {
- labels: {
- x: 8,
- y: null
- },
- title: {
- rotation: 90
- }
- },
-
- /**
- * These options extend the defaultOptions for bottom axes
- */
- defaultBottomAxisOptions: {
- labels: {
- x: 0,
- y: 14
- // overflow: undefined,
- // staggerLines: null
- },
- title: {
- rotation: 0
- }
- },
- /**
- * These options extend the defaultOptions for left axes
- */
- defaultTopAxisOptions: {
- labels: {
- x: 0,
- y: -5
- // overflow: undefined
- // staggerLines: null
- },
- title: {
- rotation: 0
- }
- },
-
- /**
- * Initialize the axis
- */
- init: function (chart, userOptions) {
-
-
- var isXAxis = userOptions.isX,
- axis = this;
-
- // Flag, is the axis horizontal
- axis.horiz = chart.inverted ? !isXAxis : isXAxis;
-
- // Flag, isXAxis
- axis.isXAxis = isXAxis;
- axis.xOrY = isXAxis ? 'x' : 'y';
-
-
- axis.opposite = userOptions.opposite; // needed in setOptions
- axis.side = axis.horiz ?
- (axis.opposite ? 0 : 2) : // top : bottom
- (axis.opposite ? 1 : 3); // right : left
-
- axis.setOptions(userOptions);
-
-
- var options = this.options,
- type = options.type,
- isDatetimeAxis = type === 'datetime';
-
- axis.labelFormatter = options.labels.formatter || axis.defaultLabelFormatter; // can be overwritten by dynamic format
-
-
- // Flag, stagger lines or not
- axis.userOptions = userOptions;
-
- //axis.axisTitleMargin = UNDEFINED,// = options.title.margin,
- axis.minPixelPadding = 0;
- //axis.ignoreMinPadding = UNDEFINED; // can be set to true by a column or bar series
- //axis.ignoreMaxPadding = UNDEFINED;
-
- axis.chart = chart;
- axis.reversed = options.reversed;
- axis.zoomEnabled = options.zoomEnabled !== false;
-
- // Initial categories
- axis.categories = options.categories || type === 'category';
-
- // Elements
- //axis.axisGroup = UNDEFINED;
- //axis.gridGroup = UNDEFINED;
- //axis.axisTitle = UNDEFINED;
- //axis.axisLine = UNDEFINED;
-
- // Shorthand types
- axis.isLog = type === 'logarithmic';
- axis.isDatetimeAxis = isDatetimeAxis;
-
- // Flag, if axis is linked to another axis
- axis.isLinked = defined(options.linkedTo);
- // Linked axis.
- //axis.linkedParent = UNDEFINED;
-
-
- // Flag if percentage mode
- //axis.usePercentage = UNDEFINED;
-
-
- // Tick positions
- //axis.tickPositions = UNDEFINED; // array containing predefined positions
- // Tick intervals
- //axis.tickInterval = UNDEFINED;
- //axis.minorTickInterval = UNDEFINED;
-
- axis.tickmarkOffset = (axis.categories && options.tickmarkPlacement === 'between') ? 0.5 : 0;
-
- // Major ticks
- axis.ticks = {};
- // Minor ticks
- axis.minorTicks = {};
- //axis.tickAmount = UNDEFINED;
-
- // List of plotLines/Bands
- axis.plotLinesAndBands = [];
-
- // Alternate bands
- axis.alternateBands = {};
-
- // Axis metrics
- //axis.left = UNDEFINED;
- //axis.top = UNDEFINED;
- //axis.width = UNDEFINED;
- //axis.height = UNDEFINED;
- //axis.bottom = UNDEFINED;
- //axis.right = UNDEFINED;
- //axis.transA = UNDEFINED;
- //axis.transB = UNDEFINED;
- //axis.oldTransA = UNDEFINED;
- axis.len = 0;
- //axis.oldMin = UNDEFINED;
- //axis.oldMax = UNDEFINED;
- //axis.oldUserMin = UNDEFINED;
- //axis.oldUserMax = UNDEFINED;
- //axis.oldAxisLength = UNDEFINED;
- axis.minRange = axis.userMinRange = options.minRange || options.maxZoom;
- axis.range = options.range;
- axis.offset = options.offset || 0;
-
-
- // Dictionary for stacks
- axis.stacks = {};
- axis.oldStacks = {};
-
- // Dictionary for stacks max values
- axis.stacksMax = {};
-
- axis._stacksTouched = 0;
-
- // Min and max in the data
- //axis.dataMin = UNDEFINED,
- //axis.dataMax = UNDEFINED,
-
- // The axis range
- axis.max = null;
- axis.min = null;
-
- // User set min and max
- //axis.userMin = UNDEFINED,
- //axis.userMax = UNDEFINED,
-
- // Run Axis
-
- var eventType,
- events = axis.options.events;
-
- // Register
- if (inArray(axis, chart.axes) === -1) { // don't add it again on Axis.update()
- chart.axes.push(axis);
- chart[isXAxis ? 'xAxis' : 'yAxis'].push(axis);
- }
-
- axis.series = axis.series || []; // populated by Series
-
- // inverted charts have reversed xAxes as default
- if (chart.inverted && isXAxis && axis.reversed === UNDEFINED) {
- axis.reversed = true;
- }
-
- axis.removePlotBand = axis.removePlotBandOrLine;
- axis.removePlotLine = axis.removePlotBandOrLine;
-
-
- // register event listeners
- for (eventType in events) {
- addEvent(axis, eventType, events[eventType]);
- }
-
- // extend logarithmic axis
- if (axis.isLog) {
- axis.val2lin = log2lin;
- axis.lin2val = lin2log;
- }
- },
-
- /**
- * Merge and set options
- */
- setOptions: function (userOptions) {
- this.options = merge(
- this.defaultOptions,
- this.isXAxis ? {} : this.defaultYAxisOptions,
- [this.defaultTopAxisOptions, this.defaultRightAxisOptions,
- this.defaultBottomAxisOptions, this.defaultLeftAxisOptions][this.side],
- merge(
- defaultOptions[this.isXAxis ? 'xAxis' : 'yAxis'], // if set in setOptions (#1053)
- userOptions
- )
- );
- },
-
- /**
- * Update the axis with a new options structure
- */
- update: function (newOptions, redraw) {
- var chart = this.chart;
-
- newOptions = chart.options[this.xOrY + 'Axis'][this.options.index] = merge(this.userOptions, newOptions);
-
- this.destroy(true);
- this._addedPlotLB = false; // #1611
-
- this.init(chart, extend(newOptions, { events: UNDEFINED }));
-
- chart.isDirtyBox = true;
- if (pick(redraw, true)) {
- chart.redraw();
- }
- },
-
- /**
- * Remove the axis from the chart
- */
- remove: function (redraw) {
- var chart = this.chart,
- key = this.xOrY + 'Axis'; // xAxis or yAxis
-
- // Remove associated series
- each(this.series, function (series) {
- series.remove(false);
- });
-
- // Remove the axis
- erase(chart.axes, this);
- erase(chart[key], this);
- chart.options[key].splice(this.options.index, 1);
- each(chart[key], function (axis, i) { // Re-index, #1706
- axis.options.index = i;
- });
- this.destroy();
- chart.isDirtyBox = true;
-
- if (pick(redraw, true)) {
- chart.redraw();
- }
- },
-
- /**
- * The default label formatter. The context is a special config object for the label.
- */
- defaultLabelFormatter: function () {
- var axis = this.axis,
- value = this.value,
- categories = axis.categories,
- dateTimeLabelFormat = this.dateTimeLabelFormat,
- numericSymbols = defaultOptions.lang.numericSymbols,
- i = numericSymbols && numericSymbols.length,
- multi,
- ret,
- formatOption = axis.options.labels.format,
-
- // make sure the same symbol is added for all labels on a linear axis
- numericSymbolDetector = axis.isLog ? value : axis.tickInterval;
-
- if (formatOption) {
- ret = format(formatOption, this);
-
- } else if (categories) {
- ret = value;
-
- } else if (dateTimeLabelFormat) { // datetime axis
- ret = dateFormat(dateTimeLabelFormat, value);
-
- } else if (i && numericSymbolDetector >= 1000) {
- // Decide whether we should add a numeric symbol like k (thousands) or M (millions).
- // If we are to enable this in tooltip or other places as well, we can move this
- // logic to the numberFormatter and enable it by a parameter.
- while (i-- && ret === UNDEFINED) {
- multi = Math.pow(1000, i + 1);
- if (numericSymbolDetector >= multi && numericSymbols[i] !== null) {
- ret = numberFormat(value / multi, -1) + numericSymbols[i];
- }
- }
- }
-
- if (ret === UNDEFINED) {
- if (value >= 1000) { // add thousands separators
- ret = numberFormat(value, 0);
-
- } else { // small numbers
- ret = numberFormat(value, -1);
- }
- }
-
- return ret;
- },
-
- /**
- * Get the minimum and maximum for the series of each axis
- */
- getSeriesExtremes: function () {
- var axis = this,
- chart = axis.chart;
-
- axis.hasVisibleSeries = false;
-
- // reset dataMin and dataMax in case we're redrawing
- axis.dataMin = axis.dataMax = null;
-
- // reset cached stacking extremes
- axis.stacksMax = {};
-
- axis.buildStacks();
-
- // loop through this axis' series
- each(axis.series, function (series) {
-
- if (series.visible || !chart.options.chart.ignoreHiddenSeries) {
-
- var seriesOptions = series.options,
- stacking,
- xData,
- threshold = seriesOptions.threshold,
- seriesDataMin,
- seriesDataMax;
-
- axis.hasVisibleSeries = true;
-
- // Validate threshold in logarithmic axes
- if (axis.isLog && threshold <= 0) {
- threshold = null;
- }
-
- // Get dataMin and dataMax for X axes
- if (axis.isXAxis) {
- xData = series.xData;
- if (xData.length) {
- axis.dataMin = mathMin(pick(axis.dataMin, xData[0]), arrayMin(xData));
- axis.dataMax = mathMax(pick(axis.dataMax, xData[0]), arrayMax(xData));
- }
-
- // Get dataMin and dataMax for Y axes, as well as handle stacking and processed data
- } else {
-
- // Handle stacking
- stacking = seriesOptions.stacking;
- axis.usePercentage = stacking === 'percent';
-
- // create a stack for this particular series type
- if (axis.usePercentage) {
- axis.dataMin = 0;
- axis.dataMax = 99;
- }
-
-
- // get this particular series extremes
- series.getExtremes();
- seriesDataMax = series.dataMax;
- seriesDataMin = series.dataMin;
-
- // Get the dataMin and dataMax so far. If percentage is used, the min and max are
- // always 0 and 100. If seriesDataMin and seriesDataMax is null, then series
- // doesn't have active y data, we continue with nulls
- if (!axis.usePercentage && defined(seriesDataMin) && defined(seriesDataMax)) {
- axis.dataMin = mathMin(pick(axis.dataMin, seriesDataMin), seriesDataMin);
- axis.dataMax = mathMax(pick(axis.dataMax, seriesDataMax), seriesDataMax);
- }
-
- // Adjust to threshold
- if (defined(threshold)) {
- if (axis.dataMin >= threshold) {
- axis.dataMin = threshold;
- axis.ignoreMinPadding = true;
- } else if (axis.dataMax < threshold) {
- axis.dataMax = threshold;
- axis.ignoreMaxPadding = true;
- }
- }
- }
- }
- });
- },
-
- /**
- * Translate from axis value to pixel position on the chart, or back
- *
- */
- translate: function (val, backwards, cvsCoord, old, handleLog, pointPlacement) {
- var axis = this,
- axisLength = axis.len,
- sign = 1,
- cvsOffset = 0,
- localA = old ? axis.oldTransA : axis.transA,
- localMin = old ? axis.oldMin : axis.min,
- returnValue,
- minPixelPadding = axis.minPixelPadding,
- postTranslate = (axis.options.ordinal || (axis.isLog && handleLog)) && axis.lin2val;
-
- if (!localA) {
- localA = axis.transA;
- }
-
- // In vertical axes, the canvas coordinates start from 0 at the top like in
- // SVG.
- if (cvsCoord) {
- sign *= -1; // canvas coordinates inverts the value
- cvsOffset = axisLength;
- }
-
- // Handle reversed axis
- if (axis.reversed) {
- sign *= -1;
- cvsOffset -= sign * axisLength;
- }
-
- // From pixels to value
- if (backwards) { // reverse translation
-
- val = val * sign + cvsOffset;
- val -= minPixelPadding;
- returnValue = val / localA + localMin; // from chart pixel to value
- if (postTranslate) { // log and ordinal axes
- returnValue = axis.lin2val(returnValue);
- }
-
- // From value to pixels
- } else {
- if (postTranslate) { // log and ordinal axes
- val = axis.val2lin(val);
- }
- if (pointPlacement === 'between') {
- pointPlacement = 0.5;
- }
- returnValue = sign * (val - localMin) * localA + cvsOffset + (sign * minPixelPadding) +
- (isNumber(pointPlacement) ? localA * pointPlacement * axis.pointRange : 0);
- }
-
- return returnValue;
- },
-
- /**
- * Utility method to translate an axis value to pixel position.
- * @param {Number} value A value in terms of axis units
- * @param {Boolean} paneCoordinates Whether to return the pixel coordinate relative to the chart
- * or just the axis/pane itself.
- */
- toPixels: function (value, paneCoordinates) {
- return this.translate(value, false, !this.horiz, null, true) + (paneCoordinates ? 0 : this.pos);
- },
-
- /*
- * Utility method to translate a pixel position in to an axis value
- * @param {Number} pixel The pixel value coordinate
- * @param {Boolean} paneCoordiantes Whether the input pixel is relative to the chart or just the
- * axis/pane itself.
- */
- toValue: function (pixel, paneCoordinates) {
- return this.translate(pixel - (paneCoordinates ? 0 : this.pos), true, !this.horiz, null, true);
- },
-
- /**
- * Create the path for a plot line that goes from the given value on
- * this axis, across the plot to the opposite side
- * @param {Number} value
- * @param {Number} lineWidth Used for calculation crisp line
- * @param {Number] old Use old coordinates (for resizing and rescaling)
- */
- getPlotLinePath: function (value, lineWidth, old, force) {
- var axis = this,
- chart = axis.chart,
- axisLeft = axis.left,
- axisTop = axis.top,
- x1,
- y1,
- x2,
- y2,
- translatedValue = axis.translate(value, null, null, old),
- cHeight = (old && chart.oldChartHeight) || chart.chartHeight,
- cWidth = (old && chart.oldChartWidth) || chart.chartWidth,
- skip,
- transB = axis.transB;
-
- x1 = x2 = mathRound(translatedValue + transB);
- y1 = y2 = mathRound(cHeight - translatedValue - transB);
-
- if (isNaN(translatedValue)) { // no min or max
- skip = true;
-
- } else if (axis.horiz) {
- y1 = axisTop;
- y2 = cHeight - axis.bottom;
- if (x1 < axisLeft || x1 > axisLeft + axis.width) {
- skip = true;
- }
- } else {
- x1 = axisLeft;
- x2 = cWidth - axis.right;
-
- if (y1 < axisTop || y1 > axisTop + axis.height) {
- skip = true;
- }
- }
- return skip && !force ?
- null :
- chart.renderer.crispLine([M, x1, y1, L, x2, y2], lineWidth || 0);
- },
-
- /**
- * Create the path for a plot band
- */
- getPlotBandPath: function (from, to) {
-
- var toPath = this.getPlotLinePath(to),
- path = this.getPlotLinePath(from);
-
- if (path && toPath) {
- path.push(
- toPath[4],
- toPath[5],
- toPath[1],
- toPath[2]
- );
- } else { // outside the axis area
- path = null;
- }
-
- return path;
- },
-
- /**
- * Set the tick positions of a linear axis to round values like whole tens or every five.
- */
- getLinearTickPositions: function (tickInterval, min, max) {
- var pos,
- lastPos,
- roundedMin = correctFloat(mathFloor(min / tickInterval) * tickInterval),
- roundedMax = correctFloat(mathCeil(max / tickInterval) * tickInterval),
- tickPositions = [];
-
- // Populate the intermediate values
- pos = roundedMin;
- while (pos <= roundedMax) {
-
- // Place the tick on the rounded value
- tickPositions.push(pos);
-
- // Always add the raw tickInterval, not the corrected one.
- pos = correctFloat(pos + tickInterval);
-
- // If the interval is not big enough in the current min - max range to actually increase
- // the loop variable, we need to break out to prevent endless loop. Issue #619
- if (pos === lastPos) {
- break;
- }
-
- // Record the last value
- lastPos = pos;
- }
- return tickPositions;
- },
-
- /**
- * Set the tick positions of a logarithmic axis
- */
- getLogTickPositions: function (interval, min, max, minor) {
- var axis = this,
- options = axis.options,
- axisLength = axis.len,
- // Since we use this method for both major and minor ticks,
- // use a local variable and return the result
- positions = [];
-
- // Reset
- if (!minor) {
- axis._minorAutoInterval = null;
- }
-
- // First case: All ticks fall on whole logarithms: 1, 10, 100 etc.
- if (interval >= 0.5) {
- interval = mathRound(interval);
- positions = axis.getLinearTickPositions(interval, min, max);
-
- // Second case: We need intermediary ticks. For example
- // 1, 2, 4, 6, 8, 10, 20, 40 etc.
- } else if (interval >= 0.08) {
- var roundedMin = mathFloor(min),
- intermediate,
- i,
- j,
- len,
- pos,
- lastPos,
- break2;
-
- if (interval > 0.3) {
- intermediate = [1, 2, 4];
- } else if (interval > 0.15) { // 0.2 equals five minor ticks per 1, 10, 100 etc
- intermediate = [1, 2, 4, 6, 8];
- } else { // 0.1 equals ten minor ticks per 1, 10, 100 etc
- intermediate = [1, 2, 3, 4, 5, 6, 7, 8, 9];
- }
-
- for (i = roundedMin; i < max + 1 && !break2; i++) {
- len = intermediate.length;
- for (j = 0; j < len && !break2; j++) {
- pos = log2lin(lin2log(i) * intermediate[j]);
-
- if (pos > min && (!minor || lastPos <= max)) { // #1670
- positions.push(lastPos);
- }
-
- if (lastPos > max) {
- break2 = true;
- }
- lastPos = pos;
- }
- }
-
- // Third case: We are so deep in between whole logarithmic values that
- // we might as well handle the tick positions like a linear axis. For
- // example 1.01, 1.02, 1.03, 1.04.
- } else {
- var realMin = lin2log(min),
- realMax = lin2log(max),
- tickIntervalOption = options[minor ? 'minorTickInterval' : 'tickInterval'],
- filteredTickIntervalOption = tickIntervalOption === 'auto' ? null : tickIntervalOption,
- tickPixelIntervalOption = options.tickPixelInterval / (minor ? 5 : 1),
- totalPixelLength = minor ? axisLength / axis.tickPositions.length : axisLength;
-
- interval = pick(
- filteredTickIntervalOption,
- axis._minorAutoInterval,
- (realMax - realMin) * tickPixelIntervalOption / (totalPixelLength || 1)
- );
-
- interval = normalizeTickInterval(
- interval,
- null,
- getMagnitude(interval)
- );
-
- positions = map(axis.getLinearTickPositions(
- interval,
- realMin,
- realMax
- ), log2lin);
-
- if (!minor) {
- axis._minorAutoInterval = interval / 5;
- }
- }
-
- // Set the axis-level tickInterval variable
- if (!minor) {
- axis.tickInterval = interval;
- }
- return positions;
- },
-
- /**
- * Return the minor tick positions. For logarithmic axes, reuse the same logic
- * as for major ticks.
- */
- getMinorTickPositions: function () {
- var axis = this,
- options = axis.options,
- tickPositions = axis.tickPositions,
- minorTickInterval = axis.minorTickInterval,
- minorTickPositions = [],
- pos,
- i,
- len;
-
- if (axis.isLog) {
- len = tickPositions.length;
- for (i = 1; i < len; i++) {
- minorTickPositions = minorTickPositions.concat(
- axis.getLogTickPositions(minorTickInterval, tickPositions[i - 1], tickPositions[i], true)
- );
- }
- } else if (axis.isDatetimeAxis && options.minorTickInterval === 'auto') { // #1314
- minorTickPositions = minorTickPositions.concat(
- getTimeTicks(
- normalizeTimeTickInterval(minorTickInterval),
- axis.min,
- axis.max,
- options.startOfWeek
- )
- );
- if (minorTickPositions[0] < axis.min) {
- minorTickPositions.shift();
- }
- } else {
- for (pos = axis.min + (tickPositions[0] - axis.min) % minorTickInterval; pos <= axis.max; pos += minorTickInterval) {
- minorTickPositions.push(pos);
- }
- }
- return minorTickPositions;
- },
-
- /**
- * Adjust the min and max for the minimum range. Keep in mind that the series data is
- * not yet processed, so we don't have information on data cropping and grouping, or
- * updated axis.pointRange or series.pointRange. The data can't be processed until
- * we have finally established min and max.
- */
- adjustForMinRange: function () {
- var axis = this,
- options = axis.options,
- min = axis.min,
- max = axis.max,
- zoomOffset,
- spaceAvailable = axis.dataMax - axis.dataMin >= axis.minRange,
- closestDataRange,
- i,
- distance,
- xData,
- loopLength,
- minArgs,
- maxArgs;
-
- // Set the automatic minimum range based on the closest point distance
- if (axis.isXAxis && axis.minRange === UNDEFINED && !axis.isLog) {
-
- if (defined(options.min) || defined(options.max)) {
- axis.minRange = null; // don't do this again
-
- } else {
-
- // Find the closest distance between raw data points, as opposed to
- // closestPointRange that applies to processed points (cropped and grouped)
- each(axis.series, function (series) {
- xData = series.xData;
- loopLength = series.xIncrement ? 1 : xData.length - 1;
- for (i = loopLength; i > 0; i--) {
- distance = xData[i] - xData[i - 1];
- if (closestDataRange === UNDEFINED || distance < closestDataRange) {
- closestDataRange = distance;
- }
- }
- });
- axis.minRange = mathMin(closestDataRange * 5, axis.dataMax - axis.dataMin);
- }
- }
-
- // if minRange is exceeded, adjust
- if (max - min < axis.minRange) {
- var minRange = axis.minRange;
- zoomOffset = (minRange - max + min) / 2;
-
- // if min and max options have been set, don't go beyond it
- minArgs = [min - zoomOffset, pick(options.min, min - zoomOffset)];
- if (spaceAvailable) { // if space is available, stay within the data range
- minArgs[2] = axis.dataMin;
- }
- min = arrayMax(minArgs);
-
- maxArgs = [min + minRange, pick(options.max, min + minRange)];
- if (spaceAvailable) { // if space is availabe, stay within the data range
- maxArgs[2] = axis.dataMax;
- }
-
- max = arrayMin(maxArgs);
-
- // now if the max is adjusted, adjust the min back
- if (max - min < minRange) {
- minArgs[0] = max - minRange;
- minArgs[1] = pick(options.min, max - minRange);
- min = arrayMax(minArgs);
- }
- }
-
- // Record modified extremes
- axis.min = min;
- axis.max = max;
- },
-
- /**
- * Update translation information
- */
- setAxisTranslation: function (saveOld) {
- var axis = this,
- range = axis.max - axis.min,
- pointRange = 0,
- closestPointRange,
- minPointOffset = 0,
- pointRangePadding = 0,
- linkedParent = axis.linkedParent,
- ordinalCorrection,
- transA = axis.transA;
-
- // adjust translation for padding
- if (axis.isXAxis) {
- if (linkedParent) {
- minPointOffset = linkedParent.minPointOffset;
- pointRangePadding = linkedParent.pointRangePadding;
-
- } else {
- each(axis.series, function (series) {
- var seriesPointRange = series.pointRange,
- pointPlacement = series.options.pointPlacement,
- seriesClosestPointRange = series.closestPointRange;
-
- if (seriesPointRange > range) { // #1446
- seriesPointRange = 0;
- }
- pointRange = mathMax(pointRange, seriesPointRange);
-
- // minPointOffset is the value padding to the left of the axis in order to make
- // room for points with a pointRange, typically columns. When the pointPlacement option
- // is 'between' or 'on', this padding does not apply.
- minPointOffset = mathMax(
- minPointOffset,
- isString(pointPlacement) ? 0 : seriesPointRange / 2
- );
-
- // Determine the total padding needed to the length of the axis to make room for the
- // pointRange. If the series' pointPlacement is 'on', no padding is added.
- pointRangePadding = mathMax(
- pointRangePadding,
- pointPlacement === 'on' ? 0 : seriesPointRange
- );
-
- // Set the closestPointRange
- if (!series.noSharedTooltip && defined(seriesClosestPointRange)) {
- closestPointRange = defined(closestPointRange) ?
- mathMin(closestPointRange, seriesClosestPointRange) :
- seriesClosestPointRange;
- }
- });
- }
-
- // Record minPointOffset and pointRangePadding
- ordinalCorrection = axis.ordinalSlope && closestPointRange ? axis.ordinalSlope / closestPointRange : 1; // #988, #1853
- axis.minPointOffset = minPointOffset = minPointOffset * ordinalCorrection;
- axis.pointRangePadding = pointRangePadding = pointRangePadding * ordinalCorrection;
-
- // pointRange means the width reserved for each point, like in a column chart
- axis.pointRange = mathMin(pointRange, range);
-
- // closestPointRange means the closest distance between points. In columns
- // it is mostly equal to pointRange, but in lines pointRange is 0 while closestPointRange
- // is some other value
- axis.closestPointRange = closestPointRange;
- }
-
- // Secondary values
- if (saveOld) {
- axis.oldTransA = transA;
- }
- axis.translationSlope = axis.transA = transA = axis.len / ((range + pointRangePadding) || 1);
- axis.transB = axis.horiz ? axis.left : axis.bottom; // translation addend
- axis.minPixelPadding = transA * minPointOffset;
- },
-
- /**
- * Set the tick positions to round values and optionally extend the extremes
- * to the nearest tick
- */
- setTickPositions: function (secondPass) {
- var axis = this,
- chart = axis.chart,
- options = axis.options,
- isLog = axis.isLog,
- isDatetimeAxis = axis.isDatetimeAxis,
- isXAxis = axis.isXAxis,
- isLinked = axis.isLinked,
- tickPositioner = axis.options.tickPositioner,
- maxPadding = options.maxPadding,
- minPadding = options.minPadding,
- length,
- linkedParentExtremes,
- tickIntervalOption = options.tickInterval,
- minTickIntervalOption = options.minTickInterval,
- tickPixelIntervalOption = options.tickPixelInterval,
- tickPositions,
- categories = axis.categories;
-
- // linked axis gets the extremes from the parent axis
- if (isLinked) {
- axis.linkedParent = chart[isXAxis ? 'xAxis' : 'yAxis'][options.linkedTo];
- linkedParentExtremes = axis.linkedParent.getExtremes();
- axis.min = pick(linkedParentExtremes.min, linkedParentExtremes.dataMin);
- axis.max = pick(linkedParentExtremes.max, linkedParentExtremes.dataMax);
- if (options.type !== axis.linkedParent.options.type) {
- error(11, 1); // Can't link axes of different type
- }
- } else { // initial min and max from the extreme data values
- axis.min = pick(axis.userMin, options.min, axis.dataMin);
- axis.max = pick(axis.userMax, options.max, axis.dataMax);
- }
-
- if (isLog) {
- if (!secondPass && mathMin(axis.min, pick(axis.dataMin, axis.min)) <= 0) { // #978
- error(10, 1); // Can't plot negative values on log axis
- }
- axis.min = correctFloat(log2lin(axis.min)); // correctFloat cures #934
- axis.max = correctFloat(log2lin(axis.max));
- }
-
- // handle zoomed range
- if (axis.range) {
- axis.userMin = axis.min = mathMax(axis.min, axis.max - axis.range); // #618
- axis.userMax = axis.max;
- if (secondPass) {
- axis.range = null; // don't use it when running setExtremes
- }
- }
-
- // Hook for adjusting this.min and this.max. Used by bubble series.
- if (axis.beforePadding) {
- axis.beforePadding();
- }
-
- // adjust min and max for the minimum range
- axis.adjustForMinRange();
-
- // Pad the values to get clear of the chart's edges. To avoid tickInterval taking the padding
- // into account, we do this after computing tick interval (#1337).
- if (!categories && !axis.usePercentage && !isLinked && defined(axis.min) && defined(axis.max)) {
- length = axis.max - axis.min;
- if (length) {
- if (!defined(options.min) && !defined(axis.userMin) && minPadding && (axis.dataMin < 0 || !axis.ignoreMinPadding)) {
- axis.min -= length * minPadding;
- }
- if (!defined(options.max) && !defined(axis.userMax) && maxPadding && (axis.dataMax > 0 || !axis.ignoreMaxPadding)) {
- axis.max += length * maxPadding;
- }
- }
- }
-
- // get tickInterval
- if (axis.min === axis.max || axis.min === undefined || axis.max === undefined) {
- axis.tickInterval = 1;
- } else if (isLinked && !tickIntervalOption &&
- tickPixelIntervalOption === axis.linkedParent.options.tickPixelInterval) {
- axis.tickInterval = axis.linkedParent.tickInterval;
- } else {
- axis.tickInterval = pick(
- tickIntervalOption,
- categories ? // for categoried axis, 1 is default, for linear axis use tickPix
- 1 :
- (axis.max - axis.min) * tickPixelIntervalOption / (axis.len || 1)
- );
- }
-
- // Now we're finished detecting min and max, crop and group series data. This
- // is in turn needed in order to find tick positions in ordinal axes.
- if (isXAxis && !secondPass) {
- each(axis.series, function (series) {
- series.processData(axis.min !== axis.oldMin || axis.max !== axis.oldMax);
- });
- }
-
- // set the translation factor used in translate function
- axis.setAxisTranslation(true);
-
- // hook for ordinal axes and radial axes
- if (axis.beforeSetTickPositions) {
- axis.beforeSetTickPositions();
- }
-
- // hook for extensions, used in Highstock ordinal axes
- if (axis.postProcessTickInterval) {
- axis.tickInterval = axis.postProcessTickInterval(axis.tickInterval);
- }
-
- // In column-like charts, don't cramp in more ticks than there are points (#1943)
- if (axis.pointRange) {
- axis.tickInterval = mathMax(axis.pointRange, axis.tickInterval);
- }
-
- // Before normalizing the tick interval, handle minimum tick interval. This applies only if tickInterval is not defined.
- if (!tickIntervalOption && axis.tickInterval < minTickIntervalOption) {
- axis.tickInterval = minTickIntervalOption;
- }
-
- // for linear axes, get magnitude and normalize the interval
- if (!isDatetimeAxis && !isLog) { // linear
- if (!tickIntervalOption) {
- axis.tickInterval = normalizeTickInterval(axis.tickInterval, null, getMagnitude(axis.tickInterval), options);
- }
- }
-
- // get minorTickInterval
- axis.minorTickInterval = options.minorTickInterval === 'auto' && axis.tickInterval ?
- axis.tickInterval / 5 : options.minorTickInterval;
-
- // find the tick positions
- axis.tickPositions = tickPositions = options.tickPositions ?
- [].concat(options.tickPositions) : // Work on a copy (#1565)
- (tickPositioner && tickPositioner.apply(axis, [axis.min, axis.max]));
- if (!tickPositions) {
- if (isDatetimeAxis) {
- tickPositions = (axis.getNonLinearTimeTicks || getTimeTicks)(
- normalizeTimeTickInterval(axis.tickInterval, options.units),
- axis.min,
- axis.max,
- options.startOfWeek,
- axis.ordinalPositions,
- axis.closestPointRange,
- true
- );
- } else if (isLog) {
- tickPositions = axis.getLogTickPositions(axis.tickInterval, axis.min, axis.max);
- } else {
- tickPositions = axis.getLinearTickPositions(axis.tickInterval, axis.min, axis.max);
- }
- axis.tickPositions = tickPositions;
- }
-
- if (!isLinked) {
-
- // reset min/max or remove extremes based on start/end on tick
- var roundedMin = tickPositions[0],
- roundedMax = tickPositions[tickPositions.length - 1],
- minPointOffset = axis.minPointOffset || 0,
- singlePad;
-
- if (options.startOnTick) {
- axis.min = roundedMin;
- } else if (axis.min - minPointOffset > roundedMin) {
- tickPositions.shift();
- }
-
- if (options.endOnTick) {
- axis.max = roundedMax;
- } else if (axis.max + minPointOffset < roundedMax) {
- tickPositions.pop();
- }
-
- // When there is only one point, or all points have the same value on this axis, then min
- // and max are equal and tickPositions.length is 1. In this case, add some padding
- // in order to center the point, but leave it with one tick. #1337.
- if (tickPositions.length === 1) {
- singlePad = 0.001; // The lowest possible number to avoid extra padding on columns
- axis.min -= singlePad;
- axis.max += singlePad;
- }
- }
- },
-
- /**
- * Set the max ticks of either the x and y axis collection
- */
- setMaxTicks: function () {
-
- var chart = this.chart,
- maxTicks = chart.maxTicks || {},
- tickPositions = this.tickPositions,
- key = this._maxTicksKey = [this.xOrY, this.pos, this.len].join('-');
-
- if (!this.isLinked && !this.isDatetimeAxis && tickPositions && tickPositions.length > (maxTicks[key] || 0) && this.options.alignTicks !== false) {
- maxTicks[key] = tickPositions.length;
- }
- chart.maxTicks = maxTicks;
- },
-
- /**
- * When using multiple axes, adjust the number of ticks to match the highest
- * number of ticks in that group
- */
- adjustTickAmount: function () {
- var axis = this,
- chart = axis.chart,
- key = axis._maxTicksKey,
- tickPositions = axis.tickPositions,
- maxTicks = chart.maxTicks;
-
- if (maxTicks && maxTicks[key] && !axis.isDatetimeAxis && !axis.categories && !axis.isLinked && axis.options.alignTicks !== false) { // only apply to linear scale
- var oldTickAmount = axis.tickAmount,
- calculatedTickAmount = tickPositions.length,
- tickAmount;
-
- // set the axis-level tickAmount to use below
- axis.tickAmount = tickAmount = maxTicks[key];
-
- if (calculatedTickAmount < tickAmount) {
- while (tickPositions.length < tickAmount) {
- tickPositions.push(correctFloat(
- tickPositions[tickPositions.length - 1] + axis.tickInterval
- ));
- }
- axis.transA *= (calculatedTickAmount - 1) / (tickAmount - 1);
- axis.max = tickPositions[tickPositions.length - 1];
-
- }
- if (defined(oldTickAmount) && tickAmount !== oldTickAmount) {
- axis.isDirty = true;
- }
- }
- },
-
- /**
- * Set the scale based on data min and max, user set min and max or options
- *
- */
- setScale: function () {
- var axis = this,
- stacks = axis.stacks,
- type,
- i,
- isDirtyData,
- isDirtyAxisLength;
-
- axis.oldMin = axis.min;
- axis.oldMax = axis.max;
- axis.oldAxisLength = axis.len;
-
- // set the new axisLength
- axis.setAxisSize();
- //axisLength = horiz ? axisWidth : axisHeight;
- isDirtyAxisLength = axis.len !== axis.oldAxisLength;
-
- // is there new data?
- each(axis.series, function (series) {
- if (series.isDirtyData || series.isDirty ||
- series.xAxis.isDirty) { // when x axis is dirty, we need new data extremes for y as well
- isDirtyData = true;
- }
- });
-
-
- // do we really need to go through all this?
- if (isDirtyAxisLength || isDirtyData || axis.isLinked || axis.forceRedraw ||
- axis.userMin !== axis.oldUserMin || axis.userMax !== axis.oldUserMax) {
-
- // reset stacks
- if (!axis.isXAxis) {
- for (type in stacks) {
- for (i in stacks[type]) {
- stacks[type][i].total = null;
- }
- }
- }
-
- axis.forceRedraw = false;
-
- // get data extremes if needed
- axis.getSeriesExtremes();
-
- // get fixed positions based on tickInterval
- axis.setTickPositions();
-
- // record old values to decide whether a rescale is necessary later on (#540)
- axis.oldUserMin = axis.userMin;
- axis.oldUserMax = axis.userMax;
-
- // Mark as dirty if it is not already set to dirty and extremes have changed. #595.
- if (!axis.isDirty) {
- axis.isDirty = isDirtyAxisLength || axis.min !== axis.oldMin || axis.max !== axis.oldMax;
- }
- } else if (!axis.isXAxis) {
- if (axis.oldStacks) {
- stacks = axis.stacks = axis.oldStacks;
- }
-
- // reset stacks
- for (type in stacks) {
- for (i in stacks[type]) {
- stacks[type][i].cum = stacks[type][i].total;
- }
- }
- }
-
- // Set the maximum tick amount
- axis.setMaxTicks();
- },
-
- /**
- * Set the extremes and optionally redraw
- * @param {Number} newMin
- * @param {Number} newMax
- * @param {Boolean} redraw
- * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
- * configuration
- * @param {Object} eventArguments
- *
- */
- setExtremes: function (newMin, newMax, redraw, animation, eventArguments) {
- var axis = this,
- chart = axis.chart;
-
- redraw = pick(redraw, true); // defaults to true
-
- // Extend the arguments with min and max
- eventArguments = extend(eventArguments, {
- min: newMin,
- max: newMax
- });
-
- // Fire the event
- fireEvent(axis, 'setExtremes', eventArguments, function () { // the default event handler
-
- axis.userMin = newMin;
- axis.userMax = newMax;
-
- // Mark for running afterSetExtremes
- axis.isDirtyExtremes = true;
-
- // redraw
- if (redraw) {
- chart.redraw(animation);
- }
- });
- },
-
- /**
- * Overridable method for zooming chart. Pulled out in a separate method to allow overriding
- * in stock charts.
- */
- zoom: function (newMin, newMax) {
-
- // Prevent pinch zooming out of range. Check for defined is for #1946.
- if (!this.allowZoomOutside) {
- if (defined(this.dataMin) && newMin <= this.dataMin) {
- newMin = UNDEFINED;
- }
- if (defined(this.dataMax) && newMax >= this.dataMax) {
- newMax = UNDEFINED;
- }
- }
-
- // In full view, displaying the reset zoom button is not required
- this.displayBtn = newMin !== UNDEFINED || newMax !== UNDEFINED;
-
- // Do it
- this.setExtremes(
- newMin,
- newMax,
- false,
- UNDEFINED,
- { trigger: 'zoom' }
- );
- return true;
- },
-
- /**
- * Update the axis metrics
- */
- setAxisSize: function () {
- var chart = this.chart,
- options = this.options,
- offsetLeft = options.offsetLeft || 0,
- offsetRight = options.offsetRight || 0,
- horiz = this.horiz,
- width,
- height,
- top,
- left;
-
- // Expose basic values to use in Series object and navigator
- this.left = left = pick(options.left, chart.plotLeft + offsetLeft);
- this.top = top = pick(options.top, chart.plotTop);
- this.width = width = pick(options.width, chart.plotWidth - offsetLeft + offsetRight);
- this.height = height = pick(options.height, chart.plotHeight);
- this.bottom = chart.chartHeight - height - top;
- this.right = chart.chartWidth - width - left;
-
- // Direction agnostic properties
- this.len = mathMax(horiz ? width : height, 0); // mathMax fixes #905
- this.pos = horiz ? left : top; // distance from SVG origin
- },
-
- /**
- * Get the actual axis extremes
- */
- getExtremes: function () {
- var axis = this,
- isLog = axis.isLog;
-
- return {
- min: isLog ? correctFloat(lin2log(axis.min)) : axis.min,
- max: isLog ? correctFloat(lin2log(axis.max)) : axis.max,
- dataMin: axis.dataMin,
- dataMax: axis.dataMax,
- userMin: axis.userMin,
- userMax: axis.userMax
- };
- },
-
- /**
- * Get the zero plane either based on zero or on the min or max value.
- * Used in bar and area plots
- */
- getThreshold: function (threshold) {
- var axis = this,
- isLog = axis.isLog;
-
- var realMin = isLog ? lin2log(axis.min) : axis.min,
- realMax = isLog ? lin2log(axis.max) : axis.max;
-
- if (realMin > threshold || threshold === null) {
- threshold = realMin;
- } else if (realMax < threshold) {
- threshold = realMax;
- }
-
- return axis.translate(threshold, 0, 1, 0, 1);
- },
-
- addPlotBand: function (options) {
- this.addPlotBandOrLine(options, 'plotBands');
- },
-
- addPlotLine: function (options) {
- this.addPlotBandOrLine(options, 'plotLines');
- },
-
- /**
- * Add a plot band or plot line after render time
- *
- * @param options {Object} The plotBand or plotLine configuration object
- */
- addPlotBandOrLine: function (options, coll) {
- var obj = new PlotLineOrBand(this, options).render(),
- userOptions = this.userOptions;
-
- // Add it to the user options for exporting and Axis.update
- if (coll) {
- userOptions[coll] = userOptions[coll] || [];
- userOptions[coll].push(options);
- }
-
- this.plotLinesAndBands.push(obj);
-
- return obj;
- },
-
- /**
- * Compute auto alignment for the axis label based on which side the axis is on
- * and the given rotation for the label
- */
- autoLabelAlign: function (rotation) {
- var ret,
- angle = (pick(rotation, 0) - (this.side * 90) + 720) % 360;
-
- if (angle > 15 && angle < 165) {
- ret = 'right';
- } else if (angle > 195 && angle < 345) {
- ret = 'left';
- } else {
- ret = 'center';
- }
- return ret;
- },
-
- /**
- * Render the tick labels to a preliminary position to get their sizes
- */
- getOffset: function () {
- var axis = this,
- chart = axis.chart,
- renderer = chart.renderer,
- options = axis.options,
- tickPositions = axis.tickPositions,
- ticks = axis.ticks,
- horiz = axis.horiz,
- side = axis.side,
- invertedSide = chart.inverted ? [1, 0, 3, 2][side] : side,
- hasData,
- showAxis,
- titleOffset = 0,
- titleOffsetOption,
- titleMargin = 0,
- axisTitleOptions = options.title,
- labelOptions = options.labels,
- labelOffset = 0, // reset
- axisOffset = chart.axisOffset,
- clipOffset = chart.clipOffset,
- directionFactor = [-1, 1, 1, -1][side],
- n,
- i,
- autoStaggerLines = 1,
- maxStaggerLines = pick(labelOptions.maxStaggerLines, 5), // docs
- lastRight,
- overlap,
- pos,
- bBox,
- x,
- w,
- lineNo;
-
- // For reuse in Axis.render
- axis.hasData = hasData = (axis.hasVisibleSeries || (defined(axis.min) && defined(axis.max) && !!tickPositions));
- axis.showAxis = showAxis = hasData || pick(options.showEmpty, true);
-
- // Set/reset staggerLines
- axis.staggerLines = axis.horiz && labelOptions.staggerLines;
-
- // Create the axisGroup and gridGroup elements on first iteration
- if (!axis.axisGroup) {
- axis.gridGroup = renderer.g('grid')
- .attr({ zIndex: options.gridZIndex || 1 })
- .add();
- axis.axisGroup = renderer.g('axis')
- .attr({ zIndex: options.zIndex || 2 })
- .add();
- axis.labelGroup = renderer.g('axis-labels')
- .attr({ zIndex: labelOptions.zIndex || 7 })
- .add();
- }
-
- if (hasData || axis.isLinked) {
-
- // Set the explicit or automatic label alignment
- axis.labelAlign = pick(labelOptions.align || axis.autoLabelAlign(labelOptions.rotation));
-
- each(tickPositions, function (pos) {
- if (!ticks[pos]) {
- ticks[pos] = new Tick(axis, pos);
- } else {
- ticks[pos].addLabel(); // update labels depending on tick interval
- }
- });
-
- // Handle automatic stagger lines
- if (axis.horiz && !axis.staggerLines && maxStaggerLines && !labelOptions.rotation) {
- while (autoStaggerLines < maxStaggerLines) {
- lastRight = [];
- overlap = false;
-
- for (i = 0; i < tickPositions.length; i++) {
- pos = tickPositions[i];
- bBox = ticks[pos].label && ticks[pos].label.bBox;
- w = bBox ? bBox.width : 0;
- lineNo = i % autoStaggerLines;
-
- if (w) {
- x = axis.translate(pos); // don't handle log
- if (lastRight[lineNo] !== UNDEFINED && x < lastRight[lineNo]) {
- overlap = true;
- }
- lastRight[lineNo] = x + w;
- }
- }
- if (overlap) {
- autoStaggerLines++;
- } else {
- break;
- }
- }
-
- if (autoStaggerLines > 1) {
- axis.staggerLines = autoStaggerLines;
- }
- }
-
-
- each(tickPositions, function (pos) {
- // left side must be align: right and right side must have align: left for labels
- if (side === 0 || side === 2 || { 1: 'left', 3: 'right' }[side] === axis.labelAlign) {
-
- // get the highest offset
- labelOffset = mathMax(
- ticks[pos].getLabelSize(),
- labelOffset
- );
- }
-
- });
- if (axis.staggerLines) {
- labelOffset *= axis.staggerLines;
- axis.labelOffset = labelOffset;
- }
-
-
- } else { // doesn't have data
- for (n in ticks) {
- ticks[n].destroy();
- delete ticks[n];
- }
- }
-
- if (axisTitleOptions && axisTitleOptions.text && axisTitleOptions.enabled !== false) {
- if (!axis.axisTitle) {
- axis.axisTitle = renderer.text(
- axisTitleOptions.text,
- 0,
- 0,
- axisTitleOptions.useHTML
- )
- .attr({
- zIndex: 7,
- rotation: axisTitleOptions.rotation || 0,
- align:
- axisTitleOptions.textAlign ||
- { low: 'left', middle: 'center', high: 'right' }[axisTitleOptions.align]
- })
- .css(axisTitleOptions.style)
- .add(axis.axisGroup);
- axis.axisTitle.isNew = true;
- }
-
- if (showAxis) {
- titleOffset = axis.axisTitle.getBBox()[horiz ? 'height' : 'width'];
- titleMargin = pick(axisTitleOptions.margin, horiz ? 5 : 10);
- titleOffsetOption = axisTitleOptions.offset;
- }
-
- // hide or show the title depending on whether showEmpty is set
- axis.axisTitle[showAxis ? 'show' : 'hide']();
- }
-
- // handle automatic or user set offset
- axis.offset = directionFactor * pick(options.offset, axisOffset[side]);
-
- axis.axisTitleMargin =
- pick(titleOffsetOption,
- labelOffset + titleMargin +
- (side !== 2 && labelOffset && directionFactor * options.labels[horiz ? 'y' : 'x'])
- );
-
- axisOffset[side] = mathMax(
- axisOffset[side],
- axis.axisTitleMargin + titleOffset + directionFactor * axis.offset
- );
- clipOffset[invertedSide] = mathMax(clipOffset[invertedSide], options.lineWidth);
-
- },
-
- /**
- * Get the path for the axis line
- */
- getLinePath: function (lineWidth) {
- var chart = this.chart,
- opposite = this.opposite,
- offset = this.offset,
- horiz = this.horiz,
- lineLeft = this.left + (opposite ? this.width : 0) + offset,
- lineTop = chart.chartHeight - this.bottom - (opposite ? this.height : 0) + offset;
-
- this.lineTop = lineTop; // used by flag series
- if (!opposite) {
- lineWidth *= -1; // crispify the other way - #1480
- }
-
- return chart.renderer.crispLine([
- M,
- horiz ?
- this.left :
- lineLeft,
- horiz ?
- lineTop :
- this.top,
- L,
- horiz ?
- chart.chartWidth - this.right :
- lineLeft,
- horiz ?
- lineTop :
- chart.chartHeight - this.bottom
- ], lineWidth);
- },
-
- /**
- * Position the title
- */
- getTitlePosition: function () {
- // compute anchor points for each of the title align options
- var horiz = this.horiz,
- axisLeft = this.left,
- axisTop = this.top,
- axisLength = this.len,
- axisTitleOptions = this.options.title,
- margin = horiz ? axisLeft : axisTop,
- opposite = this.opposite,
- offset = this.offset,
- fontSize = pInt(axisTitleOptions.style.fontSize || 12),
-
- // the position in the length direction of the axis
- alongAxis = {
- low: margin + (horiz ? 0 : axisLength),
- middle: margin + axisLength / 2,
- high: margin + (horiz ? axisLength : 0)
- }[axisTitleOptions.align],
-
- // the position in the perpendicular direction of the axis
- offAxis = (horiz ? axisTop + this.height : axisLeft) +
- (horiz ? 1 : -1) * // horizontal axis reverses the margin
- (opposite ? -1 : 1) * // so does opposite axes
- this.axisTitleMargin +
- (this.side === 2 ? fontSize : 0);
-
- return {
- x: horiz ?
- alongAxis :
- offAxis + (opposite ? this.width : 0) + offset +
- (axisTitleOptions.x || 0), // x
- y: horiz ?
- offAxis - (opposite ? this.height : 0) + offset :
- alongAxis + (axisTitleOptions.y || 0) // y
- };
- },
-
- /**
- * Render the axis
- */
- render: function () {
- var axis = this,
- chart = axis.chart,
- renderer = chart.renderer,
- options = axis.options,
- isLog = axis.isLog,
- isLinked = axis.isLinked,
- tickPositions = axis.tickPositions,
- axisTitle = axis.axisTitle,
- stacks = axis.stacks,
- ticks = axis.ticks,
- minorTicks = axis.minorTicks,
- alternateBands = axis.alternateBands,
- stackLabelOptions = options.stackLabels,
- alternateGridColor = options.alternateGridColor,
- tickmarkOffset = axis.tickmarkOffset,
- lineWidth = options.lineWidth,
- linePath,
- hasRendered = chart.hasRendered,
- slideInTicks = hasRendered && defined(axis.oldMin) && !isNaN(axis.oldMin),
- hasData = axis.hasData,
- showAxis = axis.showAxis,
- from,
- to;
-
- // Mark all elements inActive before we go over and mark the active ones
- each([ticks, minorTicks, alternateBands], function (coll) {
- var pos;
- for (pos in coll) {
- coll[pos].isActive = false;
- }
- });
-
- // If the series has data draw the ticks. Else only the line and title
- if (hasData || isLinked) {
-
- // minor ticks
- if (axis.minorTickInterval && !axis.categories) {
- each(axis.getMinorTickPositions(), function (pos) {
- if (!minorTicks[pos]) {
- minorTicks[pos] = new Tick(axis, pos, 'minor');
- }
-
- // render new ticks in old position
- if (slideInTicks && minorTicks[pos].isNew) {
- minorTicks[pos].render(null, true);
- }
-
- minorTicks[pos].render(null, false, 1);
- });
- }
-
- // Major ticks. Pull out the first item and render it last so that
- // we can get the position of the neighbour label. #808.
- if (tickPositions.length) { // #1300
- each(tickPositions.slice(1).concat([tickPositions[0]]), function (pos, i) {
-
- // Reorganize the indices
- i = (i === tickPositions.length - 1) ? 0 : i + 1;
-
- // linked axes need an extra check to find out if
- if (!isLinked || (pos >= axis.min && pos <= axis.max)) {
-
- if (!ticks[pos]) {
- ticks[pos] = new Tick(axis, pos);
- }
-
- // render new ticks in old position
- if (slideInTicks && ticks[pos].isNew) {
- ticks[pos].render(i, true);
- }
-
- ticks[pos].render(i, false, 1);
- }
-
- });
- // In a categorized axis, the tick marks are displayed between labels. So
- // we need to add a tick mark and grid line at the left edge of the X axis.
- if (tickmarkOffset && axis.min === 0) {
- if (!ticks[-1]) {
- ticks[-1] = new Tick(axis, -1, null, true);
- }
- ticks[-1].render(-1);
- }
-
- }
-
- // alternate grid color
- if (alternateGridColor) {
- each(tickPositions, function (pos, i) {
- if (i % 2 === 0 && pos < axis.max) {
- if (!alternateBands[pos]) {
- alternateBands[pos] = new PlotLineOrBand(axis);
- }
- from = pos + tickmarkOffset; // #949
- to = tickPositions[i + 1] !== UNDEFINED ? tickPositions[i + 1] + tickmarkOffset : axis.max;
- alternateBands[pos].options = {
- from: isLog ? lin2log(from) : from,
- to: isLog ? lin2log(to) : to,
- color: alternateGridColor
- };
- alternateBands[pos].render();
- alternateBands[pos].isActive = true;
- }
- });
- }
-
- // custom plot lines and bands
- if (!axis._addedPlotLB) { // only first time
- each((options.plotLines || []).concat(options.plotBands || []), function (plotLineOptions) {
- axis.addPlotBandOrLine(plotLineOptions);
- });
- axis._addedPlotLB = true;
- }
-
- } // end if hasData
-
- // Remove inactive ticks
- each([ticks, minorTicks, alternateBands], function (coll) {
- var pos,
- i,
- forDestruction = [],
- delay = globalAnimation ? globalAnimation.duration || 500 : 0,
- destroyInactiveItems = function () {
- i = forDestruction.length;
- while (i--) {
- // When resizing rapidly, the same items may be destroyed in different timeouts,
- // or the may be reactivated
- if (coll[forDestruction[i]] && !coll[forDestruction[i]].isActive) {
- coll[forDestruction[i]].destroy();
- delete coll[forDestruction[i]];
- }
- }
-
- };
-
- for (pos in coll) {
-
- if (!coll[pos].isActive) {
- // Render to zero opacity
- coll[pos].render(pos, false, 0);
- coll[pos].isActive = false;
- forDestruction.push(pos);
- }
- }
-
- // When the objects are finished fading out, destroy them
- if (coll === alternateBands || !chart.hasRendered || !delay) {
- destroyInactiveItems();
- } else if (delay) {
- setTimeout(destroyInactiveItems, delay);
- }
- });
-
- // Static items. As the axis group is cleared on subsequent calls
- // to render, these items are added outside the group.
- // axis line
- if (lineWidth) {
- linePath = axis.getLinePath(lineWidth);
- if (!axis.axisLine) {
- axis.axisLine = renderer.path(linePath)
- .attr({
- stroke: options.lineColor,
- 'stroke-width': lineWidth,
- zIndex: 7
- })
- .add(axis.axisGroup);
- } else {
- axis.axisLine.animate({ d: linePath });
- }
-
- // show or hide the line depending on options.showEmpty
- axis.axisLine[showAxis ? 'show' : 'hide']();
- }
-
- if (axisTitle && showAxis) {
-
- axisTitle[axisTitle.isNew ? 'attr' : 'animate'](
- axis.getTitlePosition()
- );
- axisTitle.isNew = false;
- }
-
- // Stacked totals:
- if (stackLabelOptions && stackLabelOptions.enabled) {
- var stackKey, oneStack, stackCategory,
- stackTotalGroup = axis.stackTotalGroup;
-
- // Create a separate group for the stack total labels
- if (!stackTotalGroup) {
- axis.stackTotalGroup = stackTotalGroup =
- renderer.g('stack-labels')
- .attr({
- visibility: VISIBLE,
- zIndex: 6
- })
- .add();
- }
-
- // plotLeft/Top will change when y axis gets wider so we need to translate the
- // stackTotalGroup at every render call. See bug #506 and #516
- stackTotalGroup.translate(chart.plotLeft, chart.plotTop);
-
- // Render each stack total
- for (stackKey in stacks) {
- oneStack = stacks[stackKey];
- for (stackCategory in oneStack) {
- oneStack[stackCategory].render(stackTotalGroup);
- }
- }
- }
- // End stacked totals
-
- axis.isDirty = false;
- },
-
- /**
- * Remove a plot band or plot line from the chart by id
- * @param {Object} id
- */
- removePlotBandOrLine: function (id) {
- var plotLinesAndBands = this.plotLinesAndBands,
- options = this.options,
- userOptions = this.userOptions,
- i = plotLinesAndBands.length;
- while (i--) {
- if (plotLinesAndBands[i].id === id) {
- plotLinesAndBands[i].destroy();
- }
- }
- each([options.plotLines || [], userOptions.plotLines || [], options.plotBands || [], userOptions.plotBands || []], function (arr) {
- i = arr.length;
- while (i--) {
- if (arr[i].id === id) {
- erase(arr, arr[i]);
- }
- }
- });
-
- },
-
- /**
- * Update the axis title by options
- */
- setTitle: function (newTitleOptions, redraw) {
- this.update({ title: newTitleOptions }, redraw);
- },
-
- /**
- * Redraw the axis to reflect changes in the data or axis extremes
- */
- redraw: function () {
- var axis = this,
- chart = axis.chart,
- pointer = chart.pointer;
-
- // hide tooltip and hover states
- if (pointer.reset) {
- pointer.reset(true);
- }
-
- // render the axis
- axis.render();
-
- // move plot lines and bands
- each(axis.plotLinesAndBands, function (plotLine) {
- plotLine.render();
- });
-
- // mark associated series as dirty and ready for redraw
- each(axis.series, function (series) {
- series.isDirty = true;
- });
-
- },
-
- /**
- *
- */
- buildStacks: function () {
- if (this.isXAxis) {
- return;
- }
-
- var series = this.series,
- last = series.length - 1;
-
- each(series, function (serie, i) {
- serie.setStackedPoints(i === last);
- });
- },
-
- /**
- * Set new axis categories and optionally redraw
- * @param {Array} categories
- * @param {Boolean} redraw
- */
- setCategories: function (categories, redraw) {
- this.update({ categories: categories }, redraw);
- },
-
- /**
- * Destroys an Axis instance.
- */
- destroy: function (keepEvents) {
- var axis = this,
- stacks = axis.stacks,
- stackKey;
-
- // Remove the events
- if (!keepEvents) {
- removeEvent(axis);
- }
-
- // Destroy each stack total
- for (stackKey in stacks) {
- destroyObjectProperties(stacks[stackKey]);
-
- stacks[stackKey] = null;
- }
-
- // Destroy collections
- each([axis.ticks, axis.minorTicks, axis.alternateBands, axis.plotLinesAndBands], function (coll) {
- destroyObjectProperties(coll);
- });
-
- // Destroy local variables
- each(['stackTotalGroup', 'axisLine', 'axisGroup', 'gridGroup', 'labelGroup', 'axisTitle'], function (prop) {
- if (axis[prop]) {
- axis[prop] = axis[prop].destroy();
- }
- });
- }
-
-
-}; // end Axis
-
-/**
- * The tooltip object
- * @param {Object} chart The chart instance
- * @param {Object} options Tooltip options
- */
-function Tooltip() {
- this.init.apply(this, arguments);
-}
-
-Tooltip.prototype = {
-
- init: function (chart, options) {
-
- var borderWidth = options.borderWidth,
- style = options.style,
- padding = pInt(style.padding);
-
- // Save the chart and options
- this.chart = chart;
- this.options = options;
-
- // Keep track of the current series
- //this.currentSeries = UNDEFINED;
-
- // List of crosshairs
- this.crosshairs = [];
-
- // Current values of x and y when animating
- this.now = { x: 0, y: 0 };
-
- // The tooltip is initially hidden
- this.isHidden = true;
-
-
- // create the label
- this.label = chart.renderer.label('', 0, 0, options.shape, null, null, options.useHTML, null, 'tooltip')
- .attr({
- padding: padding,
- fill: options.backgroundColor,
- 'stroke-width': borderWidth,
- r: options.borderRadius,
- zIndex: 8
- })
- .css(style)
- .css({ padding: 0 }) // Remove it from VML, the padding is applied as an attribute instead (#1117)
- .hide()
- .add();
-
- // When using canVG the shadow shows up as a gray circle
- // even if the tooltip is hidden.
- if (!useCanVG) {
- this.label.shadow(options.shadow);
- }
-
- // Public property for getting the shared state.
- this.shared = options.shared;
- },
-
- /**
- * Destroy the tooltip and its elements.
- */
- destroy: function () {
- each(this.crosshairs, function (crosshair) {
- if (crosshair) {
- crosshair.destroy();
- }
- });
-
- // Destroy and clear local variables
- if (this.label) {
- this.label = this.label.destroy();
- }
- clearTimeout(this.hideTimer);
- clearTimeout(this.tooltipTimeout);
- },
-
- /**
- * Provide a soft movement for the tooltip
- *
- * @param {Number} x
- * @param {Number} y
- * @private
- */
- move: function (x, y, anchorX, anchorY) {
- var tooltip = this,
- now = tooltip.now,
- animate = tooltip.options.animation !== false && !tooltip.isHidden;
-
- // get intermediate values for animation
- extend(now, {
- x: animate ? (2 * now.x + x) / 3 : x,
- y: animate ? (now.y + y) / 2 : y,
- anchorX: animate ? (2 * now.anchorX + anchorX) / 3 : anchorX,
- anchorY: animate ? (now.anchorY + anchorY) / 2 : anchorY
- });
-
- // move to the intermediate value
- tooltip.label.attr(now);
-
-
- // run on next tick of the mouse tracker
- if (animate && (mathAbs(x - now.x) > 1 || mathAbs(y - now.y) > 1)) {
-
- // never allow two timeouts
- clearTimeout(this.tooltipTimeout);
-
- // set the fixed interval ticking for the smooth tooltip
- this.tooltipTimeout = setTimeout(function () {
- // The interval function may still be running during destroy, so check that the chart is really there before calling.
- if (tooltip) {
- tooltip.move(x, y, anchorX, anchorY);
- }
- }, 32);
-
- }
- },
-
- /**
- * Hide the tooltip
- */
- hide: function () {
- var tooltip = this,
- hoverPoints;
-
- clearTimeout(this.hideTimer); // disallow duplicate timers (#1728, #1766)
- if (!this.isHidden) {
- hoverPoints = this.chart.hoverPoints;
-
- this.hideTimer = setTimeout(function () {
- tooltip.label.fadeOut();
- tooltip.isHidden = true;
- }, pick(this.options.hideDelay, 500));
-
- // hide previous hoverPoints and set new
- if (hoverPoints) {
- each(hoverPoints, function (point) {
- point.setState();
- });
- }
-
- this.chart.hoverPoints = null;
- }
- },
-
- /**
- * Hide the crosshairs
- */
- hideCrosshairs: function () {
- each(this.crosshairs, function (crosshair) {
- if (crosshair) {
- crosshair.hide();
- }
- });
- },
-
- /**
- * Extendable method to get the anchor position of the tooltip
- * from a point or set of points
- */
- getAnchor: function (points, mouseEvent) {
- var ret,
- chart = this.chart,
- inverted = chart.inverted,
- plotTop = chart.plotTop,
- plotX = 0,
- plotY = 0,
- yAxis;
-
- points = splat(points);
-
- // Pie uses a special tooltipPos
- ret = points[0].tooltipPos;
-
- // When tooltip follows mouse, relate the position to the mouse
- if (this.followPointer && mouseEvent) {
- if (mouseEvent.chartX === UNDEFINED) {
- mouseEvent = chart.pointer.normalize(mouseEvent);
- }
- ret = [
- mouseEvent.chartX - chart.plotLeft,
- mouseEvent.chartY - plotTop
- ];
- }
- // When shared, use the average position
- if (!ret) {
- each(points, function (point) {
- yAxis = point.series.yAxis;
- plotX += point.plotX;
- plotY += (point.plotLow ? (point.plotLow + point.plotHigh) / 2 : point.plotY) +
- (!inverted && yAxis ? yAxis.top - plotTop : 0); // #1151
- });
-
- plotX /= points.length;
- plotY /= points.length;
-
- ret = [
- inverted ? chart.plotWidth - plotY : plotX,
- this.shared && !inverted && points.length > 1 && mouseEvent ?
- mouseEvent.chartY - plotTop : // place shared tooltip next to the mouse (#424)
- inverted ? chart.plotHeight - plotX : plotY
- ];
- }
-
- return map(ret, mathRound);
- },
-
- /**
- * Place the tooltip in a chart without spilling over
- * and not covering the point it self.
- */
- getPosition: function (boxWidth, boxHeight, point) {
-
- // Set up the variables
- var chart = this.chart,
- plotLeft = chart.plotLeft,
- plotTop = chart.plotTop,
- plotWidth = chart.plotWidth,
- plotHeight = chart.plotHeight,
- distance = pick(this.options.distance, 12),
- pointX = point.plotX,
- pointY = point.plotY,
- x = pointX + plotLeft + (chart.inverted ? distance : -boxWidth - distance),
- y = pointY - boxHeight + plotTop + 15, // 15 means the point is 15 pixels up from the bottom of the tooltip
- alignedRight;
-
- // It is too far to the left, adjust it
- if (x < 7) {
- x = plotLeft + mathMax(pointX, 0) + distance;
- }
-
- // Test to see if the tooltip is too far to the right,
- // if it is, move it back to be inside and then up to not cover the point.
- if ((x + boxWidth) > (plotLeft + plotWidth)) {
- x -= (x + boxWidth) - (plotLeft + plotWidth);
- y = pointY - boxHeight + plotTop - distance;
- alignedRight = true;
- }
-
- // If it is now above the plot area, align it to the top of the plot area
- if (y < plotTop + 5) {
- y = plotTop + 5;
-
- // If the tooltip is still covering the point, move it below instead
- if (alignedRight && pointY >= y && pointY <= (y + boxHeight)) {
- y = pointY + plotTop + distance; // below
- }
- }
-
- // Now if the tooltip is below the chart, move it up. It's better to cover the
- // point than to disappear outside the chart. #834.
- if (y + boxHeight > plotTop + plotHeight) {
- y = mathMax(plotTop, plotTop + plotHeight - boxHeight - distance); // below
- }
-
- return {x: x, y: y};
- },
-
- /**
- * In case no user defined formatter is given, this will be used. Note that the context
- * here is an object holding point, series, x, y etc.
- */
- defaultFormatter: function (tooltip) {
- var items = this.points || splat(this),
- series = items[0].series,
- s;
-
- // build the header
- s = [series.tooltipHeaderFormatter(items[0])];
-
- // build the values
- each(items, function (item) {
- series = item.series;
- s.push((series.tooltipFormatter && series.tooltipFormatter(item)) ||
- item.point.tooltipFormatter(series.tooltipOptions.pointFormat));
- });
-
- // footer
- s.push(tooltip.options.footerFormat || '');
-
- return s.join('');
- },
-
- /**
- * Refresh the tooltip's text and position.
- * @param {Object} point
- */
- refresh: function (point, mouseEvent) {
- var tooltip = this,
- chart = tooltip.chart,
- label = tooltip.label,
- options = tooltip.options,
- x,
- y,
- show,
- anchor,
- textConfig = {},
- text,
- pointConfig = [],
- formatter = options.formatter || tooltip.defaultFormatter,
- hoverPoints = chart.hoverPoints,
- borderColor,
- crosshairsOptions = options.crosshairs,
- shared = tooltip.shared,
- currentSeries;
-
- clearTimeout(this.hideTimer);
-
- // get the reference point coordinates (pie charts use tooltipPos)
- tooltip.followPointer = splat(point)[0].series.tooltipOptions.followPointer;
- anchor = tooltip.getAnchor(point, mouseEvent);
- x = anchor[0];
- y = anchor[1];
-
- // shared tooltip, array is sent over
- if (shared && !(point.series && point.series.noSharedTooltip)) {
-
- // hide previous hoverPoints and set new
-
- chart.hoverPoints = point;
- if (hoverPoints) {
- each(hoverPoints, function (point) {
- point.setState();
- });
- }
-
- each(point, function (item) {
- item.setState(HOVER_STATE);
-
- pointConfig.push(item.getLabelConfig());
- });
-
- textConfig = {
- x: point[0].category,
- y: point[0].y
- };
- textConfig.points = pointConfig;
- point = point[0];
-
- // single point tooltip
- } else {
- textConfig = point.getLabelConfig();
- }
- text = formatter.call(textConfig, tooltip);
-
- // register the current series
- currentSeries = point.series;
-
-
- // For line type series, hide tooltip if the point falls outside the plot
- show = shared || !currentSeries.isCartesian || currentSeries.tooltipOutsidePlot || chart.isInsidePlot(x, y);
-
- // update the inner HTML
- if (text === false || !show) {
- this.hide();
- } else {
-
- // show it
- if (tooltip.isHidden) {
- stop(label);
- label.attr('opacity', 1).show();
- }
-
- // update text
- label.attr({
- text: text
- });
-
- // set the stroke color of the box
- borderColor = options.borderColor || point.color || currentSeries.color || '#606060';
- label.attr({
- stroke: borderColor
- });
-
- tooltip.updatePosition({ plotX: x, plotY: y });
-
- this.isHidden = false;
- }
-
- // crosshairs
- if (crosshairsOptions) {
- crosshairsOptions = splat(crosshairsOptions); // [x, y]
-
- var path,
- i = crosshairsOptions.length,
- attribs,
- axis,
- val,
- series;
-
- while (i--) {
- series = point.series;
- axis = series[i ? 'yAxis' : 'xAxis'];
- if (crosshairsOptions[i] && axis) {
- val = i ? pick(point.stackY, point.y) : point.x; // #814
- if (axis.isLog) { // #1671
- val = log2lin(val);
- }
- if (series.modifyValue) { // #1205
- val = series.modifyValue(val);
- }
-
- path = axis.getPlotLinePath(
- val,
- 1
- );
-
- if (tooltip.crosshairs[i]) {
- tooltip.crosshairs[i].attr({ d: path, visibility: VISIBLE });
- } else {
- attribs = {
- 'stroke-width': crosshairsOptions[i].width || 1,
- stroke: crosshairsOptions[i].color || '#C0C0C0',
- zIndex: crosshairsOptions[i].zIndex || 2
- };
- if (crosshairsOptions[i].dashStyle) {
- attribs.dashstyle = crosshairsOptions[i].dashStyle;
- }
- tooltip.crosshairs[i] = chart.renderer.path(path)
- .attr(attribs)
- .add();
- }
- }
- }
- }
- fireEvent(chart, 'tooltipRefresh', {
- text: text,
- x: x + chart.plotLeft,
- y: y + chart.plotTop,
- borderColor: borderColor
- });
- },
-
- /**
- * Find the new position and perform the move
- */
- updatePosition: function (point) {
- var chart = this.chart,
- label = this.label,
- pos = (this.options.positioner || this.getPosition).call(
- this,
- label.width,
- label.height,
- point
- );
-
- // do the move
- this.move(
- mathRound(pos.x),
- mathRound(pos.y),
- point.plotX + chart.plotLeft,
- point.plotY + chart.plotTop
- );
- }
-};
-/**
- * The mouse tracker object. All methods starting with "on" are primary DOM event handlers.
- * Subsequent methods should be named differently from what they are doing.
- * @param {Object} chart The Chart instance
- * @param {Object} options The root options object
- */
-function Pointer(chart, options) {
- this.init(chart, options);
-}
-
-Pointer.prototype = {
- /**
- * Initialize Pointer
- */
- init: function (chart, options) {
-
- var zoomType = useCanVG ? '' : options.chart.zoomType,
- inverted = chart.inverted,
- zoomX,
- zoomY;
-
- // Store references
- this.options = options;
- this.chart = chart;
-
- // Zoom status
- this.zoomX = zoomX = /x/.test(zoomType);
- this.zoomY = zoomY = /y/.test(zoomType);
- this.zoomHor = (zoomX && !inverted) || (zoomY && inverted);
- this.zoomVert = (zoomY && !inverted) || (zoomX && inverted);
-
- this.pinchDown = [];
- this.lastValidTouch = {};
-
- if (options.tooltip.enabled) {
- chart.tooltip = new Tooltip(chart, options.tooltip);
- }
-
- this.setDOMEvents();
- },
-
- /**
- * Add crossbrowser support for chartX and chartY
- * @param {Object} e The event object in standard browsers
- */
- normalize: function (e) {
- var chartPosition,
- ePos;
-
- // common IE normalizing
- e = e || win.event;
- if (!e.target) {
- e.target = e.srcElement;
- }
-
- // Framework specific normalizing (#1165)
- e = washMouseEvent(e);
-
- // iOS
- ePos = e.touches ? e.touches.item(0) : e;
-
- // get mouse position
- this.chartPosition = chartPosition = offset(this.chart.container);
-
- // Old IE and compatibility mode use clientX. #886, #2005.
- return extend(e, {
- chartX: mathRound(pick(ePos.pageX, ePos.clientX) - chartPosition.left),
- chartY: mathRound(pick(ePos.pageY, ePos.clientY) - chartPosition.top)
- });
- },
-
- /**
- * Get the click position in terms of axis values.
- *
- * @param {Object} e A pointer event
- */
- getCoordinates: function (e) {
- var coordinates = {
- xAxis: [],
- yAxis: []
- };
-
- each(this.chart.axes, function (axis) {
- coordinates[axis.isXAxis ? 'xAxis' : 'yAxis'].push({
- axis: axis,
- value: axis.toValue(e[axis.horiz ? 'chartX' : 'chartY'])
- });
- });
- return coordinates;
- },
-
- /**
- * Return the index in the tooltipPoints array, corresponding to pixel position in
- * the plot area.
- */
- getIndex: function (e) {
- var chart = this.chart;
- return chart.inverted ?
- chart.plotHeight + chart.plotTop - e.chartY :
- e.chartX - chart.plotLeft;
- },
-
- /**
- * With line type charts with a single tracker, get the point closest to the mouse.
- * Run Point.onMouseOver and display tooltip for the point or points.
- */
- runPointActions: function (e) {
- var pointer = this,
- chart = pointer.chart,
- series = chart.series,
- tooltip = chart.tooltip,
- point,
- points,
- hoverPoint = chart.hoverPoint,
- hoverSeries = chart.hoverSeries,
- i,
- j,
- distance = chart.chartWidth,
- index = pointer.getIndex(e),
- anchor;
-
- // shared tooltip
- if (tooltip && pointer.options.tooltip.shared && !(hoverSeries && hoverSeries.noSharedTooltip)) {
- points = [];
-
- // loop over all series and find the ones with points closest to the mouse
- i = series.length;
- for (j = 0; j < i; j++) {
- if (series[j].visible &&
- series[j].options.enableMouseTracking !== false &&
- !series[j].noSharedTooltip && series[j].tooltipPoints.length) {
- point = series[j].tooltipPoints[index];
- if (point.series) { // not a dummy point, #1544
- point._dist = mathAbs(index - point.clientX);
- distance = mathMin(distance, point._dist);
- points.push(point);
- }
- }
- }
- // remove furthest points
- i = points.length;
- while (i--) {
- if (points[i]._dist > distance) {
- points.splice(i, 1);
- }
- }
- // refresh the tooltip if necessary
- if (points.length && (points[0].clientX !== pointer.hoverX)) {
- tooltip.refresh(points, e);
- pointer.hoverX = points[0].clientX;
- }
- }
-
- // separate tooltip and general mouse events
- if (hoverSeries && hoverSeries.tracker) { // only use for line-type series with common tracker
-
- // get the point
- point = hoverSeries.tooltipPoints[index];
-
- // a new point is hovered, refresh the tooltip
- if (point && point !== hoverPoint) {
-
- // trigger the events
- point.onMouseOver(e);
-
- }
-
- } else if (tooltip && tooltip.followPointer && !tooltip.isHidden) {
- anchor = tooltip.getAnchor([{}], e);
- tooltip.updatePosition({ plotX: anchor[0], plotY: anchor[1] });
- }
- },
-
-
-
- /**
- * Reset the tracking by hiding the tooltip, the hover series state and the hover point
- *
- * @param allowMove {Boolean} Instead of destroying the tooltip altogether, allow moving it if possible
- */
- reset: function (allowMove) {
- var pointer = this,
- chart = pointer.chart,
- hoverSeries = chart.hoverSeries,
- hoverPoint = chart.hoverPoint,
- tooltip = chart.tooltip,
- tooltipPoints = tooltip && tooltip.shared ? chart.hoverPoints : hoverPoint;
-
- // Narrow in allowMove
- allowMove = allowMove && tooltip && tooltipPoints;
-
- // Check if the points have moved outside the plot area, #1003
- if (allowMove && splat(tooltipPoints)[0].plotX === UNDEFINED) {
- allowMove = false;
- }
-
- // Just move the tooltip, #349
- if (allowMove) {
- tooltip.refresh(tooltipPoints);
-
- // Full reset
- } else {
-
- if (hoverPoint) {
- hoverPoint.onMouseOut();
- }
-
- if (hoverSeries) {
- hoverSeries.onMouseOut();
- }
-
- if (tooltip) {
- tooltip.hide();
- tooltip.hideCrosshairs();
- }
-
- pointer.hoverX = null;
-
- }
- },
-
- /**
- * Scale series groups to a certain scale and translation
- */
- scaleGroups: function (attribs, clip) {
-
- var chart = this.chart,
- seriesAttribs;
-
- // Scale each series
- each(chart.series, function (series) {
- seriesAttribs = attribs || series.getPlotBox(); // #1701
- if (series.xAxis && series.xAxis.zoomEnabled) {
- series.group.attr(seriesAttribs);
- if (series.markerGroup) {
- series.markerGroup.attr(seriesAttribs);
- series.markerGroup.clip(clip ? chart.clipRect : null);
- }
- if (series.dataLabelsGroup) {
- series.dataLabelsGroup.attr(seriesAttribs);
- }
- }
- });
-
- // Clip
- chart.clipRect.attr(clip || chart.clipBox);
- },
-
- /**
- * Run translation operations for each direction (horizontal and vertical) independently
- */
- pinchTranslateDirection: function (horiz, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch) {
- var chart = this.chart,
- xy = horiz ? 'x' : 'y',
- XY = horiz ? 'X' : 'Y',
- sChartXY = 'chart' + XY,
- wh = horiz ? 'width' : 'height',
- plotLeftTop = chart['plot' + (horiz ? 'Left' : 'Top')],
- selectionWH,
- selectionXY,
- clipXY,
- scale = 1,
- inverted = chart.inverted,
- bounds = chart.bounds[horiz ? 'h' : 'v'],
- singleTouch = pinchDown.length === 1,
- touch0Start = pinchDown[0][sChartXY],
- touch0Now = touches[0][sChartXY],
- touch1Start = !singleTouch && pinchDown[1][sChartXY],
- touch1Now = !singleTouch && touches[1][sChartXY],
- outOfBounds,
- transformScale,
- scaleKey,
- setScale = function () {
- if (!singleTouch && mathAbs(touch0Start - touch1Start) > 20) { // Don't zoom if fingers are too close on this axis
- scale = mathAbs(touch0Now - touch1Now) / mathAbs(touch0Start - touch1Start);
- }
-
- clipXY = ((plotLeftTop - touch0Now) / scale) + touch0Start;
- selectionWH = chart['plot' + (horiz ? 'Width' : 'Height')] / scale;
- };
-
- // Set the scale, first pass
- setScale();
-
- selectionXY = clipXY; // the clip position (x or y) is altered if out of bounds, the selection position is not
-
- // Out of bounds
- if (selectionXY < bounds.min) {
- selectionXY = bounds.min;
- outOfBounds = true;
- } else if (selectionXY + selectionWH > bounds.max) {
- selectionXY = bounds.max - selectionWH;
- outOfBounds = true;
- }
-
- // Is the chart dragged off its bounds, determined by dataMin and dataMax?
- if (outOfBounds) {
-
- // Modify the touchNow position in order to create an elastic drag movement. This indicates
- // to the user that the chart is responsive but can't be dragged further.
- touch0Now -= 0.8 * (touch0Now - lastValidTouch[xy][0]);
- if (!singleTouch) {
- touch1Now -= 0.8 * (touch1Now - lastValidTouch[xy][1]);
- }
-
- // Set the scale, second pass to adapt to the modified touchNow positions
- setScale();
-
- } else {
- lastValidTouch[xy] = [touch0Now, touch1Now];
- }
-
-
- // Set geometry for clipping, selection and transformation
- if (!inverted) { // TODO: implement clipping for inverted charts
- clip[xy] = clipXY - plotLeftTop;
- clip[wh] = selectionWH;
- }
- scaleKey = inverted ? (horiz ? 'scaleY' : 'scaleX') : 'scale' + XY;
- transformScale = inverted ? 1 / scale : scale;
-
- selectionMarker[wh] = selectionWH;
- selectionMarker[xy] = selectionXY;
- transform[scaleKey] = scale;
- transform['translate' + XY] = (transformScale * plotLeftTop) + (touch0Now - (transformScale * touch0Start));
- },
-
- /**
- * Handle touch events with two touches
- */
- pinch: function (e) {
-
- var self = this,
- chart = self.chart,
- pinchDown = self.pinchDown,
- followTouchMove = chart.tooltip && chart.tooltip.options.followTouchMove,
- touches = e.touches,
- touchesLength = touches.length,
- lastValidTouch = self.lastValidTouch,
- zoomHor = self.zoomHor || self.pinchHor,
- zoomVert = self.zoomVert || self.pinchVert,
- hasZoom = zoomHor || zoomVert,
- selectionMarker = self.selectionMarker,
- transform = {},
- clip = {};
-
- // On touch devices, only proceed to trigger click if a handler is defined
- if (e.type === 'touchstart') {
- if (followTouchMove || hasZoom) {
- e.preventDefault();
- }
- }
-
- // Normalize each touch
- map(touches, function (e) {
- return self.normalize(e);
- });
-
- // Register the touch start position
- if (e.type === 'touchstart') {
- each(touches, function (e, i) {
- pinchDown[i] = { chartX: e.chartX, chartY: e.chartY };
- });
- lastValidTouch.x = [pinchDown[0].chartX, pinchDown[1] && pinchDown[1].chartX];
- lastValidTouch.y = [pinchDown[0].chartY, pinchDown[1] && pinchDown[1].chartY];
-
- // Identify the data bounds in pixels
- each(chart.axes, function (axis) {
- if (axis.zoomEnabled) {
- var bounds = chart.bounds[axis.horiz ? 'h' : 'v'],
- minPixelPadding = axis.minPixelPadding,
- min = axis.toPixels(axis.dataMin),
- max = axis.toPixels(axis.dataMax),
- absMin = mathMin(min, max),
- absMax = mathMax(min, max);
-
- // Store the bounds for use in the touchmove handler
- bounds.min = mathMin(axis.pos, absMin - minPixelPadding);
- bounds.max = mathMax(axis.pos + axis.len, absMax + minPixelPadding);
- }
- });
-
- // Event type is touchmove, handle panning and pinching
- } else if (pinchDown.length) { // can be 0 when releasing, if touchend fires first
-
-
- // Set the marker
- if (!selectionMarker) {
- self.selectionMarker = selectionMarker = extend({
- destroy: noop
- }, chart.plotBox);
- }
-
-
-
- if (zoomHor) {
- self.pinchTranslateDirection(true, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch);
- }
- if (zoomVert) {
- self.pinchTranslateDirection(false, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch);
- }
-
- self.hasPinched = hasZoom;
-
- // Scale and translate the groups to provide visual feedback during pinching
- self.scaleGroups(transform, clip);
-
- // Optionally move the tooltip on touchmove
- if (!hasZoom && followTouchMove && touchesLength === 1) {
- this.runPointActions(self.normalize(e));
- }
- }
- },
-
- /**
- * Start a drag operation
- */
- dragStart: function (e) {
- var chart = this.chart;
-
- // Record the start position
- chart.mouseIsDown = e.type;
- chart.cancelClick = false;
- chart.mouseDownX = this.mouseDownX = e.chartX;
- this.mouseDownY = e.chartY;
- },
-
- /**
- * Perform a drag operation in response to a mousemove event while the mouse is down
- */
- drag: function (e) {
-
- var chart = this.chart,
- chartOptions = chart.options.chart,
- chartX = e.chartX,
- chartY = e.chartY,
- zoomHor = this.zoomHor,
- zoomVert = this.zoomVert,
- plotLeft = chart.plotLeft,
- plotTop = chart.plotTop,
- plotWidth = chart.plotWidth,
- plotHeight = chart.plotHeight,
- clickedInside,
- size,
- mouseDownX = this.mouseDownX,
- mouseDownY = this.mouseDownY;
-
- // If the mouse is outside the plot area, adjust to cooordinates
- // inside to prevent the selection marker from going outside
- if (chartX < plotLeft) {
- chartX = plotLeft;
- } else if (chartX > plotLeft + plotWidth) {
- chartX = plotLeft + plotWidth;
- }
-
- if (chartY < plotTop) {
- chartY = plotTop;
- } else if (chartY > plotTop + plotHeight) {
- chartY = plotTop + plotHeight;
- }
-
- // determine if the mouse has moved more than 10px
- this.hasDragged = Math.sqrt(
- Math.pow(mouseDownX - chartX, 2) +
- Math.pow(mouseDownY - chartY, 2)
- );
- if (this.hasDragged > 10) {
- clickedInside = chart.isInsidePlot(mouseDownX - plotLeft, mouseDownY - plotTop);
-
- // make a selection
- if (chart.hasCartesianSeries && (this.zoomX || this.zoomY) && clickedInside) {
- if (!this.selectionMarker) {
- this.selectionMarker = chart.renderer.rect(
- plotLeft,
- plotTop,
- zoomHor ? 1 : plotWidth,
- zoomVert ? 1 : plotHeight,
- 0
- )
- .attr({
- fill: chartOptions.selectionMarkerFill || 'rgba(69,114,167,0.25)',
- zIndex: 7
- })
- .add();
- }
- }
-
- // adjust the width of the selection marker
- if (this.selectionMarker && zoomHor) {
- size = chartX - mouseDownX;
- this.selectionMarker.attr({
- width: mathAbs(size),
- x: (size > 0 ? 0 : size) + mouseDownX
- });
- }
- // adjust the height of the selection marker
- if (this.selectionMarker && zoomVert) {
- size = chartY - mouseDownY;
- this.selectionMarker.attr({
- height: mathAbs(size),
- y: (size > 0 ? 0 : size) + mouseDownY
- });
- }
-
- // panning
- if (clickedInside && !this.selectionMarker && chartOptions.panning) {
- chart.pan(chartX);
- }
- }
- },
-
- /**
- * On mouse up or touch end across the entire document, drop the selection.
- */
- drop: function (e) {
- var chart = this.chart,
- hasPinched = this.hasPinched;
-
- if (this.selectionMarker) {
- var selectionData = {
- xAxis: [],
- yAxis: [],
- originalEvent: e.originalEvent || e
- },
- selectionBox = this.selectionMarker,
- selectionLeft = selectionBox.x,
- selectionTop = selectionBox.y,
- runZoom;
- // a selection has been made
- if (this.hasDragged || hasPinched) {
-
- // record each axis' min and max
- each(chart.axes, function (axis) {
- if (axis.zoomEnabled) {
- var horiz = axis.horiz,
- selectionMin = axis.toValue((horiz ? selectionLeft : selectionTop)),
- selectionMax = axis.toValue((horiz ? selectionLeft + selectionBox.width : selectionTop + selectionBox.height));
-
- if (!isNaN(selectionMin) && !isNaN(selectionMax)) { // #859
- selectionData[axis.xOrY + 'Axis'].push({
- axis: axis,
- min: mathMin(selectionMin, selectionMax), // for reversed axes,
- max: mathMax(selectionMin, selectionMax)
- });
- runZoom = true;
- }
- }
- });
- if (runZoom) {
- fireEvent(chart, 'selection', selectionData, function (args) {
- chart.zoom(extend(args, hasPinched ? { animation: false } : null));
- });
- }
-
- }
- this.selectionMarker = this.selectionMarker.destroy();
-
- // Reset scaling preview
- if (hasPinched) {
- this.scaleGroups();
- }
- }
-
- // Reset all
- if (chart) { // it may be destroyed on mouse up - #877
- css(chart.container, { cursor: chart._cursor });
- chart.cancelClick = this.hasDragged > 10; // #370
- chart.mouseIsDown = this.hasDragged = this.hasPinched = false;
- this.pinchDown = [];
- }
- },
-
- onContainerMouseDown: function (e) {
-
- e = this.normalize(e);
-
- // issue #295, dragging not always working in Firefox
- if (e.preventDefault) {
- e.preventDefault();
- }
-
- this.dragStart(e);
- },
-
-
-
- onDocumentMouseUp: function (e) {
- this.drop(e);
- },
-
- /**
- * Special handler for mouse move that will hide the tooltip when the mouse leaves the plotarea.
- * Issue #149 workaround. The mouseleave event does not always fire.
- */
- onDocumentMouseMove: function (e) {
- var chart = this.chart,
- chartPosition = this.chartPosition,
- hoverSeries = chart.hoverSeries;
-
- // Get e.pageX and e.pageY back in MooTools
- e = washMouseEvent(e);
-
- // If we're outside, hide the tooltip
- if (chartPosition && hoverSeries && hoverSeries.isCartesian &&
- !chart.isInsidePlot(e.pageX - chartPosition.left - chart.plotLeft,
- e.pageY - chartPosition.top - chart.plotTop)) {
- this.reset();
- }
- },
-
- /**
- * When mouse leaves the container, hide the tooltip.
- */
- onContainerMouseLeave: function () {
- this.reset();
- this.chartPosition = null; // also reset the chart position, used in #149 fix
- },
-
- // The mousemove, touchmove and touchstart event handler
- onContainerMouseMove: function (e) {
-
- var chart = this.chart;
-
- // normalize
- e = this.normalize(e);
-
- // #295
- e.returnValue = false;
-
-
- if (chart.mouseIsDown === 'mousedown') {
- this.drag(e);
- }
-
- // Show the tooltip and run mouse over events (#977)
- if (chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop) && !chart.openMenu) {
- this.runPointActions(e);
- }
- },
-
- /**
- * Utility to detect whether an element has, or has a parent with, a specific
- * class name. Used on detection of tracker objects and on deciding whether
- * hovering the tooltip should cause the active series to mouse out.
- */
- inClass: function (element, className) {
- var elemClassName;
- while (element) {
- elemClassName = attr(element, 'class');
- if (elemClassName) {
- if (elemClassName.indexOf(className) !== -1) {
- return true;
- } else if (elemClassName.indexOf(PREFIX + 'container') !== -1) {
- return false;
- }
- }
- element = element.parentNode;
- }
- },
-
- onTrackerMouseOut: function (e) {
- var series = this.chart.hoverSeries;
- if (series && !series.options.stickyTracking && !this.inClass(e.toElement || e.relatedTarget, PREFIX + 'tooltip')) {
- series.onMouseOut();
- }
- },
-
- onContainerClick: function (e) {
- var chart = this.chart,
- hoverPoint = chart.hoverPoint,
- plotLeft = chart.plotLeft,
- plotTop = chart.plotTop,
- inverted = chart.inverted,
- chartPosition,
- plotX,
- plotY;
-
- e = this.normalize(e);
- e.cancelBubble = true; // IE specific
-
- if (!chart.cancelClick) {
-
- // On tracker click, fire the series and point events. #783, #1583
- if (hoverPoint && this.inClass(e.target, PREFIX + 'tracker')) {
- chartPosition = this.chartPosition;
- plotX = hoverPoint.plotX;
- plotY = hoverPoint.plotY;
-
- // add page position info
- extend(hoverPoint, {
- pageX: chartPosition.left + plotLeft +
- (inverted ? chart.plotWidth - plotY : plotX),
- pageY: chartPosition.top + plotTop +
- (inverted ? chart.plotHeight - plotX : plotY)
- });
-
- // the series click event
- fireEvent(hoverPoint.series, 'click', extend(e, {
- point: hoverPoint
- }));
-
- // the point click event
- if (chart.hoverPoint) { // it may be destroyed (#1844)
- hoverPoint.firePointEvent('click', e);
- }
-
- // When clicking outside a tracker, fire a chart event
- } else {
- extend(e, this.getCoordinates(e));
-
- // fire a click event in the chart
- if (chart.isInsidePlot(e.chartX - plotLeft, e.chartY - plotTop)) {
- fireEvent(chart, 'click', e);
- }
- }
-
-
- }
- },
-
- onContainerTouchStart: function (e) {
- var chart = this.chart;
-
- if (e.touches.length === 1) {
-
- e = this.normalize(e);
-
- if (chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) {
-
- // Prevent the click pseudo event from firing unless it is set in the options
- /*if (!chart.runChartClick) {
- e.preventDefault();
- }*/
-
- // Run mouse events and display tooltip etc
- this.runPointActions(e);
-
- this.pinch(e);
-
- } else {
- // Hide the tooltip on touching outside the plot area (#1203)
- this.reset();
- }
-
- } else if (e.touches.length === 2) {
- this.pinch(e);
- }
- },
-
- onContainerTouchMove: function (e) {
- if (e.touches.length === 1 || e.touches.length === 2) {
- this.pinch(e);
- }
- },
-
- onDocumentTouchEnd: function (e) {
- this.drop(e);
- },
-
- /**
- * Set the JS DOM events on the container and document. This method should contain
- * a one-to-one assignment between methods and their handlers. Any advanced logic should
- * be moved to the handler reflecting the event's name.
- */
- setDOMEvents: function () {
-
- var pointer = this,
- container = pointer.chart.container,
- events;
-
- this._events = events = [
- [container, 'onmousedown', 'onContainerMouseDown'],
- [container, 'onmousemove', 'onContainerMouseMove'],
- [container, 'onclick', 'onContainerClick'],
- [container, 'mouseleave', 'onContainerMouseLeave'],
- [doc, 'mousemove', 'onDocumentMouseMove'],
- [doc, 'mouseup', 'onDocumentMouseUp']
- ];
-
- if (hasTouch) {
- events.push(
- [container, 'ontouchstart', 'onContainerTouchStart'],
- [container, 'ontouchmove', 'onContainerTouchMove'],
- [doc, 'touchend', 'onDocumentTouchEnd']
- );
- }
-
- each(events, function (eventConfig) {
-
- // First, create the callback function that in turn calls the method on Pointer
- pointer['_' + eventConfig[2]] = function (e) {
- pointer[eventConfig[2]](e);
- };
-
- // Now attach the function, either as a direct property or through addEvent
- if (eventConfig[1].indexOf('on') === 0) {
- eventConfig[0][eventConfig[1]] = pointer['_' + eventConfig[2]];
- } else {
- addEvent(eventConfig[0], eventConfig[1], pointer['_' + eventConfig[2]]);
- }
- });
-
-
- },
-
- /**
- * Destroys the Pointer object and disconnects DOM events.
- */
- destroy: function () {
- var pointer = this;
-
- // Release all DOM events
- each(pointer._events, function (eventConfig) {
- if (eventConfig[1].indexOf('on') === 0) {
- eventConfig[0][eventConfig[1]] = null; // delete breaks oldIE
- } else {
- removeEvent(eventConfig[0], eventConfig[1], pointer['_' + eventConfig[2]]);
- }
- });
- delete pointer._events;
-
- // memory and CPU leak
- clearInterval(pointer.tooltipTimeout);
- }
-};
-/**
- * The overview of the chart's series
- */
-function Legend(chart, options) {
- this.init(chart, options);
-}
-
-Legend.prototype = {
-
- /**
- * Initialize the legend
- */
- init: function (chart, options) {
-
- var legend = this,
- itemStyle = options.itemStyle,
- padding = pick(options.padding, 8),
- itemMarginTop = options.itemMarginTop || 0;
-
- this.options = options;
-
- if (!options.enabled) {
- return;
- }
-
- legend.baseline = pInt(itemStyle.fontSize) + 3 + itemMarginTop; // used in Series prototype
- legend.itemStyle = itemStyle;
- legend.itemHiddenStyle = merge(itemStyle, options.itemHiddenStyle);
- legend.itemMarginTop = itemMarginTop;
- legend.padding = padding;
- legend.initialItemX = padding;
- legend.initialItemY = padding - 5; // 5 is the number of pixels above the text
- legend.maxItemWidth = 0;
- legend.chart = chart;
- legend.itemHeight = 0;
- legend.lastLineHeight = 0;
-
- // Render it
- legend.render();
-
- // move checkboxes
- addEvent(legend.chart, 'endResize', function () {
- legend.positionCheckboxes();
- });
-
- },
-
- /**
- * Set the colors for the legend item
- * @param {Object} item A Series or Point instance
- * @param {Object} visible Dimmed or colored
- */
- colorizeItem: function (item, visible) {
- var legend = this,
- options = legend.options,
- legendItem = item.legendItem,
- legendLine = item.legendLine,
- legendSymbol = item.legendSymbol,
- hiddenColor = legend.itemHiddenStyle.color,
- textColor = visible ? options.itemStyle.color : hiddenColor,
- symbolColor = visible ? item.color : hiddenColor,
- markerOptions = item.options && item.options.marker,
- symbolAttr = {
- stroke: symbolColor,
- fill: symbolColor
- },
- key,
- val;
-
- if (legendItem) {
- legendItem.css({ fill: textColor, color: textColor }); // color for #1553, oldIE
- }
- if (legendLine) {
- legendLine.attr({ stroke: symbolColor });
- }
-
- if (legendSymbol) {
-
- // Apply marker options
- if (markerOptions && legendSymbol.isMarker) { // #585
- markerOptions = item.convertAttribs(markerOptions);
- for (key in markerOptions) {
- val = markerOptions[key];
- if (val !== UNDEFINED) {
- symbolAttr[key] = val;
- }
- }
- }
-
- legendSymbol.attr(symbolAttr);
- }
- },
-
- /**
- * Position the legend item
- * @param {Object} item A Series or Point instance
- */
- positionItem: function (item) {
- var legend = this,
- options = legend.options,
- symbolPadding = options.symbolPadding,
- ltr = !options.rtl,
- legendItemPos = item._legendItemPos,
- itemX = legendItemPos[0],
- itemY = legendItemPos[1],
- checkbox = item.checkbox;
-
- if (item.legendGroup) {
- item.legendGroup.translate(
- ltr ? itemX : legend.legendWidth - itemX - 2 * symbolPadding - 4,
- itemY
- );
- }
-
- if (checkbox) {
- checkbox.x = itemX;
- checkbox.y = itemY;
- }
- },
-
- /**
- * Destroy a single legend item
- * @param {Object} item The series or point
- */
- destroyItem: function (item) {
- var checkbox = item.checkbox;
-
- // destroy SVG elements
- each(['legendItem', 'legendLine', 'legendSymbol', 'legendGroup'], function (key) {
- if (item[key]) {
- item[key] = item[key].destroy();
- }
- });
-
- if (checkbox) {
- discardElement(item.checkbox);
- }
- },
-
- /**
- * Destroys the legend.
- */
- destroy: function () {
- var legend = this,
- legendGroup = legend.group,
- box = legend.box;
-
- if (box) {
- legend.box = box.destroy();
- }
-
- if (legendGroup) {
- legend.group = legendGroup.destroy();
- }
- },
-
- /**
- * Position the checkboxes after the width is determined
- */
- positionCheckboxes: function (scrollOffset) {
- var alignAttr = this.group.alignAttr,
- translateY,
- clipHeight = this.clipHeight || this.legendHeight;
-
- if (alignAttr) {
- translateY = alignAttr.translateY;
- each(this.allItems, function (item) {
- var checkbox = item.checkbox,
- top;
-
- if (checkbox) {
- top = (translateY + checkbox.y + (scrollOffset || 0) + 3);
- css(checkbox, {
- left: (alignAttr.translateX + item.legendItemWidth + checkbox.x - 20) + PX,
- top: top + PX,
- display: top > translateY - 6 && top < translateY + clipHeight - 6 ? '' : NONE
- });
- }
- });
- }
- },
-
- /**
- * Render the legend title on top of the legend
- */
- renderTitle: function () {
- var options = this.options,
- padding = this.padding,
- titleOptions = options.title,
- titleHeight = 0,
- bBox;
-
- if (titleOptions.text) {
- if (!this.title) {
- this.title = this.chart.renderer.label(titleOptions.text, padding - 3, padding - 4, null, null, null, null, null, 'legend-title')
- .attr({ zIndex: 1 })
- .css(titleOptions.style)
- .add(this.group);
- }
- bBox = this.title.getBBox();
- titleHeight = bBox.height;
- this.offsetWidth = bBox.width; // #1717
- this.contentGroup.attr({ translateY: titleHeight });
- }
- this.titleHeight = titleHeight;
- },
-
- /**
- * Render a single specific legend item
- * @param {Object} item A series or point
- */
- renderItem: function (item) {
- var legend = this,
- chart = legend.chart,
- renderer = chart.renderer,
- options = legend.options,
- horizontal = options.layout === 'horizontal',
- symbolWidth = options.symbolWidth,
- symbolPadding = options.symbolPadding,
- itemStyle = legend.itemStyle,
- itemHiddenStyle = legend.itemHiddenStyle,
- padding = legend.padding,
- itemDistance = horizontal ? pick(options.itemDistance, 8) : 0, // docs
- ltr = !options.rtl,
- itemHeight,
- widthOption = options.width,
- itemMarginBottom = options.itemMarginBottom || 0,
- itemMarginTop = legend.itemMarginTop,
- initialItemX = legend.initialItemX,
- bBox,
- itemWidth,
- li = item.legendItem,
- series = item.series || item,
- itemOptions = series.options,
- showCheckbox = itemOptions.showCheckbox,
- useHTML = options.useHTML;
-
- if (!li) { // generate it once, later move it
-
- // Generate the group box
- // A group to hold the symbol and text. Text is to be appended in Legend class.
- item.legendGroup = renderer.g('legend-item')
- .attr({ zIndex: 1 })
- .add(legend.scrollGroup);
-
- // Draw the legend symbol inside the group box
- series.drawLegendSymbol(legend, item);
-
- // Generate the list item text and add it to the group
- item.legendItem = li = renderer.text(
- options.labelFormat ? format(options.labelFormat, item) : options.labelFormatter.call(item),
- ltr ? symbolWidth + symbolPadding : -symbolPadding,
- legend.baseline,
- useHTML
- )
- .css(merge(item.visible ? itemStyle : itemHiddenStyle)) // merge to prevent modifying original (#1021)
- .attr({
- align: ltr ? 'left' : 'right',
- zIndex: 2
- })
- .add(item.legendGroup);
-
- // Set the events on the item group, or in case of useHTML, the item itself (#1249)
- (useHTML ? li : item.legendGroup).on('mouseover', function () {
- item.setState(HOVER_STATE);
- li.css(legend.options.itemHoverStyle);
- })
- .on('mouseout', function () {
- li.css(item.visible ? itemStyle : itemHiddenStyle);
- item.setState();
- })
- .on('click', function (event) {
- var strLegendItemClick = 'legendItemClick',
- fnLegendItemClick = function () {
- item.setVisible();
- };
-
- // Pass over the click/touch event. #4.
- event = {
- browserEvent: event
- };
-
- // click the name or symbol
- if (item.firePointEvent) { // point
- item.firePointEvent(strLegendItemClick, event, fnLegendItemClick);
- } else {
- fireEvent(item, strLegendItemClick, event, fnLegendItemClick);
- }
- });
-
- // Colorize the items
- legend.colorizeItem(item, item.visible);
-
- // add the HTML checkbox on top
- if (itemOptions && showCheckbox) {
- item.checkbox = createElement('input', {
- type: 'checkbox',
- checked: item.selected,
- defaultChecked: item.selected // required by IE7
- }, options.itemCheckboxStyle, chart.container);
-
- addEvent(item.checkbox, 'click', function (event) {
- var target = event.target;
- fireEvent(item, 'checkboxClick', {
- checked: target.checked
- },
- function () {
- item.select();
- }
- );
- });
- }
- }
-
- // calculate the positions for the next line
- bBox = li.getBBox();
-
- itemWidth = item.legendItemWidth =
- options.itemWidth || symbolWidth + symbolPadding + bBox.width + itemDistance +
- (showCheckbox ? 20 : 0);
- legend.itemHeight = itemHeight = bBox.height;
-
- // if the item exceeds the width, start a new line
- if (horizontal && legend.itemX - initialItemX + itemWidth >
- (widthOption || (chart.chartWidth - 2 * padding - initialItemX))) {
- legend.itemX = initialItemX;
- legend.itemY += itemMarginTop + legend.lastLineHeight + itemMarginBottom;
- legend.lastLineHeight = 0; // reset for next line
- }
-
- // If the item exceeds the height, start a new column
- /*if (!horizontal && legend.itemY + options.y + itemHeight > chart.chartHeight - spacingTop - spacingBottom) {
- legend.itemY = legend.initialItemY;
- legend.itemX += legend.maxItemWidth;
- legend.maxItemWidth = 0;
- }*/
-
- // Set the edge positions
- legend.maxItemWidth = mathMax(legend.maxItemWidth, itemWidth);
- legend.lastItemY = itemMarginTop + legend.itemY + itemMarginBottom;
- legend.lastLineHeight = mathMax(itemHeight, legend.lastLineHeight); // #915
-
- // cache the position of the newly generated or reordered items
- item._legendItemPos = [legend.itemX, legend.itemY];
-
- // advance
- if (horizontal) {
- legend.itemX += itemWidth;
-
- } else {
- legend.itemY += itemMarginTop + itemHeight + itemMarginBottom;
- legend.lastLineHeight = itemHeight;
- }
-
- // the width of the widest item
- legend.offsetWidth = widthOption || mathMax(
- (horizontal ? legend.itemX - initialItemX - itemDistance : itemWidth) + padding,
- legend.offsetWidth
- );
- },
-
- /**
- * Render the legend. This method can be called both before and after
- * chart.render. If called after, it will only rearrange items instead
- * of creating new ones.
- */
- render: function () {
- var legend = this,
- chart = legend.chart,
- renderer = chart.renderer,
- legendGroup = legend.group,
- allItems,
- display,
- legendWidth,
- legendHeight,
- box = legend.box,
- options = legend.options,
- padding = legend.padding,
- legendBorderWidth = options.borderWidth,
- legendBackgroundColor = options.backgroundColor;
-
- legend.itemX = legend.initialItemX;
- legend.itemY = legend.initialItemY;
- legend.offsetWidth = 0;
- legend.lastItemY = 0;
-
- if (!legendGroup) {
- legend.group = legendGroup = renderer.g('legend')
- .attr({ zIndex: 7 })
- .add();
- legend.contentGroup = renderer.g()
- .attr({ zIndex: 1 }) // above background
- .add(legendGroup);
- legend.scrollGroup = renderer.g()
- .add(legend.contentGroup);
- }
-
- legend.renderTitle();
-
- // add each series or point
- allItems = [];
- each(chart.series, function (serie) {
- var seriesOptions = serie.options;
-
- if (!seriesOptions.showInLegend || defined(seriesOptions.linkedTo)) {
- return;
- }
-
- // use points or series for the legend item depending on legendType
- allItems = allItems.concat(
- serie.legendItems ||
- (seriesOptions.legendType === 'point' ?
- serie.data :
- serie)
- );
- });
-
- // sort by legendIndex
- stableSort(allItems, function (a, b) {
- return ((a.options && a.options.legendIndex) || 0) - ((b.options && b.options.legendIndex) || 0);
- });
-
- // reversed legend
- if (options.reversed) {
- allItems.reverse();
- }
-
- legend.allItems = allItems;
- legend.display = display = !!allItems.length;
-
- // render the items
- each(allItems, function (item) {
- legend.renderItem(item);
- });
-
- // Draw the border
- legendWidth = options.width || legend.offsetWidth;
- legendHeight = legend.lastItemY + legend.lastLineHeight + legend.titleHeight;
-
-
- legendHeight = legend.handleOverflow(legendHeight);
-
- if (legendBorderWidth || legendBackgroundColor) {
- legendWidth += padding;
- legendHeight += padding;
-
- if (!box) {
- legend.box = box = renderer.rect(
- 0,
- 0,
- legendWidth,
- legendHeight,
- options.borderRadius,
- legendBorderWidth || 0
- ).attr({
- stroke: options.borderColor,
- 'stroke-width': legendBorderWidth || 0,
- fill: legendBackgroundColor || NONE
- })
- .add(legendGroup)
- .shadow(options.shadow);
- box.isNew = true;
-
- } else if (legendWidth > 0 && legendHeight > 0) {
- box[box.isNew ? 'attr' : 'animate'](
- box.crisp(null, null, null, legendWidth, legendHeight)
- );
- box.isNew = false;
- }
-
- // hide the border if no items
- box[display ? 'show' : 'hide']();
- }
-
- legend.legendWidth = legendWidth;
- legend.legendHeight = legendHeight;
-
- // Now that the legend width and height are established, put the items in the
- // final position
- each(allItems, function (item) {
- legend.positionItem(item);
- });
-
- // 1.x compatibility: positioning based on style
- /*var props = ['left', 'right', 'top', 'bottom'],
- prop,
- i = 4;
- while (i--) {
- prop = props[i];
- if (options.style[prop] && options.style[prop] !== 'auto') {
- options[i < 2 ? 'align' : 'verticalAlign'] = prop;
- options[i < 2 ? 'x' : 'y'] = pInt(options.style[prop]) * (i % 2 ? -1 : 1);
- }
- }*/
-
- if (display) {
- legendGroup.align(extend({
- width: legendWidth,
- height: legendHeight
- }, options), true, 'spacingBox');
- }
-
- if (!chart.isResizing) {
- this.positionCheckboxes();
- }
- },
-
- /**
- * Set up the overflow handling by adding navigation with up and down arrows below the
- * legend.
- */
- handleOverflow: function (legendHeight) {
- var legend = this,
- chart = this.chart,
- renderer = chart.renderer,
- pageCount,
- options = this.options,
- optionsY = options.y,
- alignTop = options.verticalAlign === 'top',
- spaceHeight = chart.spacingBox.height + (alignTop ? -optionsY : optionsY) - this.padding,
- maxHeight = options.maxHeight,
- clipHeight,
- clipRect = this.clipRect,
- navOptions = options.navigation,
- animation = pick(navOptions.animation, true),
- arrowSize = navOptions.arrowSize || 12,
- nav = this.nav;
-
- // Adjust the height
- if (options.layout === 'horizontal') {
- spaceHeight /= 2;
- }
- if (maxHeight) {
- spaceHeight = mathMin(spaceHeight, maxHeight);
- }
-
- // Reset the legend height and adjust the clipping rectangle
- if (legendHeight > spaceHeight && !options.useHTML) {
-
- this.clipHeight = clipHeight = spaceHeight - 20 - this.titleHeight;
- this.pageCount = pageCount = mathCeil(legendHeight / clipHeight);
- this.currentPage = pick(this.currentPage, 1);
- this.fullHeight = legendHeight;
-
- // Only apply clipping if needed. Clipping causes blurred legend in PDF export (#1787)
- if (!clipRect) {
- clipRect = legend.clipRect = renderer.clipRect(0, 0, 9999, 0);
- legend.contentGroup.clip(clipRect);
- }
- clipRect.attr({
- height: clipHeight
- });
-
- // Add navigation elements
- if (!nav) {
- this.nav = nav = renderer.g().attr({ zIndex: 1 }).add(this.group);
- this.up = renderer.symbol('triangle', 0, 0, arrowSize, arrowSize)
- .on('click', function () {
- legend.scroll(-1, animation);
- })
- .add(nav);
- this.pager = renderer.text('', 15, 10)
- .css(navOptions.style)
- .add(nav);
- this.down = renderer.symbol('triangle-down', 0, 0, arrowSize, arrowSize)
- .on('click', function () {
- legend.scroll(1, animation);
- })
- .add(nav);
- }
-
- // Set initial position
- legend.scroll(0);
-
- legendHeight = spaceHeight;
-
- } else if (nav) {
- clipRect.attr({
- height: chart.chartHeight
- });
- nav.hide();
- this.scrollGroup.attr({
- translateY: 1
- });
- this.clipHeight = 0; // #1379
- }
-
- return legendHeight;
- },
-
- /**
- * Scroll the legend by a number of pages
- * @param {Object} scrollBy
- * @param {Object} animation
- */
- scroll: function (scrollBy, animation) {
- var pageCount = this.pageCount,
- currentPage = this.currentPage + scrollBy,
- clipHeight = this.clipHeight,
- navOptions = this.options.navigation,
- activeColor = navOptions.activeColor,
- inactiveColor = navOptions.inactiveColor,
- pager = this.pager,
- padding = this.padding,
- scrollOffset;
-
- // When resizing while looking at the last page
- if (currentPage > pageCount) {
- currentPage = pageCount;
- }
-
- if (currentPage > 0) {
-
- if (animation !== UNDEFINED) {
- setAnimation(animation, this.chart);
- }
-
- this.nav.attr({
- translateX: padding,
- translateY: clipHeight + 7 + this.titleHeight,
- visibility: VISIBLE
- });
- this.up.attr({
- fill: currentPage === 1 ? inactiveColor : activeColor
- })
- .css({
- cursor: currentPage === 1 ? 'default' : 'pointer'
- });
- pager.attr({
- text: currentPage + '/' + this.pageCount
- });
- this.down.attr({
- x: 18 + this.pager.getBBox().width, // adjust to text width
- fill: currentPage === pageCount ? inactiveColor : activeColor
- })
- .css({
- cursor: currentPage === pageCount ? 'default' : 'pointer'
- });
-
- scrollOffset = -mathMin(clipHeight * (currentPage - 1), this.fullHeight - clipHeight + padding) + 1;
- this.scrollGroup.animate({
- translateY: scrollOffset
- });
- pager.attr({
- text: currentPage + '/' + pageCount
- });
-
-
- this.currentPage = currentPage;
- this.positionCheckboxes(scrollOffset);
- }
-
- }
-
-};
-
-/**
- * The chart class
- * @param {Object} options
- * @param {Function} callback Function to run when the chart has loaded
- */
-function Chart() {
- this.init.apply(this, arguments);
-}
-
-Chart.prototype = {
-
- /**
- * Initialize the chart
- */
- init: function (userOptions, callback) {
-
- // Handle regular options
- var options,
- seriesOptions = userOptions.series; // skip merging data points to increase performance
-
- userOptions.series = null;
- options = merge(defaultOptions, userOptions); // do the merge
- options.series = userOptions.series = seriesOptions; // set back the series data
-
- var optionsChart = options.chart,
- optionsMargin = optionsChart.margin,
- margin = isObject(optionsMargin) ?
- optionsMargin :
- [optionsMargin, optionsMargin, optionsMargin, optionsMargin];
-
- this.optionsMarginTop = pick(optionsChart.marginTop, margin[0]);
- this.optionsMarginRight = pick(optionsChart.marginRight, margin[1]);
- this.optionsMarginBottom = pick(optionsChart.marginBottom, margin[2]);
- this.optionsMarginLeft = pick(optionsChart.marginLeft, margin[3]);
-
- var chartEvents = optionsChart.events;
-
- //this.runChartClick = chartEvents && !!chartEvents.click;
- this.bounds = { h: {}, v: {} }; // Pixel data bounds for touch zoom
-
- this.callback = callback;
- this.isResizing = 0;
- this.options = options;
- //chartTitleOptions = UNDEFINED;
- //chartSubtitleOptions = UNDEFINED;
-
- this.axes = [];
- this.series = [];
- this.hasCartesianSeries = optionsChart.showAxes;
- //this.axisOffset = UNDEFINED;
- //this.maxTicks = UNDEFINED; // handle the greatest amount of ticks on grouped axes
- //this.inverted = UNDEFINED;
- //this.loadingShown = UNDEFINED;
- //this.container = UNDEFINED;
- //this.chartWidth = UNDEFINED;
- //this.chartHeight = UNDEFINED;
- //this.marginRight = UNDEFINED;
- //this.marginBottom = UNDEFINED;
- //this.containerWidth = UNDEFINED;
- //this.containerHeight = UNDEFINED;
- //this.oldChartWidth = UNDEFINED;
- //this.oldChartHeight = UNDEFINED;
-
- //this.renderTo = UNDEFINED;
- //this.renderToClone = UNDEFINED;
-
- //this.spacingBox = UNDEFINED
-
- //this.legend = UNDEFINED;
-
- // Elements
- //this.chartBackground = UNDEFINED;
- //this.plotBackground = UNDEFINED;
- //this.plotBGImage = UNDEFINED;
- //this.plotBorder = UNDEFINED;
- //this.loadingDiv = UNDEFINED;
- //this.loadingSpan = UNDEFINED;
-
- var chart = this,
- eventType;
-
- // Add the chart to the global lookup
- chart.index = charts.length;
- charts.push(chart);
-
- // Set up auto resize
- if (optionsChart.reflow !== false) {
- addEvent(chart, 'load', function () {
- chart.initReflow();
- });
- }
-
- // Chart event handlers
- if (chartEvents) {
- for (eventType in chartEvents) {
- addEvent(chart, eventType, chartEvents[eventType]);
- }
- }
-
- chart.xAxis = [];
- chart.yAxis = [];
-
- // Expose methods and variables
- chart.animation = useCanVG ? false : pick(optionsChart.animation, true);
- chart.pointCount = 0;
- chart.counters = new ChartCounters();
-
- chart.firstRender();
- },
-
- /**
- * Initialize an individual series, called internally before render time
- */
- initSeries: function (options) {
- var chart = this,
- optionsChart = chart.options.chart,
- type = options.type || optionsChart.type || optionsChart.defaultSeriesType,
- series,
- constr = seriesTypes[type];
-
- // No such series type
- if (!constr) {
- error(17, true);
- }
-
- series = new constr();
- series.init(this, options);
- return series;
- },
-
- /**
- * Add a series dynamically after time
- *
- * @param {Object} options The config options
- * @param {Boolean} redraw Whether to redraw the chart after adding. Defaults to true.
- * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
- * configuration
- *
- * @return {Object} series The newly created series object
- */
- addSeries: function (options, redraw, animation) {
- var series,
- chart = this;
-
- if (options) {
- redraw = pick(redraw, true); // defaults to true
-
- fireEvent(chart, 'addSeries', { options: options }, function () {
- series = chart.initSeries(options);
-
- chart.isDirtyLegend = true; // the series array is out of sync with the display
- if (redraw) {
- chart.redraw(animation);
- }
- });
- }
-
- return series;
- },
-
- /**
- * Add an axis to the chart
- * @param {Object} options The axis option
- * @param {Boolean} isX Whether it is an X axis or a value axis
- */
- addAxis: function (options, isX, redraw, animation) {
- var key = isX ? 'xAxis' : 'yAxis',
- chartOptions = this.options,
- axis;
-
- /*jslint unused: false*/
- axis = new Axis(this, merge(options, {
- index: this[key].length,
- isX: isX
- }));
- /*jslint unused: true*/
-
- // Push the new axis options to the chart options
- chartOptions[key] = splat(chartOptions[key] || {});
- chartOptions[key].push(options);
-
- if (pick(redraw, true)) {
- this.redraw(animation);
- }
- },
-
- /**
- * Check whether a given point is within the plot area
- *
- * @param {Number} plotX Pixel x relative to the plot area
- * @param {Number} plotY Pixel y relative to the plot area
- * @param {Boolean} inverted Whether the chart is inverted
- */
- isInsidePlot: function (plotX, plotY, inverted) {
- var x = inverted ? plotY : plotX,
- y = inverted ? plotX : plotY;
-
- return x >= 0 &&
- x <= this.plotWidth &&
- y >= 0 &&
- y <= this.plotHeight;
- },
-
- /**
- * Adjust all axes tick amounts
- */
- adjustTickAmounts: function () {
- if (this.options.chart.alignTicks !== false) {
- each(this.axes, function (axis) {
- axis.adjustTickAmount();
- });
- }
- this.maxTicks = null;
- },
-
- /**
- * Redraw legend, axes or series based on updated data
- *
- * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
- * configuration
- */
- redraw: function (animation) {
- var chart = this,
- axes = chart.axes,
- series = chart.series,
- pointer = chart.pointer,
- legend = chart.legend,
- redrawLegend = chart.isDirtyLegend,
- hasStackedSeries,
- hasDirtyStacks,
- isDirtyBox = chart.isDirtyBox, // todo: check if it has actually changed?
- seriesLength = series.length,
- i = seriesLength,
- serie,
- renderer = chart.renderer,
- isHiddenChart = renderer.isHidden(),
- afterRedraw = [];
-
- setAnimation(animation, chart);
-
- if (isHiddenChart) {
- chart.cloneRenderTo();
- }
-
- // Adjust title layout (reflow multiline text)
- chart.layOutTitles();
-
- // link stacked series
- while (i--) {
- serie = series[i];
-
- if (serie.options.stacking) {
- hasStackedSeries = true;
-
- if (serie.isDirty) {
- hasDirtyStacks = true;
- break;
- }
- }
- }
- if (hasDirtyStacks) { // mark others as dirty
- i = seriesLength;
- while (i--) {
- serie = series[i];
- if (serie.options.stacking) {
- serie.isDirty = true;
- }
- }
- }
-
- // handle updated data in the series
- each(series, function (serie) {
- if (serie.isDirty) { // prepare the data so axis can read it
- if (serie.options.legendType === 'point') {
- redrawLegend = true;
- }
- }
- });
-
- // handle added or removed series
- if (redrawLegend && legend.options.enabled) { // series or pie points are added or removed
- // draw legend graphics
- legend.render();
-
- chart.isDirtyLegend = false;
- }
-
- // reset stacks
- if (hasStackedSeries) {
- chart.getStacks();
- }
-
-
- if (chart.hasCartesianSeries) {
- if (!chart.isResizing) {
-
- // reset maxTicks
- chart.maxTicks = null;
-
- // set axes scales
- each(axes, function (axis) {
- axis.setScale();
- });
- } else {
- // build stacks
- each(axes, function (axis) {
- axis.buildStacks();
- });
- }
- chart.adjustTickAmounts();
- chart.getMargins();
-
- // redraw axes
- each(axes, function (axis) {
-
- // Fire 'afterSetExtremes' only if extremes are set
- if (axis.isDirtyExtremes) { // #821
- axis.isDirtyExtremes = false;
- afterRedraw.push(function () { // prevent a recursive call to chart.redraw() (#1119)
- fireEvent(axis, 'afterSetExtremes', axis.getExtremes()); // #747, #751
- });
- }
-
- if (axis.isDirty || isDirtyBox || hasStackedSeries) {
- axis.redraw();
- isDirtyBox = true; // #792
- }
- });
-
-
- }
- // the plot areas size has changed
- if (isDirtyBox) {
- chart.drawChartBox();
- }
-
-
- // redraw affected series
- each(series, function (serie) {
- if (serie.isDirty && serie.visible &&
- (!serie.isCartesian || serie.xAxis)) { // issue #153
- serie.redraw();
- }
- });
-
- // move tooltip or reset
- if (pointer && pointer.reset) {
- pointer.reset(true);
- }
-
- // redraw if canvas
- renderer.draw();
-
- // fire the event
- fireEvent(chart, 'redraw'); // jQuery breaks this when calling it from addEvent. Overwrites chart.redraw
-
- if (isHiddenChart) {
- chart.cloneRenderTo(true);
- }
-
- // Fire callbacks that are put on hold until after the redraw
- each(afterRedraw, function (callback) {
- callback.call();
- });
- },
-
-
-
- /**
- * Dim the chart and show a loading text or symbol
- * @param {String} str An optional text to show in the loading label instead of the default one
- */
- showLoading: function (str) {
- var chart = this,
- options = chart.options,
- loadingDiv = chart.loadingDiv;
-
- var loadingOptions = options.loading;
-
- // create the layer at the first call
- if (!loadingDiv) {
- chart.loadingDiv = loadingDiv = createElement(DIV, {
- className: PREFIX + 'loading'
- }, extend(loadingOptions.style, {
- zIndex: 10,
- display: NONE
- }), chart.container);
-
- chart.loadingSpan = createElement(
- 'span',
- null,
- loadingOptions.labelStyle,
- loadingDiv
- );
-
- }
-
- // update text
- chart.loadingSpan.innerHTML = str || options.lang.loading;
-
- // show it
- if (!chart.loadingShown) {
- css(loadingDiv, {
- opacity: 0,
- display: '',
- left: chart.plotLeft + PX,
- top: chart.plotTop + PX,
- width: chart.plotWidth + PX,
- height: chart.plotHeight + PX
- });
- animate(loadingDiv, {
- opacity: loadingOptions.style.opacity
- }, {
- duration: loadingOptions.showDuration || 0
- });
- chart.loadingShown = true;
- }
- },
-
- /**
- * Hide the loading layer
- */
- hideLoading: function () {
- var options = this.options,
- loadingDiv = this.loadingDiv;
-
- if (loadingDiv) {
- animate(loadingDiv, {
- opacity: 0
- }, {
- duration: options.loading.hideDuration || 100,
- complete: function () {
- css(loadingDiv, { display: NONE });
- }
- });
- }
- this.loadingShown = false;
- },
-
- /**
- * Get an axis, series or point object by id.
- * @param id {String} The id as given in the configuration options
- */
- get: function (id) {
- var chart = this,
- axes = chart.axes,
- series = chart.series;
-
- var i,
- j,
- points;
-
- // search axes
- for (i = 0; i < axes.length; i++) {
- if (axes[i].options.id === id) {
- return axes[i];
- }
- }
-
- // search series
- for (i = 0; i < series.length; i++) {
- if (series[i].options.id === id) {
- return series[i];
- }
- }
-
- // search points
- for (i = 0; i < series.length; i++) {
- points = series[i].points || [];
- for (j = 0; j < points.length; j++) {
- if (points[j].id === id) {
- return points[j];
- }
- }
- }
- return null;
- },
-
- /**
- * Create the Axis instances based on the config options
- */
- getAxes: function () {
- var chart = this,
- options = this.options,
- xAxisOptions = options.xAxis = splat(options.xAxis || {}),
- yAxisOptions = options.yAxis = splat(options.yAxis || {}),
- optionsArray,
- axis;
-
- // make sure the options are arrays and add some members
- each(xAxisOptions, function (axis, i) {
- axis.index = i;
- axis.isX = true;
- });
-
- each(yAxisOptions, function (axis, i) {
- axis.index = i;
- });
-
- // concatenate all axis options into one array
- optionsArray = xAxisOptions.concat(yAxisOptions);
-
- each(optionsArray, function (axisOptions) {
- axis = new Axis(chart, axisOptions);
- });
-
- chart.adjustTickAmounts();
- },
-
-
- /**
- * Get the currently selected points from all series
- */
- getSelectedPoints: function () {
- var points = [];
- each(this.series, function (serie) {
- points = points.concat(grep(serie.points || [], function (point) {
- return point.selected;
- }));
- });
- return points;
- },
-
- /**
- * Get the currently selected series
- */
- getSelectedSeries: function () {
- return grep(this.series, function (serie) {
- return serie.selected;
- });
- },
-
- /**
- * Generate stacks for each series and calculate stacks total values
- */
- getStacks: function () {
- var chart = this;
-
- // reset stacks for each yAxis
- each(chart.yAxis, function (axis) {
- if (axis.stacks && axis.hasVisibleSeries) {
- axis.oldStacks = axis.stacks;
- }
- });
-
- each(chart.series, function (series) {
- if (series.options.stacking && (series.visible === true || chart.options.chart.ignoreHiddenSeries === false)) {
- series.stackKey = series.type + pick(series.options.stack, '');
- }
- });
- },
-
- /**
- * Display the zoom button
- */
- showResetZoom: function () {
- var chart = this,
- lang = defaultOptions.lang,
- btnOptions = chart.options.chart.resetZoomButton,
- theme = btnOptions.theme,
- states = theme.states,
- alignTo = btnOptions.relativeTo === 'chart' ? null : 'plotBox';
-
- this.resetZoomButton = chart.renderer.button(lang.resetZoom, null, null, function () { chart.zoomOut(); }, theme, states && states.hover)
- .attr({
- align: btnOptions.position.align,
- title: lang.resetZoomTitle
- })
- .add()
- .align(btnOptions.position, false, alignTo);
-
- },
-
- /**
- * Zoom out to 1:1
- */
- zoomOut: function () {
- var chart = this;
- fireEvent(chart, 'selection', { resetSelection: true }, function () {
- chart.zoom();
- });
- },
-
- /**
- * Zoom into a given portion of the chart given by axis coordinates
- * @param {Object} event
- */
- zoom: function (event) {
- var chart = this,
- hasZoomed,
- pointer = chart.pointer,
- displayButton = false,
- resetZoomButton;
-
- // If zoom is called with no arguments, reset the axes
- if (!event || event.resetSelection) {
- each(chart.axes, function (axis) {
- hasZoomed = axis.zoom();
- });
- } else { // else, zoom in on all axes
- each(event.xAxis.concat(event.yAxis), function (axisData) {
- var axis = axisData.axis,
- isXAxis = axis.isXAxis;
-
- // don't zoom more than minRange
- if (pointer[isXAxis ? 'zoomX' : 'zoomY'] || pointer[isXAxis ? 'pinchX' : 'pinchY']) {
- hasZoomed = axis.zoom(axisData.min, axisData.max);
- if (axis.displayBtn) {
- displayButton = true;
- }
- }
- });
- }
-
- // Show or hide the Reset zoom button
- resetZoomButton = chart.resetZoomButton;
- if (displayButton && !resetZoomButton) {
- chart.showResetZoom();
- } else if (!displayButton && isObject(resetZoomButton)) {
- chart.resetZoomButton = resetZoomButton.destroy();
- }
-
-
- // Redraw
- if (hasZoomed) {
- chart.redraw(
- pick(chart.options.chart.animation, event && event.animation, chart.pointCount < 100) // animation
- );
- }
- },
-
- /**
- * Pan the chart by dragging the mouse across the pane. This function is called
- * on mouse move, and the distance to pan is computed from chartX compared to
- * the first chartX position in the dragging operation.
- */
- pan: function (chartX) {
- var chart = this,
- xAxis = chart.xAxis[0],
- mouseDownX = chart.mouseDownX,
- halfPointRange = xAxis.pointRange / 2,
- extremes = xAxis.getExtremes(),
- newMin = xAxis.translate(mouseDownX - chartX, true) + halfPointRange,
- newMax = xAxis.translate(mouseDownX + chart.plotWidth - chartX, true) - halfPointRange,
- hoverPoints = chart.hoverPoints;
-
- // remove active points for shared tooltip
- if (hoverPoints) {
- each(hoverPoints, function (point) {
- point.setState();
- });
- }
-
- if (xAxis.series.length && newMin > mathMin(extremes.dataMin, extremes.min) && newMax < mathMax(extremes.dataMax, extremes.max)) {
- xAxis.setExtremes(newMin, newMax, true, false, { trigger: 'pan' });
- }
-
- chart.mouseDownX = chartX; // set new reference for next run
- css(chart.container, { cursor: 'move' });
- },
-
- /**
- * Show the title and subtitle of the chart
- *
- * @param titleOptions {Object} New title options
- * @param subtitleOptions {Object} New subtitle options
- *
- */
- setTitle: function (titleOptions, subtitleOptions) {
- var chart = this,
- options = chart.options,
- chartTitleOptions,
- chartSubtitleOptions;
-
- chartTitleOptions = options.title = merge(options.title, titleOptions);
- chartSubtitleOptions = options.subtitle = merge(options.subtitle, subtitleOptions);
-
- // add title and subtitle
- each([
- ['title', titleOptions, chartTitleOptions],
- ['subtitle', subtitleOptions, chartSubtitleOptions]
- ], function (arr) {
- var name = arr[0],
- title = chart[name],
- titleOptions = arr[1],
- chartTitleOptions = arr[2];
-
- if (title && titleOptions) {
- chart[name] = title = title.destroy(); // remove old
- }
-
- if (chartTitleOptions && chartTitleOptions.text && !title) {
- chart[name] = chart.renderer.text(
- chartTitleOptions.text,
- 0,
- 0,
- chartTitleOptions.useHTML
- )
- .attr({
- align: chartTitleOptions.align,
- 'class': PREFIX + name,
- zIndex: chartTitleOptions.zIndex || 4
- })
- .css(chartTitleOptions.style)
- .add();
- }
- });
- chart.layOutTitles();
- },
-
- /**
- * Lay out the chart titles and cache the full offset height for use in getMargins
- */
- layOutTitles: function () {
- var titleOffset = 0,
- title = this.title,
- subtitle = this.subtitle,
- options = this.options,
- titleOptions = options.title,
- subtitleOptions = options.subtitle,
- autoWidth = this.spacingBox.width - 44; // 44 makes room for default context button
-
- if (title) {
- title
- .css({ width: (titleOptions.width || autoWidth) + PX })
- .align(extend({ y: 15 }, titleOptions), false, 'spacingBox');
-
- if (!titleOptions.floating && !titleOptions.verticalAlign) {
- titleOffset = title.getBBox().height;
-
- // Adjust for browser consistency + backwards compat after #776 fix
- if (titleOffset >= 18 && titleOffset <= 25) {
- titleOffset = 15;
- }
- }
- }
- if (subtitle) {
- subtitle
- .css({ width: (subtitleOptions.width || autoWidth) + PX })
- .align(extend({ y: titleOffset + titleOptions.margin }, subtitleOptions), false, 'spacingBox');
-
- if (!subtitleOptions.floating && !subtitleOptions.verticalAlign) {
- titleOffset = mathCeil(titleOffset + subtitle.getBBox().height);
- }
- }
-
- this.titleOffset = titleOffset; // used in getMargins
- },
-
- /**
- * Get chart width and height according to options and container size
- */
- getChartSize: function () {
- var chart = this,
- optionsChart = chart.options.chart,
- renderTo = chart.renderToClone || chart.renderTo;
-
- // get inner width and height from jQuery (#824)
- chart.containerWidth = adapterRun(renderTo, 'width');
- chart.containerHeight = adapterRun(renderTo, 'height');
-
- chart.chartWidth = mathMax(0, optionsChart.width || chart.containerWidth || 600); // #1393, 1460
- chart.chartHeight = mathMax(0, pick(optionsChart.height,
- // the offsetHeight of an empty container is 0 in standard browsers, but 19 in IE7:
- chart.containerHeight > 19 ? chart.containerHeight : 400));
- },
-
- /**
- * Create a clone of the chart's renderTo div and place it outside the viewport to allow
- * size computation on chart.render and chart.redraw
- */
- cloneRenderTo: function (revert) {
- var clone = this.renderToClone,
- container = this.container;
-
- // Destroy the clone and bring the container back to the real renderTo div
- if (revert) {
- if (clone) {
- this.renderTo.appendChild(container);
- discardElement(clone);
- delete this.renderToClone;
- }
-
- // Set up the clone
- } else {
- if (container && container.parentNode === this.renderTo) {
- this.renderTo.removeChild(container); // do not clone this
- }
- this.renderToClone = clone = this.renderTo.cloneNode(0);
- css(clone, {
- position: ABSOLUTE,
- top: '-9999px',
- display: 'block' // #833
- });
- doc.body.appendChild(clone);
- if (container) {
- clone.appendChild(container);
- }
- }
- },
-
- /**
- * Get the containing element, determine the size and create the inner container
- * div to hold the chart
- */
- getContainer: function () {
- var chart = this,
- container,
- optionsChart = chart.options.chart,
- chartWidth,
- chartHeight,
- renderTo,
- indexAttrName = 'data-highcharts-chart',
- oldChartIndex,
- containerId;
-
- chart.renderTo = renderTo = optionsChart.renderTo;
- containerId = PREFIX + idCounter++;
-
- if (isString(renderTo)) {
- chart.renderTo = renderTo = doc.getElementById(renderTo);
- }
-
- // Display an error if the renderTo is wrong
- if (!renderTo) {
- error(13, true);
- }
-
- // If the container already holds a chart, destroy it
- oldChartIndex = pInt(attr(renderTo, indexAttrName));
- if (!isNaN(oldChartIndex) && charts[oldChartIndex]) {
- charts[oldChartIndex].destroy();
- }
-
- // Make a reference to the chart from the div
- attr(renderTo, indexAttrName, chart.index);
-
- // remove previous chart
- renderTo.innerHTML = '';
-
- // If the container doesn't have an offsetWidth, it has or is a child of a node
- // that has display:none. We need to temporarily move it out to a visible
- // state to determine the size, else the legend and tooltips won't render
- // properly
- if (!renderTo.offsetWidth) {
- chart.cloneRenderTo();
- }
-
- // get the width and height
- chart.getChartSize();
- chartWidth = chart.chartWidth;
- chartHeight = chart.chartHeight;
-
- // create the inner container
- chart.container = container = createElement(DIV, {
- className: PREFIX + 'container' +
- (optionsChart.className ? ' ' + optionsChart.className : ''),
- id: containerId
- }, extend({
- position: RELATIVE,
- overflow: HIDDEN, // needed for context menu (avoid scrollbars) and
- // content overflow in IE
- width: chartWidth + PX,
- height: chartHeight + PX,
- textAlign: 'left',
- lineHeight: 'normal', // #427
- zIndex: 0, // #1072
- '-webkit-tap-highlight-color': 'rgba(0,0,0,0)'
- }, optionsChart.style),
- chart.renderToClone || renderTo
- );
-
- // cache the cursor (#1650)
- chart._cursor = container.style.cursor;
-
- chart.renderer =
- optionsChart.forExport ? // force SVG, used for SVG export
- new SVGRenderer(container, chartWidth, chartHeight, true) :
- new Renderer(container, chartWidth, chartHeight);
-
- if (useCanVG) {
- // If we need canvg library, extend and configure the renderer
- // to get the tracker for translating mouse events
- chart.renderer.create(chart, container, chartWidth, chartHeight);
- }
- },
-
- /**
- * Calculate margins by rendering axis labels in a preliminary position. Title,
- * subtitle and legend have already been rendered at this stage, but will be
- * moved into their final positions
- */
- getMargins: function () {
- var chart = this,
- optionsChart = chart.options.chart,
- spacingTop = optionsChart.spacingTop,
- spacingRight = optionsChart.spacingRight,
- spacingBottom = optionsChart.spacingBottom,
- spacingLeft = optionsChart.spacingLeft,
- axisOffset,
- legend = chart.legend,
- optionsMarginTop = chart.optionsMarginTop,
- optionsMarginLeft = chart.optionsMarginLeft,
- optionsMarginRight = chart.optionsMarginRight,
- optionsMarginBottom = chart.optionsMarginBottom,
- legendOptions = chart.options.legend,
- legendMargin = pick(legendOptions.margin, 10),
- legendX = legendOptions.x,
- legendY = legendOptions.y,
- align = legendOptions.align,
- verticalAlign = legendOptions.verticalAlign,
- titleOffset = chart.titleOffset;
-
- chart.resetMargins();
- axisOffset = chart.axisOffset;
-
- // Adjust for title and subtitle
- if (titleOffset && !defined(optionsMarginTop)) {
- chart.plotTop = mathMax(chart.plotTop, titleOffset + chart.options.title.margin + spacingTop);
- }
-
- // Adjust for legend
- if (legend.display && !legendOptions.floating) {
- if (align === 'right') { // horizontal alignment handled first
- if (!defined(optionsMarginRight)) {
- chart.marginRight = mathMax(
- chart.marginRight,
- legend.legendWidth - legendX + legendMargin + spacingRight
- );
- }
- } else if (align === 'left') {
- if (!defined(optionsMarginLeft)) {
- chart.plotLeft = mathMax(
- chart.plotLeft,
- legend.legendWidth + legendX + legendMargin + spacingLeft
- );
- }
-
- } else if (verticalAlign === 'top') {
- if (!defined(optionsMarginTop)) {
- chart.plotTop = mathMax(
- chart.plotTop,
- legend.legendHeight + legendY + legendMargin + spacingTop
- );
- }
-
- } else if (verticalAlign === 'bottom') {
- if (!defined(optionsMarginBottom)) {
- chart.marginBottom = mathMax(
- chart.marginBottom,
- legend.legendHeight - legendY + legendMargin + spacingBottom
- );
- }
- }
- }
-
- // adjust for scroller
- if (chart.extraBottomMargin) {
- chart.marginBottom += chart.extraBottomMargin;
- }
- if (chart.extraTopMargin) {
- chart.plotTop += chart.extraTopMargin;
- }
-
- // pre-render axes to get labels offset width
- if (chart.hasCartesianSeries) {
- each(chart.axes, function (axis) {
- axis.getOffset();
- });
- }
-
- if (!defined(optionsMarginLeft)) {
- chart.plotLeft += axisOffset[3];
- }
- if (!defined(optionsMarginTop)) {
- chart.plotTop += axisOffset[0];
- }
- if (!defined(optionsMarginBottom)) {
- chart.marginBottom += axisOffset[2];
- }
- if (!defined(optionsMarginRight)) {
- chart.marginRight += axisOffset[1];
- }
-
- chart.setChartSize();
-
- },
-
- /**
- * Add the event handlers necessary for auto resizing
- *
- */
- initReflow: function () {
- var chart = this,
- optionsChart = chart.options.chart,
- renderTo = chart.renderTo,
- reflowTimeout;
-
- function reflow(e) {
- var width = optionsChart.width || adapterRun(renderTo, 'width'),
- height = optionsChart.height || adapterRun(renderTo, 'height'),
- target = e ? e.target : win; // #805 - MooTools doesn't supply e
-
- // Width and height checks for display:none. Target is doc in IE8 and Opera,
- // win in Firefox, Chrome and IE9.
- if (!chart.hasUserSize && width && height && (target === win || target === doc)) {
-
- if (width !== chart.containerWidth || height !== chart.containerHeight) {
- clearTimeout(reflowTimeout);
- chart.reflowTimeout = reflowTimeout = setTimeout(function () {
- if (chart.container) { // It may have been destroyed in the meantime (#1257)
- chart.setSize(width, height, false);
- chart.hasUserSize = null;
- }
- }, 100);
- }
- chart.containerWidth = width;
- chart.containerHeight = height;
- }
- }
- addEvent(win, 'resize', reflow);
- addEvent(chart, 'destroy', function () {
- removeEvent(win, 'resize', reflow);
- });
- },
-
- /**
- * Resize the chart to a given width and height
- * @param {Number} width
- * @param {Number} height
- * @param {Object|Boolean} animation
- */
- setSize: function (width, height, animation) {
- var chart = this,
- chartWidth,
- chartHeight,
- fireEndResize;
-
- // Handle the isResizing counter
- chart.isResizing += 1;
- fireEndResize = function () {
- if (chart) {
- fireEvent(chart, 'endResize', null, function () {
- chart.isResizing -= 1;
- });
- }
- };
-
- // set the animation for the current process
- setAnimation(animation, chart);
-
- chart.oldChartHeight = chart.chartHeight;
- chart.oldChartWidth = chart.chartWidth;
- if (defined(width)) {
- chart.chartWidth = chartWidth = mathMax(0, mathRound(width));
- chart.hasUserSize = !!chartWidth;
- }
- if (defined(height)) {
- chart.chartHeight = chartHeight = mathMax(0, mathRound(height));
- }
-
- css(chart.container, {
- width: chartWidth + PX,
- height: chartHeight + PX
- });
- chart.setChartSize(true);
- chart.renderer.setSize(chartWidth, chartHeight, animation);
-
- // handle axes
- chart.maxTicks = null;
- each(chart.axes, function (axis) {
- axis.isDirty = true;
- axis.setScale();
- });
-
- // make sure non-cartesian series are also handled
- each(chart.series, function (serie) {
- serie.isDirty = true;
- });
-
- chart.isDirtyLegend = true; // force legend redraw
- chart.isDirtyBox = true; // force redraw of plot and chart border
-
- chart.getMargins();
-
- chart.redraw(animation);
-
-
- chart.oldChartHeight = null;
- fireEvent(chart, 'resize');
-
- // fire endResize and set isResizing back
- // If animation is disabled, fire without delay
- if (globalAnimation === false) {
- fireEndResize();
- } else { // else set a timeout with the animation duration
- setTimeout(fireEndResize, (globalAnimation && globalAnimation.duration) || 500);
- }
- },
-
- /**
- * Set the public chart properties. This is done before and after the pre-render
- * to determine margin sizes
- */
- setChartSize: function (skipAxes) {
- var chart = this,
- inverted = chart.inverted,
- renderer = chart.renderer,
- chartWidth = chart.chartWidth,
- chartHeight = chart.chartHeight,
- optionsChart = chart.options.chart,
- spacingTop = optionsChart.spacingTop,
- spacingRight = optionsChart.spacingRight,
- spacingBottom = optionsChart.spacingBottom,
- spacingLeft = optionsChart.spacingLeft,
- clipOffset = chart.clipOffset,
- clipX,
- clipY,
- plotLeft,
- plotTop,
- plotWidth,
- plotHeight,
- plotBorderWidth;
-
- chart.plotLeft = plotLeft = mathRound(chart.plotLeft);
- chart.plotTop = plotTop = mathRound(chart.plotTop);
- chart.plotWidth = plotWidth = mathMax(0, mathRound(chartWidth - plotLeft - chart.marginRight));
- chart.plotHeight = plotHeight = mathMax(0, mathRound(chartHeight - plotTop - chart.marginBottom));
-
- chart.plotSizeX = inverted ? plotHeight : plotWidth;
- chart.plotSizeY = inverted ? plotWidth : plotHeight;
-
- chart.plotBorderWidth = plotBorderWidth = optionsChart.plotBorderWidth || 0;
-
- // Set boxes used for alignment
- chart.spacingBox = renderer.spacingBox = {
- x: spacingLeft,
- y: spacingTop,
- width: chartWidth - spacingLeft - spacingRight,
- height: chartHeight - spacingTop - spacingBottom
- };
- chart.plotBox = renderer.plotBox = {
- x: plotLeft,
- y: plotTop,
- width: plotWidth,
- height: plotHeight
- };
- clipX = mathCeil(mathMax(plotBorderWidth, clipOffset[3]) / 2);
- clipY = mathCeil(mathMax(plotBorderWidth, clipOffset[0]) / 2);
- chart.clipBox = {
- x: clipX,
- y: clipY,
- width: mathFloor(chart.plotSizeX - mathMax(plotBorderWidth, clipOffset[1]) / 2 - clipX),
- height: mathFloor(chart.plotSizeY - mathMax(plotBorderWidth, clipOffset[2]) / 2 - clipY)
- };
-
- if (!skipAxes) {
- each(chart.axes, function (axis) {
- axis.setAxisSize();
- axis.setAxisTranslation();
- });
- }
- },
-
- /**
- * Initial margins before auto size margins are applied
- */
- resetMargins: function () {
- var chart = this,
- optionsChart = chart.options.chart,
- spacingTop = optionsChart.spacingTop,
- spacingRight = optionsChart.spacingRight,
- spacingBottom = optionsChart.spacingBottom,
- spacingLeft = optionsChart.spacingLeft;
-
- chart.plotTop = pick(chart.optionsMarginTop, spacingTop);
- chart.marginRight = pick(chart.optionsMarginRight, spacingRight);
- chart.marginBottom = pick(chart.optionsMarginBottom, spacingBottom);
- chart.plotLeft = pick(chart.optionsMarginLeft, spacingLeft);
- chart.axisOffset = [0, 0, 0, 0]; // top, right, bottom, left
- chart.clipOffset = [0, 0, 0, 0];
- },
-
- /**
- * Draw the borders and backgrounds for chart and plot area
- */
- drawChartBox: function () {
- var chart = this,
- optionsChart = chart.options.chart,
- renderer = chart.renderer,
- chartWidth = chart.chartWidth,
- chartHeight = chart.chartHeight,
- chartBackground = chart.chartBackground,
- plotBackground = chart.plotBackground,
- plotBorder = chart.plotBorder,
- plotBGImage = chart.plotBGImage,
- chartBorderWidth = optionsChart.borderWidth || 0,
- chartBackgroundColor = optionsChart.backgroundColor,
- plotBackgroundColor = optionsChart.plotBackgroundColor,
- plotBackgroundImage = optionsChart.plotBackgroundImage,
- plotBorderWidth = optionsChart.plotBorderWidth || 0,
- mgn,
- bgAttr,
- plotLeft = chart.plotLeft,
- plotTop = chart.plotTop,
- plotWidth = chart.plotWidth,
- plotHeight = chart.plotHeight,
- plotBox = chart.plotBox,
- clipRect = chart.clipRect,
- clipBox = chart.clipBox;
-
- // Chart area
- mgn = chartBorderWidth + (optionsChart.shadow ? 8 : 0);
-
- if (chartBorderWidth || chartBackgroundColor) {
- if (!chartBackground) {
-
- bgAttr = {
- fill: chartBackgroundColor || NONE
- };
- if (chartBorderWidth) { // #980
- bgAttr.stroke = optionsChart.borderColor;
- bgAttr['stroke-width'] = chartBorderWidth;
- }
- chart.chartBackground = renderer.rect(mgn / 2, mgn / 2, chartWidth - mgn, chartHeight - mgn,
- optionsChart.borderRadius, chartBorderWidth)
- .attr(bgAttr)
- .add()
- .shadow(optionsChart.shadow);
-
- } else { // resize
- chartBackground.animate(
- chartBackground.crisp(null, null, null, chartWidth - mgn, chartHeight - mgn)
- );
- }
- }
-
-
- // Plot background
- if (plotBackgroundColor) {
- if (!plotBackground) {
- chart.plotBackground = renderer.rect(plotLeft, plotTop, plotWidth, plotHeight, 0)
- .attr({
- fill: plotBackgroundColor
- })
- .add()
- .shadow(optionsChart.plotShadow);
- } else {
- plotBackground.animate(plotBox);
- }
- }
- if (plotBackgroundImage) {
- if (!plotBGImage) {
- chart.plotBGImage = renderer.image(plotBackgroundImage, plotLeft, plotTop, plotWidth, plotHeight)
- .add();
- } else {
- plotBGImage.animate(plotBox);
- }
- }
-
- // Plot clip
- if (!clipRect) {
- chart.clipRect = renderer.clipRect(clipBox);
- } else {
- clipRect.animate({
- width: clipBox.width,
- height: clipBox.height
- });
- }
-
- // Plot area border
- if (plotBorderWidth) {
- if (!plotBorder) {
- chart.plotBorder = renderer.rect(plotLeft, plotTop, plotWidth, plotHeight, 0, plotBorderWidth)
- .attr({
- stroke: optionsChart.plotBorderColor,
- 'stroke-width': plotBorderWidth,
- zIndex: 1
- })
- .add();
- } else {
- plotBorder.animate(
- plotBorder.crisp(null, plotLeft, plotTop, plotWidth, plotHeight)
- );
- }
- }
-
- // reset
- chart.isDirtyBox = false;
- },
-
- /**
- * Detect whether a certain chart property is needed based on inspecting its options
- * and series. This mainly applies to the chart.invert property, and in extensions to
- * the chart.angular and chart.polar properties.
- */
- propFromSeries: function () {
- var chart = this,
- optionsChart = chart.options.chart,
- klass,
- seriesOptions = chart.options.series,
- i,
- value;
-
-
- each(['inverted', 'angular', 'polar'], function (key) {
-
- // The default series type's class
- klass = seriesTypes[optionsChart.type || optionsChart.defaultSeriesType];
-
- // Get the value from available chart-wide properties
- value = (
- chart[key] || // 1. it is set before
- optionsChart[key] || // 2. it is set in the options
- (klass && klass.prototype[key]) // 3. it's default series class requires it
- );
-
- // 4. Check if any the chart's series require it
- i = seriesOptions && seriesOptions.length;
- while (!value && i--) {
- klass = seriesTypes[seriesOptions[i].type];
- if (klass && klass.prototype[key]) {
- value = true;
- }
- }
-
- // Set the chart property
- chart[key] = value;
- });
-
- },
-
- /**
- * Render all graphics for the chart
- */
- render: function () {
- var chart = this,
- axes = chart.axes,
- renderer = chart.renderer,
- options = chart.options;
-
- var labels = options.labels,
- credits = options.credits,
- creditsHref;
-
- // Title
- chart.setTitle();
-
-
- // Legend
- chart.legend = new Legend(chart, options.legend);
-
- chart.getStacks(); // render stacks
-
- // Get margins by pre-rendering axes
- // set axes scales
- each(axes, function (axis) {
- axis.setScale();
- });
-
- chart.getMargins();
-
- chart.maxTicks = null; // reset for second pass
- each(axes, function (axis) {
- axis.setTickPositions(true); // update to reflect the new margins
- axis.setMaxTicks();
- });
- chart.adjustTickAmounts();
- chart.getMargins(); // second pass to check for new labels
-
-
- // Draw the borders and backgrounds
- chart.drawChartBox();
-
-
- // Axes
- if (chart.hasCartesianSeries) {
- each(axes, function (axis) {
- axis.render();
- });
- }
-
- // The series
- if (!chart.seriesGroup) {
- chart.seriesGroup = renderer.g('series-group')
- .attr({ zIndex: 3 })
- .add();
- }
- each(chart.series, function (serie) {
- serie.translate();
- serie.setTooltipPoints();
- serie.render();
- });
-
- // Labels
- if (labels.items) {
- each(labels.items, function (label) {
- var style = extend(labels.style, label.style),
- x = pInt(style.left) + chart.plotLeft,
- y = pInt(style.top) + chart.plotTop + 12;
-
- // delete to prevent rewriting in IE
- delete style.left;
- delete style.top;
-
- renderer.text(
- label.html,
- x,
- y
- )
- .attr({ zIndex: 2 })
- .css(style)
- .add();
-
- });
- }
-
- // Credits
- if (credits.enabled && !chart.credits) {
- creditsHref = credits.href;
- chart.credits = renderer.text(
- credits.text,
- 0,
- 0
- )
- .on('click', function () {
- if (creditsHref) {
- location.href = creditsHref;
- }
- })
- .attr({
- align: credits.position.align,
- zIndex: 8
- })
- .css(credits.style)
- .add()
- .align(credits.position);
- }
-
- // Set flag
- chart.hasRendered = true;
-
- },
-
- /**
- * Clean up memory usage
- */
- destroy: function () {
- var chart = this,
- axes = chart.axes,
- series = chart.series,
- container = chart.container,
- i,
- parentNode = container && container.parentNode;
-
- // fire the chart.destoy event
- fireEvent(chart, 'destroy');
-
- // Delete the chart from charts lookup array
- charts[chart.index] = UNDEFINED;
- chart.renderTo.removeAttribute('data-highcharts-chart');
-
- // remove events
- removeEvent(chart);
-
- // ==== Destroy collections:
- // Destroy axes
- i = axes.length;
- while (i--) {
- axes[i] = axes[i].destroy();
- }
-
- // Destroy each series
- i = series.length;
- while (i--) {
- series[i] = series[i].destroy();
- }
-
- // ==== Destroy chart properties:
- each(['title', 'subtitle', 'chartBackground', 'plotBackground', 'plotBGImage',
- 'plotBorder', 'seriesGroup', 'clipRect', 'credits', 'pointer', 'scroller',
- 'rangeSelector', 'legend', 'resetZoomButton', 'tooltip', 'renderer'], function (name) {
- var prop = chart[name];
-
- if (prop && prop.destroy) {
- chart[name] = prop.destroy();
- }
- });
-
- // remove container and all SVG
- if (container) { // can break in IE when destroyed before finished loading
- container.innerHTML = '';
- removeEvent(container);
- if (parentNode) {
- discardElement(container);
- }
-
- }
-
- // clean it all up
- for (i in chart) {
- delete chart[i];
- }
-
- },
-
-
- /**
- * VML namespaces can't be added until after complete. Listening
- * for Perini's doScroll hack is not enough.
- */
- isReadyToRender: function () {
- var chart = this;
-
- // Note: in spite of JSLint's complaints, win == win.top is required
- /*jslint eqeq: true*/
- if ((!hasSVG && (win == win.top && doc.readyState !== 'complete')) || (useCanVG && !win.canvg)) {
- /*jslint eqeq: false*/
- if (useCanVG) {
- // Delay rendering until canvg library is downloaded and ready
- CanVGController.push(function () { chart.firstRender(); }, chart.options.global.canvasToolsURL);
- } else {
- doc.attachEvent('onreadystatechange', function () {
- doc.detachEvent('onreadystatechange', chart.firstRender);
- if (doc.readyState === 'complete') {
- chart.firstRender();
- }
- });
- }
- return false;
- }
- return true;
- },
-
- /**
- * Prepare for first rendering after all data are loaded
- */
- firstRender: function () {
- var chart = this,
- options = chart.options,
- callback = chart.callback;
-
- // Check whether the chart is ready to render
- if (!chart.isReadyToRender()) {
- return;
- }
-
- // Create the container
- chart.getContainer();
-
- // Run an early event after the container and renderer are established
- fireEvent(chart, 'init');
-
-
- chart.resetMargins();
- chart.setChartSize();
-
- // Set the common chart properties (mainly invert) from the given series
- chart.propFromSeries();
-
- // get axes
- chart.getAxes();
-
- // Initialize the series
- each(options.series || [], function (serieOptions) {
- chart.initSeries(serieOptions);
- });
-
- // Run an event after axes and series are initialized, but before render. At this stage,
- // the series data is indexed and cached in the xData and yData arrays, so we can access
- // those before rendering. Used in Highstock.
- fireEvent(chart, 'beforeRender');
-
- // depends on inverted and on margins being set
- chart.pointer = new Pointer(chart, options);
-
- chart.render();
-
- // add canvas
- chart.renderer.draw();
- // run callbacks
- if (callback) {
- callback.apply(chart, [chart]);
- }
- each(chart.callbacks, function (fn) {
- fn.apply(chart, [chart]);
- });
-
-
- // If the chart was rendered outside the top container, put it back in
- chart.cloneRenderTo(true);
-
- fireEvent(chart, 'load');
-
- }
-}; // end Chart
-
-// Hook for exporting module
-Chart.prototype.callbacks = [];
-/**
- * The Point object and prototype. Inheritable and used as base for PiePoint
- */
-var Point = function () {};
-Point.prototype = {
-
- /**
- * Initialize the point
- * @param {Object} series The series object containing this point
- * @param {Object} options The data in either number, array or object format
- */
- init: function (series, options, x) {
-
- var point = this,
- colors;
- point.series = series;
- point.applyOptions(options, x);
- point.pointAttr = {};
-
- if (series.options.colorByPoint) {
- colors = series.options.colors || series.chart.options.colors;
- point.color = point.color || colors[series.colorCounter++];
- // loop back to zero
- if (series.colorCounter === colors.length) {
- series.colorCounter = 0;
- }
- }
-
- series.chart.pointCount++;
- return point;
- },
- /**
- * Apply the options containing the x and y data and possible some extra properties.
- * This is called on point init or from point.update.
- *
- * @param {Object} options
- */
- applyOptions: function (options, x) {
- var point = this,
- series = point.series,
- pointValKey = series.pointValKey;
-
- options = Point.prototype.optionsToObject.call(this, options);
-
- // copy options directly to point
- extend(point, options);
- point.options = point.options ? extend(point.options, options) : options;
-
- // For higher dimension series types. For instance, for ranges, point.y is mapped to point.low.
- if (pointValKey) {
- point.y = point[pointValKey];
- }
-
- // If no x is set by now, get auto incremented value. All points must have an
- // x value, however the y value can be null to create a gap in the series
- if (point.x === UNDEFINED && series) {
- point.x = x === UNDEFINED ? series.autoIncrement() : x;
- }
-
- return point;
- },
-
- /**
- * Transform number or array configs into objects
- */
- optionsToObject: function (options) {
- var ret,
- series = this.series,
- pointArrayMap = series.pointArrayMap || ['y'],
- valueCount = pointArrayMap.length,
- firstItemType,
- i = 0,
- j = 0;
-
- if (typeof options === 'number' || options === null) {
- ret = { y: options };
-
- } else if (isArray(options)) {
- ret = {};
- // with leading x value
- if (options.length > valueCount) {
- firstItemType = typeof options[0];
- if (firstItemType === 'string') {
- ret.name = options[0];
- } else if (firstItemType === 'number') {
- ret.x = options[0];
- }
- i++;
- }
- while (j < valueCount) {
- ret[pointArrayMap[j++]] = options[i++];
- }
- } else if (typeof options === 'object') {
- ret = options;
-
- // This is the fastest way to detect if there are individual point dataLabels that need
- // to be considered in drawDataLabels. These can only occur in object configs.
- if (options.dataLabels) {
- series._hasPointLabels = true;
- }
-
- // Same approach as above for markers
- if (options.marker) {
- series._hasPointMarkers = true;
- }
- }
- return ret;
- },
-
- /**
- * Destroy a point to clear memory. Its reference still stays in series.data.
- */
- destroy: function () {
- var point = this,
- series = point.series,
- chart = series.chart,
- hoverPoints = chart.hoverPoints,
- prop;
-
- chart.pointCount--;
-
- if (hoverPoints) {
- point.setState();
- erase(hoverPoints, point);
- if (!hoverPoints.length) {
- chart.hoverPoints = null;
- }
-
- }
- if (point === chart.hoverPoint) {
- point.onMouseOut();
- }
-
- // remove all events
- if (point.graphic || point.dataLabel) { // removeEvent and destroyElements are performance expensive
- removeEvent(point);
- point.destroyElements();
- }
-
- if (point.legendItem) { // pies have legend items
- chart.legend.destroyItem(point);
- }
-
- for (prop in point) {
- point[prop] = null;
- }
-
-
- },
-
- /**
- * Destroy SVG elements associated with the point
- */
- destroyElements: function () {
- var point = this,
- props = ['graphic', 'dataLabel', 'dataLabelUpper', 'group', 'connector', 'shadowGroup'],
- prop,
- i = 6;
- while (i--) {
- prop = props[i];
- if (point[prop]) {
- point[prop] = point[prop].destroy();
- }
- }
- },
-
- /**
- * Return the configuration hash needed for the data label and tooltip formatters
- */
- getLabelConfig: function () {
- var point = this;
- return {
- x: point.category,
- y: point.y,
- key: point.name || point.category,
- series: point.series,
- point: point,
- percentage: point.percentage,
- total: point.total || point.stackTotal
- };
- },
-
- /**
- * Toggle the selection status of a point
- * @param {Boolean} selected Whether to select or unselect the point.
- * @param {Boolean} accumulate Whether to add to the previous selection. By default,
- * this happens if the control key (Cmd on Mac) was pressed during clicking.
- */
- select: function (selected, accumulate) {
- var point = this,
- series = point.series,
- chart = series.chart;
-
- selected = pick(selected, !point.selected);
-
- // fire the event with the defalut handler
- point.firePointEvent(selected ? 'select' : 'unselect', { accumulate: accumulate }, function () {
- point.selected = point.options.selected = selected;
- series.options.data[inArray(point, series.data)] = point.options;
-
- point.setState(selected && SELECT_STATE);
-
- // unselect all other points unless Ctrl or Cmd + click
- if (!accumulate) {
- each(chart.getSelectedPoints(), function (loopPoint) {
- if (loopPoint.selected && loopPoint !== point) {
- loopPoint.selected = loopPoint.options.selected = false;
- series.options.data[inArray(loopPoint, series.data)] = loopPoint.options;
- loopPoint.setState(NORMAL_STATE);
- loopPoint.firePointEvent('unselect');
- }
- });
- }
- });
- },
-
- /**
- * Runs on mouse over the point
- */
- onMouseOver: function (e) {
- var point = this,
- series = point.series,
- chart = series.chart,
- tooltip = chart.tooltip,
- hoverPoint = chart.hoverPoint;
-
- // set normal state to previous series
- if (hoverPoint && hoverPoint !== point) {
- hoverPoint.onMouseOut();
- }
-
- // trigger the event
- point.firePointEvent('mouseOver');
-
- // update the tooltip
- if (tooltip && (!tooltip.shared || series.noSharedTooltip)) {
- tooltip.refresh(point, e);
- }
-
- // hover this
- point.setState(HOVER_STATE);
- chart.hoverPoint = point;
- },
-
- /**
- * Runs on mouse out from the point
- */
- onMouseOut: function () {
- var chart = this.series.chart,
- hoverPoints = chart.hoverPoints;
-
- if (!hoverPoints || inArray(this, hoverPoints) === -1) { // #887
- this.firePointEvent('mouseOut');
-
- this.setState();
- chart.hoverPoint = null;
- }
- },
-
- /**
- * Extendable method for formatting each point's tooltip line
- *
- * @return {String} A string to be concatenated in to the common tooltip text
- */
- tooltipFormatter: function (pointFormat) {
-
- // Insert options for valueDecimals, valuePrefix, and valueSuffix
- var series = this.series,
- seriesTooltipOptions = series.tooltipOptions,
- valueDecimals = pick(seriesTooltipOptions.valueDecimals, ''),
- valuePrefix = seriesTooltipOptions.valuePrefix || '',
- valueSuffix = seriesTooltipOptions.valueSuffix || '';
-
- // Loop over the point array map and replace unformatted values with sprintf formatting markup
- each(series.pointArrayMap || ['y'], function (key) {
- key = '{point.' + key; // without the closing bracket
- if (valuePrefix || valueSuffix) {
- pointFormat = pointFormat.replace(key + '}', valuePrefix + key + '}' + valueSuffix);
- }
- pointFormat = pointFormat.replace(key + '}', key + ':,.' + valueDecimals + 'f}');
- });
-
- return format(pointFormat, {
- point: this,
- series: this.series
- });
- },
-
- /**
- * Update the point with new options (typically x/y data) and optionally redraw the series.
- *
- * @param {Object} options Point options as defined in the series.data array
- * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
- * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
- * configuration
- *
- */
- update: function (options, redraw, animation) {
- var point = this,
- series = point.series,
- graphic = point.graphic,
- i,
- data = series.data,
- chart = series.chart,
- seriesOptions = series.options;
-
- redraw = pick(redraw, true);
-
- // fire the event with a default handler of doing the update
- point.firePointEvent('update', { options: options }, function () {
-
- point.applyOptions(options);
-
- // update visuals
- if (isObject(options)) {
- series.getAttribs();
- if (graphic) {
- graphic.attr(point.pointAttr[series.state]);
- }
- }
-
- // record changes in the parallel arrays
- i = inArray(point, data);
- series.xData[i] = point.x;
- series.yData[i] = series.toYData ? series.toYData(point) : point.y;
- series.zData[i] = point.z;
- seriesOptions.data[i] = point.options;
-
- // redraw
- series.isDirty = series.isDirtyData = chart.isDirtyBox = true;
- if (seriesOptions.legendType === 'point') { // #1831, #1885
- chart.legend.destroyItem(point);
- }
- if (redraw) {
- chart.redraw(animation);
- }
- });
- },
-
- /**
- * Remove a point and optionally redraw the series and if necessary the axes
- * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
- * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
- * configuration
- */
- remove: function (redraw, animation) {
- var point = this,
- series = point.series,
- chart = series.chart,
- i,
- data = series.data;
-
- setAnimation(animation, chart);
- redraw = pick(redraw, true);
-
- // fire the event with a default handler of removing the point
- point.firePointEvent('remove', null, function () {
-
- // splice all the parallel arrays
- i = inArray(point, data);
- data.splice(i, 1);
- series.options.data.splice(i, 1);
- series.xData.splice(i, 1);
- series.yData.splice(i, 1);
- series.zData.splice(i, 1);
-
- point.destroy();
-
-
- // redraw
- series.isDirty = true;
- series.isDirtyData = true;
- if (redraw) {
- chart.redraw();
- }
- });
-
-
- },
-
- /**
- * Fire an event on the Point object. Must not be renamed to fireEvent, as this
- * causes a name clash in MooTools
- * @param {String} eventType
- * @param {Object} eventArgs Additional event arguments
- * @param {Function} defaultFunction Default event handler
- */
- firePointEvent: function (eventType, eventArgs, defaultFunction) {
- var point = this,
- series = this.series,
- seriesOptions = series.options;
-
- // load event handlers on demand to save time on mouseover/out
- if (seriesOptions.point.events[eventType] || (point.options && point.options.events && point.options.events[eventType])) {
- this.importEvents();
- }
-
- // add default handler if in selection mode
- if (eventType === 'click' && seriesOptions.allowPointSelect) {
- defaultFunction = function (event) {
- // Control key is for Windows, meta (= Cmd key) for Mac, Shift for Opera
- point.select(null, event.ctrlKey || event.metaKey || event.shiftKey);
- };
- }
-
- fireEvent(this, eventType, eventArgs, defaultFunction);
- },
- /**
- * Import events from the series' and point's options. Only do it on
- * demand, to save processing time on hovering.
- */
- importEvents: function () {
- if (!this.hasImportedEvents) {
- var point = this,
- options = merge(point.series.options.point, point.options),
- events = options.events,
- eventType;
-
- point.events = events;
-
- for (eventType in events) {
- addEvent(point, eventType, events[eventType]);
- }
- this.hasImportedEvents = true;
-
- }
- },
-
- /**
- * Set the point's state
- * @param {String} state
- */
- setState: function (state) {
- var point = this,
- plotX = point.plotX,
- plotY = point.plotY,
- series = point.series,
- stateOptions = series.options.states,
- markerOptions = defaultPlotOptions[series.type].marker && series.options.marker,
- normalDisabled = markerOptions && !markerOptions.enabled,
- markerStateOptions = markerOptions && markerOptions.states[state],
- stateDisabled = markerStateOptions && markerStateOptions.enabled === false,
- stateMarkerGraphic = series.stateMarkerGraphic,
- pointMarker = point.marker || {},
- chart = series.chart,
- radius,
- newSymbol,
- pointAttr = point.pointAttr;
-
- state = state || NORMAL_STATE; // empty string
-
- if (
- // already has this state
- state === point.state ||
- // selected points don't respond to hover
- (point.selected && state !== SELECT_STATE) ||
- // series' state options is disabled
- (stateOptions[state] && stateOptions[state].enabled === false) ||
- // point marker's state options is disabled
- (state && (stateDisabled || (normalDisabled && !markerStateOptions.enabled)))
-
- ) {
- return;
- }
-
- // apply hover styles to the existing point
- if (point.graphic) {
- radius = markerOptions && point.graphic.symbolName && pointAttr[state].r;
- point.graphic.attr(merge(
- pointAttr[state],
- radius ? { // new symbol attributes (#507, #612)
- x: plotX - radius,
- y: plotY - radius,
- width: 2 * radius,
- height: 2 * radius
- } : {}
- ));
- } else {
- // if a graphic is not applied to each point in the normal state, create a shared
- // graphic for the hover state
- if (state && markerStateOptions) {
- radius = markerStateOptions.radius;
- newSymbol = pointMarker.symbol || series.symbol;
-
- // If the point has another symbol than the previous one, throw away the
- // state marker graphic and force a new one (#1459)
- if (stateMarkerGraphic && stateMarkerGraphic.currentSymbol !== newSymbol) {
- stateMarkerGraphic = stateMarkerGraphic.destroy();
- }
-
- // Add a new state marker graphic
- if (!stateMarkerGraphic) {
- series.stateMarkerGraphic = stateMarkerGraphic = chart.renderer.symbol(
- newSymbol,
- plotX - radius,
- plotY - radius,
- 2 * radius,
- 2 * radius
- )
- .attr(pointAttr[state])
- .add(series.markerGroup);
- stateMarkerGraphic.currentSymbol = newSymbol;
-
- // Move the existing graphic
- } else {
- stateMarkerGraphic.attr({ // #1054
- x: plotX - radius,
- y: plotY - radius
- });
- }
- }
-
- if (stateMarkerGraphic) {
- stateMarkerGraphic[state && chart.isInsidePlot(plotX, plotY) ? 'show' : 'hide']();
- }
- }
-
- point.state = state;
- }
-};
-
-/**
- * @classDescription The base function which all other series types inherit from. The data in the series is stored
- * in various arrays.
- *
- * - First, series.options.data contains all the original config options for
- * each point whether added by options or methods like series.addPoint.
- * - Next, series.data contains those values converted to points, but in case the series data length
- * exceeds the cropThreshold, or if the data is grouped, series.data doesn't contain all the points. It
- * only contains the points that have been created on demand.
- * - Then there's series.points that contains all currently visible point objects. In case of cropping,
- * the cropped-away points are not part of this array. The series.points array starts at series.cropStart
- * compared to series.data and series.options.data. If however the series data is grouped, these can't
- * be correlated one to one.
- * - series.xData and series.processedXData contain clean x values, equivalent to series.data and series.points.
- * - series.yData and series.processedYData contain clean x values, equivalent to series.data and series.points.
- *
- * @param {Object} chart
- * @param {Object} options
- */
-var Series = function () {};
-
-Series.prototype = {
-
- isCartesian: true,
- type: 'line',
- pointClass: Point,
- sorted: true, // requires the data to be sorted
- requireSorting: true,
- pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
- stroke: 'lineColor',
- 'stroke-width': 'lineWidth',
- fill: 'fillColor',
- r: 'radius'
- },
- colorCounter: 0,
- init: function (chart, options) {
- var series = this,
- eventType,
- events,
- linkedTo,
- chartSeries = chart.series;
-
- series.chart = chart;
- series.options = options = series.setOptions(options); // merge with plotOptions
-
- // bind the axes
- series.bindAxes();
-
- // set some variables
- extend(series, {
- name: options.name,
- state: NORMAL_STATE,
- pointAttr: {},
- visible: options.visible !== false, // true by default
- selected: options.selected === true // false by default
- });
-
- // special
- if (useCanVG) {
- options.animation = false;
- }
-
- // register event listeners
- events = options.events;
- for (eventType in events) {
- addEvent(series, eventType, events[eventType]);
- }
- if (
- (events && events.click) ||
- (options.point && options.point.events && options.point.events.click) ||
- options.allowPointSelect
- ) {
- chart.runTrackerClick = true;
- }
-
- series.getColor();
- series.getSymbol();
-
- // set the data
- series.setData(options.data, false);
-
- // Mark cartesian
- if (series.isCartesian) {
- chart.hasCartesianSeries = true;
- }
-
- // Register it in the chart
- chartSeries.push(series);
- series._i = chartSeries.length - 1;
-
- // Sort series according to index option (#248, #1123)
- stableSort(chartSeries, function (a, b) {
- return pick(a.options.index, a._i) - pick(b.options.index, a._i);
- });
- each(chartSeries, function (series, i) {
- series.index = i;
- series.name = series.name || 'Series ' + (i + 1);
- });
-
- // Linked series
- linkedTo = options.linkedTo;
- series.linkedSeries = [];
- if (isString(linkedTo)) {
- if (linkedTo === ':previous') {
- linkedTo = chartSeries[series.index - 1];
- } else {
- linkedTo = chart.get(linkedTo);
- }
- if (linkedTo) {
- linkedTo.linkedSeries.push(series);
- series.linkedParent = linkedTo;
- }
- }
- },
-
- /**
- * Set the xAxis and yAxis properties of cartesian series, and register the series
- * in the axis.series array
- */
- bindAxes: function () {
- var series = this,
- seriesOptions = series.options,
- chart = series.chart,
- axisOptions;
-
- if (series.isCartesian) {
-
- each(['xAxis', 'yAxis'], function (AXIS) { // repeat for xAxis and yAxis
-
- each(chart[AXIS], function (axis) { // loop through the chart's axis objects
-
- axisOptions = axis.options;
-
- // apply if the series xAxis or yAxis option mathches the number of the
- // axis, or if undefined, use the first axis
- if ((seriesOptions[AXIS] === axisOptions.index) ||
- (seriesOptions[AXIS] !== UNDEFINED && seriesOptions[AXIS] === axisOptions.id) ||
- (seriesOptions[AXIS] === UNDEFINED && axisOptions.index === 0)) {
-
- // register this series in the axis.series lookup
- axis.series.push(series);
-
- // set this series.xAxis or series.yAxis reference
- series[AXIS] = axis;
-
- // mark dirty for redraw
- axis.isDirty = true;
- }
- });
-
- // The series needs an X and an Y axis
- if (!series[AXIS]) {
- error(18, true);
- }
-
- });
- }
- },
-
-
- /**
- * Return an auto incremented x value based on the pointStart and pointInterval options.
- * This is only used if an x value is not given for the point that calls autoIncrement.
- */
- autoIncrement: function () {
- var series = this,
- options = series.options,
- xIncrement = series.xIncrement;
-
- xIncrement = pick(xIncrement, options.pointStart, 0);
-
- series.pointInterval = pick(series.pointInterval, options.pointInterval, 1);
-
- series.xIncrement = xIncrement + series.pointInterval;
- return xIncrement;
- },
-
- /**
- * Divide the series data into segments divided by null values.
- */
- getSegments: function () {
- var series = this,
- lastNull = -1,
- segments = [],
- i,
- points = series.points,
- pointsLength = points.length;
-
- if (pointsLength) { // no action required for []
-
- // if connect nulls, just remove null points
- if (series.options.connectNulls) {
- i = pointsLength;
- while (i--) {
- if (points[i].y === null) {
- points.splice(i, 1);
- }
- }
- if (points.length) {
- segments = [points];
- }
-
- // else, split on null points
- } else {
- each(points, function (point, i) {
- if (point.y === null) {
- if (i > lastNull + 1) {
- segments.push(points.slice(lastNull + 1, i));
- }
- lastNull = i;
- } else if (i === pointsLength - 1) { // last value
- segments.push(points.slice(lastNull + 1, i + 1));
- }
- });
- }
- }
-
- // register it
- series.segments = segments;
- },
-
- /**
- * Set the series options by merging from the options tree
- * @param {Object} itemOptions
- */
- setOptions: function (itemOptions) {
- var chart = this.chart,
- chartOptions = chart.options,
- plotOptions = chartOptions.plotOptions,
- typeOptions = plotOptions[this.type],
- options;
-
- this.userOptions = itemOptions;
-
- options = merge(
- typeOptions,
- plotOptions.series,
- itemOptions
- );
-
- // the tooltip options are merged between global and series specific options
- this.tooltipOptions = merge(chartOptions.tooltip, options.tooltip);
-
- // Delte marker object if not allowed (#1125)
- if (typeOptions.marker === null) {
- delete options.marker;
- }
-
- return options;
-
- },
- /**
- * Get the series' color
- */
- getColor: function () {
- var options = this.options,
- userOptions = this.userOptions,
- defaultColors = this.chart.options.colors,
- counters = this.chart.counters,
- color,
- colorIndex;
-
- color = options.color || defaultPlotOptions[this.type].color;
-
- if (!color && !options.colorByPoint) {
- if (defined(userOptions._colorIndex)) { // after Series.update()
- colorIndex = userOptions._colorIndex;
- } else {
- userOptions._colorIndex = counters.color;
- colorIndex = counters.color++;
- }
- color = defaultColors[colorIndex];
- }
-
- this.color = color;
- counters.wrapColor(defaultColors.length);
- },
- /**
- * Get the series' symbol
- */
- getSymbol: function () {
- var series = this,
- userOptions = series.userOptions,
- seriesMarkerOption = series.options.marker,
- chart = series.chart,
- defaultSymbols = chart.options.symbols,
- counters = chart.counters,
- symbolIndex;
-
- series.symbol = seriesMarkerOption.symbol;
- if (!series.symbol) {
- if (defined(userOptions._symbolIndex)) { // after Series.update()
- symbolIndex = userOptions._symbolIndex;
- } else {
- userOptions._symbolIndex = counters.symbol;
- symbolIndex = counters.symbol++;
- }
- series.symbol = defaultSymbols[symbolIndex];
- }
-
- // don't substract radius in image symbols (#604)
- if (/^url/.test(series.symbol)) {
- seriesMarkerOption.radius = 0;
- }
- counters.wrapSymbol(defaultSymbols.length);
- },
-
- /**
- * Get the series' symbol in the legend. This method should be overridable to create custom
- * symbols through Highcharts.seriesTypes[type].prototype.drawLegendSymbols.
- *
- * @param {Object} legend The legend object
- */
- drawLegendSymbol: function (legend) {
-
- var options = this.options,
- markerOptions = options.marker,
- radius,
- legendOptions = legend.options,
- legendSymbol,
- symbolWidth = legendOptions.symbolWidth,
- renderer = this.chart.renderer,
- legendItemGroup = this.legendGroup,
- verticalCenter = legend.baseline - mathRound(renderer.fontMetrics(legendOptions.itemStyle.fontSize).b * 0.3),
- attr;
-
- // Draw the line
- if (options.lineWidth) {
- attr = {
- 'stroke-width': options.lineWidth
- };
- if (options.dashStyle) {
- attr.dashstyle = options.dashStyle;
- }
- this.legendLine = renderer.path([
- M,
- 0,
- verticalCenter,
- L,
- symbolWidth,
- verticalCenter
- ])
- .attr(attr)
- .add(legendItemGroup);
- }
-
- // Draw the marker
- if (markerOptions && markerOptions.enabled) {
- radius = markerOptions.radius;
- this.legendSymbol = legendSymbol = renderer.symbol(
- this.symbol,
- (symbolWidth / 2) - radius,
- verticalCenter - radius,
- 2 * radius,
- 2 * radius
- )
- .add(legendItemGroup);
- legendSymbol.isMarker = true;
- }
- },
-
- /**
- * Add a point dynamically after chart load time
- * @param {Object} options Point options as given in series.data
- * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
- * @param {Boolean} shift If shift is true, a point is shifted off the start
- * of the series as one is appended to the end.
- * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
- * configuration
- */
- addPoint: function (options, redraw, shift, animation) {
- var series = this,
- seriesOptions = series.options,
- data = series.data,
- graph = series.graph,
- area = series.area,
- chart = series.chart,
- xData = series.xData,
- yData = series.yData,
- zData = series.zData,
- names = series.names,
- currentShift = (graph && graph.shift) || 0,
- dataOptions = seriesOptions.data,
- point;
-
- setAnimation(animation, chart);
-
- // Make graph animate sideways
- if (shift) {
- each([graph, area, series.graphNeg, series.areaNeg], function (shape) {
- if (shape) {
- shape.shift = currentShift + 1;
- }
- });
- }
- if (area) {
- area.isArea = true; // needed in animation, both with and without shift
- }
-
- // Optional redraw, defaults to true
- redraw = pick(redraw, true);
-
- // Get options and push the point to xData, yData and series.options. In series.generatePoints
- // the Point instance will be created on demand and pushed to the series.data array.
- point = { series: series };
- series.pointClass.prototype.applyOptions.apply(point, [options]);
- xData.push(point.x);
- yData.push(series.toYData ? series.toYData(point) : point.y);
- zData.push(point.z);
- if (names) {
- names[point.x] = point.name;
- }
- dataOptions.push(options);
-
- // Generate points to be added to the legend (#1329)
- if (seriesOptions.legendType === 'point') {
- series.generatePoints();
- }
-
- // Shift the first point off the parallel arrays
- // todo: consider series.removePoint(i) method
- if (shift) {
- if (data[0] && data[0].remove) {
- data[0].remove(false);
- } else {
- data.shift();
- xData.shift();
- yData.shift();
- zData.shift();
- dataOptions.shift();
- }
- }
-
- // redraw
- series.isDirty = true;
- series.isDirtyData = true;
- if (redraw) {
- series.getAttribs(); // #1937
- chart.redraw();
- }
- },
-
- /**
- * Replace the series data with a new set of data
- * @param {Object} data
- * @param {Object} redraw
- */
- setData: function (data, redraw) {
- var series = this,
- oldData = series.points,
- options = series.options,
- chart = series.chart,
- firstPoint = null,
- xAxis = series.xAxis,
- names = xAxis && xAxis.categories && !xAxis.categories.length ? [] : null,
- i;
-
- // reset properties
- series.xIncrement = null;
- series.pointRange = xAxis && xAxis.categories ? 1 : options.pointRange;
-
- series.colorCounter = 0; // for series with colorByPoint (#1547)
-
- // parallel arrays
- var xData = [],
- yData = [],
- zData = [],
- dataLength = data ? data.length : [],
- turboThreshold = pick(options.turboThreshold, 1000), // docs: 0 to disable
- pt,
- pointArrayMap = series.pointArrayMap,
- valueCount = pointArrayMap && pointArrayMap.length,
- hasToYData = !!series.toYData;
-
- // In turbo mode, only one- or twodimensional arrays of numbers are allowed. The
- // first value is tested, and we assume that all the rest are defined the same
- // way. Although the 'for' loops are similar, they are repeated inside each
- // if-else conditional for max performance.
- if (turboThreshold && dataLength > turboThreshold) {
-
- // find the first non-null point
- i = 0;
- while (firstPoint === null && i < dataLength) {
- firstPoint = data[i];
- i++;
- }
-
-
- if (isNumber(firstPoint)) { // assume all points are numbers
- var x = pick(options.pointStart, 0),
- pointInterval = pick(options.pointInterval, 1);
-
- for (i = 0; i < dataLength; i++) {
- xData[i] = x;
- yData[i] = data[i];
- x += pointInterval;
- }
- series.xIncrement = x;
- } else if (isArray(firstPoint)) { // assume all points are arrays
- if (valueCount) { // [x, low, high] or [x, o, h, l, c]
- for (i = 0; i < dataLength; i++) {
- pt = data[i];
- xData[i] = pt[0];
- yData[i] = pt.slice(1, valueCount + 1);
- }
- } else { // [x, y]
- for (i = 0; i < dataLength; i++) {
- pt = data[i];
- xData[i] = pt[0];
- yData[i] = pt[1];
- }
- }
- } /* else {
- error(12); // Highcharts expects configs to be numbers or arrays in turbo mode
- }*/
- } else {
- for (i = 0; i < dataLength; i++) {
- if (data[i] !== UNDEFINED) { // stray commas in oldIE
- pt = { series: series };
- series.pointClass.prototype.applyOptions.apply(pt, [data[i]]);
- xData[i] = pt.x;
- yData[i] = hasToYData ? series.toYData(pt) : pt.y;
- zData[i] = pt.z;
- if (names && pt.name) {
- names[pt.x] = pt.name; // #2046
- }
- }
- }
- }
-
- // Forgetting to cast strings to numbers is a common caveat when handling CSV or JSON
- if (isString(yData[0])) {
- error(14, true);
- }
-
- series.data = [];
- series.options.data = data;
- series.xData = xData;
- series.yData = yData;
- series.zData = zData;
- series.names = names;
-
- // destroy old points
- i = (oldData && oldData.length) || 0;
- while (i--) {
- if (oldData[i] && oldData[i].destroy) {
- oldData[i].destroy();
- }
- }
-
- // reset minRange (#878)
- if (xAxis) {
- xAxis.minRange = xAxis.userMinRange;
- }
-
- // redraw
- series.isDirty = series.isDirtyData = chart.isDirtyBox = true;
- if (pick(redraw, true)) {
- chart.redraw(false);
- }
- },
-
- /**
- * Remove a series and optionally redraw the chart
- *
- * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
- * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
- * configuration
- */
-
- remove: function (redraw, animation) {
- var series = this,
- chart = series.chart;
- redraw = pick(redraw, true);
-
- if (!series.isRemoving) { /* prevent triggering native event in jQuery
- (calling the remove function from the remove event) */
- series.isRemoving = true;
-
- // fire the event with a default handler of removing the point
- fireEvent(series, 'remove', null, function () {
-
-
- // destroy elements
- series.destroy();
-
-
- // redraw
- chart.isDirtyLegend = chart.isDirtyBox = true;
- if (redraw) {
- chart.redraw(animation);
- }
- });
-
- }
- series.isRemoving = false;
- },
-
- /**
- * Process the data by cropping away unused data points if the series is longer
- * than the crop threshold. This saves computing time for lage series.
- */
- processData: function (force) {
- var series = this,
- processedXData = series.xData, // copied during slice operation below
- processedYData = series.yData,
- dataLength = processedXData.length,
- croppedData,
- cropStart = 0,
- cropped,
- distance,
- closestPointRange,
- xAxis = series.xAxis,
- i, // loop variable
- options = series.options,
- cropThreshold = options.cropThreshold,
- isCartesian = series.isCartesian;
-
- // If the series data or axes haven't changed, don't go through this. Return false to pass
- // the message on to override methods like in data grouping.
- if (isCartesian && !series.isDirty && !xAxis.isDirty && !series.yAxis.isDirty && !force) {
- return false;
- }
-
-
- // optionally filter out points outside the plot area
- if (isCartesian && series.sorted && (!cropThreshold || dataLength > cropThreshold || series.forceCrop)) {
- var min = xAxis.min,
- max = xAxis.max;
-
- // it's outside current extremes
- if (processedXData[dataLength - 1] < min || processedXData[0] > max) {
- processedXData = [];
- processedYData = [];
-
- // only crop if it's actually spilling out
- } else if (processedXData[0] < min || processedXData[dataLength - 1] > max) {
- croppedData = this.cropData(series.xData, series.yData, min, max);
- processedXData = croppedData.xData;
- processedYData = croppedData.yData;
- cropStart = croppedData.start;
- cropped = true;
- }
- }
-
-
- // Find the closest distance between processed points
- for (i = processedXData.length - 1; i >= 0; i--) {
- distance = processedXData[i] - processedXData[i - 1];
- if (distance > 0 && (closestPointRange === UNDEFINED || distance < closestPointRange)) {
- closestPointRange = distance;
-
- // Unsorted data is not supported by the line tooltip, as well as data grouping and
- // navigation in Stock charts (#725) and width calculation of columns (#1900)
- } else if (distance < 0 && series.requireSorting) {
- error(15);
- }
- }
-
- // Record the properties
- series.cropped = cropped; // undefined or true
- series.cropStart = cropStart;
- series.processedXData = processedXData;
- series.processedYData = processedYData;
-
- if (options.pointRange === null) { // null means auto, as for columns, candlesticks and OHLC
- series.pointRange = closestPointRange || 1;
- }
- series.closestPointRange = closestPointRange;
-
- },
-
- /**
- * Iterate over xData and crop values between min and max. Returns object containing crop start/end
- * cropped xData with corresponding part of yData, dataMin and dataMax within the cropped range
- */
- cropData: function (xData, yData, min, max) {
- var dataLength = xData.length,
- cropStart = 0,
- cropEnd = dataLength,
- i;
-
- // iterate up to find slice start
- for (i = 0; i < dataLength; i++) {
- if (xData[i] >= min) {
- cropStart = mathMax(0, i - 1);
- break;
- }
- }
-
- // proceed to find slice end
- for (; i < dataLength; i++) {
- if (xData[i] > max) {
- cropEnd = i + 1;
- break;
- }
- }
-
- return {
- xData: xData.slice(cropStart, cropEnd),
- yData: yData.slice(cropStart, cropEnd),
- start: cropStart,
- end: cropEnd
- };
- },
-
-
- /**
- * Generate the data point after the data has been processed by cropping away
- * unused points and optionally grouped in Highcharts Stock.
- */
- generatePoints: function () {
- var series = this,
- options = series.options,
- dataOptions = options.data,
- data = series.data,
- dataLength,
- processedXData = series.processedXData,
- processedYData = series.processedYData,
- pointClass = series.pointClass,
- processedDataLength = processedXData.length,
- cropStart = series.cropStart || 0,
- cursor,
- hasGroupedData = series.hasGroupedData,
- point,
- points = [],
- i;
-
- if (!data && !hasGroupedData) {
- var arr = [];
- arr.length = dataOptions.length;
- data = series.data = arr;
- }
-
- for (i = 0; i < processedDataLength; i++) {
- cursor = cropStart + i;
- if (!hasGroupedData) {
- if (data[cursor]) {
- point = data[cursor];
- } else if (dataOptions[cursor] !== UNDEFINED) { // #970
- data[cursor] = point = (new pointClass()).init(series, dataOptions[cursor], processedXData[i]);
- }
- points[i] = point;
- } else {
- // splat the y data in case of ohlc data array
- points[i] = (new pointClass()).init(series, [processedXData[i]].concat(splat(processedYData[i])));
- }
- }
-
- // Hide cropped-away points - this only runs when the number of points is above cropThreshold, or when
- // swithching view from non-grouped data to grouped data (#637)
- if (data && (processedDataLength !== (dataLength = data.length) || hasGroupedData)) {
- for (i = 0; i < dataLength; i++) {
- if (i === cropStart && !hasGroupedData) { // when has grouped data, clear all points
- i += processedDataLength;
- }
- if (data[i]) {
- data[i].destroyElements();
- data[i].plotX = UNDEFINED; // #1003
- }
- }
- }
-
- series.data = data;
- series.points = points;
- },
-
- /**
- * Adds series' points value to corresponding stack
- */
- setStackedPoints: function () {
- if (!this.options.stacking || (this.visible !== true && this.chart.options.chart.ignoreHiddenSeries !== false)) {
- return;
- }
-
- var series = this,
- xData = series.processedXData,
- yData = series.processedYData,
- yDataLength = yData.length,
- seriesOptions = series.options,
- threshold = seriesOptions.threshold,
- stackOption = seriesOptions.stack,
- stacking = seriesOptions.stacking,
- stackKey = series.stackKey,
- negKey = '-' + stackKey,
- yAxis = series.yAxis,
- stacks = yAxis.stacks,
- oldStacks = yAxis.oldStacks,
- stacksMax = yAxis.stacksMax,
- isNegative,
- total,
- stack,
- key,
- i,
- x,
- y;
-
- // loop over the non-null y values and read them into a local array
- for (i = 0; i < yDataLength; i++) {
- x = xData[i];
- y = yData[i];
-
- // Read stacked values into a stack based on the x value,
- // the sign of y and the stack key. Stacking is also handled for null values (#739)
- isNegative = y < threshold;
- key = isNegative ? negKey : stackKey;
-
- // Set default stacksMax value for this stack
- if (!stacksMax[key]) {
- stacksMax[key] = y;
- }
-
- // Create empty object for this stack if it doesn't exist yet
- if (!stacks[key]) {
- stacks[key] = {};
- }
-
- // Initialize StackItem for this x
- if (oldStacks[key] && oldStacks[key][x]) {
- stacks[key][x] = oldStacks[key][x];
- stacks[key][x].total = null;
- } else if (!stacks[key][x]) {
- stacks[key][x] = new StackItem(yAxis, yAxis.options.stackLabels, isNegative, x, stackOption, stacking);
- }
-
- // If the StackItem doesn't exist, create it first
- stack = stacks[key][x];
- total = stack.total;
-
-
- // add value to the stack total
- stack.addValue(y);
-
- stack.cacheExtremes(series, [total, total + y]);
-
-
- if (stack.total > stacksMax[key] && !isNegative) {
- stacksMax[key] = stack.total;
- } else if (stack.total < stacksMax[key] && isNegative) {
- stacksMax[key] = stack.total;
- }
- }
-
- // reset old stacks
- yAxis.oldStacks = {};
- },
-
- /**
- * Calculate x and y extremes for visible data
- */
- getExtremes: function () {
- var xAxis = this.xAxis,
- yAxis = this.yAxis,
- stackKey = this.stackKey,
- options = this.options,
- threshold = options.threshold,
- xData = this.processedXData,
- yData = this.processedYData,
- yDataLength = yData.length,
- activeYData = [],
- activeCounter = 0,
- xMin = xAxis.min,
- xMax = xAxis.max,
- validValue,
- withinRange,
- dataMin,
- dataMax,
- x,
- y,
- i,
- j;
-
- // For stacked series, get the value from the stack
- if (options.stacking) {
- dataMin = yAxis.stacksMax['-' + stackKey] || threshold;
- dataMax = yAxis.stacksMax[stackKey] || threshold;
- }
-
- // If not stacking or threshold is null, iterate over values that are within the visible range
- if (!defined(dataMin) || !defined(dataMax)) {
-
- for (i = 0; i < yDataLength; i++) {
-
- x = xData[i];
- y = yData[i];
-
- // For points within the visible range, including the first point outside the
- // visible range, consider y extremes
- validValue = y !== null && y !== UNDEFINED && (!yAxis.isLog || (y.length || y > 0));
- withinRange = this.getExtremesFromAll || this.cropped || ((xData[i + 1] || x) >= xMin &&
- (xData[i - 1] || x) <= xMax);
-
- if (validValue && withinRange) {
-
- j = y.length;
- if (j) { // array, like ohlc or range data
- while (j--) {
- if (y[j] !== null) {
- activeYData[activeCounter++] = y[j];
- }
- }
- } else {
- activeYData[activeCounter++] = y;
- }
- }
- }
- dataMin = pick(dataMin, arrayMin(activeYData));
- dataMax = pick(dataMax, arrayMax(activeYData));
- }
-
- // Set
- this.dataMin = dataMin;
- this.dataMax = dataMax;
- },
-
- /**
- * Translate data points from raw data values to chart specific positioning data
- * needed later in drawPoints, drawGraph and drawTracker.
- */
- translate: function () {
- if (!this.processedXData) { // hidden series
- this.processData();
- }
- this.generatePoints();
- var series = this,
- options = series.options,
- stacking = options.stacking,
- xAxis = series.xAxis,
- categories = xAxis.categories,
- yAxis = series.yAxis,
- points = series.points,
- dataLength = points.length,
- hasModifyValue = !!series.modifyValue,
- i,
- pointPlacement = options.pointPlacement, // docs: accept numbers
- dynamicallyPlaced = pointPlacement === 'between' || isNumber(pointPlacement),
- threshold = options.threshold;
-
-
- // Translate each point
- for (i = 0; i < dataLength; i++) {
- var point = points[i],
- xValue = point.x,
- yValue = point.y,
- yBottom = point.low,
- stack = yAxis.stacks[(yValue < threshold ? '-' : '') + series.stackKey],
- pointStack,
- pointStackTotal;
-
- // Discard disallowed y values for log axes
- if (yAxis.isLog && yValue <= 0) {
- point.y = yValue = null;
- }
-
- // Get the plotX translation
- point.plotX = xAxis.translate(xValue, 0, 0, 0, 1, pointPlacement); // Math.round fixes #591
-
- // Calculate the bottom y value for stacked series
- if (stacking && series.visible && stack && stack[xValue]) {
-
-
- pointStack = stack[xValue];
- pointStackTotal = pointStack.total;
- pointStack.cum = yBottom = pointStack.cum - yValue; // start from top
- yValue = yBottom + yValue;
-
- if (pointStack.cum === 0) {
- yBottom = pick(threshold, yAxis.min);
- }
-
- if (yAxis.isLog && yBottom <= 0) { // #1200, #1232
- yBottom = null;
- }
-
- if (stacking === 'percent') {
- yBottom = pointStackTotal ? yBottom * 100 / pointStackTotal : 0;
- yValue = pointStackTotal ? yValue * 100 / pointStackTotal : 0;
- }
-
- point.percentage = pointStackTotal ? point.y * 100 / pointStackTotal : 0;
- point.total = point.stackTotal = pointStackTotal;
- point.stackY = yValue;
-
- // Place the stack label
- pointStack.setOffset(series.pointXOffset || 0, series.barW || 0);
-
- }
-
- // Set translated yBottom or remove it
- point.yBottom = defined(yBottom) ?
- yAxis.translate(yBottom, 0, 1, 0, 1) :
- null;
-
- // general hook, used for Highstock compare mode
- if (hasModifyValue) {
- yValue = series.modifyValue(yValue, point);
- }
-
- // Set the the plotY value, reset it for redraws
- point.plotY = (typeof yValue === 'number' && yValue !== Infinity) ?
- mathRound(yAxis.translate(yValue, 0, 1, 0, 1) * 10) / 10 : // Math.round fixes #591
- UNDEFINED;
-
- // Set client related positions for mouse tracking
- point.clientX = dynamicallyPlaced ? xAxis.translate(xValue, 0, 0, 0, 1) : point.plotX; // #1514
-
- point.negative = point.y < (threshold || 0);
-
- // some API data
- point.category = categories && categories[point.x] !== UNDEFINED ?
- categories[point.x] : point.x;
-
-
- }
-
- // now that we have the cropped data, build the segments
- series.getSegments();
- },
- /**
- * Memoize tooltip texts and positions
- */
- setTooltipPoints: function (renew) {
- var series = this,
- points = [],
- pointsLength,
- low,
- high,
- xAxis = series.xAxis,
- axisLength = xAxis ? (xAxis.tooltipLen || xAxis.len) : series.chart.plotSizeX, // tooltipLen and tooltipPosName used in polar
- point,
- nextPoint,
- i,
- tooltipPoints = []; // a lookup array for each pixel in the x dimension
-
- // don't waste resources if tracker is disabled
- if (series.options.enableMouseTracking === false) {
- return;
- }
-
- // renew
- if (renew) {
- series.tooltipPoints = null;
- }
-
- // concat segments to overcome null values
- each(series.segments || series.points, function (segment) {
- points = points.concat(segment);
- });
-
- // Reverse the points in case the X axis is reversed
- if (xAxis && xAxis.reversed) {
- points = points.reverse();
- }
-
- // Polar needs additional shaping
- if (series.orderTooltipPoints) {
- series.orderTooltipPoints(points);
- }
-
- // Assign each pixel position to the nearest point
- pointsLength = points.length;
- for (i = 0; i < pointsLength; i++) {
- point = points[i];
- nextPoint = points[i + 1];
-
- // Set this range's low to the last range's high plus one
- low = points[i - 1] ? high + 1 : 0;
- // Now find the new high
- high = points[i + 1] ?
- mathMin(mathMax(0, mathFloor( // #2070
- (point.clientX + (nextPoint ? (nextPoint.wrappedClientX || nextPoint.clientX) : axisLength)) / 2
- )), axisLength) :
- axisLength;
-
- while (low >= 0 && low <= high) {
- tooltipPoints[low++] = point;
- }
- }
- series.tooltipPoints = tooltipPoints;
- },
-
- /**
- * Format the header of the tooltip
- */
- tooltipHeaderFormatter: function (point) {
- var series = this,
- tooltipOptions = series.tooltipOptions,
- xDateFormat = tooltipOptions.xDateFormat,
- dateTimeLabelFormats = tooltipOptions.dateTimeLabelFormats,
- xAxis = series.xAxis,
- isDateTime = xAxis && xAxis.options.type === 'datetime',
- headerFormat = tooltipOptions.headerFormat,
- closestPointRange = xAxis && xAxis.closestPointRange,
- n;
-
- // Guess the best date format based on the closest point distance (#568)
- if (isDateTime && !xDateFormat) {
- if (closestPointRange) {
- for (n in timeUnits) {
- if (timeUnits[n] >= closestPointRange) {
- xDateFormat = dateTimeLabelFormats[n];
- break;
- }
- }
- } else {
- xDateFormat = dateTimeLabelFormats.day;
- }
- }
-
- // Insert the header date format if any
- if (isDateTime && xDateFormat && isNumber(point.key)) {
- headerFormat = headerFormat.replace('{point.key}', '{point.key:' + xDateFormat + '}');
- }
-
- return format(headerFormat, {
- point: point,
- series: series
- });
- },
-
- /**
- * Series mouse over handler
- */
- onMouseOver: function () {
- var series = this,
- chart = series.chart,
- hoverSeries = chart.hoverSeries;
-
- // set normal state to previous series
- if (hoverSeries && hoverSeries !== series) {
- hoverSeries.onMouseOut();
- }
-
- // trigger the event, but to save processing time,
- // only if defined
- if (series.options.events.mouseOver) {
- fireEvent(series, 'mouseOver');
- }
-
- // hover this
- series.setState(HOVER_STATE);
- chart.hoverSeries = series;
- },
-
- /**
- * Series mouse out handler
- */
- onMouseOut: function () {
- // trigger the event only if listeners exist
- var series = this,
- options = series.options,
- chart = series.chart,
- tooltip = chart.tooltip,
- hoverPoint = chart.hoverPoint;
-
- // trigger mouse out on the point, which must be in this series
- if (hoverPoint) {
- hoverPoint.onMouseOut();
- }
-
- // fire the mouse out event
- if (series && options.events.mouseOut) {
- fireEvent(series, 'mouseOut');
- }
-
-
- // hide the tooltip
- if (tooltip && !options.stickyTracking && (!tooltip.shared || series.noSharedTooltip)) {
- tooltip.hide();
- }
-
- // set normal state
- series.setState();
- chart.hoverSeries = null;
- },
-
- /**
- * Animate in the series
- */
- animate: function (init) {
- var series = this,
- chart = series.chart,
- renderer = chart.renderer,
- clipRect,
- markerClipRect,
- animation = series.options.animation,
- clipBox = chart.clipBox,
- inverted = chart.inverted,
- sharedClipKey;
-
- // Animation option is set to true
- if (animation && !isObject(animation)) {
- animation = defaultPlotOptions[series.type].animation;
- }
- sharedClipKey = '_sharedClip' + animation.duration + animation.easing;
-
- // Initialize the animation. Set up the clipping rectangle.
- if (init) {
-
- // If a clipping rectangle with the same properties is currently present in the chart, use that.
- clipRect = chart[sharedClipKey];
- markerClipRect = chart[sharedClipKey + 'm'];
- if (!clipRect) {
- chart[sharedClipKey] = clipRect = renderer.clipRect(
- extend(clipBox, { width: 0 })
- );
-
- chart[sharedClipKey + 'm'] = markerClipRect = renderer.clipRect(
- -99, // include the width of the first marker
- inverted ? -chart.plotLeft : -chart.plotTop,
- 99,
- inverted ? chart.chartWidth : chart.chartHeight
- );
- }
- series.group.clip(clipRect);
- series.markerGroup.clip(markerClipRect);
- series.sharedClipKey = sharedClipKey;
-
- // Run the animation
- } else {
- clipRect = chart[sharedClipKey];
- if (clipRect) {
- clipRect.animate({
- width: chart.plotSizeX
- }, animation);
- chart[sharedClipKey + 'm'].animate({
- width: chart.plotSizeX + 99
- }, animation);
- }
-
- // Delete this function to allow it only once
- series.animate = null;
-
- // Call the afterAnimate function on animation complete (but don't overwrite the animation.complete option
- // which should be available to the user).
- series.animationTimeout = setTimeout(function () {
- series.afterAnimate();
- }, animation.duration);
- }
- },
-
- /**
- * This runs after animation to land on the final plot clipping
- */
- afterAnimate: function () {
- var chart = this.chart,
- sharedClipKey = this.sharedClipKey,
- group = this.group;
-
- if (group && this.options.clip !== false) {
- group.clip(chart.clipRect);
- this.markerGroup.clip(); // no clip
- }
-
- // Remove the shared clipping rectancgle when all series are shown
- setTimeout(function () {
- if (sharedClipKey && chart[sharedClipKey]) {
- chart[sharedClipKey] = chart[sharedClipKey].destroy();
- chart[sharedClipKey + 'm'] = chart[sharedClipKey + 'm'].destroy();
- }
- }, 100);
- },
-
- /**
- * Draw the markers
- */
- drawPoints: function () {
- var series = this,
- pointAttr,
- points = series.points,
- chart = series.chart,
- plotX,
- plotY,
- i,
- point,
- radius,
- symbol,
- isImage,
- graphic,
- options = series.options,
- seriesMarkerOptions = options.marker,
- pointMarkerOptions,
- enabled,
- isInside,
- markerGroup = series.markerGroup;
-
- if (seriesMarkerOptions.enabled || series._hasPointMarkers) {
-
- i = points.length;
- while (i--) {
- point = points[i];
- plotX = mathFloor(point.plotX); // #1843
- plotY = point.plotY;
- graphic = point.graphic;
- pointMarkerOptions = point.marker || {};
- enabled = (seriesMarkerOptions.enabled && pointMarkerOptions.enabled === UNDEFINED) || pointMarkerOptions.enabled;
- isInside = chart.isInsidePlot(mathRound(plotX), plotY, chart.inverted); // #1858
-
- // only draw the point if y is defined
- if (enabled && plotY !== UNDEFINED && !isNaN(plotY) && point.y !== null) {
-
- // shortcuts
- pointAttr = point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE];
- radius = pointAttr.r;
- symbol = pick(pointMarkerOptions.symbol, series.symbol);
- isImage = symbol.indexOf('url') === 0;
-
- if (graphic) { // update
- graphic
- .attr({ // Since the marker group isn't clipped, each individual marker must be toggled
- visibility: isInside ? (hasSVG ? 'inherit' : VISIBLE) : HIDDEN
- })
- .animate(extend({
- x: plotX - radius,
- y: plotY - radius
- }, graphic.symbolName ? { // don't apply to image symbols #507
- width: 2 * radius,
- height: 2 * radius
- } : {}));
- } else if (isInside && (radius > 0 || isImage)) {
- point.graphic = graphic = chart.renderer.symbol(
- symbol,
- plotX - radius,
- plotY - radius,
- 2 * radius,
- 2 * radius
- )
- .attr(pointAttr)
- .add(markerGroup);
- }
-
- } else if (graphic) {
- point.graphic = graphic.destroy(); // #1269
- }
- }
- }
-
- },
-
- /**
- * Convert state properties from API naming conventions to SVG attributes
- *
- * @param {Object} options API options object
- * @param {Object} base1 SVG attribute object to inherit from
- * @param {Object} base2 Second level SVG attribute object to inherit from
- */
- convertAttribs: function (options, base1, base2, base3) {
- var conversion = this.pointAttrToOptions,
- attr,
- option,
- obj = {};
-
- options = options || {};
- base1 = base1 || {};
- base2 = base2 || {};
- base3 = base3 || {};
-
- for (attr in conversion) {
- option = conversion[attr];
- obj[attr] = pick(options[option], base1[attr], base2[attr], base3[attr]);
- }
- return obj;
- },
-
- /**
- * Get the state attributes. Each series type has its own set of attributes
- * that are allowed to change on a point's state change. Series wide attributes are stored for
- * all series, and additionally point specific attributes are stored for all
- * points with individual marker options. If such options are not defined for the point,
- * a reference to the series wide attributes is stored in point.pointAttr.
- */
- getAttribs: function () {
- var series = this,
- seriesOptions = series.options,
- normalOptions = defaultPlotOptions[series.type].marker ? seriesOptions.marker : seriesOptions,
- stateOptions = normalOptions.states,
- stateOptionsHover = stateOptions[HOVER_STATE],
- pointStateOptionsHover,
- seriesColor = series.color,
- normalDefaults = {
- stroke: seriesColor,
- fill: seriesColor
- },
- points = series.points || [], // #927
- i,
- point,
- seriesPointAttr = [],
- pointAttr,
- pointAttrToOptions = series.pointAttrToOptions,
- hasPointSpecificOptions,
- negativeColor = seriesOptions.negativeColor,
- key;
-
- // series type specific modifications
- if (seriesOptions.marker) { // line, spline, area, areaspline, scatter
-
- // if no hover radius is given, default to normal radius + 2
- stateOptionsHover.radius = stateOptionsHover.radius || normalOptions.radius + 2;
- stateOptionsHover.lineWidth = stateOptionsHover.lineWidth || normalOptions.lineWidth + 1;
-
- } else { // column, bar, pie
-
- // if no hover color is given, brighten the normal color
- stateOptionsHover.color = stateOptionsHover.color ||
- Color(stateOptionsHover.color || seriesColor)
- .brighten(stateOptionsHover.brightness).get();
- }
-
- // general point attributes for the series normal state
- seriesPointAttr[NORMAL_STATE] = series.convertAttribs(normalOptions, normalDefaults);
-
- // HOVER_STATE and SELECT_STATE states inherit from normal state except the default radius
- each([HOVER_STATE, SELECT_STATE], function (state) {
- seriesPointAttr[state] =
- series.convertAttribs(stateOptions[state], seriesPointAttr[NORMAL_STATE]);
- });
-
- // set it
- series.pointAttr = seriesPointAttr;
-
-
- // Generate the point-specific attribute collections if specific point
- // options are given. If not, create a referance to the series wide point
- // attributes
- i = points.length;
- while (i--) {
- point = points[i];
- normalOptions = (point.options && point.options.marker) || point.options;
- if (normalOptions && normalOptions.enabled === false) {
- normalOptions.radius = 0;
- }
-
- if (point.negative && negativeColor) {
- point.color = point.fillColor = negativeColor;
- }
-
- hasPointSpecificOptions = seriesOptions.colorByPoint || point.color; // #868
-
- // check if the point has specific visual options
- if (point.options) {
- for (key in pointAttrToOptions) {
- if (defined(normalOptions[pointAttrToOptions[key]])) {
- hasPointSpecificOptions = true;
- }
- }
- }
-
- // a specific marker config object is defined for the individual point:
- // create it's own attribute collection
- if (hasPointSpecificOptions) {
- normalOptions = normalOptions || {};
- pointAttr = [];
- stateOptions = normalOptions.states || {}; // reassign for individual point
- pointStateOptionsHover = stateOptions[HOVER_STATE] = stateOptions[HOVER_STATE] || {};
-
- // Handle colors for column and pies
- if (!seriesOptions.marker) { // column, bar, point
- // if no hover color is given, brighten the normal color
- pointStateOptionsHover.color =
- Color(pointStateOptionsHover.color || point.color)
- .brighten(pointStateOptionsHover.brightness ||
- stateOptionsHover.brightness).get();
-
- }
-
- // normal point state inherits series wide normal state
- pointAttr[NORMAL_STATE] = series.convertAttribs(extend({
- color: point.color // #868
- }, normalOptions), seriesPointAttr[NORMAL_STATE]);
-
- // inherit from point normal and series hover
- pointAttr[HOVER_STATE] = series.convertAttribs(
- stateOptions[HOVER_STATE],
- seriesPointAttr[HOVER_STATE],
- pointAttr[NORMAL_STATE]
- );
-
- // inherit from point normal and series hover
- pointAttr[SELECT_STATE] = series.convertAttribs(
- stateOptions[SELECT_STATE],
- seriesPointAttr[SELECT_STATE],
- pointAttr[NORMAL_STATE]
- );
-
- // Force the fill to negativeColor on markers
- if (point.negative && seriesOptions.marker && negativeColor) {
- pointAttr[NORMAL_STATE].fill = pointAttr[HOVER_STATE].fill = pointAttr[SELECT_STATE].fill =
- series.convertAttribs({ fillColor: negativeColor }).fill;
- }
-
-
- // no marker config object is created: copy a reference to the series-wide
- // attribute collection
- } else {
- pointAttr = seriesPointAttr;
- }
-
- point.pointAttr = pointAttr;
-
- }
-
- },
- /**
- * Update the series with a new set of options
- */
- update: function (newOptions, redraw) {
- var chart = this.chart,
- // must use user options when changing type because this.options is merged
- // in with type specific plotOptions
- oldOptions = this.userOptions,
- oldType = this.type;
-
- // Do the merge, with some forced options
- newOptions = merge(oldOptions, {
- animation: false,
- index: this.index,
- pointStart: this.xData[0] // when updating after addPoint
- }, { data: this.options.data }, newOptions);
-
- // Destroy the series and reinsert methods from the type prototype
- this.remove(false);
- extend(this, seriesTypes[newOptions.type || oldType].prototype);
-
-
- this.init(chart, newOptions);
- if (pick(redraw, true)) {
- chart.redraw(false);
- }
- },
-
- /**
- * Clear DOM objects and free up memory
- */
- destroy: function () {
- var series = this,
- chart = series.chart,
- issue134 = /AppleWebKit\/533/.test(userAgent),
- destroy,
- i,
- data = series.data || [],
- point,
- prop,
- axis;
-
- // add event hook
- fireEvent(series, 'destroy');
-
- // remove all events
- removeEvent(series);
-
- // erase from axes
- each(['xAxis', 'yAxis'], function (AXIS) {
- axis = series[AXIS];
- if (axis) {
- erase(axis.series, series);
- axis.isDirty = axis.forceRedraw = true;
- }
- });
-
- // remove legend items
- if (series.legendItem) {
- series.chart.legend.destroyItem(series);
- }
-
- // destroy all points with their elements
- i = data.length;
- while (i--) {
- point = data[i];
- if (point && point.destroy) {
- point.destroy();
- }
- }
- series.points = null;
-
- // Clear the animation timeout if we are destroying the series during initial animation
- clearTimeout(series.animationTimeout);
-
- // destroy all SVGElements associated to the series
- each(['area', 'graph', 'dataLabelsGroup', 'group', 'markerGroup', 'tracker',
- 'graphNeg', 'areaNeg', 'posClip', 'negClip'], function (prop) {
- if (series[prop]) {
-
- // issue 134 workaround
- destroy = issue134 && prop === 'group' ?
- 'hide' :
- 'destroy';
-
- series[prop][destroy]();
- }
- });
-
- // remove from hoverSeries
- if (chart.hoverSeries === series) {
- chart.hoverSeries = null;
- }
- erase(chart.series, series);
-
- // clear all members
- for (prop in series) {
- delete series[prop];
- }
- },
-
- /**
- * Draw the data labels
- */
- drawDataLabels: function () {
-
- var series = this,
- seriesOptions = series.options,
- options = seriesOptions.dataLabels,
- points = series.points,
- pointOptions,
- generalOptions,
- str,
- dataLabelsGroup;
-
- if (options.enabled || series._hasPointLabels) {
-
- // Process default alignment of data labels for columns
- if (series.dlProcessOptions) {
- series.dlProcessOptions(options);
- }
-
- // Create a separate group for the data labels to avoid rotation
- dataLabelsGroup = series.plotGroup(
- 'dataLabelsGroup',
- 'data-labels',
- series.visible ? VISIBLE : HIDDEN,
- options.zIndex || 6
- );
-
- // Make the labels for each point
- generalOptions = options;
- each(points, function (point) {
-
- var enabled,
- dataLabel = point.dataLabel,
- labelConfig,
- attr,
- name,
- rotation,
- connector = point.connector,
- isNew = true;
-
- // Determine if each data label is enabled
- pointOptions = point.options && point.options.dataLabels;
- enabled = generalOptions.enabled || (pointOptions && pointOptions.enabled);
-
-
- // If the point is outside the plot area, destroy it. #678, #820
- if (dataLabel && !enabled) {
- point.dataLabel = dataLabel.destroy();
-
- // Individual labels are disabled if the are explicitly disabled
- // in the point options, or if they fall outside the plot area.
- } else if (enabled) {
-
- // Create individual options structure that can be extended without
- // affecting others
- options = merge(generalOptions, pointOptions);
-
- rotation = options.rotation;
-
- // Get the string
- labelConfig = point.getLabelConfig();
- str = options.format ?
- format(options.format, labelConfig) :
- options.formatter.call(labelConfig, options);
-
- // Determine the color
- options.style.color = pick(options.color, options.style.color, series.color, 'black');
-
-
- // update existing label
- if (dataLabel) {
-
- if (defined(str)) {
- dataLabel
- .attr({
- text: str
- });
- isNew = false;
-
- } else { // #1437 - the label is shown conditionally
- point.dataLabel = dataLabel = dataLabel.destroy();
- if (connector) {
- point.connector = connector.destroy();
- }
- }
-
- // create new label
- } else if (defined(str)) {
- attr = {
- //align: align,
- fill: options.backgroundColor,
- stroke: options.borderColor,
- 'stroke-width': options.borderWidth,
- r: options.borderRadius || 0,
- rotation: rotation,
- padding: options.padding,
- zIndex: 1
- };
- // Remove unused attributes (#947)
- for (name in attr) {
- if (attr[name] === UNDEFINED) {
- delete attr[name];
- }
- }
-
- dataLabel = point.dataLabel = series.chart.renderer[rotation ? 'text' : 'label']( // labels don't support rotation
- str,
- 0,
- -999,
- null,
- null,
- null,
- options.useHTML
- )
- .attr(attr)
- .css(options.style)
- .add(dataLabelsGroup)
- .shadow(options.shadow);
-
- }
-
- if (dataLabel) {
- // Now the data label is created and placed at 0,0, so we need to align it
- series.alignDataLabel(point, dataLabel, options, null, isNew);
- }
- }
- });
- }
- },
-
- /**
- * Align each individual data label
- */
- alignDataLabel: function (point, dataLabel, options, alignTo, isNew) {
- var chart = this.chart,
- inverted = chart.inverted,
- plotX = pick(point.plotX, -999),
- plotY = pick(point.plotY, -999),
- bBox = dataLabel.getBBox(),
- alignAttr; // the final position;
-
- // The alignment box is a singular point
- alignTo = extend({
- x: inverted ? chart.plotWidth - plotY : plotX,
- y: mathRound(inverted ? chart.plotHeight - plotX : plotY),
- width: 0,
- height: 0
- }, alignTo);
-
- // Add the text size for alignment calculation
- extend(options, {
- width: bBox.width,
- height: bBox.height
- });
-
- // Allow a hook for changing alignment in the last moment, then do the alignment
- if (options.rotation) { // Fancy box alignment isn't supported for rotated text
- alignAttr = {
- align: options.align,
- x: alignTo.x + options.x + alignTo.width / 2,
- y: alignTo.y + options.y + alignTo.height / 2
- };
- dataLabel[isNew ? 'attr' : 'animate'](alignAttr);
- } else {
- dataLabel.align(options, null, alignTo);
- alignAttr = dataLabel.alignAttr;
- }
-
- // Show or hide based on the final aligned position
- dataLabel.attr({
- visibility: options.crop === false ||
- (chart.isInsidePlot(alignAttr.x, alignAttr.y) && chart.isInsidePlot(alignAttr.x + bBox.width, alignAttr.y + bBox.height)) ?
- (chart.renderer.isSVG ? 'inherit' : VISIBLE) :
- HIDDEN
- });
-
- },
-
- /**
- * Return the graph path of a segment
- */
- getSegmentPath: function (segment) {
- var series = this,
- segmentPath = [],
- step = series.options.step;
-
- // build the segment line
- each(segment, function (point, i) {
-
- var plotX = point.plotX,
- plotY = point.plotY,
- lastPoint;
-
- if (series.getPointSpline) { // generate the spline as defined in the SplineSeries object
- segmentPath.push.apply(segmentPath, series.getPointSpline(segment, point, i));
-
- } else {
-
- // moveTo or lineTo
- segmentPath.push(i ? L : M);
-
- // step line?
- if (step && i) {
- lastPoint = segment[i - 1];
- if (step === 'right') {
- segmentPath.push(
- lastPoint.plotX,
- plotY
- );
-
- } else if (step === 'center') {
- segmentPath.push(
- (lastPoint.plotX + plotX) / 2,
- lastPoint.plotY,
- (lastPoint.plotX + plotX) / 2,
- plotY
- );
-
- } else {
- segmentPath.push(
- plotX,
- lastPoint.plotY
- );
- }
- }
-
- // normal line to next point
- segmentPath.push(
- point.plotX,
- point.plotY
- );
- }
- });
-
- return segmentPath;
- },
-
- /**
- * Get the graph path
- */
- getGraphPath: function () {
- var series = this,
- graphPath = [],
- segmentPath,
- singlePoints = []; // used in drawTracker
-
- // Divide into segments and build graph and area paths
- each(series.segments, function (segment) {
-
- segmentPath = series.getSegmentPath(segment);
-
- // add the segment to the graph, or a single point for tracking
- if (segment.length > 1) {
- graphPath = graphPath.concat(segmentPath);
- } else {
- singlePoints.push(segment[0]);
- }
- });
-
- // Record it for use in drawGraph and drawTracker, and return graphPath
- series.singlePoints = singlePoints;
- series.graphPath = graphPath;
-
- return graphPath;
-
- },
-
- /**
- * Draw the actual graph
- */
- drawGraph: function () {
- var series = this,
- options = this.options,
- props = [['graph', options.lineColor || this.color]],
- lineWidth = options.lineWidth,
- dashStyle = options.dashStyle,
- graphPath = this.getGraphPath(),
- negativeColor = options.negativeColor;
-
- if (negativeColor) {
- props.push(['graphNeg', negativeColor]);
- }
-
- // draw the graph
- each(props, function (prop, i) {
- var graphKey = prop[0],
- graph = series[graphKey],
- attribs;
-
- if (graph) {
- stop(graph); // cancel running animations, #459
- graph.animate({ d: graphPath });
-
- } else if (lineWidth && graphPath.length) { // #1487
- attribs = {
- stroke: prop[1],
- 'stroke-width': lineWidth,
- zIndex: 1 // #1069
- };
- if (dashStyle) {
- attribs.dashstyle = dashStyle;
- }
-
- series[graphKey] = series.chart.renderer.path(graphPath)
- .attr(attribs)
- .add(series.group)
- .shadow(!i && options.shadow);
- }
- });
- },
-
- /**
- * Clip the graphs into the positive and negative coloured graphs
- */
- clipNeg: function () {
- var options = this.options,
- chart = this.chart,
- renderer = chart.renderer,
- negativeColor = options.negativeColor || options.negativeFillColor,
- translatedThreshold,
- posAttr,
- negAttr,
- graph = this.graph,
- area = this.area,
- posClip = this.posClip,
- negClip = this.negClip,
- chartWidth = chart.chartWidth,
- chartHeight = chart.chartHeight,
- chartSizeMax = mathMax(chartWidth, chartHeight),
- yAxis = this.yAxis,
- above,
- below;
-
- if (negativeColor && (graph || area)) {
- translatedThreshold = mathRound(yAxis.toPixels(options.threshold || 0, true));
- above = {
- x: 0,
- y: 0,
- width: chartSizeMax,
- height: translatedThreshold
- };
- below = {
- x: 0,
- y: translatedThreshold,
- width: chartSizeMax,
- height: chartSizeMax
- };
-
- if (chart.inverted) {
-
- above.height = below.y = chart.plotWidth - translatedThreshold;
- if (renderer.isVML) {
- above = {
- x: chart.plotWidth - translatedThreshold - chart.plotLeft,
- y: 0,
- width: chartWidth,
- height: chartHeight
- };
- below = {
- x: translatedThreshold + chart.plotLeft - chartWidth,
- y: 0,
- width: chart.plotLeft + translatedThreshold,
- height: chartWidth
- };
- }
- }
-
- if (yAxis.reversed) {
- posAttr = below;
- negAttr = above;
- } else {
- posAttr = above;
- negAttr = below;
- }
-
- if (posClip) { // update
- posClip.animate(posAttr);
- negClip.animate(negAttr);
- } else {
-
- this.posClip = posClip = renderer.clipRect(posAttr);
- this.negClip = negClip = renderer.clipRect(negAttr);
-
- if (graph && this.graphNeg) {
- graph.clip(posClip);
- this.graphNeg.clip(negClip);
- }
-
- if (area) {
- area.clip(posClip);
- this.areaNeg.clip(negClip);
- }
- }
- }
- },
-
- /**
- * Initialize and perform group inversion on series.group and series.markerGroup
- */
- invertGroups: function () {
- var series = this,
- chart = series.chart;
-
- // Pie, go away (#1736)
- if (!series.xAxis) {
- return;
- }
-
- // A fixed size is needed for inversion to work
- function setInvert() {
- var size = {
- width: series.yAxis.len,
- height: series.xAxis.len
- };
-
- each(['group', 'markerGroup'], function (groupName) {
- if (series[groupName]) {
- series[groupName].attr(size).invert();
- }
- });
- }
-
- addEvent(chart, 'resize', setInvert); // do it on resize
- addEvent(series, 'destroy', function () {
- removeEvent(chart, 'resize', setInvert);
- });
-
- // Do it now
- setInvert(); // do it now
-
- // On subsequent render and redraw, just do setInvert without setting up events again
- series.invertGroups = setInvert;
- },
-
- /**
- * General abstraction for creating plot groups like series.group, series.dataLabelsGroup and
- * series.markerGroup. On subsequent calls, the group will only be adjusted to the updated plot size.
- */
- plotGroup: function (prop, name, visibility, zIndex, parent) {
- var group = this[prop],
- isNew = !group;
-
- // Generate it on first call
- if (isNew) {
- this[prop] = group = this.chart.renderer.g(name)
- .attr({
- visibility: visibility,
- zIndex: zIndex || 0.1 // IE8 needs this
- })
- .add(parent);
- }
- // Place it on first and subsequent (redraw) calls
- group[isNew ? 'attr' : 'animate'](this.getPlotBox());
- return group;
- },
-
- /**
- * Get the translation and scale for the plot area of this series
- */
- getPlotBox: function () {
- return {
- translateX: this.xAxis ? this.xAxis.left : this.chart.plotLeft,
- translateY: this.yAxis ? this.yAxis.top : this.chart.plotTop,
- scaleX: 1, // #1623
- scaleY: 1
- };
- },
-
- /**
- * Render the graph and markers
- */
- render: function () {
- var series = this,
- chart = series.chart,
- group,
- options = series.options,
- animation = options.animation,
- doAnimation = animation && !!series.animate &&
- chart.renderer.isSVG, // this animation doesn't work in IE8 quirks when the group div is hidden,
- // and looks bad in other oldIE
- visibility = series.visible ? VISIBLE : HIDDEN,
- zIndex = options.zIndex,
- hasRendered = series.hasRendered,
- chartSeriesGroup = chart.seriesGroup;
-
- // the group
- group = series.plotGroup(
- 'group',
- 'series',
- visibility,
- zIndex,
- chartSeriesGroup
- );
-
- series.markerGroup = series.plotGroup(
- 'markerGroup',
- 'markers',
- visibility,
- zIndex,
- chartSeriesGroup
- );
-
- // initiate the animation
- if (doAnimation) {
- series.animate(true);
- }
-
- // cache attributes for shapes
- series.getAttribs();
-
- // SVGRenderer needs to know this before drawing elements (#1089, #1795)
- group.inverted = series.isCartesian ? chart.inverted : false;
-
- // draw the graph if any
- if (series.drawGraph) {
- series.drawGraph();
- series.clipNeg();
- }
-
- // draw the data labels (inn pies they go before the points)
- series.drawDataLabels();
-
- // draw the points
- series.drawPoints();
-
-
- // draw the mouse tracking area
- if (series.options.enableMouseTracking !== false) {
- series.drawTracker();
- }
-
- // Handle inverted series and tracker groups
- if (chart.inverted) {
- series.invertGroups();
- }
-
- // Initial clipping, must be defined after inverting groups for VML
- if (options.clip !== false && !series.sharedClipKey && !hasRendered) {
- group.clip(chart.clipRect);
- }
-
- // Run the animation
- if (doAnimation) {
- series.animate();
- } else if (!hasRendered) {
- series.afterAnimate();
- }
-
- series.isDirty = series.isDirtyData = false; // means data is in accordance with what you see
- // (See #322) series.isDirty = series.isDirtyData = false; // means data is in accordance with what you see
- series.hasRendered = true;
- },
-
- /**
- * Redraw the series after an update in the axes.
- */
- redraw: function () {
- var series = this,
- chart = series.chart,
- wasDirtyData = series.isDirtyData, // cache it here as it is set to false in render, but used after
- group = series.group,
- xAxis = series.xAxis,
- yAxis = series.yAxis;
-
- // reposition on resize
- if (group) {
- if (chart.inverted) {
- group.attr({
- width: chart.plotWidth,
- height: chart.plotHeight
- });
- }
-
- group.animate({
- translateX: pick(xAxis && xAxis.left, chart.plotLeft),
- translateY: pick(yAxis && yAxis.top, chart.plotTop)
- });
- }
-
- series.translate();
- series.setTooltipPoints(true);
-
- series.render();
- if (wasDirtyData) {
- fireEvent(series, 'updatedData');
- }
- },
-
- /**
- * Set the state of the graph
- */
- setState: function (state) {
- var series = this,
- options = series.options,
- graph = series.graph,
- graphNeg = series.graphNeg,
- stateOptions = options.states,
- lineWidth = options.lineWidth,
- attribs;
-
- state = state || NORMAL_STATE;
-
- if (series.state !== state) {
- series.state = state;
-
- if (stateOptions[state] && stateOptions[state].enabled === false) {
- return;
- }
-
- if (state) {
- lineWidth = stateOptions[state].lineWidth || lineWidth + 1;
- }
-
- if (graph && !graph.dashstyle) { // hover is turned off for dashed lines in VML
- attribs = {
- 'stroke-width': lineWidth
- };
- // use attr because animate will cause any other animation on the graph to stop
- graph.attr(attribs);
- if (graphNeg) {
- graphNeg.attr(attribs);
- }
- }
- }
- },
-
- /**
- * Set the visibility of the graph
- *
- * @param vis {Boolean} True to show the series, false to hide. If UNDEFINED,
- * the visibility is toggled.
- */
- setVisible: function (vis, redraw) {
- var series = this,
- chart = series.chart,
- legendItem = series.legendItem,
- showOrHide,
- ignoreHiddenSeries = chart.options.chart.ignoreHiddenSeries,
- oldVisibility = series.visible;
-
- // if called without an argument, toggle visibility
- series.visible = vis = series.userOptions.visible = vis === UNDEFINED ? !oldVisibility : vis;
- showOrHide = vis ? 'show' : 'hide';
-
- // show or hide elements
- each(['group', 'dataLabelsGroup', 'markerGroup', 'tracker'], function (key) {
- if (series[key]) {
- series[key][showOrHide]();
- }
- });
-
-
- // hide tooltip (#1361)
- if (chart.hoverSeries === series) {
- series.onMouseOut();
- }
-
-
- if (legendItem) {
- chart.legend.colorizeItem(series, vis);
- }
-
-
- // rescale or adapt to resized chart
- series.isDirty = true;
- // in a stack, all other series are affected
- if (series.options.stacking) {
- each(chart.series, function (otherSeries) {
- if (otherSeries.options.stacking && otherSeries.visible) {
- otherSeries.isDirty = true;
- }
- });
- }
-
- // show or hide linked series
- each(series.linkedSeries, function (otherSeries) {
- otherSeries.setVisible(vis, false);
- });
-
- if (ignoreHiddenSeries) {
- chart.isDirtyBox = true;
- }
- if (redraw !== false) {
- chart.redraw();
- }
-
- fireEvent(series, showOrHide);
- },
-
- /**
- * Show the graph
- */
- show: function () {
- this.setVisible(true);
- },
-
- /**
- * Hide the graph
- */
- hide: function () {
- this.setVisible(false);
- },
-
-
- /**
- * Set the selected state of the graph
- *
- * @param selected {Boolean} True to select the series, false to unselect. If
- * UNDEFINED, the selection state is toggled.
- */
- select: function (selected) {
- var series = this;
- // if called without an argument, toggle
- series.selected = selected = (selected === UNDEFINED) ? !series.selected : selected;
-
- if (series.checkbox) {
- series.checkbox.checked = selected;
- }
-
- fireEvent(series, selected ? 'select' : 'unselect');
- },
-
- /**
- * Draw the tracker object that sits above all data labels and markers to
- * track mouse events on the graph or points. For the line type charts
- * the tracker uses the same graphPath, but with a greater stroke width
- * for better control.
- */
- drawTracker: function () {
- var series = this,
- options = series.options,
- trackByArea = options.trackByArea,
- trackerPath = [].concat(trackByArea ? series.areaPath : series.graphPath),
- trackerPathLength = trackerPath.length,
- chart = series.chart,
- pointer = chart.pointer,
- renderer = chart.renderer,
- snap = chart.options.tooltip.snap,
- tracker = series.tracker,
- cursor = options.cursor,
- css = cursor && { cursor: cursor },
- singlePoints = series.singlePoints,
- singlePoint,
- i,
- onMouseOver = function () {
- if (chart.hoverSeries !== series) {
- series.onMouseOver();
- }
- };
-
- // Extend end points. A better way would be to use round linecaps,
- // but those are not clickable in VML.
- if (trackerPathLength && !trackByArea) {
- i = trackerPathLength + 1;
- while (i--) {
- if (trackerPath[i] === M) { // extend left side
- trackerPath.splice(i + 1, 0, trackerPath[i + 1] - snap, trackerPath[i + 2], L);
- }
- if ((i && trackerPath[i] === M) || i === trackerPathLength) { // extend right side
- trackerPath.splice(i, 0, L, trackerPath[i - 2] + snap, trackerPath[i - 1]);
- }
- }
- }
-
- // handle single points
- for (i = 0; i < singlePoints.length; i++) {
- singlePoint = singlePoints[i];
- trackerPath.push(M, singlePoint.plotX - snap, singlePoint.plotY,
- L, singlePoint.plotX + snap, singlePoint.plotY);
- }
-
-
-
- // draw the tracker
- if (tracker) {
- tracker.attr({ d: trackerPath });
-
- } else { // create
-
- series.tracker = tracker = renderer.path(trackerPath)
- .attr({
- 'class': PREFIX + 'tracker',
- 'stroke-linejoin': 'round', // #1225
- visibility: series.visible ? VISIBLE : HIDDEN,
- stroke: TRACKER_FILL,
- fill: trackByArea ? TRACKER_FILL : NONE,
- 'stroke-width' : options.lineWidth + (trackByArea ? 0 : 2 * snap),
- zIndex: 2
- })
- .addClass(PREFIX + 'tracker')
- .on('mouseover', onMouseOver)
- .on('mouseout', function (e) { pointer.onTrackerMouseOut(e); })
- .css(css)
- .add(series.markerGroup);
-
- if (hasTouch) {
- tracker.on('touchstart', onMouseOver);
- }
- }
-
- }
-
-}; // end Series prototype
-
-
-/**
- * LineSeries object
- */
-var LineSeries = extendClass(Series);
-seriesTypes.line = LineSeries;
-
-/**
- * Set the default options for area
- */
-defaultPlotOptions.area = merge(defaultSeriesOptions, {
- threshold: 0
- // trackByArea: false,
- // lineColor: null, // overrides color, but lets fillColor be unaltered
- // fillOpacity: 0.75,
- // fillColor: null
-});
-
-/**
- * AreaSeries object
- */
-var AreaSeries = extendClass(Series, {
- type: 'area',
-
- /**
- * For stacks, don't split segments on null values. Instead, draw null values with
- * no marker. Also insert dummy points for any X position that exists in other series
- * in the stack.
- */
- getSegments: function () {
- var segments = [],
- segment = [],
- keys = [],
- xAxis = this.xAxis,
- yAxis = this.yAxis,
- stack = yAxis.stacks[this.stackKey],
- pointMap = {},
- plotX,
- plotY,
- points = this.points,
- val,
- i,
- x;
-
- if (this.options.stacking && !this.cropped) { // cropped causes artefacts in Stock, and perf issue
- // Create a map where we can quickly look up the points by their X value.
- for (i = 0; i < points.length; i++) {
- pointMap[points[i].x] = points[i];
- }
-
- // Sort the keys (#1651)
- for (x in stack) {
- keys.push(+x);
- }
- keys.sort(function (a, b) {
- return a - b;
- });
-
- each(keys, function (x) {
- // The point exists, push it to the segment
- if (pointMap[x]) {
- segment.push(pointMap[x]);
-
- // There is no point for this X value in this series, so we
- // insert a dummy point in order for the areas to be drawn
- // correctly.
- } else {
- plotX = xAxis.translate(x);
- val = stack[x].percent ? (stack[x].total ? stack[x].cum * 100 / stack[x].total : 0) : stack[x].cum; // #1991
- plotY = yAxis.toPixels(val, true);
- segment.push({
- y: null,
- plotX: plotX,
- clientX: plotX,
- plotY: plotY,
- yBottom: plotY,
- onMouseOver: noop
- });
- }
- });
-
- if (segment.length) {
- segments.push(segment);
- }
-
- } else {
- Series.prototype.getSegments.call(this);
- segments = this.segments;
- }
-
- this.segments = segments;
- },
-
- /**
- * Extend the base Series getSegmentPath method by adding the path for the area.
- * This path is pushed to the series.areaPath property.
- */
- getSegmentPath: function (segment) {
-
- var segmentPath = Series.prototype.getSegmentPath.call(this, segment), // call base method
- areaSegmentPath = [].concat(segmentPath), // work on a copy for the area path
- i,
- options = this.options,
- segLength = segmentPath.length;
-
- if (segLength === 3) { // for animation from 1 to two points
- areaSegmentPath.push(L, segmentPath[1], segmentPath[2]);
- }
- if (options.stacking && !this.closedStacks) {
-
- // Follow stack back. Todo: implement areaspline. A general solution could be to
- // reverse the entire graphPath of the previous series, though may be hard with
- // splines and with series with different extremes
- for (i = segment.length - 1; i >= 0; i--) {
-
- // step line?
- if (i < segment.length - 1 && options.step) {
- areaSegmentPath.push(segment[i + 1].plotX, segment[i].yBottom);
- }
-
- areaSegmentPath.push(segment[i].plotX, segment[i].yBottom);
- }
-
- } else { // follow zero line back
- this.closeSegment(areaSegmentPath, segment);
- }
- this.areaPath = this.areaPath.concat(areaSegmentPath);
-
- return segmentPath;
- },
-
- /**
- * Extendable method to close the segment path of an area. This is overridden in polar
- * charts.
- */
- closeSegment: function (path, segment) {
- var translatedThreshold = this.yAxis.getThreshold(this.options.threshold);
- path.push(
- L,
- segment[segment.length - 1].plotX,
- translatedThreshold,
- L,
- segment[0].plotX,
- translatedThreshold
- );
- },
-
- /**
- * Draw the graph and the underlying area. This method calls the Series base
- * function and adds the area. The areaPath is calculated in the getSegmentPath
- * method called from Series.prototype.drawGraph.
- */
- drawGraph: function () {
-
- // Define or reset areaPath
- this.areaPath = [];
-
- // Call the base method
- Series.prototype.drawGraph.apply(this);
-
- // Define local variables
- var series = this,
- areaPath = this.areaPath,
- options = this.options,
- negativeColor = options.negativeColor,
- negativeFillColor = options.negativeFillColor,
- props = [['area', this.color, options.fillColor]]; // area name, main color, fill color
-
- if (negativeColor || negativeFillColor) {
- props.push(['areaNeg', negativeColor, negativeFillColor]);
- }
-
- each(props, function (prop) {
- var areaKey = prop[0],
- area = series[areaKey];
-
- // Create or update the area
- if (area) { // update
- area.animate({ d: areaPath });
-
- } else { // create
- series[areaKey] = series.chart.renderer.path(areaPath)
- .attr({
- fill: pick(
- prop[2],
- Color(prop[1]).setOpacity(pick(options.fillOpacity, 0.75)).get()
- ),
- zIndex: 0 // #1069
- }).add(series.group);
- }
- });
- },
-
- /**
- * Get the series' symbol in the legend
- *
- * @param {Object} legend The legend object
- * @param {Object} item The series (this) or point
- */
- drawLegendSymbol: function (legend, item) {
-
- item.legendSymbol = this.chart.renderer.rect(
- 0,
- legend.baseline - 11,
- legend.options.symbolWidth,
- 12,
- 2
- ).attr({
- zIndex: 3
- }).add(item.legendGroup);
-
- }
-});
-
-seriesTypes.area = AreaSeries;/**
- * Set the default options for spline
- */
-defaultPlotOptions.spline = merge(defaultSeriesOptions);
-
-/**
- * SplineSeries object
- */
-var SplineSeries = extendClass(Series, {
- type: 'spline',
-
- /**
- * Get the spline segment from a given point's previous neighbour to the given point
- */
- getPointSpline: function (segment, point, i) {
- var smoothing = 1.5, // 1 means control points midway between points, 2 means 1/3 from the point, 3 is 1/4 etc
- denom = smoothing + 1,
- plotX = point.plotX,
- plotY = point.plotY,
- lastPoint = segment[i - 1],
- nextPoint = segment[i + 1],
- leftContX,
- leftContY,
- rightContX,
- rightContY,
- ret;
-
- // find control points
- if (lastPoint && nextPoint) {
-
- var lastX = lastPoint.plotX,
- lastY = lastPoint.plotY,
- nextX = nextPoint.plotX,
- nextY = nextPoint.plotY,
- correction;
-
- leftContX = (smoothing * plotX + lastX) / denom;
- leftContY = (smoothing * plotY + lastY) / denom;
- rightContX = (smoothing * plotX + nextX) / denom;
- rightContY = (smoothing * plotY + nextY) / denom;
-
- // have the two control points make a straight line through main point
- correction = ((rightContY - leftContY) * (rightContX - plotX)) /
- (rightContX - leftContX) + plotY - rightContY;
-
- leftContY += correction;
- rightContY += correction;
-
- // to prevent false extremes, check that control points are between
- // neighbouring points' y values
- if (leftContY > lastY && leftContY > plotY) {
- leftContY = mathMax(lastY, plotY);
- rightContY = 2 * plotY - leftContY; // mirror of left control point
- } else if (leftContY < lastY && leftContY < plotY) {
- leftContY = mathMin(lastY, plotY);
- rightContY = 2 * plotY - leftContY;
- }
- if (rightContY > nextY && rightContY > plotY) {
- rightContY = mathMax(nextY, plotY);
- leftContY = 2 * plotY - rightContY;
- } else if (rightContY < nextY && rightContY < plotY) {
- rightContY = mathMin(nextY, plotY);
- leftContY = 2 * plotY - rightContY;
- }
-
- // record for drawing in next point
- point.rightContX = rightContX;
- point.rightContY = rightContY;
-
- }
-
- // Visualize control points for debugging
- /*
- if (leftContX) {
- this.chart.renderer.circle(leftContX + this.chart.plotLeft, leftContY + this.chart.plotTop, 2)
- .attr({
- stroke: 'red',
- 'stroke-width': 1,
- fill: 'none'
- })
- .add();
- this.chart.renderer.path(['M', leftContX + this.chart.plotLeft, leftContY + this.chart.plotTop,
- 'L', plotX + this.chart.plotLeft, plotY + this.chart.plotTop])
- .attr({
- stroke: 'red',
- 'stroke-width': 1
- })
- .add();
- this.chart.renderer.circle(rightContX + this.chart.plotLeft, rightContY + this.chart.plotTop, 2)
- .attr({
- stroke: 'green',
- 'stroke-width': 1,
- fill: 'none'
- })
- .add();
- this.chart.renderer.path(['M', rightContX + this.chart.plotLeft, rightContY + this.chart.plotTop,
- 'L', plotX + this.chart.plotLeft, plotY + this.chart.plotTop])
- .attr({
- stroke: 'green',
- 'stroke-width': 1
- })
- .add();
- }
- */
-
- // moveTo or lineTo
- if (!i) {
- ret = [M, plotX, plotY];
- } else { // curve from last point to this
- ret = [
- 'C',
- lastPoint.rightContX || lastPoint.plotX,
- lastPoint.rightContY || lastPoint.plotY,
- leftContX || plotX,
- leftContY || plotY,
- plotX,
- plotY
- ];
- lastPoint.rightContX = lastPoint.rightContY = null; // reset for updating series later
- }
- return ret;
- }
-});
-seriesTypes.spline = SplineSeries;
-
-/**
- * Set the default options for areaspline
- */
-defaultPlotOptions.areaspline = merge(defaultPlotOptions.area);
-
-/**
- * AreaSplineSeries object
- */
-var areaProto = AreaSeries.prototype,
- AreaSplineSeries = extendClass(SplineSeries, {
- type: 'areaspline',
- closedStacks: true, // instead of following the previous graph back, follow the threshold back
-
- // Mix in methods from the area series
- getSegmentPath: areaProto.getSegmentPath,
- closeSegment: areaProto.closeSegment,
- drawGraph: areaProto.drawGraph,
- drawLegendSymbol: areaProto.drawLegendSymbol
- });
-seriesTypes.areaspline = AreaSplineSeries;
-
-/**
- * Set the default options for column
- */
-defaultPlotOptions.column = merge(defaultSeriesOptions, {
- borderColor: '#FFFFFF',
- borderWidth: 1,
- borderRadius: 0,
- //colorByPoint: undefined,
- groupPadding: 0.2,
- //grouping: true,
- marker: null, // point options are specified in the base options
- pointPadding: 0.1,
- //pointWidth: null,
- minPointLength: 0,
- cropThreshold: 50, // when there are more points, they will not animate out of the chart on xAxis.setExtremes
- pointRange: null, // null means auto, meaning 1 in a categorized axis and least distance between points if not categories
- states: {
- hover: {
- brightness: 0.1,
- shadow: false
- },
- select: {
- color: '#C0C0C0',
- borderColor: '#000000',
- shadow: false
- }
- },
- dataLabels: {
- align: null, // auto
- verticalAlign: null, // auto
- y: null
- },
- stickyTracking: false,
- threshold: 0
-});
-
-/**
- * ColumnSeries object
- */
-var ColumnSeries = extendClass(Series, {
- type: 'column',
- tooltipOutsidePlot: true,
- pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
- stroke: 'borderColor',
- 'stroke-width': 'borderWidth',
- fill: 'color',
- r: 'borderRadius'
- },
- trackerGroups: ['group', 'dataLabelsGroup'],
-
- /**
- * Initialize the series
- */
- init: function () {
- Series.prototype.init.apply(this, arguments);
-
- var series = this,
- chart = series.chart;
-
- // if the series is added dynamically, force redraw of other
- // series affected by a new column
- if (chart.hasRendered) {
- each(chart.series, function (otherSeries) {
- if (otherSeries.type === series.type) {
- otherSeries.isDirty = true;
- }
- });
- }
- },
-
- /**
- * Return the width and x offset of the columns adjusted for grouping, groupPadding, pointPadding,
- * pointWidth etc.
- */
- getColumnMetrics: function () {
-
- var series = this,
- options = series.options,
- xAxis = this.xAxis,
- reversedXAxis = xAxis.reversed,
- stackKey,
- stackGroups = {},
- columnIndex,
- columnCount = 0;
-
- // Get the total number of column type series.
- // This is called on every series. Consider moving this logic to a
- // chart.orderStacks() function and call it on init, addSeries and removeSeries
- if (options.grouping === false) {
- columnCount = 1;
- } else {
- each(series.yAxis.series, function (otherSeries) { // use Y axes separately, #642
- var otherOptions = otherSeries.options;
- if (otherSeries.type === series.type && otherSeries.visible &&
- series.options.group === otherOptions.group) { // used in Stock charts navigator series
- if (otherOptions.stacking) {
- stackKey = otherSeries.stackKey;
- if (stackGroups[stackKey] === UNDEFINED) {
- stackGroups[stackKey] = columnCount++;
- }
- columnIndex = stackGroups[stackKey];
- } else if (otherOptions.grouping !== false) { // #1162
- columnIndex = columnCount++;
- }
- otherSeries.columnIndex = columnIndex;
- }
- });
- }
-
- var categoryWidth = mathMin(
- mathAbs(xAxis.transA) * (xAxis.ordinalSlope || options.pointRange || xAxis.closestPointRange || 1),
- xAxis.len // #1535
- ),
- groupPadding = categoryWidth * options.groupPadding,
- groupWidth = categoryWidth - 2 * groupPadding,
- pointOffsetWidth = groupWidth / columnCount,
- optionPointWidth = options.pointWidth,
- pointPadding = defined(optionPointWidth) ? (pointOffsetWidth - optionPointWidth) / 2 :
- pointOffsetWidth * options.pointPadding,
- pointWidth = pick(optionPointWidth, pointOffsetWidth - 2 * pointPadding), // exact point width, used in polar charts
- colIndex = (reversedXAxis ?
- columnCount - (series.columnIndex || 0) : // #1251
- series.columnIndex) || 0,
- pointXOffset = pointPadding + (groupPadding + colIndex *
- pointOffsetWidth - (categoryWidth / 2)) *
- (reversedXAxis ? -1 : 1);
-
- // Save it for reading in linked series (Error bars particularly)
- return (series.columnMetrics = {
- width: pointWidth,
- offset: pointXOffset
- });
-
- },
-
- /**
- * Translate each point to the plot area coordinate system and find shape positions
- */
- translate: function () {
- var series = this,
- chart = series.chart,
- options = series.options,
- borderWidth = options.borderWidth,
- yAxis = series.yAxis,
- threshold = options.threshold,
- translatedThreshold = series.translatedThreshold = yAxis.getThreshold(threshold),
- minPointLength = pick(options.minPointLength, 5),
- metrics = series.getColumnMetrics(),
- pointWidth = metrics.width,
- barW = series.barW = mathCeil(mathMax(pointWidth, 1 + 2 * borderWidth)), // rounded and postprocessed for border width
- pointXOffset = series.pointXOffset = metrics.offset;
-
- Series.prototype.translate.apply(series);
-
- // record the new values
- each(series.points, function (point) {
- var plotY = mathMin(mathMax(-999, point.plotY), yAxis.len + 999), // Don't draw too far outside plot area (#1303)
- yBottom = pick(point.yBottom, translatedThreshold),
- barX = point.plotX + pointXOffset,
- barY = mathCeil(mathMin(plotY, yBottom)),
- barH = mathCeil(mathMax(plotY, yBottom) - barY),
- shapeArgs;
-
- // handle options.minPointLength
- if (mathAbs(barH) < minPointLength) {
- if (minPointLength) {
- barH = minPointLength;
- barY =
- mathRound(mathAbs(barY - translatedThreshold) > minPointLength ? // stacked
- yBottom - minPointLength : // keep position
- translatedThreshold - (yAxis.translate(point.y, 0, 1, 0, 1) <= translatedThreshold ? minPointLength : 0)); // use exact yAxis.translation (#1485)
- }
- }
-
- point.barX = barX;
- point.pointWidth = pointWidth;
-
- // create shape type and shape args that are reused in drawPoints and drawTracker
- point.shapeType = 'rect';
- point.shapeArgs = shapeArgs = chart.renderer.Element.prototype.crisp.call(0, borderWidth, barX, barY, barW, barH);
-
- if (borderWidth % 2) { // correct for shorting in crisp method, visible in stacked columns with 1px border
- shapeArgs.y -= 1;
- shapeArgs.height += 1;
- }
-
- });
-
- },
-
- getSymbol: noop,
-
- /**
- * Use a solid rectangle like the area series types
- */
- drawLegendSymbol: AreaSeries.prototype.drawLegendSymbol,
-
-
- /**
- * Columns have no graph
- */
- drawGraph: noop,
-
- /**
- * Draw the columns. For bars, the series.group is rotated, so the same coordinates
- * apply for columns and bars. This method is inherited by scatter series.
- *
- */
- drawPoints: function () {
- var series = this,
- options = series.options,
- renderer = series.chart.renderer,
- shapeArgs;
-
-
- // draw the columns
- each(series.points, function (point) {
- var plotY = point.plotY,
- graphic = point.graphic;
-
- if (plotY !== UNDEFINED && !isNaN(plotY) && point.y !== null) {
- shapeArgs = point.shapeArgs;
-
- if (graphic) { // update
- stop(graphic);
- graphic.animate(merge(shapeArgs));
-
- } else {
- point.graphic = graphic = renderer[point.shapeType](shapeArgs)
- .attr(point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE])
- .add(series.group)
- .shadow(options.shadow, null, options.stacking && !options.borderRadius);
- }
-
- } else if (graphic) {
- point.graphic = graphic.destroy(); // #1269
- }
- });
- },
-
- /**
- * Add tracking event listener to the series group, so the point graphics
- * themselves act as trackers
- */
- drawTracker: function () {
- var series = this,
- chart = series.chart,
- pointer = chart.pointer,
- cursor = series.options.cursor,
- css = cursor && { cursor: cursor },
- onMouseOver = function (e) {
- var target = e.target,
- point;
-
- if (chart.hoverSeries !== series) {
- series.onMouseOver();
- }
- while (target && !point) {
- point = target.point;
- target = target.parentNode;
- }
- if (point !== UNDEFINED && point !== chart.hoverPoint) { // undefined on graph in scatterchart
- point.onMouseOver(e);
- }
- };
-
- // Add reference to the point
- each(series.points, function (point) {
- if (point.graphic) {
- point.graphic.element.point = point;
- }
- if (point.dataLabel) {
- point.dataLabel.element.point = point;
- }
- });
-
- // Add the event listeners, we need to do this only once
- if (!series._hasTracking) {
- each(series.trackerGroups, function (key) {
- if (series[key]) { // we don't always have dataLabelsGroup
- series[key]
- .addClass(PREFIX + 'tracker')
- .on('mouseover', onMouseOver)
- .on('mouseout', function (e) { pointer.onTrackerMouseOut(e); })
- .css(css);
- if (hasTouch) {
- series[key].on('touchstart', onMouseOver);
- }
- }
- });
-
- } else {
- series._hasTracking = true;
- }
- },
-
- /**
- * Override the basic data label alignment by adjusting for the position of the column
- */
- alignDataLabel: function (point, dataLabel, options, alignTo, isNew) {
- var chart = this.chart,
- inverted = chart.inverted,
- dlBox = point.dlBox || point.shapeArgs, // data label box for alignment
- below = point.below || (point.plotY > pick(this.translatedThreshold, chart.plotSizeY)),
- inside = pick(options.inside, !!this.options.stacking); // draw it inside the box?
-
- // Align to the column itself, or the top of it
- if (dlBox) { // Area range uses this method but not alignTo
- alignTo = merge(dlBox);
- if (inverted) {
- alignTo = {
- x: chart.plotWidth - alignTo.y - alignTo.height,
- y: chart.plotHeight - alignTo.x - alignTo.width,
- width: alignTo.height,
- height: alignTo.width
- };
- }
-
- // Compute the alignment box
- if (!inside) {
- if (inverted) {
- alignTo.x += below ? 0 : alignTo.width;
- alignTo.width = 0;
- } else {
- alignTo.y += below ? alignTo.height : 0;
- alignTo.height = 0;
- }
- }
- }
-
- // When alignment is undefined (typically columns and bars), display the individual
- // point below or above the point depending on the threshold
- options.align = pick(
- options.align,
- !inverted || inside ? 'center' : below ? 'right' : 'left'
- );
- options.verticalAlign = pick(
- options.verticalAlign,
- inverted || inside ? 'middle' : below ? 'top' : 'bottom'
- );
-
- // Call the parent method
- Series.prototype.alignDataLabel.call(this, point, dataLabel, options, alignTo, isNew);
- },
-
-
- /**
- * Animate the column heights one by one from zero
- * @param {Boolean} init Whether to initialize the animation or run it
- */
- animate: function (init) {
- var series = this,
- yAxis = this.yAxis,
- options = series.options,
- inverted = this.chart.inverted,
- attr = {},
- translatedThreshold;
-
- if (hasSVG) { // VML is too slow anyway
- if (init) {
- attr.scaleY = 0.001;
- translatedThreshold = mathMin(yAxis.pos + yAxis.len, mathMax(yAxis.pos, yAxis.toPixels(options.threshold)));
- if (inverted) {
- attr.translateX = translatedThreshold - yAxis.len;
- } else {
- attr.translateY = translatedThreshold;
- }
- series.group.attr(attr);
-
- } else { // run the animation
-
- attr.scaleY = 1;
- attr[inverted ? 'translateX' : 'translateY'] = yAxis.pos;
- series.group.animate(attr, series.options.animation);
-
- // delete this function to allow it only once
- series.animate = null;
- }
- }
- },
-
- /**
- * Remove this series from the chart
- */
- remove: function () {
- var series = this,
- chart = series.chart;
-
- // column and bar series affects other series of the same type
- // as they are either stacked or grouped
- if (chart.hasRendered) {
- each(chart.series, function (otherSeries) {
- if (otherSeries.type === series.type) {
- otherSeries.isDirty = true;
- }
- });
- }
-
- Series.prototype.remove.apply(series, arguments);
- }
-});
-seriesTypes.column = ColumnSeries;
-/**
- * Set the default options for bar
- */
-defaultPlotOptions.bar = merge(defaultPlotOptions.column);
-/**
- * The Bar series class
- */
-var BarSeries = extendClass(ColumnSeries, {
- type: 'bar',
- inverted: true
-});
-seriesTypes.bar = BarSeries;
-
-/**
- * Set the default options for scatter
- */
-defaultPlotOptions.scatter = merge(defaultSeriesOptions, {
- lineWidth: 0,
- tooltip: {
- headerFormat: '<span style="font-size: 10px; color:{series.color}">{series.name}</span><br/>',
- pointFormat: 'x: <b>{point.x}</b><br/>y: <b>{point.y}</b><br/>',
- followPointer: true
- },
- stickyTracking: false
-});
-
-/**
- * The scatter series class
- */
-var ScatterSeries = extendClass(Series, {
- type: 'scatter',
- sorted: false,
- requireSorting: false,
- noSharedTooltip: true,
- trackerGroups: ['markerGroup'],
-
- drawTracker: ColumnSeries.prototype.drawTracker,
-
- setTooltipPoints: noop
-});
-seriesTypes.scatter = ScatterSeries;
-
-/**
- * Set the default options for pie
- */
-defaultPlotOptions.pie = merge(defaultSeriesOptions, {
- borderColor: '#FFFFFF',
- borderWidth: 1,
- center: [null, null],
- clip: false,
- colorByPoint: true, // always true for pies
- dataLabels: {
- // align: null,
- // connectorWidth: 1,
- // connectorColor: point.color,
- // connectorPadding: 5,
- distance: 30,
- enabled: true,
- formatter: function () {
- return this.point.name;
- }
- // softConnector: true,
- //y: 0
- },
- ignoreHiddenPoint: true,
- //innerSize: 0,
- legendType: 'point',
- marker: null, // point options are specified in the base options
- size: null,
- showInLegend: false,
- slicedOffset: 10,
- states: {
- hover: {
- brightness: 0.1,
- shadow: false
- }
- },
- stickyTracking: false,
- tooltip: {
- followPointer: true
- }
-});
-
-/**
- * Extended point object for pies
- */
-var PiePoint = extendClass(Point, {
- /**
- * Initiate the pie slice
- */
- init: function () {
-
- Point.prototype.init.apply(this, arguments);
-
- var point = this,
- toggleSlice;
-
- // Disallow negative values (#1530)
- if (point.y < 0) {
- point.y = null;
- }
-
- //visible: options.visible !== false,
- extend(point, {
- visible: point.visible !== false,
- name: pick(point.name, 'Slice')
- });
-
- // add event listener for select
- toggleSlice = function (e) {
- point.slice(e.type === 'select');
- };
- addEvent(point, 'select', toggleSlice);
- addEvent(point, 'unselect', toggleSlice);
-
- return point;
- },
-
- /**
- * Toggle the visibility of the pie slice
- * @param {Boolean} vis Whether to show the slice or not. If undefined, the
- * visibility is toggled
- */
- setVisible: function (vis) {
- var point = this,
- series = point.series,
- chart = series.chart,
- method;
-
- // if called without an argument, toggle visibility
- point.visible = point.options.visible = vis = vis === UNDEFINED ? !point.visible : vis;
- series.options.data[inArray(point, series.data)] = point.options; // update userOptions.data
-
- method = vis ? 'show' : 'hide';
-
- // Show and hide associated elements
- each(['graphic', 'dataLabel', 'connector', 'shadowGroup'], function (key) {
- if (point[key]) {
- point[key][method]();
- }
- });
-
- if (point.legendItem) {
- chart.legend.colorizeItem(point, vis);
- }
-
- // Handle ignore hidden slices
- if (!series.isDirty && series.options.ignoreHiddenPoint) {
- series.isDirty = true;
- chart.redraw();
- }
- },
-
- /**
- * Set or toggle whether the slice is cut out from the pie
- * @param {Boolean} sliced When undefined, the slice state is toggled
- * @param {Boolean} redraw Whether to redraw the chart. True by default.
- */
- slice: function (sliced, redraw, animation) {
- var point = this,
- series = point.series,
- chart = series.chart,
- translation;
-
- setAnimation(animation, chart);
-
- // redraw is true by default
- redraw = pick(redraw, true);
-
- // if called without an argument, toggle
- point.sliced = point.options.sliced = sliced = defined(sliced) ? sliced : !point.sliced;
- series.options.data[inArray(point, series.data)] = point.options; // update userOptions.data
-
- translation = sliced ? point.slicedTranslation : {
- translateX: 0,
- translateY: 0
- };
-
- point.graphic.animate(translation);
-
- if (point.shadowGroup) {
- point.shadowGroup.animate(translation);
- }
-
- }
-});
-
-/**
- * The Pie series class
- */
-var PieSeries = {
- type: 'pie',
- isCartesian: false,
- pointClass: PiePoint,
- requireSorting: false,
- noSharedTooltip: true,
- trackerGroups: ['group', 'dataLabelsGroup'],
- pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
- stroke: 'borderColor',
- 'stroke-width': 'borderWidth',
- fill: 'color'
- },
-
- /**
- * Pies have one color each point
- */
- getColor: noop,
-
- /**
- * Animate the pies in
- */
- animate: function (init) {
- var series = this,
- points = series.points,
- startAngleRad = series.startAngleRad;
-
- if (!init) {
- each(points, function (point) {
- var graphic = point.graphic,
- args = point.shapeArgs;
-
- if (graphic) {
- // start values
- graphic.attr({
- r: series.center[3] / 2, // animate from inner radius (#779)
- start: startAngleRad,
- end: startAngleRad
- });
-
- // animate
- graphic.animate({
- r: args.r,
- start: args.start,
- end: args.end
- }, series.options.animation);
- }
- });
-
- // delete this function to allow it only once
- series.animate = null;
- }
- },
-
- /**
- * Extend the basic setData method by running processData and generatePoints immediately,
- * in order to access the points from the legend.
- */
- setData: function (data, redraw) {
- Series.prototype.setData.call(this, data, false);
- this.processData();
- this.generatePoints();
- if (pick(redraw, true)) {
- this.chart.redraw();
- }
- },
-
- /**
- * Extend the generatePoints method by adding total and percentage properties to each point
- */
- generatePoints: function () {
- var i,
- total = 0,
- points,
- len,
- point,
- ignoreHiddenPoint = this.options.ignoreHiddenPoint;
-
- Series.prototype.generatePoints.call(this);
-
- // Populate local vars
- points = this.points;
- len = points.length;
-
- // Get the total sum
- for (i = 0; i < len; i++) {
- point = points[i];
- total += (ignoreHiddenPoint && !point.visible) ? 0 : point.y;
- }
- this.total = total;
-
- // Set each point's properties
- for (i = 0; i < len; i++) {
- point = points[i];
- point.percentage = (point.y / total) * 100;
- point.total = total;
- }
-
- },
-
- /**
- * Get the center of the pie based on the size and center options relative to the
- * plot area. Borrowed by the polar and gauge series types.
- */
- getCenter: function () {
-
- var options = this.options,
- chart = this.chart,
- slicingRoom = 2 * (options.slicedOffset || 0),
- handleSlicingRoom,
- plotWidth = chart.plotWidth - 2 * slicingRoom,
- plotHeight = chart.plotHeight - 2 * slicingRoom,
- centerOption = options.center,
- positions = [pick(centerOption[0], '50%'), pick(centerOption[1], '50%'), options.size || '100%', options.innerSize || 0],
- smallestSize = mathMin(plotWidth, plotHeight),
- isPercent;
-
- return map(positions, function (length, i) {
- isPercent = /%$/.test(length);
- handleSlicingRoom = i < 2 || (i === 2 && isPercent);
- return (isPercent ?
- // i == 0: centerX, relative to width
- // i == 1: centerY, relative to height
- // i == 2: size, relative to smallestSize
- // i == 4: innerSize, relative to smallestSize
- [plotWidth, plotHeight, smallestSize, smallestSize][i] *
- pInt(length) / 100 :
- length) + (handleSlicingRoom ? slicingRoom : 0);
- });
- },
-
- /**
- * Do translation for pie slices
- */
- translate: function (positions) {
- this.generatePoints();
-
- var series = this,
- cumulative = 0,
- precision = 1000, // issue #172
- options = series.options,
- slicedOffset = options.slicedOffset,
- connectorOffset = slicedOffset + options.borderWidth,
- start,
- end,
- angle,
- startAngleRad = series.startAngleRad = mathPI / 180 * ((options.startAngle || 0) % 360 - 90),
- points = series.points,
- circ = 2 * mathPI,
- radiusX, // the x component of the radius vector for a given point
- radiusY,
- labelDistance = options.dataLabels.distance,
- ignoreHiddenPoint = options.ignoreHiddenPoint,
- i,
- len = points.length,
- point;
-
- // Get positions - either an integer or a percentage string must be given.
- // If positions are passed as a parameter, we're in a recursive loop for adjusting
- // space for data labels.
- if (!positions) {
- series.center = positions = series.getCenter();
- }
-
- // utility for getting the x value from a given y, used for anticollision logic in data labels
- series.getX = function (y, left) {
-
- angle = math.asin((y - positions[1]) / (positions[2] / 2 + labelDistance));
-
- return positions[0] +
- (left ? -1 : 1) *
- (mathCos(angle) * (positions[2] / 2 + labelDistance));
- };
-
- // Calculate the geometry for each point
- for (i = 0; i < len; i++) {
-
- point = points[i];
-
- // set start and end angle
- start = mathRound((startAngleRad + (cumulative * circ)) * precision) / precision;
- if (!ignoreHiddenPoint || point.visible) {
- cumulative += point.percentage / 100;
- }
- end = mathRound((startAngleRad + (cumulative * circ)) * precision) / precision;
-
- // set the shape
- point.shapeType = 'arc';
- point.shapeArgs = {
- x: positions[0],
- y: positions[1],
- r: positions[2] / 2,
- innerR: positions[3] / 2,
- start: start,
- end: end
- };
-
- // center for the sliced out slice
- angle = (end + start) / 2;
- if (angle > 0.75 * circ) {
- angle -= 2 * mathPI;
- }
- point.slicedTranslation = {
- translateX: mathRound(mathCos(angle) * slicedOffset),
- translateY: mathRound(mathSin(angle) * slicedOffset)
- };
-
- // set the anchor point for tooltips
- radiusX = mathCos(angle) * positions[2] / 2;
- radiusY = mathSin(angle) * positions[2] / 2;
- point.tooltipPos = [
- positions[0] + radiusX * 0.7,
- positions[1] + radiusY * 0.7
- ];
-
- point.half = angle < circ / 4 ? 0 : 1;
- point.angle = angle;
-
- // set the anchor point for data labels
- connectorOffset = mathMin(connectorOffset, labelDistance / 2); // #1678
- point.labelPos = [
- positions[0] + radiusX + mathCos(angle) * labelDistance, // first break of connector
- positions[1] + radiusY + mathSin(angle) * labelDistance, // a/a
- positions[0] + radiusX + mathCos(angle) * connectorOffset, // second break, right outside pie
- positions[1] + radiusY + mathSin(angle) * connectorOffset, // a/a
- positions[0] + radiusX, // landing point for connector
- positions[1] + radiusY, // a/a
- labelDistance < 0 ? // alignment
- 'center' :
- point.half ? 'right' : 'left', // alignment
- angle // center angle
- ];
-
- }
-
-
- this.setTooltipPoints();
- },
-
- drawGraph: null,
-
- /**
- * Draw the data points
- */
- drawPoints: function () {
- var series = this,
- chart = series.chart,
- renderer = chart.renderer,
- groupTranslation,
- //center,
- graphic,
- //group,
- shadow = series.options.shadow,
- shadowGroup,
- shapeArgs;
-
- if (shadow && !series.shadowGroup) {
- series.shadowGroup = renderer.g('shadow')
- .add(series.group);
- }
-
- // draw the slices
- each(series.points, function (point) {
- graphic = point.graphic;
- shapeArgs = point.shapeArgs;
- shadowGroup = point.shadowGroup;
-
- // put the shadow behind all points
- if (shadow && !shadowGroup) {
- shadowGroup = point.shadowGroup = renderer.g('shadow')
- .add(series.shadowGroup);
- }
-
- // if the point is sliced, use special translation, else use plot area traslation
- groupTranslation = point.sliced ? point.slicedTranslation : {
- translateX: 0,
- translateY: 0
- };
-
- //group.translate(groupTranslation[0], groupTranslation[1]);
- if (shadowGroup) {
- shadowGroup.attr(groupTranslation);
- }
-
- // draw the slice
- if (graphic) {
- graphic.animate(extend(shapeArgs, groupTranslation));
- } else {
- point.graphic = graphic = renderer.arc(shapeArgs)
- .setRadialReference(series.center)
- .attr(
- point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE]
- )
- .attr({ 'stroke-linejoin': 'round' })
- .attr(groupTranslation)
- .add(series.group)
- .shadow(shadow, shadowGroup);
- }
-
- // detect point specific visibility
- if (point.visible === false) {
- point.setVisible(false);
- }
-
- });
-
- },
-
- /**
- * Override the base drawDataLabels method by pie specific functionality
- */
- drawDataLabels: function () {
- var series = this,
- data = series.data,
- point,
- chart = series.chart,
- options = series.options.dataLabels,
- connectorPadding = pick(options.connectorPadding, 10),
- connectorWidth = pick(options.connectorWidth, 1),
- plotWidth = chart.plotWidth,
- plotHeight = chart.plotHeight,
- connector,
- connectorPath,
- softConnector = pick(options.softConnector, true),
- distanceOption = options.distance,
- seriesCenter = series.center,
- radius = seriesCenter[2] / 2,
- centerY = seriesCenter[1],
- outside = distanceOption > 0,
- dataLabel,
- dataLabelWidth,
- labelPos,
- labelHeight,
- halves = [// divide the points into right and left halves for anti collision
- [], // right
- [] // left
- ],
- x,
- y,
- visibility,
- rankArr,
- i,
- j,
- overflow = [0, 0, 0, 0], // top, right, bottom, left
- sort = function (a, b) {
- return b.y - a.y;
- },
- sortByAngle = function (points, sign) {
- points.sort(function (a, b) {
- return a.angle !== undefined && (b.angle - a.angle) * sign;
- });
- };
-
- // get out if not enabled
- if (!series.visible || (!options.enabled && !series._hasPointLabels)) {
- return;
- }
-
- // run parent method
- Series.prototype.drawDataLabels.apply(series);
-
- // arrange points for detection collision
- each(data, function (point) {
- if (point.dataLabel) { // it may have been cancelled in the base method (#407)
- halves[point.half].push(point);
- }
- });
-
- // assume equal label heights
- i = 0;
- while (!labelHeight && data[i]) { // #1569
- labelHeight = data[i] && data[i].dataLabel && (data[i].dataLabel.getBBox().height || 21); // 21 is for #968
- i++;
- }
-
- /* Loop over the points in each half, starting from the top and bottom
- * of the pie to detect overlapping labels.
- */
- i = 2;
- while (i--) {
-
- var slots = [],
- slotsLength,
- usedSlots = [],
- points = halves[i],
- pos,
- length = points.length,
- slotIndex;
-
- // Sort by angle
- sortByAngle(points, i - 0.5);
-
- // Only do anti-collision when we are outside the pie and have connectors (#856)
- if (distanceOption > 0) {
-
- // build the slots
- for (pos = centerY - radius - distanceOption; pos <= centerY + radius + distanceOption; pos += labelHeight) {
- slots.push(pos);
-
- // visualize the slot
- /*
- var slotX = series.getX(pos, i) + chart.plotLeft - (i ? 100 : 0),
- slotY = pos + chart.plotTop;
- if (!isNaN(slotX)) {
- chart.renderer.rect(slotX, slotY - 7, 100, labelHeight, 1)
- .attr({
- 'stroke-width': 1,
- stroke: 'silver'
- })
- .add();
- chart.renderer.text('Slot '+ (slots.length - 1), slotX, slotY + 4)
- .attr({
- fill: 'silver'
- }).add();
- }
- */
- }
- slotsLength = slots.length;
-
- // if there are more values than available slots, remove lowest values
- if (length > slotsLength) {
- // create an array for sorting and ranking the points within each quarter
- rankArr = [].concat(points);
- rankArr.sort(sort);
- j = length;
- while (j--) {
- rankArr[j].rank = j;
- }
- j = length;
- while (j--) {
- if (points[j].rank >= slotsLength) {
- points.splice(j, 1);
- }
- }
- length = points.length;
- }
-
- // The label goes to the nearest open slot, but not closer to the edge than
- // the label's index.
- for (j = 0; j < length; j++) {
-
- point = points[j];
- labelPos = point.labelPos;
-
- var closest = 9999,
- distance,
- slotI;
-
- // find the closest slot index
- for (slotI = 0; slotI < slotsLength; slotI++) {
- distance = mathAbs(slots[slotI] - labelPos[1]);
- if (distance < closest) {
- closest = distance;
- slotIndex = slotI;
- }
- }
-
- // if that slot index is closer to the edges of the slots, move it
- // to the closest appropriate slot
- if (slotIndex < j && slots[j] !== null) { // cluster at the top
- slotIndex = j;
- } else if (slotsLength < length - j + slotIndex && slots[j] !== null) { // cluster at the bottom
- slotIndex = slotsLength - length + j;
- while (slots[slotIndex] === null) { // make sure it is not taken
- slotIndex++;
- }
- } else {
- // Slot is taken, find next free slot below. In the next run, the next slice will find the
- // slot above these, because it is the closest one
- while (slots[slotIndex] === null) { // make sure it is not taken
- slotIndex++;
- }
- }
-
- usedSlots.push({ i: slotIndex, y: slots[slotIndex] });
- slots[slotIndex] = null; // mark as taken
- }
- // sort them in order to fill in from the top
- usedSlots.sort(sort);
- }
-
- // now the used slots are sorted, fill them up sequentially
- for (j = 0; j < length; j++) {
-
- var slot, naturalY;
-
- point = points[j];
- labelPos = point.labelPos;
- dataLabel = point.dataLabel;
- visibility = point.visible === false ? HIDDEN : VISIBLE;
- naturalY = labelPos[1];
-
- if (distanceOption > 0) {
- slot = usedSlots.pop();
- slotIndex = slot.i;
-
- // if the slot next to currrent slot is free, the y value is allowed
- // to fall back to the natural position
- y = slot.y;
- if ((naturalY > y && slots[slotIndex + 1] !== null) ||
- (naturalY < y && slots[slotIndex - 1] !== null)) {
- y = naturalY;
- }
-
- } else {
- y = naturalY;
- }
-
- // get the x - use the natural x position for first and last slot, to prevent the top
- // and botton slice connectors from touching each other on either side
- x = options.justify ?
- seriesCenter[0] + (i ? -1 : 1) * (radius + distanceOption) :
- series.getX(slotIndex === 0 || slotIndex === slots.length - 1 ? naturalY : y, i);
-
-
- // Record the placement and visibility
- dataLabel._attr = {
- visibility: visibility,
- align: labelPos[6]
- };
- dataLabel._pos = {
- x: x + options.x +
- ({ left: connectorPadding, right: -connectorPadding }[labelPos[6]] || 0),
- y: y + options.y - 10 // 10 is for the baseline (label vs text)
- };
- dataLabel.connX = x;
- dataLabel.connY = y;
-
-
- // Detect overflowing data labels
- if (this.options.size === null) {
- dataLabelWidth = dataLabel.width;
- // Overflow left
- if (x - dataLabelWidth < connectorPadding) {
- overflow[3] = mathMax(mathRound(dataLabelWidth - x + connectorPadding), overflow[3]);
-
- // Overflow right
- } else if (x + dataLabelWidth > plotWidth - connectorPadding) {
- overflow[1] = mathMax(mathRound(x + dataLabelWidth - plotWidth + connectorPadding), overflow[1]);
- }
-
- // Overflow top
- if (y - labelHeight / 2 < 0) {
- overflow[0] = mathMax(mathRound(-y + labelHeight / 2), overflow[0]);
-
- // Overflow left
- } else if (y + labelHeight / 2 > plotHeight) {
- overflow[2] = mathMax(mathRound(y + labelHeight / 2 - plotHeight), overflow[2]);
- }
- }
- } // for each point
- } // for each half
-
- // Do not apply the final placement and draw the connectors until we have verified
- // that labels are not spilling over.
- if (arrayMax(overflow) === 0 || this.verifyDataLabelOverflow(overflow)) {
-
- // Place the labels in the final position
- this.placeDataLabels();
-
- // Draw the connectors
- if (outside && connectorWidth) {
- each(this.points, function (point) {
- connector = point.connector;
- labelPos = point.labelPos;
- dataLabel = point.dataLabel;
-
- if (dataLabel && dataLabel._pos) {
- visibility = dataLabel._attr.visibility;
- x = dataLabel.connX;
- y = dataLabel.connY;
- connectorPath = softConnector ? [
- M,
- x + (labelPos[6] === 'left' ? 5 : -5), y, // end of the string at the label
- 'C',
- x, y, // first break, next to the label
- 2 * labelPos[2] - labelPos[4], 2 * labelPos[3] - labelPos[5],
- labelPos[2], labelPos[3], // second break
- L,
- labelPos[4], labelPos[5] // base
- ] : [
- M,
- x + (labelPos[6] === 'left' ? 5 : -5), y, // end of the string at the label
- L,
- labelPos[2], labelPos[3], // second break
- L,
- labelPos[4], labelPos[5] // base
- ];
-
- if (connector) {
- connector.animate({ d: connectorPath });
- connector.attr('visibility', visibility);
-
- } else {
- point.connector = connector = series.chart.renderer.path(connectorPath).attr({
- 'stroke-width': connectorWidth,
- stroke: options.connectorColor || point.color || '#606060',
- visibility: visibility
- })
- .add(series.group);
- }
- } else if (connector) {
- point.connector = connector.destroy();
- }
- });
- }
- }
- },
-
- /**
- * Verify whether the data labels are allowed to draw, or we should run more translation and data
- * label positioning to keep them inside the plot area. Returns true when data labels are ready
- * to draw.
- */
- verifyDataLabelOverflow: function (overflow) {
-
- var center = this.center,
- options = this.options,
- centerOption = options.center,
- minSize = options.minSize || 80,
- newSize = minSize,
- ret;
-
- // Handle horizontal size and center
- if (centerOption[0] !== null) { // Fixed center
- newSize = mathMax(center[2] - mathMax(overflow[1], overflow[3]), minSize);
-
- } else { // Auto center
- newSize = mathMax(
- center[2] - overflow[1] - overflow[3], // horizontal overflow
- minSize
- );
- center[0] += (overflow[3] - overflow[1]) / 2; // horizontal center
- }
-
- // Handle vertical size and center
- if (centerOption[1] !== null) { // Fixed center
- newSize = mathMax(mathMin(newSize, center[2] - mathMax(overflow[0], overflow[2])), minSize);
-
- } else { // Auto center
- newSize = mathMax(
- mathMin(
- newSize,
- center[2] - overflow[0] - overflow[2] // vertical overflow
- ),
- minSize
- );
- center[1] += (overflow[0] - overflow[2]) / 2; // vertical center
- }
-
- // If the size must be decreased, we need to run translate and drawDataLabels again
- if (newSize < center[2]) {
- center[2] = newSize;
- this.translate(center);
- each(this.points, function (point) {
- if (point.dataLabel) {
- point.dataLabel._pos = null; // reset
- }
- });
- this.drawDataLabels();
-
- // Else, return true to indicate that the pie and its labels is within the plot area
- } else {
- ret = true;
- }
- return ret;
- },
-
- /**
- * Perform the final placement of the data labels after we have verified that they
- * fall within the plot area.
- */
- placeDataLabels: function () {
- each(this.points, function (point) {
- var dataLabel = point.dataLabel,
- _pos;
-
- if (dataLabel) {
- _pos = dataLabel._pos;
- if (_pos) {
- dataLabel.attr(dataLabel._attr);
- dataLabel[dataLabel.moved ? 'animate' : 'attr'](_pos);
- dataLabel.moved = true;
- } else if (dataLabel) {
- dataLabel.attr({ y: -999 });
- }
- }
- });
- },
-
- alignDataLabel: noop,
-
- /**
- * Draw point specific tracker objects. Inherit directly from column series.
- */
- drawTracker: ColumnSeries.prototype.drawTracker,
-
- /**
- * Use a simple symbol from column prototype
- */
- drawLegendSymbol: AreaSeries.prototype.drawLegendSymbol,
-
- /**
- * Pies don't have point marker symbols
- */
- getSymbol: noop
-
-};
-PieSeries = extendClass(Series, PieSeries);
-seriesTypes.pie = PieSeries;
-
-
-// global variables
-extend(Highcharts, {
-
- // Constructors
- Axis: Axis,
- Chart: Chart,
- Color: Color,
- Legend: Legend,
- Pointer: Pointer,
- Point: Point,
- Tick: Tick,
- Tooltip: Tooltip,
- Renderer: Renderer,
- Series: Series,
- SVGElement: SVGElement,
- SVGRenderer: SVGRenderer,
-
- // Various
- arrayMin: arrayMin,
- arrayMax: arrayMax,
- charts: charts,
- dateFormat: dateFormat,
- format: format,
- pathAnim: pathAnim,
- getOptions: getOptions,
- hasBidiBug: hasBidiBug,
- isTouchDevice: isTouchDevice,
- numberFormat: numberFormat,
- seriesTypes: seriesTypes,
- setOptions: setOptions,
- addEvent: addEvent,
- removeEvent: removeEvent,
- createElement: createElement,
- discardElement: discardElement,
- css: css,
- each: each,
- extend: extend,
- map: map,
- merge: merge,
- pick: pick,
- splat: splat,
- extendClass: extendClass,
- pInt: pInt,
- wrap: wrap,
- svg: hasSVG,
- canvas: useCanVG,
- vml: !hasSVG && !useCanVG,
- product: PRODUCT,
- version: VERSION
-});
-}());
+ License: www.highcharts.com/license
+*/
+(function(){function s(a,b){var c;a||(a={});for(c in b)a[c]=b[c];return a}function x(){var a,b=arguments.length,c={},d=function(a,b){var c,h;typeof a!=="object"&&(a={});for(h in b)b.hasOwnProperty(h)&&(c=b[h],a[h]=c&&typeof c==="object"&&Object.prototype.toString.call(c)!=="[object Array]"&&typeof c.nodeType!=="number"?d(a[h]||{},c):b[h]);return a};for(a=0;a<b;a++)c=d(c,arguments[a]);return c}function A(a,b){return parseInt(a,b||10)}function ea(a){return typeof a==="string"}function T(a){return typeof a===
+"object"}function Ha(a){return Object.prototype.toString.call(a)==="[object Array]"}function qa(a){return typeof a==="number"}function na(a){return R.log(a)/R.LN10}function fa(a){return R.pow(10,a)}function ga(a,b){for(var c=a.length;c--;)if(a[c]===b){a.splice(c,1);break}}function t(a){return a!==v&&a!==null}function w(a,b,c){var d,e;if(ea(b))t(c)?a.setAttribute(b,c):a&&a.getAttribute&&(e=a.getAttribute(b));else if(t(b)&&T(b))for(d in b)a.setAttribute(d,b[d]);return e}function ia(a){return Ha(a)?
+a:[a]}function p(){var a=arguments,b,c,d=a.length;for(b=0;b<d;b++)if(c=a[b],typeof c!=="undefined"&&c!==null)return c}function L(a,b){if(ra&&b&&b.opacity!==v)b.filter="alpha(opacity="+b.opacity*100+")";s(a.style,b)}function U(a,b,c,d,e){a=z.createElement(a);b&&s(a,b);e&&L(a,{padding:0,border:S,margin:0});c&&L(a,c);d&&d.appendChild(a);return a}function ha(a,b){var c=function(){};c.prototype=new a;s(c.prototype,b);return c}function za(a,b,c,d){var e=M.lang,a=+a||0,f=b===-1?(a.toString().split(".")[1]||
+"").length:isNaN(b=O(b))?2:b,b=c===void 0?e.decimalPoint:c,d=d===void 0?e.thousandsSep:d,e=a<0?"-":"",c=String(A(a=O(a).toFixed(f))),g=c.length>3?c.length%3:0;return e+(g?c.substr(0,g)+d:"")+c.substr(g).replace(/(\d{3})(?=\d)/g,"$1"+d)+(f?b+O(a-c).toFixed(f).slice(2):"")}function Aa(a,b){return Array((b||2)+1-String(a).length).join(0)+a}function Bb(a,b,c){var d=a[b];a[b]=function(){var a=Array.prototype.slice.call(arguments);a.unshift(d);return c.apply(this,a)}}function Ba(a,b){for(var c="{",d=!1,
+e,f,g,h,i,j=[];(c=a.indexOf(c))!==-1;){e=a.slice(0,c);if(d){f=e.split(":");g=f.shift().split(".");i=g.length;e=b;for(h=0;h<i;h++)e=e[g[h]];if(f.length)f=f.join(":"),g=/\.([0-9])/,h=M.lang,i=void 0,/f$/.test(f)?(i=(i=f.match(g))?i[1]:-1,e=za(e,i,h.decimalPoint,f.indexOf(",")>-1?h.thousandsSep:"")):e=Xa(f,e)}j.push(e);a=a.slice(c+1);c=(d=!d)?"}":"{"}j.push(a);return j.join("")}function lb(a){return R.pow(10,P(R.log(a)/R.LN10))}function mb(a,b,c,d){var e,c=p(c,1);e=a/c;b||(b=[1,2,2.5,5,10],d&&d.allowDecimals===
+!1&&(c===1?b=[1,2,5,10]:c<=0.1&&(b=[1/c])));for(d=0;d<b.length;d++)if(a=b[d],e<=(b[d]+(b[d+1]||b[d]))/2)break;a*=c;return a}function Cb(a,b){var c=b||[[Db,[1,2,5,10,20,25,50,100,200,500]],[nb,[1,2,5,10,15,30]],[Ya,[1,2,5,10,15,30]],[Qa,[1,2,3,4,6,8,12]],[sa,[1,2]],[Za,[1,2]],[Ra,[1,2,3,4,6]],[ta,null]],d=c[c.length-1],e=H[d[0]],f=d[1],g;for(g=0;g<c.length;g++)if(d=c[g],e=H[d[0]],f=d[1],c[g+1]&&a<=(e*f[f.length-1]+H[c[g+1][0]])/2)break;e===H[ta]&&a<5*e&&(f=[1,2,5]);e===H[ta]&&a<5*e&&(f=[1,2,5]);c=
+mb(a/e,f,d[0]===ta?lb(a/e):1);return{unitRange:e,count:c,unitName:d[0]}}function Eb(a,b,c,d){var e=[],f={},g=M.global.useUTC,h,i=new Date(b),j=a.unitRange,k=a.count;if(t(b)){j>=H[nb]&&(i.setMilliseconds(0),i.setSeconds(j>=H[Ya]?0:k*P(i.getSeconds()/k)));if(j>=H[Ya])i[Fb](j>=H[Qa]?0:k*P(i[ob]()/k));if(j>=H[Qa])i[Gb](j>=H[sa]?0:k*P(i[pb]()/k));if(j>=H[sa])i[qb](j>=H[Ra]?1:k*P(i[Sa]()/k));j>=H[Ra]&&(i[Hb](j>=H[ta]?0:k*P(i[$a]()/k)),h=i[ab]());j>=H[ta]&&(h-=h%k,i[Ib](h));if(j===H[Za])i[qb](i[Sa]()-i[rb]()+
+p(d,1));b=1;h=i[ab]();for(var d=i.getTime(),l=i[$a](),m=i[Sa](),o=g?0:(864E5+i.getTimezoneOffset()*6E4)%864E5;d<c;)e.push(d),j===H[ta]?d=bb(h+b*k,0):j===H[Ra]?d=bb(h,l+b*k):!g&&(j===H[sa]||j===H[Za])?d=bb(h,l,m+b*k*(j===H[sa]?1:7)):d+=j*k,b++;e.push(d);n(sb(e,function(a){return j<=H[Qa]&&a%H[sa]===o}),function(a){f[a]=sa})}e.info=s(a,{higherRanks:f,totalRange:j*k});return e}function Jb(){this.symbol=this.color=0}function Kb(a,b){var c=a.length,d,e;for(e=0;e<c;e++)a[e].ss_i=e;a.sort(function(a,c){d=
+b(a,c);return d===0?a.ss_i-c.ss_i:d});for(e=0;e<c;e++)delete a[e].ss_i}function Ia(a){for(var b=a.length,c=a[0];b--;)a[b]<c&&(c=a[b]);return c}function ua(a){for(var b=a.length,c=a[0];b--;)a[b]>c&&(c=a[b]);return c}function Ja(a,b){for(var c in a)a[c]&&a[c]!==b&&a[c].destroy&&a[c].destroy(),delete a[c]}function Ta(a){cb||(cb=U(Ca));a&&cb.appendChild(a);cb.innerHTML=""}function ja(a,b){var c="Highcharts error #"+a+": www.highcharts.com/errors/"+a;if(b)throw c;else N.console&&console.log(c)}function ka(a){return parseFloat(a.toPrecision(14))}
+function Ka(a,b){Da=p(a,b.animation)}function Lb(){var a=M.global.useUTC,b=a?"getUTC":"get",c=a?"setUTC":"set";bb=a?Date.UTC:function(a,b,c,g,h,i){return(new Date(a,b,p(c,1),p(g,0),p(h,0),p(i,0))).getTime()};ob=b+"Minutes";pb=b+"Hours";rb=b+"Day";Sa=b+"Date";$a=b+"Month";ab=b+"FullYear";Fb=c+"Minutes";Gb=c+"Hours";qb=c+"Date";Hb=c+"Month";Ib=c+"FullYear"}function va(){}function La(a,b,c,d){this.axis=a;this.pos=b;this.type=c||"";this.isNew=!0;!c&&!d&&this.addLabel()}function tb(a,b){this.axis=a;if(b)this.options=
+b,this.id=b.id}function Mb(a,b,c,d,e,f){var g=a.chart.inverted;this.axis=a;this.isNegative=c;this.options=b;this.x=d;this.total=null;this.points={};this.stack=e;this.percent=f==="percent";this.alignOptions={align:b.align||(g?c?"left":"right":"center"),verticalAlign:b.verticalAlign||(g?"middle":c?"bottom":"top"),y:p(b.y,g?4:c?14:-6),x:p(b.x,g?c?-6:6:0)};this.textAlign=b.textAlign||(g?c?"right":"left":"center")}function db(){this.init.apply(this,arguments)}function ub(){this.init.apply(this,arguments)}
+function vb(a,b){this.init(a,b)}function wb(a,b){this.init(a,b)}function xb(){this.init.apply(this,arguments)}var v,z=document,N=window,R=Math,u=R.round,P=R.floor,wa=R.ceil,r=R.max,C=R.min,O=R.abs,W=R.cos,ca=R.sin,Ma=R.PI,Ua=Ma*2/360,Ea=navigator.userAgent,Nb=N.opera,ra=/msie/i.test(Ea)&&!Nb,eb=z.documentMode===8,fb=/AppleWebKit/.test(Ea),gb=/Firefox/.test(Ea),Ob=/(Mobile|Android|Windows Phone)/.test(Ea),xa="http://www.w3.org/2000/svg",Z=!!z.createElementNS&&!!z.createElementNS(xa,"svg").createSVGRect,
+Ub=gb&&parseInt(Ea.split("Firefox/")[1],10)<4,$=!Z&&!ra&&!!z.createElement("canvas").getContext,Va,hb=z.documentElement.ontouchstart!==v,Pb={},yb=0,cb,M,Xa,Da,zb,H,ya=function(){},Fa=[],Ca="div",S="none",Qb="rgba(192,192,192,"+(Z?1.0E-4:0.002)+")",Db="millisecond",nb="second",Ya="minute",Qa="hour",sa="day",Za="week",Ra="month",ta="year",Rb="stroke-width",bb,ob,pb,rb,Sa,$a,ab,Fb,Gb,qb,Hb,Ib,aa={};N.Highcharts=N.Highcharts?ja(16,!0):{};Xa=function(a,b,c){if(!t(b)||isNaN(b))return"Invalid date";var a=
+p(a,"%Y-%m-%d %H:%M:%S"),d=new Date(b),e,f=d[pb](),g=d[rb](),h=d[Sa](),i=d[$a](),j=d[ab](),k=M.lang,l=k.weekdays,d=s({a:l[g].substr(0,3),A:l[g],d:Aa(h),e:h,b:k.shortMonths[i],B:k.months[i],m:Aa(i+1),y:j.toString().substr(2,2),Y:j,H:Aa(f),I:Aa(f%12||12),l:f%12||12,M:Aa(d[ob]()),p:f<12?"AM":"PM",P:f<12?"am":"pm",S:Aa(d.getSeconds()),L:Aa(u(b%1E3),3)},Highcharts.dateFormats);for(e in d)for(;a.indexOf("%"+e)!==-1;)a=a.replace("%"+e,typeof d[e]==="function"?d[e](b):d[e]);return c?a.substr(0,1).toUpperCase()+
+a.substr(1):a};Jb.prototype={wrapColor:function(a){if(this.color>=a)this.color=0},wrapSymbol:function(a){if(this.symbol>=a)this.symbol=0}};H=function(){for(var a=0,b=arguments,c=b.length,d={};a<c;a++)d[b[a++]]=b[a];return d}(Db,1,nb,1E3,Ya,6E4,Qa,36E5,sa,864E5,Za,6048E5,Ra,26784E5,ta,31556952E3);zb={init:function(a,b,c){var b=b||"",d=a.shift,e=b.indexOf("C")>-1,f=e?7:3,g,b=b.split(" "),c=[].concat(c),h,i,j=function(a){for(g=a.length;g--;)a[g]==="M"&&a.splice(g+1,0,a[g+1],a[g+2],a[g+1],a[g+2])};e&&
+(j(b),j(c));a.isArea&&(h=b.splice(b.length-6,6),i=c.splice(c.length-6,6));if(d<=c.length/f)for(;d--;)c=[].concat(c).splice(0,f).concat(c);a.shift=0;if(b.length)for(a=c.length;b.length<a;)d=[].concat(b).splice(b.length-f,f),e&&(d[f-6]=d[f-2],d[f-5]=d[f-1]),b=b.concat(d);h&&(b=b.concat(h),c=c.concat(i));return[b,c]},step:function(a,b,c,d){var e=[],f=a.length;if(c===1)e=d;else if(f===b.length&&c<1)for(;f--;)d=parseFloat(a[f]),e[f]=isNaN(d)?a[f]:c*parseFloat(b[f]-d)+d;else e=b;return e}};(function(a){N.HighchartsAdapter=
+N.HighchartsAdapter||a&&{init:function(b){var c=a.fx,d=c.step,e,f=a.Tween,g=f&&f.propHooks;e=a.cssHooks.opacity;a.extend(a.easing,{easeOutQuad:function(a,b,c,d,e){return-d*(b/=e)*(b-2)+c}});a.each(["cur","_default","width","height","opacity"],function(a,b){var e=d,k,l;b==="cur"?e=c.prototype:b==="_default"&&f&&(e=g[b],b="set");(k=e[b])&&(e[b]=function(c){c=a?c:this;l=c.elem;return l.attr?l.attr(c.prop,b==="cur"?v:c.now):k.apply(this,arguments)})});Bb(e,"get",function(a,b,c){return b.attr?b.opacity||
+0:a.call(this,b,c)});e=function(a){var c=a.elem,d;if(!a.started)d=b.init(c,c.d,c.toD),a.start=d[0],a.end=d[1],a.started=!0;c.attr("d",b.step(a.start,a.end,a.pos,c.toD))};f?g.d={set:e}:d.d=e;this.each=Array.prototype.forEach?function(a,b){return Array.prototype.forEach.call(a,b)}:function(a,b){for(var c=0,d=a.length;c<d;c++)if(b.call(a[c],a[c],c,a)===!1)return c};a.fn.highcharts=function(){var a="Chart",b=arguments,c,d;ea(b[0])&&(a=b[0],b=Array.prototype.slice.call(b,1));c=b[0];if(c!==v)c.chart=c.chart||
+{},c.chart.renderTo=this[0],new Highcharts[a](c,b[1]),d=this;c===v&&(d=Fa[w(this[0],"data-highcharts-chart")]);return d}},getScript:a.getScript,inArray:a.inArray,adapterRun:function(b,c){return a(b)[c]()},grep:a.grep,map:function(a,c){for(var d=[],e=0,f=a.length;e<f;e++)d[e]=c.call(a[e],a[e],e,a);return d},offset:function(b){return a(b).offset()},addEvent:function(b,c,d){a(b).bind(c,d)},removeEvent:function(b,c,d){var e=z.removeEventListener?"removeEventListener":"detachEvent";z[e]&&b&&!b[e]&&(b[e]=
+function(){});a(b).unbind(c,d)},fireEvent:function(b,c,d,e){var f=a.Event(c),g="detached"+c,h;!ra&&d&&(delete d.layerX,delete d.layerY);s(f,d);b[c]&&(b[g]=b[c],b[c]=null);a.each(["preventDefault","stopPropagation"],function(a,b){var c=f[b];f[b]=function(){try{c.call(f)}catch(a){b==="preventDefault"&&(h=!0)}}});a(b).trigger(f);b[g]&&(b[c]=b[g],b[g]=null);e&&!f.isDefaultPrevented()&&!h&&e(f)},washMouseEvent:function(a){var c=a.originalEvent||a;if(c.pageX===v)c.pageX=a.pageX,c.pageY=a.pageY;return c},
+animate:function(b,c,d){var e=a(b);if(!b.style)b.style={};if(c.d)b.toD=c.d,c.d=1;e.stop();c.opacity!==v&&b.attr&&(c.opacity+="px");e.animate(c,d)},stop:function(b){a(b).stop()}}})(N.jQuery);var X=N.HighchartsAdapter,F=X||{};X&&X.init.call(X,zb);var ib=F.adapterRun,Vb=F.getScript,oa=F.inArray,n=F.each,sb=F.grep,Wb=F.offset,Na=F.map,J=F.addEvent,ba=F.removeEvent,K=F.fireEvent,Sb=F.washMouseEvent,Ab=F.animate,Wa=F.stop,F={enabled:!0,x:0,y:15,style:{color:"#666",cursor:"default",fontSize:"11px",lineHeight:"14px"}};
+M={colors:"#2f7ed8,#0d233a,#8bbc21,#910000,#1aadce,#492970,#f28f43,#77a1e5,#c42525,#a6c96a".split(","),symbols:["circle","diamond","square","triangle","triangle-down"],lang:{loading:"Loading...",months:"January,February,March,April,May,June,July,August,September,October,November,December".split(","),shortMonths:"Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec".split(","),weekdays:"Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday".split(","),decimalPoint:".",numericSymbols:"k,M,G,T,P,E".split(","),
+resetZoom:"Reset zoom",resetZoomTitle:"Reset zoom level 1:1",thousandsSep:","},global:{useUTC:!0,canvasToolsURL:"http://code.highcharts.com/3.0.5/modules/canvas-tools.js",VMLRadialGradientURL:"http://code.highcharts.com/3.0.5/gfx/vml-radial-gradient.png"},chart:{borderColor:"#4572A7",borderRadius:5,defaultSeriesType:"line",ignoreHiddenSeries:!0,spacingTop:10,spacingRight:10,spacingBottom:15,spacingLeft:10,style:{fontFamily:'"Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, Helvetica, sans-serif',
+fontSize:"12px"},backgroundColor:"#FFFFFF",plotBorderColor:"#C0C0C0",resetZoomButton:{theme:{zIndex:20},position:{align:"right",x:-10,y:10}}},title:{text:"Chart title",align:"center",margin:15,style:{color:"#274b6d",fontSize:"16px"}},subtitle:{text:"",align:"center",style:{color:"#4d759e"}},plotOptions:{line:{allowPointSelect:!1,showCheckbox:!1,animation:{duration:1E3},events:{},lineWidth:2,marker:{enabled:!0,lineWidth:0,radius:4,lineColor:"#FFFFFF",states:{hover:{enabled:!0},select:{fillColor:"#FFFFFF",
+lineColor:"#000000",lineWidth:2}}},point:{events:{}},dataLabels:x(F,{align:"center",enabled:!1,formatter:function(){return this.y===null?"":za(this.y,-1)},verticalAlign:"bottom",y:0}),cropThreshold:300,pointRange:0,showInLegend:!0,states:{hover:{marker:{}},select:{marker:{}}},stickyTracking:!0}},labels:{style:{position:"absolute",color:"#3E576F"}},legend:{enabled:!0,align:"center",layout:"horizontal",labelFormatter:function(){return this.name},borderWidth:1,borderColor:"#909090",borderRadius:5,navigation:{activeColor:"#274b6d",
+inactiveColor:"#CCC"},shadow:!1,itemStyle:{cursor:"pointer",color:"#274b6d",fontSize:"12px"},itemHoverStyle:{color:"#000"},itemHiddenStyle:{color:"#CCC"},itemCheckboxStyle:{position:"absolute",width:"13px",height:"13px"},symbolWidth:16,symbolPadding:5,verticalAlign:"bottom",x:0,y:0,title:{style:{fontWeight:"bold"}}},loading:{labelStyle:{fontWeight:"bold",position:"relative",top:"1em"},style:{position:"absolute",backgroundColor:"white",opacity:0.5,textAlign:"center"}},tooltip:{enabled:!0,animation:Z,
+backgroundColor:"rgba(255, 255, 255, .85)",borderWidth:1,borderRadius:3,dateTimeLabelFormats:{millisecond:"%A, %b %e, %H:%M:%S.%L",second:"%A, %b %e, %H:%M:%S",minute:"%A, %b %e, %H:%M",hour:"%A, %b %e, %H:%M",day:"%A, %b %e, %Y",week:"Week from %A, %b %e, %Y",month:"%B %Y",year:"%Y"},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:!0,snap:Ob?25:10,style:{color:"#333333",cursor:"default",
+fontSize:"12px",padding:"8px",whiteSpace:"nowrap"}},credits:{enabled:!0,text:"Highcharts.com",href:"http://www.highcharts.com",position:{align:"right",x:-10,verticalAlign:"bottom",y:-5},style:{cursor:"pointer",color:"#909090",fontSize:"9px"}}};var Y=M.plotOptions,X=Y.line;Lb();var pa=function(a){var b=[],c,d;(function(a){a&&a.stops?d=Na(a.stops,function(a){return pa(a[1])}):(c=/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(a))?b=[A(c[1]),A(c[2]),
+A(c[3]),parseFloat(c[4],10)]:(c=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(a))?b=[A(c[1],16),A(c[2],16),A(c[3],16),1]:(c=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(a))&&(b=[A(c[1]),A(c[2]),A(c[3]),1])})(a);return{get:function(c){var f;d?(f=x(a),f.stops=[].concat(f.stops),n(d,function(a,b){f.stops[b]=[f.stops[b][0],a.get(c)]})):f=b&&!isNaN(b[0])?c==="rgb"?"rgb("+b[0]+","+b[1]+","+b[2]+")":c==="a"?b[3]:"rgba("+b.join(",")+")":a;return f},brighten:function(a){if(d)n(d,
+function(b){b.brighten(a)});else if(qa(a)&&a!==0){var c;for(c=0;c<3;c++)b[c]+=A(a*255),b[c]<0&&(b[c]=0),b[c]>255&&(b[c]=255)}return this},rgba:b,setOpacity:function(a){b[3]=a;return this}}};va.prototype={init:function(a,b){this.element=b==="span"?U(b):z.createElementNS(xa,b);this.renderer=a;this.attrSetters={}},opacity:1,animate:function(a,b,c){b=p(b,Da,!0);Wa(this);if(b){b=x(b);if(c)b.complete=c;Ab(this,a,b)}else this.attr(a),c&&c()},attr:function(a,b){var c,d,e,f,g=this.element,h=g.nodeName.toLowerCase(),
+i=this.renderer,j,k=this.attrSetters,l=this.shadows,m,o,q=this;ea(a)&&t(b)&&(c=a,a={},a[c]=b);if(ea(a))c=a,h==="circle"?c={x:"cx",y:"cy"}[c]||c:c==="strokeWidth"&&(c="stroke-width"),q=w(g,c)||this[c]||0,c!=="d"&&c!=="visibility"&&(q=parseFloat(q));else{for(c in a)if(j=!1,d=a[c],e=k[c]&&k[c].call(this,d,c),e!==!1){e!==v&&(d=e);if(c==="d")d&&d.join&&(d=d.join(" ")),/(NaN| {2}|^$)/.test(d)&&(d="M 0 0");else if(c==="x"&&h==="text")for(e=0;e<g.childNodes.length;e++)f=g.childNodes[e],w(f,"x")===w(g,"x")&&
+w(f,"x",d);else if(this.rotation&&(c==="x"||c==="y"))o=!0;else if(c==="fill")d=i.color(d,g,c);else if(h==="circle"&&(c==="x"||c==="y"))c={x:"cx",y:"cy"}[c]||c;else if(h==="rect"&&c==="r")w(g,{rx:d,ry:d}),j=!0;else if(c==="translateX"||c==="translateY"||c==="rotation"||c==="verticalAlign"||c==="scaleX"||c==="scaleY")j=o=!0;else if(c==="stroke")d=i.color(d,g,c);else if(c==="dashstyle")if(c="stroke-dasharray",d=d&&d.toLowerCase(),d==="solid")d=S;else{if(d){d=d.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(",");for(e=d.length;e--;)d[e]=A(d[e])*p(a["stroke-width"],this["stroke-width"]);d=d.join(",")}}else if(c==="width")d=A(d);else if(c==="align")c="text-anchor",d={left:"start",center:"middle",right:"end"}[d];else if(c==="title")e=g.getElementsByTagName("title")[0],e||(e=z.createElementNS(xa,"title"),g.appendChild(e)),e.textContent=d;c==="strokeWidth"&&
+(c="stroke-width");if(c==="stroke-width"||c==="stroke"){this[c]=d;if(this.stroke&&this["stroke-width"])w(g,"stroke",this.stroke),w(g,"stroke-width",this["stroke-width"]),this.hasStroke=!0;else if(c==="stroke-width"&&d===0&&this.hasStroke)g.removeAttribute("stroke"),this.hasStroke=!1;j=!0}this.symbolName&&/^(x|y|width|height|r|start|end|innerR|anchorX|anchorY)/.test(c)&&(m||(this.symbolAttr(a),m=!0),j=!0);if(l&&/^(width|height|visibility|x|y|d|transform|cx|cy|r)$/.test(c))for(e=l.length;e--;)w(l[e],
+c,c==="height"?r(d-(l[e].cutHeight||0),0):d);if((c==="width"||c==="height")&&h==="rect"&&d<0)d=0;this[c]=d;c==="text"?(d!==this.textStr&&delete this.bBox,this.textStr=d,this.added&&i.buildText(this)):j||w(g,c,d)}o&&this.updateTransform()}return q},addClass:function(a){var b=this.element,c=w(b,"class")||"";c.indexOf(a)===-1&&w(b,"class",c+" "+a);return this},symbolAttr:function(a){var b=this;n("x,y,r,start,end,width,height,innerR,anchorX,anchorY".split(","),function(c){b[c]=p(a[c],b[c])});b.attr({d:b.renderer.symbols[b.symbolName](b.x,
+b.y,b.width,b.height,b)})},clip:function(a){return this.attr("clip-path",a?"url("+this.renderer.url+"#"+a.id+")":S)},crisp:function(a,b,c,d,e){var f,g={},h={},i,a=a||this.strokeWidth||this.attr&&this.attr("stroke-width")||0;i=u(a)%2/2;h.x=P(b||this.x||0)+i;h.y=P(c||this.y||0)+i;h.width=P((d||this.width||0)-2*i);h.height=P((e||this.height||0)-2*i);h.strokeWidth=a;for(f in h)this[f]!==h[f]&&(this[f]=g[f]=h[f]);return g},css:function(a){var b=this.element,c=a&&a.width&&b.nodeName.toLowerCase()==="text",
+d,e="",f=function(a,b){return"-"+b.toLowerCase()};if(a&&a.color)a.fill=a.color;this.styles=a=s(this.styles,a);$&&c&&delete a.width;if(ra&&!Z)c&&delete a.width,L(this.element,a);else{for(d in a)e+=d.replace(/([A-Z])/g,f)+":"+a[d]+";";w(b,"style",e)}c&&this.added&&this.renderer.buildText(this);return this},on:function(a,b){var c=this.element;if(hb&&a==="click")c.ontouchstart=function(a){a.preventDefault();b.call(c,a)};c["on"+a]=b;return this},setRadialReference:function(a){this.element.radialReference=
+a;return this},translate:function(a,b){return this.attr({translateX:a,translateY:b})},invert:function(){this.inverted=!0;this.updateTransform();return this},htmlCss:function(a){var b=this.element;if(b=a&&b.tagName==="SPAN"&&a.width)delete a.width,this.textWidth=b,this.updateTransform();this.styles=s(this.styles,a);L(this.element,a);return this},htmlGetBBox:function(){var a=this.element,b=this.bBox;if(!b){if(a.nodeName==="text")a.style.position="absolute";b=this.bBox={x:a.offsetLeft,y:a.offsetTop,
+width:a.offsetWidth,height:a.offsetHeight}}return b},htmlUpdateTransform:function(){if(this.added){var a=this.renderer,b=this.element,c=this.translateX||0,d=this.translateY||0,e=this.x||0,f=this.y||0,g=this.textAlign||"left",h={left:0,center:0.5,right:1}[g],i=g&&g!=="left",j=this.shadows;L(b,{marginLeft:c,marginTop:d});j&&n(j,function(a){L(a,{marginLeft:c+1,marginTop:d+1})});this.inverted&&n(b.childNodes,function(c){a.invertChild(c,b)});if(b.tagName==="SPAN"){var k,l,j=this.rotation,m;k=0;var o=1,
+q=0,la;m=A(this.textWidth);var y=this.xCorr||0,V=this.yCorr||0,r=[j,g,b.innerHTML,this.textWidth].join(",");if(r!==this.cTT){t(j)&&(k=j*Ua,o=W(k),q=ca(k),this.setSpanRotation(j,q,o));k=p(this.elemWidth,b.offsetWidth);l=p(this.elemHeight,b.offsetHeight);if(k>m&&/[ \-]/.test(b.textContent||b.innerText))L(b,{width:m+"px",display:"block",whiteSpace:"normal"}),k=m;m=a.fontMetrics(b.style.fontSize).b;y=o<0&&-k;V=q<0&&-l;la=o*q<0;y+=q*m*(la?1-h:h);V-=o*m*(j?la?h:1-h:1);i&&(y-=k*h*(o<0?-1:1),j&&(V-=l*h*(q<
+0?-1:1)),L(b,{textAlign:g}));this.xCorr=y;this.yCorr=V}L(b,{left:e+y+"px",top:f+V+"px"});if(fb)l=b.offsetHeight;this.cTT=r}}else this.alignOnAdd=!0},setSpanRotation:function(a){var b={};b[ra?"-ms-transform":fb?"-webkit-transform":gb?"MozTransform":Nb?"-o-transform":""]=b.transform="rotate("+a+"deg)";L(this.element,b)},updateTransform:function(){var a=this.translateX||0,b=this.translateY||0,c=this.scaleX,d=this.scaleY,e=this.inverted,f=this.rotation;e&&(a+=this.attr("width"),b+=this.attr("height"));
+a=["translate("+a+","+b+")"];e?a.push("rotate(90) scale(-1,1)"):f&&a.push("rotate("+f+" "+(this.x||0)+" "+(this.y||0)+")");(t(c)||t(d))&&a.push("scale("+p(c,1)+" "+p(d,1)+")");a.length&&w(this.element,"transform",a.join(" "))},toFront:function(){var a=this.element;a.parentNode.appendChild(a);return this},align:function(a,b,c){var d,e,f,g,h={};e=this.renderer;f=e.alignedObjects;if(a){if(this.alignOptions=a,this.alignByTranslate=b,!c||ea(c))this.alignTo=d=c||"renderer",ga(f,this),f.push(this),c=null}else a=
+this.alignOptions,b=this.alignByTranslate,d=this.alignTo;c=p(c,e[d],e);d=a.align;e=a.verticalAlign;f=(c.x||0)+(a.x||0);g=(c.y||0)+(a.y||0);if(d==="right"||d==="center")f+=(c.width-(a.width||0))/{right:1,center:2}[d];h[b?"translateX":"x"]=u(f);if(e==="bottom"||e==="middle")g+=(c.height-(a.height||0))/({bottom:1,middle:2}[e]||1);h[b?"translateY":"y"]=u(g);this[this.placed?"animate":"attr"](h);this.placed=!0;this.alignAttr=h;return this},getBBox:function(){var a=this.bBox,b=this.renderer,c,d=this.rotation;
+c=this.element;var e=this.styles,f=d*Ua;if(!a){if(c.namespaceURI===xa||b.forExport){try{a=c.getBBox?s({},c.getBBox()):{width:c.offsetWidth,height:c.offsetHeight}}catch(g){}if(!a||a.width<0)a={width:0,height:0}}else a=this.htmlGetBBox();if(b.isSVG){b=a.width;c=a.height;if(ra&&e&&e.fontSize==="11px"&&c.toPrecision(3)==="22.7")a.height=c=14;if(d)a.width=O(c*ca(f))+O(b*W(f)),a.height=O(c*W(f))+O(b*ca(f))}this.bBox=a}return a},show:function(){return this.attr({visibility:"visible"})},hide:function(){return this.attr({visibility:"hidden"})},
+fadeOut:function(a){var b=this;b.animate({opacity:0},{duration:a||150,complete:function(){b.hide()}})},add:function(a){var b=this.renderer,c=a||b,d=c.element||b.box,e=d.childNodes,f=this.element,g=w(f,"zIndex"),h;if(a)this.parentGroup=a;this.parentInverted=a&&a.inverted;this.textStr!==void 0&&b.buildText(this);if(g)c.handleZ=!0,g=A(g);if(c.handleZ)for(c=0;c<e.length;c++)if(a=e[c],b=w(a,"zIndex"),a!==f&&(A(b)>g||!t(g)&&t(b))){d.insertBefore(f,a);h=!0;break}h||d.appendChild(f);this.added=!0;K(this,
+"add");return this},safeRemoveChild:function(a){var b=a.parentNode;b&&b.removeChild(a)},destroy:function(){var a=this,b=a.element||{},c=a.shadows,d=a.renderer.isSVG&&b.nodeName==="SPAN"&&b.parentNode,e,f;b.onclick=b.onmouseout=b.onmouseover=b.onmousemove=b.point=null;Wa(a);if(a.clipPath)a.clipPath=a.clipPath.destroy();if(a.stops){for(f=0;f<a.stops.length;f++)a.stops[f]=a.stops[f].destroy();a.stops=null}a.safeRemoveChild(b);for(c&&n(c,function(b){a.safeRemoveChild(b)});d&&d.childNodes.length===0;)b=
+d.parentNode,a.safeRemoveChild(d),d=b;a.alignTo&&ga(a.renderer.alignedObjects,a);for(e in a)delete a[e];return null},shadow:function(a,b,c){var d=[],e,f,g=this.element,h,i,j,k;if(a){i=p(a.width,3);j=(a.opacity||0.15)/i;k=this.parentInverted?"(-1,-1)":"("+p(a.offsetX,1)+", "+p(a.offsetY,1)+")";for(e=1;e<=i;e++){f=g.cloneNode(0);h=i*2+1-2*e;w(f,{isShadow:"true",stroke:a.color||"black","stroke-opacity":j*e,"stroke-width":h,transform:"translate"+k,fill:S});if(c)w(f,"height",r(w(f,"height")-h,0)),f.cutHeight=
+h;b?b.element.appendChild(f):g.parentNode.insertBefore(f,g);d.push(f)}this.shadows=d}return this}};var Ga=function(){this.init.apply(this,arguments)};Ga.prototype={Element:va,init:function(a,b,c,d){var e=location,f,g;f=this.createElement("svg").attr({version:"1.1"});g=f.element;a.appendChild(g);a.innerHTML.indexOf("xmlns")===-1&&w(g,"xmlns",xa);this.isSVG=!0;this.box=g;this.boxWrapper=f;this.alignedObjects=[];this.url=(gb||fb)&&z.getElementsByTagName("base").length?e.href.replace(/#.*?$/,"").replace(/([\('\)])/g,
+"\\$1").replace(/ /g,"%20"):"";this.createElement("desc").add().element.appendChild(z.createTextNode("Created with Highcharts 3.0.5"));this.defs=this.createElement("defs").add();this.forExport=d;this.gradients={};this.setSize(b,c,!1);var h;if(gb&&a.getBoundingClientRect)this.subPixelFix=b=function(){L(a,{left:0,top:0});h=a.getBoundingClientRect();L(a,{left:wa(h.left)-h.left+"px",top:wa(h.top)-h.top+"px"})},b(),J(N,"resize",b)},isHidden:function(){return!this.boxWrapper.getBBox().width},destroy:function(){var a=
+this.defs;this.box=null;this.boxWrapper=this.boxWrapper.destroy();Ja(this.gradients||{});this.gradients=null;if(a)this.defs=a.destroy();this.subPixelFix&&ba(N,"resize",this.subPixelFix);return this.alignedObjects=null},createElement:function(a){var b=new this.Element;b.init(this,a);return b},draw:function(){},buildText:function(a){for(var b=a.element,c=this,d=c.forExport,e=p(a.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),f=b.childNodes,g=/style="([^"]+)"/,h=/href="(http[^"]+)"/,i=w(b,"x"),j=a.styles,k=j&&j.width&&A(j.width),l=j&&j.lineHeight,m=f.length;m--;)b.removeChild(f[m]);k&&!a.added&&this.box.appendChild(b);e[e.length-1]===""&&e.pop();n(e,function(e,f){var m,p=0,e=e.replace(/<span/g,"|||<span").replace(/<\/span>/g,"</span>|||");m=e.split("|||");n(m,function(e){if(e!==""||m.length===1){var o={},n=z.createElementNS(xa,"tspan"),r;g.test(e)&&
+(r=e.match(g)[1].replace(/(;| |^)color([ :])/,"$1fill$2"),w(n,"style",r));h.test(e)&&!d&&(w(n,"onclick",'location.href="'+e.match(h)[1]+'"'),L(n,{cursor:"pointer"}));e=(e.replace(/<(.|\n)*?>/g,"")||" ").replace(/</g,"<").replace(/>/g,">");if(e!==" "&&(n.appendChild(z.createTextNode(e)),p?o.dx=0:o.x=i,w(n,o),!p&&f&&(!Z&&d&&L(n,{display:"block"}),w(n,"dy",l||c.fontMetrics(/px$/.test(n.style.fontSize)?n.style.fontSize:j.fontSize).h,fb&&n.offsetHeight)),b.appendChild(n),p++,k))for(var e=e.replace(/([^\^])-/g,
+"$1- ").split(" "),t,u=[];e.length||u.length;)delete a.bBox,t=a.getBBox().width,o=t>k,!o||e.length===1?(e=u,u=[],e.length&&(n=z.createElementNS(xa,"tspan"),w(n,{dy:l||16,x:i}),r&&w(n,"style",r),b.appendChild(n),t>k&&(k=t))):(n.removeChild(n.firstChild),u.unshift(e.pop())),e.length&&n.appendChild(z.createTextNode(e.join(" ").replace(/- /g,"-")))}})})},button:function(a,b,c,d,e,f,g,h){var i=this.label(a,b,c,null,null,null,null,null,"button"),j=0,k,l,m,o,q,n,a={x1:0,y1:0,x2:0,y2:1},e=x({"stroke-width":1,
+stroke:"#CCCCCC",fill:{linearGradient:a,stops:[[0,"#FEFEFE"],[1,"#F6F6F6"]]},r:2,padding:5,style:{color:"black"}},e);m=e.style;delete e.style;f=x(e,{stroke:"#68A",fill:{linearGradient:a,stops:[[0,"#FFF"],[1,"#ACF"]]}},f);o=f.style;delete f.style;g=x(e,{stroke:"#68A",fill:{linearGradient:a,stops:[[0,"#9BD"],[1,"#CDF"]]}},g);q=g.style;delete g.style;h=x(e,{style:{color:"#CCC"}},h);n=h.style;delete h.style;J(i.element,ra?"mouseover":"mouseenter",function(){j!==3&&i.attr(f).css(o)});J(i.element,ra?"mouseout":
+"mouseleave",function(){j!==3&&(k=[e,f,g][j],l=[m,o,q][j],i.attr(k).css(l))});i.setState=function(a){(i.state=j=a)?a===2?i.attr(g).css(q):a===3&&i.attr(h).css(n):i.attr(e).css(m)};return i.on("click",function(){j!==3&&d.call(i)}).attr(e).css(s({cursor:"default"},m))},crispLine:function(a,b){a[1]===a[4]&&(a[1]=a[4]=u(a[1])-b%2/2);a[2]===a[5]&&(a[2]=a[5]=u(a[2])+b%2/2);return a},path:function(a){var b={fill:S};Ha(a)?b.d=a:T(a)&&s(b,a);return this.createElement("path").attr(b)},circle:function(a,b,c){a=
+T(a)?a:{x:a,y:b,r:c};return this.createElement("circle").attr(a)},arc:function(a,b,c,d,e,f){if(T(a))b=a.y,c=a.r,d=a.innerR,e=a.start,f=a.end,a=a.x;a=this.symbol("arc",a||0,b||0,c||0,c||0,{innerR:d||0,start:e||0,end:f||0});a.r=c;return a},rect:function(a,b,c,d,e,f){e=T(a)?a.r:e;e=this.createElement("rect").attr({rx:e,ry:e,fill:S});return e.attr(T(a)?a:e.crisp(f,a,b,r(c,0),r(d,0)))},setSize:function(a,b,c){var d=this.alignedObjects,e=d.length;this.width=a;this.height=b;for(this.boxWrapper[p(c,!0)?"animate":
+"attr"]({width:a,height:b});e--;)d[e].align()},g:function(a){var b=this.createElement("g");return t(a)?b.attr({"class":"highcharts-"+a}):b},image:function(a,b,c,d,e){var f={preserveAspectRatio:S};arguments.length>1&&s(f,{x:b,y:c,width:d,height:e});f=this.createElement("image").attr(f);f.element.setAttributeNS?f.element.setAttributeNS("http://www.w3.org/1999/xlink","href",a):f.element.setAttribute("hc-svg-href",a);return f},symbol:function(a,b,c,d,e,f){var g,h=this.symbols[a],h=h&&h(u(b),u(c),d,e,
+f),i=/^url\((.*?)\)$/,j,k;if(h)g=this.path(h),s(g,{symbolName:a,x:b,y:c,width:d,height:e}),f&&s(g,f);else if(i.test(a))k=function(a,b){a.element&&(a.attr({width:b[0],height:b[1]}),a.alignByTranslate||a.translate(u((d-b[0])/2),u((e-b[1])/2)))},j=a.match(i)[1],a=Pb[j],g=this.image(j).attr({x:b,y:c}),g.isImg=!0,a?k(g,a):(g.attr({width:0,height:0}),U("img",{onload:function(){k(g,Pb[j]=[this.width,this.height])},src:j}));return g},symbols:{circle:function(a,b,c,d){var e=0.166*c;return["M",a+c/2,b,"C",
+a+c+e,b,a+c+e,b+d,a+c/2,b+d,"C",a-e,b+d,a-e,b,a+c/2,b,"Z"]},square:function(a,b,c,d){return["M",a,b,"L",a+c,b,a+c,b+d,a,b+d,"Z"]},triangle:function(a,b,c,d){return["M",a+c/2,b,"L",a+c,b+d,a,b+d,"Z"]},"triangle-down":function(a,b,c,d){return["M",a,b,"L",a+c,b,a+c/2,b+d,"Z"]},diamond:function(a,b,c,d){return["M",a+c/2,b,"L",a+c,b+d/2,a+c/2,b+d,a,b+d/2,"Z"]},arc:function(a,b,c,d,e){var f=e.start,c=e.r||c||d,g=e.end-0.001,d=e.innerR,h=e.open,i=W(f),j=ca(f),k=W(g),g=ca(g),e=e.end-f<Ma?0:1;return["M",a+
+c*i,b+c*j,"A",c,c,0,e,1,a+c*k,b+c*g,h?"M":"L",a+d*k,b+d*g,"A",d,d,0,e,0,a+d*i,b+d*j,h?"":"Z"]}},clipRect:function(a,b,c,d){var e="highcharts-"+yb++,f=this.createElement("clipPath").attr({id:e}).add(this.defs),a=this.rect(a,b,c,d,0).add(f);a.id=e;a.clipPath=f;return a},color:function(a,b,c){var d=this,e,f=/^rgba/,g,h,i,j,k,l,m,o=[];a&&a.linearGradient?g="linearGradient":a&&a.radialGradient&&(g="radialGradient");if(g){c=a[g];h=d.gradients;j=a.stops;b=b.radialReference;Ha(c)&&(a[g]=c={x1:c[0],y1:c[1],
+x2:c[2],y2:c[3],gradientUnits:"userSpaceOnUse"});g==="radialGradient"&&b&&!t(c.gradientUnits)&&(c=x(c,{cx:b[0]-b[2]/2+c.cx*b[2],cy:b[1]-b[2]/2+c.cy*b[2],r:c.r*b[2],gradientUnits:"userSpaceOnUse"}));for(m in c)m!=="id"&&o.push(m,c[m]);for(m in j)o.push(j[m]);o=o.join(",");h[o]?a=h[o].id:(c.id=a="highcharts-"+yb++,h[o]=i=d.createElement(g).attr(c).add(d.defs),i.stops=[],n(j,function(a){f.test(a[1])?(e=pa(a[1]),k=e.get("rgb"),l=e.get("a")):(k=a[1],l=1);a=d.createElement("stop").attr({offset:a[0],"stop-color":k,
+"stop-opacity":l}).add(i);i.stops.push(a)}));return"url("+d.url+"#"+a+")"}else return f.test(a)?(e=pa(a),w(b,c+"-opacity",e.get("a")),e.get("rgb")):(b.removeAttribute(c+"-opacity"),a)},text:function(a,b,c,d){var e=M.chart.style,f=$||!Z&&this.forExport;if(d&&!this.forExport)return this.html(a,b,c);b=u(p(b,0));c=u(p(c,0));a=this.createElement("text").attr({x:b,y:c,text:a}).css({fontFamily:e.fontFamily,fontSize:e.fontSize});f&&a.css({position:"absolute"});a.x=b;a.y=c;return a},html:function(a,b,c){var d=
+M.chart.style,e=this.createElement("span"),f=e.attrSetters,g=e.element,h=e.renderer;f.text=function(a){a!==g.innerHTML&&delete this.bBox;g.innerHTML=a;return!1};f.x=f.y=f.align=function(a,b){b==="align"&&(b="textAlign");e[b]=a;e.htmlUpdateTransform();return!1};e.attr({text:a,x:u(b),y:u(c)}).css({position:"absolute",whiteSpace:"nowrap",fontFamily:d.fontFamily,fontSize:d.fontSize});e.css=e.htmlCss;if(h.isSVG)e.add=function(a){var b,c=h.box.parentNode,d=[];if(a){if(b=a.div,!b){for(;a;)d.push(a),a=a.parentGroup;
+n(d.reverse(),function(a){var d;b=a.div=a.div||U(Ca,{className:w(a.element,"class")},{position:"absolute",left:(a.translateX||0)+"px",top:(a.translateY||0)+"px"},b||c);d=b.style;s(a.attrSetters,{translateX:function(a){d.left=a+"px"},translateY:function(a){d.top=a+"px"},visibility:function(a,b){d[b]=a}})})}}else b=c;b.appendChild(g);e.added=!0;e.alignOnAdd&&e.htmlUpdateTransform();return e};return e},fontMetrics:function(a){var a=A(a||11),a=a<24?a+4:u(a*1.2),b=u(a*0.8);return{h:a,b:b}},label:function(a,
+b,c,d,e,f,g,h,i){function j(){var a,b;a=p.element.style;V=(Oa===void 0||E===void 0||q.styles.textAlign)&&p.getBBox();q.width=(Oa||V.width||0)+2*da+jb;q.height=(E||V.height||0)+2*da;w=da+o.fontMetrics(a&&a.fontSize).b;if(A){if(!y)a=u(-r*da),b=h?-w:0,q.box=y=d?o.symbol(d,a,b,q.width,q.height):o.rect(a,b,q.width,q.height,0,kb[Rb]),y.add(q);y.isImg||y.attr(x({width:q.width,height:q.height},kb));kb=null}}function k(){var a=q.styles,a=a&&a.textAlign,b=jb+da*(1-r),c;c=h?0:w;if(t(Oa)&&(a==="center"||a===
+"right"))b+={center:0.5,right:1}[a]*(Oa-V.width);(b!==p.x||c!==p.y)&&p.attr({x:b,y:c});p.x=b;p.y=c}function l(a,b){y?y.attr(a,b):kb[a]=b}function m(){p.add(q);q.attr({text:a,x:b,y:c});y&&t(e)&&q.attr({anchorX:e,anchorY:f})}var o=this,q=o.g(i),p=o.text("",0,0,g).attr({zIndex:1}),y,V,r=0,da=3,jb=0,Oa,E,G,I,B=0,kb={},w,g=q.attrSetters,A;J(q,"add",m);g.width=function(a){Oa=a;return!1};g.height=function(a){E=a;return!1};g.padding=function(a){t(a)&&a!==da&&(da=a,k());return!1};g.paddingLeft=function(a){t(a)&&
+a!==jb&&(jb=a,k());return!1};g.align=function(a){r={left:0,center:0.5,right:1}[a];return!1};g.text=function(a,b){p.attr(b,a);j();k();return!1};g[Rb]=function(a,b){A=!0;B=a%2/2;l(b,a);return!1};g.stroke=g.fill=g.r=function(a,b){b==="fill"&&(A=!0);l(b,a);return!1};g.anchorX=function(a,b){e=a;l(b,a+B-G);return!1};g.anchorY=function(a,b){f=a;l(b,a-I);return!1};g.x=function(a){q.x=a;a-=r*((Oa||V.width)+da);G=u(a);q.attr("translateX",G);return!1};g.y=function(a){I=q.y=u(a);q.attr("translateY",I);return!1};
+var z=q.css;return s(q,{css:function(a){if(a){var b={},a=x(a);n("fontSize,fontWeight,fontFamily,color,lineHeight,width,textDecoration,textShadow".split(","),function(c){a[c]!==v&&(b[c]=a[c],delete a[c])});p.css(b)}return z.call(q,a)},getBBox:function(){return{width:V.width+2*da,height:V.height+2*da,x:V.x-da,y:V.y-da}},shadow:function(a){y&&y.shadow(a);return q},destroy:function(){ba(q,"add",m);ba(q.element,"mouseenter");ba(q.element,"mouseleave");p&&(p=p.destroy());y&&(y=y.destroy());va.prototype.destroy.call(q);
+q=o=j=k=l=m=null}})}};Va=Ga;var D;if(!Z&&!$){Highcharts.VMLElement=D={init:function(a,b){var c=["<",b,' filled="f" stroked="f"'],d=["position: ","absolute",";"],e=b===Ca;(b==="shape"||e)&&d.push("left:0;top:0;width:1px;height:1px;");d.push("visibility: ",e?"hidden":"visible");c.push(' style="',d.join(""),'"/>');if(b)c=e||b==="span"||b==="img"?c.join(""):a.prepVML(c),this.element=U(c);this.renderer=a;this.attrSetters={}},add:function(a){var b=this.renderer,c=this.element,d=b.box,d=a?a.element||a:d;
+a&&a.inverted&&b.invertChild(c,d);d.appendChild(c);this.added=!0;this.alignOnAdd&&!this.deferUpdateTransform&&this.updateTransform();K(this,"add");return this},updateTransform:va.prototype.htmlUpdateTransform,setSpanRotation:function(a,b,c){L(this.element,{filter:a?["progid:DXImageTransform.Microsoft.Matrix(M11=",c,", M12=",-b,", M21=",b,", M22=",c,", sizingMethod='auto expand')"].join(""):S})},pathToVML:function(a){for(var b=a.length,c=[],d;b--;)if(qa(a[b]))c[b]=u(a[b]*10)-5;else if(a[b]==="Z")c[b]=
+"x";else if(c[b]=a[b],a.isArc&&(a[b]==="wa"||a[b]==="at"))d=a[b]==="wa"?1:-1,c[b+5]===c[b+7]&&(c[b+7]-=d),c[b+6]===c[b+8]&&(c[b+8]-=d);return c.join(" ")||"x"},attr:function(a,b){var c,d,e,f=this.element||{},g=f.style,h=f.nodeName,i=this.renderer,j=this.symbolName,k,l=this.shadows,m,o=this.attrSetters,q=this;ea(a)&&t(b)&&(c=a,a={},a[c]=b);if(ea(a))c=a,q=c==="strokeWidth"||c==="stroke-width"?this.strokeweight:this[c];else for(c in a)if(d=a[c],m=!1,e=o[c]&&o[c].call(this,d,c),e!==!1&&d!==null){e!==
+v&&(d=e);if(j&&/^(x|y|r|start|end|width|height|innerR|anchorX|anchorY)/.test(c))k||(this.symbolAttr(a),k=!0),m=!0;else if(c==="d"){d=d||[];this.d=d.join(" ");f.path=d=this.pathToVML(d);if(l)for(e=l.length;e--;)l[e].path=l[e].cutOff?this.cutOffPath(d,l[e].cutOff):d;m=!0}else if(c==="visibility"){if(l)for(e=l.length;e--;)l[e].style[c]=d;h==="DIV"&&(d=d==="hidden"?"-999em":0,eb||(g[c]=d?"visible":"hidden"),c="top");g[c]=d;m=!0}else if(c==="zIndex")d&&(g[c]=d),m=!0;else if(oa(c,["x","y","width","height"])!==
+-1)this[c]=d,c==="x"||c==="y"?c={x:"left",y:"top"}[c]:d=r(0,d),this.updateClipping?(this[c]=d,this.updateClipping()):g[c]=d,m=!0;else if(c==="class"&&h==="DIV")f.className=d;else if(c==="stroke")d=i.color(d,f,c),c="strokecolor";else if(c==="stroke-width"||c==="strokeWidth")f.stroked=d?!0:!1,c="strokeweight",this[c]=d,qa(d)&&(d+="px");else if(c==="dashstyle")(f.getElementsByTagName("stroke")[0]||U(i.prepVML(["<stroke/>"]),null,null,f))[c]=d||"solid",this.dashstyle=d,m=!0;else if(c==="fill")if(h===
+"SPAN")g.color=d;else{if(h!=="IMG")f.filled=d!==S?!0:!1,d=i.color(d,f,c,this),c="fillcolor"}else if(c==="opacity")m=!0;else if(h==="shape"&&c==="rotation")this[c]=f.style[c]=d,f.style.left=-u(ca(d*Ua)+1)+"px",f.style.top=u(W(d*Ua))+"px";else if(c==="translateX"||c==="translateY"||c==="rotation")this[c]=d,this.updateTransform(),m=!0;else if(c==="text")this.bBox=null,f.innerHTML=d,m=!0;m||(eb?f[c]=d:w(f,c,d))}return q},clip:function(a){var b=this,c;a?(c=a.members,ga(c,b),c.push(b),b.destroyClip=function(){ga(c,
+b)},a=a.getCSS(b)):(b.destroyClip&&b.destroyClip(),a={clip:eb?"inherit":"rect(auto)"});return b.css(a)},css:va.prototype.htmlCss,safeRemoveChild:function(a){a.parentNode&&Ta(a)},destroy:function(){this.destroyClip&&this.destroyClip();return va.prototype.destroy.apply(this)},on:function(a,b){this.element["on"+a]=function(){var a=N.event;a.target=a.srcElement;b(a)};return this},cutOffPath:function(a,b){var c,a=a.split(/[ ,]/);c=a.length;if(c===9||c===11)a[c-4]=a[c-2]=A(a[c-2])-10*b;return a.join(" ")},
+shadow:function(a,b,c){var d=[],e,f=this.element,g=this.renderer,h,i=f.style,j,k=f.path,l,m,o,q;k&&typeof k.value!=="string"&&(k="x");m=k;if(a){o=p(a.width,3);q=(a.opacity||0.15)/o;for(e=1;e<=3;e++){l=o*2+1-2*e;c&&(m=this.cutOffPath(k.value,l+0.5));j=['<shape isShadow="true" strokeweight="',l,'" filled="false" path="',m,'" coordsize="10 10" style="',f.style.cssText,'" />'];h=U(g.prepVML(j),null,{left:A(i.left)+p(a.offsetX,1),top:A(i.top)+p(a.offsetY,1)});if(c)h.cutOff=l+1;j=['<stroke color="',a.color||
+"black",'" opacity="',q*e,'"/>'];U(g.prepVML(j),null,null,h);b?b.element.appendChild(h):f.parentNode.insertBefore(h,f);d.push(h)}this.shadows=d}return this}};D=ha(va,D);var ma={Element:D,isIE8:Ea.indexOf("MSIE 8.0")>-1,init:function(a,b,c){var d,e;this.alignedObjects=[];d=this.createElement(Ca);e=d.element;e.style.position="relative";a.appendChild(d.element);this.isVML=!0;this.box=e;this.boxWrapper=d;this.setSize(b,c,!1);if(!z.namespaces.hcv)z.namespaces.add("hcv","urn:schemas-microsoft-com:vml"),
+z.createStyleSheet().cssText="hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke{ behavior:url(#default#VML); display: inline-block; } "},isHidden:function(){return!this.box.offsetWidth},clipRect:function(a,b,c,d){var e=this.createElement(),f=T(a);return s(e,{members:[],left:(f?a.x:a)+1,top:(f?a.y:b)+1,width:(f?a.width:c)-1,height:(f?a.height:d)-1,getCSS:function(a){var b=a.element,c=b.nodeName,a=a.inverted,d=this.top-(c==="shape"?b.offsetTop:0),e=this.left,b=e+this.width,f=d+this.height,d={clip:"rect("+
+u(a?e:d)+"px,"+u(a?f:b)+"px,"+u(a?b:f)+"px,"+u(a?d:e)+"px)"};!a&&eb&&c==="DIV"&&s(d,{width:b+"px",height:f+"px"});return d},updateClipping:function(){n(e.members,function(a){a.css(e.getCSS(a))})}})},color:function(a,b,c,d){var e=this,f,g=/^rgba/,h,i,j=S;a&&a.linearGradient?i="gradient":a&&a.radialGradient&&(i="pattern");if(i){var k,l,m=a.linearGradient||a.radialGradient,o,q,p,y,r,t="",a=a.stops,u,v=[],s=function(){h=['<fill colors="'+v.join(",")+'" opacity="',p,'" o:opacity2="',q,'" type="',i,'" ',
+t,'focus="100%" method="any" />'];U(e.prepVML(h),null,null,b)};o=a[0];u=a[a.length-1];o[0]>0&&a.unshift([0,o[1]]);u[0]<1&&a.push([1,u[1]]);n(a,function(a,b){g.test(a[1])?(f=pa(a[1]),k=f.get("rgb"),l=f.get("a")):(k=a[1],l=1);v.push(a[0]*100+"% "+k);b?(p=l,y=k):(q=l,r=k)});if(c==="fill")if(i==="gradient")c=m.x1||m[0]||0,a=m.y1||m[1]||0,o=m.x2||m[2]||0,m=m.y2||m[3]||0,t='angle="'+(90-R.atan((m-a)/(o-c))*180/Ma)+'"',s();else{var j=m.r,E=j*2,G=j*2,I=m.cx,B=m.cy,x=b.radialReference,w,j=function(){x&&(w=
+d.getBBox(),I+=(x[0]-w.x)/w.width-0.5,B+=(x[1]-w.y)/w.height-0.5,E*=x[2]/w.width,G*=x[2]/w.height);t='src="'+M.global.VMLRadialGradientURL+'" size="'+E+","+G+'" origin="0.5,0.5" position="'+I+","+B+'" color2="'+r+'" ';s()};d.added?j():J(d,"add",j);j=y}else j=k}else if(g.test(a)&&b.tagName!=="IMG")f=pa(a),h=["<",c,' opacity="',f.get("a"),'"/>'],U(this.prepVML(h),null,null,b),j=f.get("rgb");else{j=b.getElementsByTagName(c);if(j.length)j[0].opacity=1,j[0].type="solid";j=a}return j},prepVML:function(a){var b=
+this.isIE8,a=a.join("");b?(a=a.replace("/>",' xmlns="urn:schemas-microsoft-com:vml" />'),a=a.indexOf('style="')===-1?a.replace("/>",' style="display:inline-block;behavior:url(#default#VML);" />'):a.replace('style="','style="display:inline-block;behavior:url(#default#VML);')):a=a.replace("<","<hcv:");return a},text:Ga.prototype.html,path:function(a){var b={coordsize:"10 10"};Ha(a)?b.d=a:T(a)&&s(b,a);return this.createElement("shape").attr(b)},circle:function(a,b,c){var d=this.symbol("circle");if(T(a))c=
+a.r,b=a.y,a=a.x;d.isCircle=!0;return d.attr({x:a,y:b,width:2*c,height:2*c})},g:function(a){var b;a&&(b={className:"highcharts-"+a,"class":"highcharts-"+a});return this.createElement(Ca).attr(b)},image:function(a,b,c,d,e){var f=this.createElement("img").attr({src:a});arguments.length>1&&f.attr({x:b,y:c,width:d,height:e});return f},rect:function(a,b,c,d,e,f){var g=this.symbol("rect");g.r=T(a)?a.r:e;return g.attr(T(a)?a:g.crisp(f,a,b,r(c,0),r(d,0)))},invertChild:function(a,b){var c=b.style;L(a,{flip:"x",
+left:A(c.width)-1,top:A(c.height)-1,rotation:-90})},symbols:{arc:function(a,b,c,d,e){var f=e.start,g=e.end,h=e.r||c||d,c=e.innerR,d=W(f),i=ca(f),j=W(g),k=ca(g);if(g-f===0)return["x"];f=["wa",a-h,b-h,a+h,b+h,a+h*d,b+h*i,a+h*j,b+h*k];e.open&&!c&&f.push("e","M",a,b);f.push("at",a-c,b-c,a+c,b+c,a+c*j,b+c*k,a+c*d,b+c*i,"x","e");f.isArc=!0;return f},circle:function(a,b,c,d,e){e&&e.isCircle&&(a-=c/2,b-=d/2);return["wa",a,b,a+c,b+d,a+c,b+d/2,a+c,b+d/2,"e"]},rect:function(a,b,c,d,e){var f=a+c,g=b+d,h;!t(e)||
+!e.r?f=Ga.prototype.symbols.square.apply(0,arguments):(h=C(e.r,c,d),f=["M",a+h,b,"L",f-h,b,"wa",f-2*h,b,f,b+2*h,f-h,b,f,b+h,"L",f,g-h,"wa",f-2*h,g-2*h,f,g,f,g-h,f-h,g,"L",a+h,g,"wa",a,g-2*h,a+2*h,g,a+h,g,a,g-h,"L",a,b+h,"wa",a,b,a+2*h,b+2*h,a,b+h,a+h,b,"x","e"]);return f}}};Highcharts.VMLRenderer=D=function(){this.init.apply(this,arguments)};D.prototype=x(Ga.prototype,ma);Va=D}var Tb;if($)Highcharts.CanVGRenderer=D=function(){xa="http://www.w3.org/1999/xhtml"},D.prototype.symbols={},Tb=function(){function a(){var a=
+b.length,d;for(d=0;d<a;d++)b[d]();b=[]}var b=[];return{push:function(c,d){b.length===0&&Vb(d,a);b.push(c)}}}(),Va=D;La.prototype={addLabel:function(){var a=this.axis,b=a.options,c=a.chart,d=a.horiz,e=a.categories,f=a.series[0]&&a.series[0].names,g=this.pos,h=b.labels,i=a.tickPositions,d=d&&e&&!h.step&&!h.staggerLines&&!h.rotation&&c.plotWidth/i.length||!d&&(c.optionsMarginLeft||c.chartWidth*0.33),j=g===i[0],k=g===i[i.length-1],f=e?p(e[g],f&&f[g],g):g,e=this.label,i=i.info,l;a.isDatetimeAxis&&i&&(l=
+b.dateTimeLabelFormats[i.higherRanks[g]||i.unitName]);this.isFirst=j;this.isLast=k;b=a.labelFormatter.call({axis:a,chart:c,isFirst:j,isLast:k,dateTimeLabelFormat:l,value:a.isLog?ka(fa(f)):f});g=d&&{width:r(1,u(d-2*(h.padding||10)))+"px"};g=s(g,h.style);if(t(e))e&&e.attr({text:b}).css(g);else{d={align:a.labelAlign};if(qa(h.rotation))d.rotation=h.rotation;this.label=t(b)&&h.enabled?c.renderer.text(b,0,0,h.useHTML).attr(d).css(g).add(a.labelGroup):null}},getLabelSize:function(){var a=this.label,b=this.axis;
+return a?(this.labelBBox=a.getBBox())[b.horiz?"height":"width"]:0},getLabelSides:function(){var a=this.axis,b=this.labelBBox.width,a=b*{left:0,center:0.5,right:1}[a.labelAlign]-a.options.labels.x;return[-a,b-a]},handleOverflow:function(a,b){var c=!0,d=this.axis,e=d.chart,f=this.isFirst,g=this.isLast,h=b.x,i=d.reversed,j=d.tickPositions;if(f||g){var k=this.getLabelSides(),l=k[0],k=k[1],e=e.plotLeft,m=e+d.len,j=(d=d.ticks[j[a+(f?1:-1)]])&&d.label.xy&&d.label.xy.x+d.getLabelSides()[f?0:1];f&&!i||g&&
+i?h+l<e&&(h=e-l,d&&h+k>j&&(c=!1)):h+k>m&&(h=m-k,d&&h+l<j&&(c=!1));b.x=h}return c},getPosition:function(a,b,c,d){var e=this.axis,f=e.chart,g=d&&f.oldChartHeight||f.chartHeight;return{x:a?e.translate(b+c,null,null,d)+e.transB:e.left+e.offset+(e.opposite?(d&&f.oldChartWidth||f.chartWidth)-e.right-e.left:0),y:a?g-e.bottom+e.offset-(e.opposite?e.height:0):g-e.translate(b+c,null,null,d)-e.transB}},getLabelPosition:function(a,b,c,d,e,f,g,h){var i=this.axis,j=i.transA,k=i.reversed,l=i.staggerLines,m=i.chart.renderer.fontMetrics(e.style.fontSize).b,
+o=e.rotation,a=a+e.x-(f&&d?f*j*(k?-1:1):0),b=b+e.y-(f&&!d?f*j*(k?1:-1):0);o&&i.side===2&&(b-=m-m*W(o*Ua));!t(e.y)&&!o&&(b+=m-c.getBBox().height/2);l&&(b+=g/(h||1)%l*(i.labelOffset/l));return{x:a,y:b}},getMarkPath:function(a,b,c,d,e,f){return f.crispLine(["M",a,b,"L",a+(e?0:-c),b+(e?c:0)],d)},render:function(a,b,c){var d=this.axis,e=d.options,f=d.chart.renderer,g=d.horiz,h=this.type,i=this.label,j=this.pos,k=e.labels,l=this.gridLine,m=h?h+"Grid":"grid",o=h?h+"Tick":"tick",q=e[m+"LineWidth"],n=e[m+
+"LineColor"],y=e[m+"LineDashStyle"],r=e[o+"Length"],m=e[o+"Width"]||0,t=e[o+"Color"],u=e[o+"Position"],o=this.mark,s=k.step,w=!0,E=d.tickmarkOffset,G=this.getPosition(g,j,E,b),I=G.x,G=G.y,B=g&&I===d.pos+d.len||!g&&G===d.pos?-1:1,x=d.staggerLines;this.isActive=!0;if(q){j=d.getPlotLinePath(j+E,q*B,b,!0);if(l===v){l={stroke:n,"stroke-width":q};if(y)l.dashstyle=y;if(!h)l.zIndex=1;if(b)l.opacity=0;this.gridLine=l=q?f.path(j).attr(l).add(d.gridGroup):null}if(!b&&l&&j)l[this.isNew?"attr":"animate"]({d:j,
+opacity:c})}if(m&&r)u==="inside"&&(r=-r),d.opposite&&(r=-r),b=this.getMarkPath(I,G,r,m*B,g,f),o?o.animate({d:b,opacity:c}):this.mark=f.path(b).attr({stroke:t,"stroke-width":m,opacity:c}).add(d.axisGroup);if(i&&!isNaN(I))i.xy=G=this.getLabelPosition(I,G,i,g,k,E,a,s),this.isFirst&&!this.isLast&&!p(e.showFirstLabel,1)||this.isLast&&!this.isFirst&&!p(e.showLastLabel,1)?w=!1:!x&&g&&k.overflow==="justify"&&!this.handleOverflow(a,G)&&(w=!1),s&&a%s&&(w=!1),w&&!isNaN(G.y)?(G.opacity=c,i[this.isNew?"attr":
+"animate"](G),this.isNew=!1):i.attr("y",-9999)},destroy:function(){Ja(this,this.axis)}};tb.prototype={render:function(){var a=this,b=a.axis,c=b.horiz,d=(b.pointRange||0)/2,e=a.options,f=e.label,g=a.label,h=e.width,i=e.to,j=e.from,k=t(j)&&t(i),l=e.value,m=e.dashStyle,o=a.svgElem,q=[],n,y=e.color,u=e.zIndex,s=e.events,v=b.chart.renderer;b.isLog&&(j=na(j),i=na(i),l=na(l));if(h){if(q=b.getPlotLinePath(l,h),d={stroke:y,"stroke-width":h},m)d.dashstyle=m}else if(k){if(j=r(j,b.min-d),i=C(i,b.max+d),q=b.getPlotBandPath(j,
+i,e),d={fill:y},e.borderWidth)d.stroke=e.borderColor,d["stroke-width"]=e.borderWidth}else return;if(t(u))d.zIndex=u;if(o)q?o.animate({d:q},null,o.onGetPath):(o.hide(),o.onGetPath=function(){o.show()});else if(q&&q.length&&(a.svgElem=o=v.path(q).attr(d).add(),s))for(n in e=function(b){o.on(b,function(c){s[b].apply(a,[c])})},s)e(n);if(f&&t(f.text)&&q&&q.length&&b.width>0&&b.height>0){f=x({align:c&&k&&"center",x:c?!k&&4:10,verticalAlign:!c&&k&&"middle",y:c?k?16:10:k?6:-4,rotation:c&&!k&&90},f);if(!g)a.label=
+g=v.text(f.text,0,0,f.useHTML).attr({align:f.textAlign||f.align,rotation:f.rotation,zIndex:u}).css(f.style).add();b=[q[1],q[4],p(q[6],q[1])];q=[q[2],q[5],p(q[7],q[2])];c=Ia(b);k=Ia(q);g.align(f,!1,{x:c,y:k,width:ua(b)-c,height:ua(q)-k});g.show()}else g&&g.hide();return a},destroy:function(){ga(this.axis.plotLinesAndBands,this);delete this.axis;Ja(this)}};Mb.prototype={destroy:function(){Ja(this,this.axis)},setTotal:function(a){this.cum=this.total=a},addValue:function(a){this.setTotal(ka(this.total+
+a))},render:function(a){var b=this.options,c=b.format,c=c?Ba(c,this):b.formatter.call(this);this.label?this.label.attr({text:c,visibility:"hidden"}):this.label=this.axis.chart.renderer.text(c,0,0,b.useHTML).css(b.style).attr({align:this.textAlign,rotation:b.rotation,visibility:"hidden"}).add(a)},cacheExtremes:function(a,b){this.points[a.index]=b},setOffset:function(a,b){var c=this.axis,d=c.chart,e=d.inverted,f=this.isNegative,g=c.translate(this.percent?100:this.total,0,0,0,1),c=c.translate(0),c=O(g-
+c),h=d.xAxis[0].translate(this.x)+a,i=d.plotHeight,f={x:e?f?g:g-c:h,y:e?i-h-b:f?i-g-c:i-g,width:e?c:b,height:e?b:c};if(e=this.label)e.align(this.alignOptions,null,f),f=e.alignAttr,e.attr({visibility:this.options.crop===!1||d.isInsidePlot(f.x,f.y)?Z?"inherit":"visible":"hidden"})}};db.prototype={defaultOptions:{dateTimeLabelFormats:{millisecond:"%H:%M:%S.%L",second:"%H:%M:%S",minute:"%H:%M",hour:"%H:%M",day:"%e. %b",week:"%e. %b",month:"%b '%y",year:"%Y"},endOnTick:!1,gridLineColor:"#C0C0C0",labels:F,
+lineColor:"#C0D0E0",lineWidth:1,minPadding:0.01,maxPadding:0.01,minorGridLineColor:"#E0E0E0",minorGridLineWidth:1,minorTickColor:"#A0A0A0",minorTickLength:2,minorTickPosition:"outside",startOfWeek:1,startOnTick:!1,tickColor:"#C0D0E0",tickLength:5,tickmarkPlacement:"between",tickPixelInterval:100,tickPosition:"outside",tickWidth:1,title:{align:"middle",style:{color:"#4d759e",fontWeight:"bold"}},type:"linear"},defaultYAxisOptions:{endOnTick:!0,gridLineWidth:1,tickPixelInterval:72,showLastLabel:!0,labels:{x:-8,
+y:3},lineWidth:0,maxPadding:0.05,minPadding:0.05,startOnTick:!0,tickWidth:0,title:{rotation:270,text:"Values"},stackLabels:{enabled:!1,formatter:function(){return za(this.total,-1)},style:F.style}},defaultLeftAxisOptions:{labels:{x:-8,y:null},title:{rotation:270}},defaultRightAxisOptions:{labels:{x:8,y:null},title:{rotation:90}},defaultBottomAxisOptions:{labels:{x:0,y:14},title:{rotation:0}},defaultTopAxisOptions:{labels:{x:0,y:-5},title:{rotation:0}},init:function(a,b){var c=b.isX;this.horiz=a.inverted?
+!c:c;this.xOrY=(this.isXAxis=c)?"x":"y";this.opposite=b.opposite;this.side=this.horiz?this.opposite?0:2:this.opposite?1:3;this.setOptions(b);var d=this.options,e=d.type;this.labelFormatter=d.labels.formatter||this.defaultLabelFormatter;this.userOptions=b;this.minPixelPadding=0;this.chart=a;this.reversed=d.reversed;this.zoomEnabled=d.zoomEnabled!==!1;this.categories=d.categories||e==="category";this.isLog=e==="logarithmic";this.isDatetimeAxis=e==="datetime";this.isLinked=t(d.linkedTo);this.tickmarkOffset=
+this.categories&&d.tickmarkPlacement==="between"?0.5:0;this.ticks={};this.minorTicks={};this.plotLinesAndBands=[];this.alternateBands={};this.len=0;this.minRange=this.userMinRange=d.minRange||d.maxZoom;this.range=d.range;this.offset=d.offset||0;this.stacks={};this.oldStacks={};this.stackExtremes={};this.min=this.max=null;var f,d=this.options.events;oa(this,a.axes)===-1&&(a.axes.push(this),a[c?"xAxis":"yAxis"].push(this));this.series=this.series||[];if(a.inverted&&c&&this.reversed===v)this.reversed=
+!0;this.removePlotLine=this.removePlotBand=this.removePlotBandOrLine;for(f in d)J(this,f,d[f]);if(this.isLog)this.val2lin=na,this.lin2val=fa},setOptions:function(a){this.options=x(this.defaultOptions,this.isXAxis?{}:this.defaultYAxisOptions,[this.defaultTopAxisOptions,this.defaultRightAxisOptions,this.defaultBottomAxisOptions,this.defaultLeftAxisOptions][this.side],x(M[this.isXAxis?"xAxis":"yAxis"],a))},update:function(a,b){var c=this.chart,a=c.options[this.xOrY+"Axis"][this.options.index]=x(this.userOptions,
+a);this.destroy(!0);this._addedPlotLB=!1;this.init(c,s(a,{events:v}));c.isDirtyBox=!0;p(b,!0)&&c.redraw()},remove:function(a){var b=this.chart,c=this.xOrY+"Axis";n(this.series,function(a){a.remove(!1)});ga(b.axes,this);ga(b[c],this);b.options[c].splice(this.options.index,1);n(b[c],function(a,b){a.options.index=b});this.destroy();b.isDirtyBox=!0;p(a,!0)&&b.redraw()},defaultLabelFormatter:function(){var a=this.axis,b=this.value,c=a.categories,d=this.dateTimeLabelFormat,e=M.lang.numericSymbols,f=e&&
+e.length,g,h=a.options.labels.format,a=a.isLog?b:a.tickInterval;if(h)g=Ba(h,this);else if(c)g=b;else if(d)g=Xa(d,b);else if(f&&a>=1E3)for(;f--&&g===v;)c=Math.pow(1E3,f+1),a>=c&&e[f]!==null&&(g=za(b/c,-1)+e[f]);g===v&&(g=b>=1E3?za(b,0):za(b,-1));return g},getSeriesExtremes:function(){var a=this,b=a.chart;a.hasVisibleSeries=!1;a.dataMin=a.dataMax=null;a.stackExtremes={};a.buildStacks();n(a.series,function(c){if(c.visible||!b.options.chart.ignoreHiddenSeries){var d=c.options,e;e=d.threshold;a.hasVisibleSeries=
+!0;a.isLog&&e<=0&&(e=null);if(a.isXAxis){if(e=c.xData,e.length)a.dataMin=C(p(a.dataMin,e[0]),Ia(e)),a.dataMax=r(p(a.dataMax,e[0]),ua(e))}else{d=d.stacking;a.usePercentage=d==="percent";if(a.usePercentage)a.dataMin=0,a.dataMax=99;c.getExtremes();d=c.dataMax;c=c.dataMin;if(!a.usePercentage&&t(c)&&t(d))a.dataMin=C(p(a.dataMin,c),c),a.dataMax=r(p(a.dataMax,d),d);if(t(e))if(a.dataMin>=e)a.dataMin=e,a.ignoreMinPadding=!0;else if(a.dataMax<e)a.dataMax=e,a.ignoreMaxPadding=!0}}})},translate:function(a,b,
+c,d,e,f){var g=this.len,h=1,i=0,j=d?this.oldTransA:this.transA,d=d?this.oldMin:this.min,k=this.minPixelPadding,e=(this.options.ordinal||this.isLog&&e)&&this.lin2val;if(!j)j=this.transA;c&&(h*=-1,i=g);this.reversed&&(h*=-1,i-=h*g);b?(a=a*h+i,a-=k,a=a/j+d,e&&(a=this.lin2val(a))):(e&&(a=this.val2lin(a)),f==="between"&&(f=0.5),a=h*(a-d)*j+i+h*k+(qa(f)?j*f*this.pointRange:0));return a},toPixels:function(a,b){return this.translate(a,!1,!this.horiz,null,!0)+(b?0:this.pos)},toValue:function(a,b){return this.translate(a-
+(b?0:this.pos),!0,!this.horiz,null,!0)},getPlotLinePath:function(a,b,c,d){var e=this.chart,f=this.left,g=this.top,h,i,j,a=this.translate(a,null,null,c),k=c&&e.oldChartHeight||e.chartHeight,l=c&&e.oldChartWidth||e.chartWidth,m;h=this.transB;c=i=u(a+h);h=j=u(k-a-h);if(isNaN(a))m=!0;else if(this.horiz){if(h=g,j=k-this.bottom,c<f||c>f+this.width)m=!0}else if(c=f,i=l-this.right,h<g||h>g+this.height)m=!0;return m&&!d?null:e.renderer.crispLine(["M",c,h,"L",i,j],b||0)},getPlotBandPath:function(a,b){var c=
+this.getPlotLinePath(b),d=this.getPlotLinePath(a);d&&c?d.push(c[4],c[5],c[1],c[2]):d=null;return d},getLinearTickPositions:function(a,b,c){for(var d,b=ka(P(b/a)*a),c=ka(wa(c/a)*a),e=[];b<=c;){e.push(b);b=ka(b+a);if(b===d)break;d=b}return e},getLogTickPositions:function(a,b,c,d){var e=this.options,f=this.len,g=[];if(!d)this._minorAutoInterval=null;if(a>=0.5)a=u(a),g=this.getLinearTickPositions(a,b,c);else if(a>=0.08)for(var f=P(b),h,i,j,k,l,e=a>0.3?[1,2,4]:a>0.15?[1,2,4,6,8]:[1,2,3,4,5,6,7,8,9];f<
+c+1&&!l;f++){i=e.length;for(h=0;h<i&&!l;h++)j=na(fa(f)*e[h]),j>b&&(!d||k<=c)&&g.push(k),k>c&&(l=!0),k=j}else if(b=fa(b),c=fa(c),a=e[d?"minorTickInterval":"tickInterval"],a=p(a==="auto"?null:a,this._minorAutoInterval,(c-b)*(e.tickPixelInterval/(d?5:1))/((d?f/this.tickPositions.length:f)||1)),a=mb(a,null,lb(a)),g=Na(this.getLinearTickPositions(a,b,c),na),!d)this._minorAutoInterval=a/5;if(!d)this.tickInterval=a;return g},getMinorTickPositions:function(){var a=this.options,b=this.tickPositions,c=this.minorTickInterval,
+d=[],e;if(this.isLog){e=b.length;for(a=1;a<e;a++)d=d.concat(this.getLogTickPositions(c,b[a-1],b[a],!0))}else if(this.isDatetimeAxis&&a.minorTickInterval==="auto")d=d.concat(Eb(Cb(c),this.min,this.max,a.startOfWeek)),d[0]<this.min&&d.shift();else for(b=this.min+(b[0]-this.min)%c;b<=this.max;b+=c)d.push(b);return d},adjustForMinRange:function(){var a=this.options,b=this.min,c=this.max,d,e=this.dataMax-this.dataMin>=this.minRange,f,g,h,i,j;if(this.isXAxis&&this.minRange===v&&!this.isLog)t(a.min)||t(a.max)?
+this.minRange=null:(n(this.series,function(a){i=a.xData;for(g=j=a.xIncrement?1:i.length-1;g>0;g--)if(h=i[g]-i[g-1],f===v||h<f)f=h}),this.minRange=C(f*5,this.dataMax-this.dataMin));if(c-b<this.minRange){var k=this.minRange;d=(k-c+b)/2;d=[b-d,p(a.min,b-d)];if(e)d[2]=this.dataMin;b=ua(d);c=[b+k,p(a.max,b+k)];if(e)c[2]=this.dataMax;c=Ia(c);c-b<k&&(d[0]=c-k,d[1]=p(a.min,c-k),b=ua(d))}this.min=b;this.max=c},setAxisTranslation:function(a){var b=this.max-this.min,c=0,d,e=0,f=0,g=this.linkedParent,h=this.transA;
+if(this.isXAxis)g?(e=g.minPointOffset,f=g.pointRangePadding):n(this.series,function(a){var g=a.pointRange,h=a.options.pointPlacement,l=a.closestPointRange;g>b&&(g=0);c=r(c,g);e=r(e,ea(h)?0:g/2);f=r(f,h==="on"?0:g);!a.noSharedTooltip&&t(l)&&(d=t(d)?C(d,l):l)}),g=this.ordinalSlope&&d?this.ordinalSlope/d:1,this.minPointOffset=e*=g,this.pointRangePadding=f*=g,this.pointRange=C(c,b),this.closestPointRange=d;if(a)this.oldTransA=h;this.translationSlope=this.transA=h=this.len/(b+f||1);this.transB=this.horiz?
+this.left:this.bottom;this.minPixelPadding=h*e},setTickPositions:function(a){var b=this,c=b.chart,d=b.options,e=b.isLog,f=b.isDatetimeAxis,g=b.isXAxis,h=b.isLinked,i=b.options.tickPositioner,j=d.maxPadding,k=d.minPadding,l=d.tickInterval,m=d.minTickInterval,o=d.tickPixelInterval,q=b.categories;h?(b.linkedParent=c[g?"xAxis":"yAxis"][d.linkedTo],c=b.linkedParent.getExtremes(),b.min=p(c.min,c.dataMin),b.max=p(c.max,c.dataMax),d.type!==b.linkedParent.options.type&&ja(11,1)):(b.min=p(b.userMin,d.min,b.dataMin),
+b.max=p(b.userMax,d.max,b.dataMax));if(e)!a&&C(b.min,p(b.dataMin,b.min))<=0&&ja(10,1),b.min=ka(na(b.min)),b.max=ka(na(b.max));if(b.range&&(b.userMin=b.min=r(b.min,b.max-b.range),b.userMax=b.max,a))b.range=null;b.beforePadding&&b.beforePadding();b.adjustForMinRange();if(!q&&!b.usePercentage&&!h&&t(b.min)&&t(b.max)&&(c=b.max-b.min)){if(!t(d.min)&&!t(b.userMin)&&k&&(b.dataMin<0||!b.ignoreMinPadding))b.min-=c*k;if(!t(d.max)&&!t(b.userMax)&&j&&(b.dataMax>0||!b.ignoreMaxPadding))b.max+=c*j}b.tickInterval=
+b.min===b.max||b.min===void 0||b.max===void 0?1:h&&!l&&o===b.linkedParent.options.tickPixelInterval?b.linkedParent.tickInterval:p(l,q?1:(b.max-b.min)*o/(b.len||1));g&&!a&&n(b.series,function(a){a.processData(b.min!==b.oldMin||b.max!==b.oldMax)});b.setAxisTranslation(!0);b.beforeSetTickPositions&&b.beforeSetTickPositions();if(b.postProcessTickInterval)b.tickInterval=b.postProcessTickInterval(b.tickInterval);if(b.pointRange)b.tickInterval=r(b.pointRange,b.tickInterval);if(!l&&b.tickInterval<m)b.tickInterval=
+m;if(!f&&!e&&!l)b.tickInterval=mb(b.tickInterval,null,lb(b.tickInterval),d);b.minorTickInterval=d.minorTickInterval==="auto"&&b.tickInterval?b.tickInterval/5:d.minorTickInterval;b.tickPositions=a=d.tickPositions?[].concat(d.tickPositions):i&&i.apply(b,[b.min,b.max]);if(!a)(b.max-b.min)/b.tickInterval>2*b.len&&ja(19,!0),a=f?(b.getNonLinearTimeTicks||Eb)(Cb(b.tickInterval,d.units),b.min,b.max,d.startOfWeek,b.ordinalPositions,b.closestPointRange,!0):e?b.getLogTickPositions(b.tickInterval,b.min,b.max):
+b.getLinearTickPositions(b.tickInterval,b.min,b.max),b.tickPositions=a;if(!h)e=a[0],f=a[a.length-1],h=b.minPointOffset||0,d.startOnTick?b.min=e:b.min-h>e&&a.shift(),d.endOnTick?b.max=f:b.max+h<f&&a.pop(),a.length===1&&(b.min-=0.001,b.max+=0.001)},setMaxTicks:function(){var a=this.chart,b=a.maxTicks||{},c=this.tickPositions,d=this._maxTicksKey=[this.xOrY,this.pos,this.len].join("-");if(!this.isLinked&&!this.isDatetimeAxis&&c&&c.length>(b[d]||0)&&this.options.alignTicks!==!1)b[d]=c.length;a.maxTicks=
+b},adjustTickAmount:function(){var a=this._maxTicksKey,b=this.tickPositions,c=this.chart.maxTicks;if(c&&c[a]&&!this.isDatetimeAxis&&!this.categories&&!this.isLinked&&this.options.alignTicks!==!1){var d=this.tickAmount,e=b.length;this.tickAmount=a=c[a];if(e<a){for(;b.length<a;)b.push(ka(b[b.length-1]+this.tickInterval));this.transA*=(e-1)/(a-1);this.max=b[b.length-1]}if(t(d)&&a!==d)this.isDirty=!0}},setScale:function(){var a=this.stacks,b,c,d,e;this.oldMin=this.min;this.oldMax=this.max;this.oldAxisLength=
+this.len;this.setAxisSize();e=this.len!==this.oldAxisLength;n(this.series,function(a){if(a.isDirtyData||a.isDirty||a.xAxis.isDirty)d=!0});if(e||d||this.isLinked||this.forceRedraw||this.userMin!==this.oldUserMin||this.userMax!==this.oldUserMax){if(!this.isXAxis)for(b in a)for(c in a[b])a[b][c].total=null;this.forceRedraw=!1;this.getSeriesExtremes();this.setTickPositions();this.oldUserMin=this.userMin;this.oldUserMax=this.userMax;if(!this.isDirty)this.isDirty=e||this.min!==this.oldMin||this.max!==this.oldMax}else if(!this.isXAxis){if(this.oldStacks)a=
+this.stacks=this.oldStacks;for(b in a)for(c in a[b])a[b][c].cum=a[b][c].total}this.setMaxTicks()},setExtremes:function(a,b,c,d,e){var f=this,g=f.chart,c=p(c,!0),e=s(e,{min:a,max:b});K(f,"setExtremes",e,function(){f.userMin=a;f.userMax=b;f.isDirtyExtremes=!0;c&&g.redraw(d)})},zoom:function(a,b){this.allowZoomOutside||(t(this.dataMin)&&a<=this.dataMin&&(a=v),t(this.dataMax)&&b>=this.dataMax&&(b=v));this.displayBtn=a!==v||b!==v;this.setExtremes(a,b,!1,v,{trigger:"zoom"});return!0},setAxisSize:function(){var a=
+this.chart,b=this.options,c=b.offsetLeft||0,d=b.offsetRight||0,e=this.horiz,f,g;this.left=g=p(b.left,a.plotLeft+c);this.top=f=p(b.top,a.plotTop);this.width=c=p(b.width,a.plotWidth-c+d);this.height=b=p(b.height,a.plotHeight);this.bottom=a.chartHeight-b-f;this.right=a.chartWidth-c-g;this.len=r(e?c:b,0);this.pos=e?g:f},getExtremes:function(){var a=this.isLog;return{min:a?ka(fa(this.min)):this.min,max:a?ka(fa(this.max)):this.max,dataMin:this.dataMin,dataMax:this.dataMax,userMin:this.userMin,userMax:this.userMax}},
+getThreshold:function(a){var b=this.isLog,c=b?fa(this.min):this.min,b=b?fa(this.max):this.max;c>a||a===null?a=c:b<a&&(a=b);return this.translate(a,0,1,0,1)},addPlotBand:function(a){this.addPlotBandOrLine(a,"plotBands")},addPlotLine:function(a){this.addPlotBandOrLine(a,"plotLines")},addPlotBandOrLine:function(a,b){var c=(new tb(this,a)).render(),d=this.userOptions;b&&(d[b]=d[b]||[],d[b].push(a));this.plotLinesAndBands.push(c);return c},autoLabelAlign:function(a){a=(p(a,0)-this.side*90+720)%360;return a>
+15&&a<165?"right":a>195&&a<345?"left":"center"},getOffset:function(){var a=this,b=a.chart,c=b.renderer,d=a.options,e=a.tickPositions,f=a.ticks,g=a.horiz,h=a.side,i=b.inverted?[1,0,3,2][h]:h,j,k=0,l,m=0,o=d.title,q=d.labels,la=0,y=b.axisOffset,u=b.clipOffset,s=[-1,1,1,-1][h],w,x=1,A=p(q.maxStaggerLines,5),E,G,I,B;a.hasData=j=a.hasVisibleSeries||t(a.min)&&t(a.max)&&!!e;a.showAxis=b=j||p(d.showEmpty,!0);a.staggerLines=a.horiz&&q.staggerLines;if(!a.axisGroup)a.gridGroup=c.g("grid").attr({zIndex:d.gridZIndex||
+1}).add(),a.axisGroup=c.g("axis").attr({zIndex:d.zIndex||2}).add(),a.labelGroup=c.g("axis-labels").attr({zIndex:q.zIndex||7}).add();if(j||a.isLinked){a.labelAlign=p(q.align||a.autoLabelAlign(q.rotation));n(e,function(b){f[b]?f[b].addLabel():f[b]=new La(a,b)});if(a.horiz&&!a.staggerLines&&A&&!q.rotation){for(w=a.reversed?[].concat(e).reverse():e;x<A;){j=[];E=!1;for(q=0;q<w.length;q++)G=w[q],I=(I=f[G].label&&f[G].label.bBox)?I.width:0,B=q%x,I&&(G=a.translate(G),j[B]!==v&&G<j[B]&&(E=!0),j[B]=G+I);if(E)x++;
+else break}if(x>1)a.staggerLines=x}n(e,function(b){if(h===0||h===2||{1:"left",3:"right"}[h]===a.labelAlign)la=r(f[b].getLabelSize(),la)});if(a.staggerLines)la*=a.staggerLines,a.labelOffset=la}else for(w in f)f[w].destroy(),delete f[w];if(o&&o.text&&o.enabled!==!1){if(!a.axisTitle)a.axisTitle=c.text(o.text,0,0,o.useHTML).attr({zIndex:7,rotation:o.rotation||0,align:o.textAlign||{low:"left",middle:"center",high:"right"}[o.align]}).css(o.style).add(a.axisGroup),a.axisTitle.isNew=!0;if(b)k=a.axisTitle.getBBox()[g?
+"height":"width"],m=p(o.margin,g?5:10),l=o.offset;a.axisTitle[b?"show":"hide"]()}a.offset=s*p(d.offset,y[h]);a.axisTitleMargin=p(l,la+m+(h!==2&&la&&s*d.labels[g?"y":"x"]));y[h]=r(y[h],a.axisTitleMargin+k+s*a.offset);u[i]=r(u[i],P(d.lineWidth/2)*2)},getLinePath:function(a){var b=this.chart,c=this.opposite,d=this.offset,e=this.horiz,f=this.left+(c?this.width:0)+d;this.lineTop=d=b.chartHeight-this.bottom-(c?this.height:0)+d;c&&(a*=-1);return b.renderer.crispLine(["M",e?this.left:f,e?d:this.top,"L",e?
+b.chartWidth-this.right:f,e?d:b.chartHeight-this.bottom],a)},getTitlePosition:function(){var a=this.horiz,b=this.left,c=this.top,d=this.len,e=this.options.title,f=a?b:c,g=this.opposite,h=this.offset,i=A(e.style.fontSize||12),d={low:f+(a?0:d),middle:f+d/2,high:f+(a?d:0)}[e.align],b=(a?c+this.height:b)+(a?1:-1)*(g?-1:1)*this.axisTitleMargin+(this.side===2?i:0);return{x:a?d:b+(g?this.width:0)+h+(e.x||0),y:a?b-(g?this.height:0)+h:d+(e.y||0)}},render:function(){var a=this,b=a.chart,c=b.renderer,d=a.options,
+e=a.isLog,f=a.isLinked,g=a.tickPositions,h=a.axisTitle,i=a.stacks,j=a.ticks,k=a.minorTicks,l=a.alternateBands,m=d.stackLabels,o=d.alternateGridColor,q=a.tickmarkOffset,p=d.lineWidth,y,r=b.hasRendered&&t(a.oldMin)&&!isNaN(a.oldMin);y=a.hasData;var u=a.showAxis,s,w;n([j,k,l],function(a){for(var b in a)a[b].isActive=!1});if(y||f)if(a.minorTickInterval&&!a.categories&&n(a.getMinorTickPositions(),function(b){k[b]||(k[b]=new La(a,b,"minor"));r&&k[b].isNew&&k[b].render(null,!0);k[b].render(null,!1,1)}),
+g.length&&(n(g.slice(1).concat([g[0]]),function(b,c){c=c===g.length-1?0:c+1;if(!f||b>=a.min&&b<=a.max)j[b]||(j[b]=new La(a,b)),r&&j[b].isNew&&j[b].render(c,!0),j[b].render(c,!1,1)}),q&&a.min===0&&(j[-1]||(j[-1]=new La(a,-1,null,!0)),j[-1].render(-1))),o&&n(g,function(b,c){if(c%2===0&&b<a.max)l[b]||(l[b]=new tb(a)),s=b+q,w=g[c+1]!==v?g[c+1]+q:a.max,l[b].options={from:e?fa(s):s,to:e?fa(w):w,color:o},l[b].render(),l[b].isActive=!0}),!a._addedPlotLB)n((d.plotLines||[]).concat(d.plotBands||[]),function(b){a.addPlotBandOrLine(b)}),
+a._addedPlotLB=!0;n([j,k,l],function(a){var c,d,e=[],f=Da?Da.duration||500:0,g=function(){for(d=e.length;d--;)a[e[d]]&&!a[e[d]].isActive&&(a[e[d]].destroy(),delete a[e[d]])};for(c in a)if(!a[c].isActive)a[c].render(c,!1,0),a[c].isActive=!1,e.push(c);a===l||!b.hasRendered||!f?g():f&&setTimeout(g,f)});if(p)y=a.getLinePath(p),a.axisLine?a.axisLine.animate({d:y}):a.axisLine=c.path(y).attr({stroke:d.lineColor,"stroke-width":p,zIndex:7}).add(a.axisGroup),a.axisLine[u?"show":"hide"]();if(h&&u)h[h.isNew?
+"attr":"animate"](a.getTitlePosition()),h.isNew=!1;if(m&&m.enabled){var x,E,d=a.stackTotalGroup;if(!d)a.stackTotalGroup=d=c.g("stack-labels").attr({visibility:"visible",zIndex:6}).add();d.translate(b.plotLeft,b.plotTop);for(x in i)for(E in c=i[x],c)c[E].render(d)}a.isDirty=!1},removePlotBandOrLine:function(a){for(var b=this.plotLinesAndBands,c=this.options,d=this.userOptions,e=b.length;e--;)b[e].id===a&&b[e].destroy();n([c.plotLines||[],d.plotLines||[],c.plotBands||[],d.plotBands||[]],function(b){for(e=
+b.length;e--;)b[e].id===a&&ga(b,b[e])})},setTitle:function(a,b){this.update({title:a},b)},redraw:function(){var a=this.chart.pointer;a.reset&&a.reset(!0);this.render();n(this.plotLinesAndBands,function(a){a.render()});n(this.series,function(a){a.isDirty=!0})},buildStacks:function(){this.isXAxis||n(this.series,function(a){a.setStackedPoints()})},setCategories:function(a,b){this.update({categories:a},b)},destroy:function(a){var b=this,c=b.stacks,d,e=b.plotLinesAndBands;a||ba(b);for(d in c)Ja(c[d]),
+c[d]=null;n([b.ticks,b.minorTicks,b.alternateBands],function(a){Ja(a)});for(a=e.length;a--;)e[a].destroy();n("stackTotalGroup,axisLine,axisGroup,gridGroup,labelGroup,axisTitle".split(","),function(a){b[a]&&(b[a]=b[a].destroy())})}};ub.prototype={init:function(a,b){var c=b.borderWidth,d=b.style,e=A(d.padding);this.chart=a;this.options=b;this.crosshairs=[];this.now={x:0,y:0};this.isHidden=!0;this.label=a.renderer.label("",0,0,b.shape,null,null,b.useHTML,null,"tooltip").attr({padding:e,fill:b.backgroundColor,
+"stroke-width":c,r:b.borderRadius,zIndex:8}).css(d).css({padding:0}).hide().add();$||this.label.shadow(b.shadow);this.shared=b.shared},destroy:function(){n(this.crosshairs,function(a){a&&a.destroy()});if(this.label)this.label=this.label.destroy();clearTimeout(this.hideTimer);clearTimeout(this.tooltipTimeout)},move:function(a,b,c,d){var e=this,f=e.now,g=e.options.animation!==!1&&!e.isHidden;s(f,{x:g?(2*f.x+a)/3:a,y:g?(f.y+b)/2:b,anchorX:g?(2*f.anchorX+c)/3:c,anchorY:g?(f.anchorY+d)/2:d});e.label.attr(f);
+if(g&&(O(a-f.x)>1||O(b-f.y)>1))clearTimeout(this.tooltipTimeout),this.tooltipTimeout=setTimeout(function(){e&&e.move(a,b,c,d)},32)},hide:function(){var a=this,b;clearTimeout(this.hideTimer);if(!this.isHidden)b=this.chart.hoverPoints,this.hideTimer=setTimeout(function(){a.label.fadeOut();a.isHidden=!0},p(this.options.hideDelay,500)),b&&n(b,function(a){a.setState()}),this.chart.hoverPoints=null},hideCrosshairs:function(){n(this.crosshairs,function(a){a&&a.hide()})},getAnchor:function(a,b){var c,d=this.chart,
+e=d.inverted,f=d.plotTop,g=0,h=0,i,a=ia(a);c=a[0].tooltipPos;this.followPointer&&b&&(b.chartX===v&&(b=d.pointer.normalize(b)),c=[b.chartX-d.plotLeft,b.chartY-f]);c||(n(a,function(a){i=a.series.yAxis;g+=a.plotX;h+=(a.plotLow?(a.plotLow+a.plotHigh)/2:a.plotY)+(!e&&i?i.top-f:0)}),g/=a.length,h/=a.length,c=[e?d.plotWidth-h:g,this.shared&&!e&&a.length>1&&b?b.chartY-f:e?d.plotHeight-g:h]);return Na(c,u)},getPosition:function(a,b,c){var d=this.chart,e=d.plotLeft,f=d.plotTop,g=d.plotWidth,h=d.plotHeight,
+i=p(this.options.distance,12),j=c.plotX,c=c.plotY,d=j+e+(d.inverted?i:-a-i),k=c-b+f+15,l;d<7&&(d=e+r(j,0)+i);d+a>e+g&&(d-=d+a-(e+g),k=c-b+f-i,l=!0);k<f+5&&(k=f+5,l&&c>=k&&c<=k+b&&(k=c+f+i));k+b>f+h&&(k=r(f,f+h-b-i));return{x:d,y:k}},defaultFormatter:function(a){var b=this.points||ia(this),c=b[0].series,d;d=[c.tooltipHeaderFormatter(b[0])];n(b,function(a){c=a.series;d.push(c.tooltipFormatter&&c.tooltipFormatter(a)||a.point.tooltipFormatter(c.tooltipOptions.pointFormat))});d.push(a.options.footerFormat||
+"");return d.join("")},refresh:function(a,b){var c=this.chart,d=this.label,e=this.options,f,g,h={},i,j=[];i=e.formatter||this.defaultFormatter;var h=c.hoverPoints,k,l=e.crosshairs,m=this.shared;clearTimeout(this.hideTimer);this.followPointer=ia(a)[0].series.tooltipOptions.followPointer;g=this.getAnchor(a,b);f=g[0];g=g[1];m&&(!a.series||!a.series.noSharedTooltip)?(c.hoverPoints=a,h&&n(h,function(a){a.setState()}),n(a,function(a){a.setState("hover");j.push(a.getLabelConfig())}),h={x:a[0].category,y:a[0].y},
+h.points=j,a=a[0]):h=a.getLabelConfig();i=i.call(h,this);h=a.series;i===!1?this.hide():(this.isHidden&&(Wa(d),d.attr("opacity",1).show()),d.attr({text:i}),k=e.borderColor||a.color||h.color||"#606060",d.attr({stroke:k}),this.updatePosition({plotX:f,plotY:g}),this.isHidden=!1);if(l){l=ia(l);for(d=l.length;d--;)if(m=a.series,e=m[d?"yAxis":"xAxis"],l[d]&&e)if(h=d?p(a.stackY,a.y):a.x,e.isLog&&(h=na(h)),m.modifyValue&&(h=m.modifyValue(h)),e=e.getPlotLinePath(h,1),this.crosshairs[d])this.crosshairs[d].attr({d:e,
+visibility:"visible"});else{h={"stroke-width":l[d].width||1,stroke:l[d].color||"#C0C0C0",zIndex:l[d].zIndex||2};if(l[d].dashStyle)h.dashstyle=l[d].dashStyle;this.crosshairs[d]=c.renderer.path(e).attr(h).add()}}K(c,"tooltipRefresh",{text:i,x:f+c.plotLeft,y:g+c.plotTop,borderColor:k})},updatePosition:function(a){var b=this.chart,c=this.label,c=(this.options.positioner||this.getPosition).call(this,c.width,c.height,a);this.move(u(c.x),u(c.y),a.plotX+b.plotLeft,a.plotY+b.plotTop)}};vb.prototype={init:function(a,
+b){var c=$?"":b.chart.zoomType,d=a.inverted,e;this.options=b;this.chart=a;this.zoomX=e=/x/.test(c);this.zoomY=c=/y/.test(c);this.zoomHor=e&&!d||c&&d;this.zoomVert=c&&!d||e&&d;this.pinchDown=[];this.lastValidTouch={};if(b.tooltip.enabled)a.tooltip=new ub(a,b.tooltip);this.setDOMEvents()},normalize:function(a){var b,c,d,a=a||N.event;if(!a.target)a.target=a.srcElement;a=Sb(a);d=a.touches?a.touches.item(0):a;this.chartPosition=b=Wb(this.chart.container);d.pageX===v?(c=r(a.x,a.clientX-b.left),b=a.y):(c=
+d.pageX-b.left,b=d.pageY-b.top);return s(a,{chartX:u(c),chartY:u(b)})},getCoordinates:function(a){var b={xAxis:[],yAxis:[]};n(this.chart.axes,function(c){b[c.isXAxis?"xAxis":"yAxis"].push({axis:c,value:c.toValue(a[c.horiz?"chartX":"chartY"])})});return b},getIndex:function(a){var b=this.chart;return b.inverted?b.plotHeight+b.plotTop-a.chartY:a.chartX-b.plotLeft},runPointActions:function(a){var b=this.chart,c=b.series,d=b.tooltip,e,f=b.hoverPoint,g=b.hoverSeries,h,i,j=b.chartWidth,k=this.getIndex(a);
+if(d&&this.options.tooltip.shared&&(!g||!g.noSharedTooltip)){e=[];h=c.length;for(i=0;i<h;i++)if(c[i].visible&&c[i].options.enableMouseTracking!==!1&&!c[i].noSharedTooltip&&c[i].tooltipPoints.length&&(b=c[i].tooltipPoints[k],b.series))b._dist=O(k-b.clientX),j=C(j,b._dist),e.push(b);for(h=e.length;h--;)e[h]._dist>j&&e.splice(h,1);if(e.length&&e[0].clientX!==this.hoverX)d.refresh(e,a),this.hoverX=e[0].clientX}if(g&&g.tracker){if((b=g.tooltipPoints[k])&&b!==f)b.onMouseOver(a)}else d&&d.followPointer&&
+!d.isHidden&&(a=d.getAnchor([{}],a),d.updatePosition({plotX:a[0],plotY:a[1]}))},reset:function(a){var b=this.chart,c=b.hoverSeries,d=b.hoverPoint,e=b.tooltip,b=e&&e.shared?b.hoverPoints:d;(a=a&&e&&b)&&ia(b)[0].plotX===v&&(a=!1);if(a)e.refresh(b);else{if(d)d.onMouseOut();if(c)c.onMouseOut();e&&(e.hide(),e.hideCrosshairs());this.hoverX=null}},scaleGroups:function(a,b){var c=this.chart,d;n(c.series,function(e){d=a||e.getPlotBox();e.xAxis&&e.xAxis.zoomEnabled&&(e.group.attr(d),e.markerGroup&&(e.markerGroup.attr(d),
+e.markerGroup.clip(b?c.clipRect:null)),e.dataLabelsGroup&&e.dataLabelsGroup.attr(d))});c.clipRect.attr(b||c.clipBox)},pinchTranslateDirection:function(a,b,c,d,e,f,g){var h=this.chart,i=a?"x":"y",j=a?"X":"Y",k="chart"+j,l=a?"width":"height",m=h["plot"+(a?"Left":"Top")],o,q,p=1,n=h.inverted,r=h.bounds[a?"h":"v"],u=b.length===1,t=b[0][k],s=c[0][k],w=!u&&b[1][k],v=!u&&c[1][k],x,c=function(){!u&&O(t-w)>20&&(p=O(s-v)/O(t-w));q=(m-s)/p+t;o=h["plot"+(a?"Width":"Height")]/p};c();b=q;b<r.min?(b=r.min,x=!0):
+b+o>r.max&&(b=r.max-o,x=!0);x?(s-=0.8*(s-g[i][0]),u||(v-=0.8*(v-g[i][1])),c()):g[i]=[s,v];n||(f[i]=q-m,f[l]=o);f=n?1/p:p;e[l]=o;e[i]=b;d[n?a?"scaleY":"scaleX":"scale"+j]=p;d["translate"+j]=f*m+(s-f*t)},pinch:function(a){var b=this,c=b.chart,d=b.pinchDown,e=c.tooltip&&c.tooltip.options.followTouchMove,f=a.touches,g=f.length,h=b.lastValidTouch,i=b.zoomHor||b.pinchHor,j=b.zoomVert||b.pinchVert,k=i||j,l=b.selectionMarker,m={},o={};(e||k)&&a.preventDefault();Na(f,function(a){return b.normalize(a)});if(a.type===
+"touchstart")n(f,function(a,b){d[b]={chartX:a.chartX,chartY:a.chartY}}),h.x=[d[0].chartX,d[1]&&d[1].chartX],h.y=[d[0].chartY,d[1]&&d[1].chartY],n(c.axes,function(a){if(a.zoomEnabled){var b=c.bounds[a.horiz?"h":"v"],d=a.minPixelPadding,e=a.toPixels(a.dataMin),f=a.toPixels(a.dataMax),g=C(e,f),e=r(e,f);b.min=C(a.pos,g-d);b.max=r(a.pos+a.len,e+d)}});else if(d.length){if(!l)b.selectionMarker=l=s({destroy:ya},c.plotBox);i&&b.pinchTranslateDirection(!0,d,f,m,l,o,h);j&&b.pinchTranslateDirection(!1,d,f,m,
+l,o,h);b.hasPinched=k;b.scaleGroups(m,o);!k&&e&&g===1&&this.runPointActions(b.normalize(a))}},dragStart:function(a){var b=this.chart;b.mouseIsDown=a.type;b.cancelClick=!1;b.mouseDownX=this.mouseDownX=a.chartX;b.mouseDownY=this.mouseDownY=a.chartY},drag:function(a){var b=this.chart,c=b.options.chart,d=a.chartX,e=a.chartY,f=this.zoomHor,g=this.zoomVert,h=b.plotLeft,i=b.plotTop,j=b.plotWidth,k=b.plotHeight,l,m=this.mouseDownX,o=this.mouseDownY;d<h?d=h:d>h+j&&(d=h+j);e<i?e=i:e>i+k&&(e=i+k);this.hasDragged=
+Math.sqrt(Math.pow(m-d,2)+Math.pow(o-e,2));if(this.hasDragged>10){l=b.isInsidePlot(m-h,o-i);if(b.hasCartesianSeries&&(this.zoomX||this.zoomY)&&l&&!this.selectionMarker)this.selectionMarker=b.renderer.rect(h,i,f?1:j,g?1:k,0).attr({fill:c.selectionMarkerFill||"rgba(69,114,167,0.25)",zIndex:7}).add();this.selectionMarker&&f&&(d-=m,this.selectionMarker.attr({width:O(d),x:(d>0?0:d)+m}));this.selectionMarker&&g&&(d=e-o,this.selectionMarker.attr({height:O(d),y:(d>0?0:d)+o}));l&&!this.selectionMarker&&c.panning&&
+b.pan(a,c.panning)}},drop:function(a){var b=this.chart,c=this.hasPinched;if(this.selectionMarker){var d={xAxis:[],yAxis:[],originalEvent:a.originalEvent||a},e=this.selectionMarker,f=e.x,g=e.y,h;if(this.hasDragged||c)n(b.axes,function(a){if(a.zoomEnabled){var b=a.horiz,c=a.toValue(b?f:g),b=a.toValue(b?f+e.width:g+e.height);!isNaN(c)&&!isNaN(b)&&(d[a.xOrY+"Axis"].push({axis:a,min:C(c,b),max:r(c,b)}),h=!0)}}),h&&K(b,"selection",d,function(a){b.zoom(s(a,c?{animation:!1}:null))});this.selectionMarker=
+this.selectionMarker.destroy();c&&this.scaleGroups()}if(b)L(b.container,{cursor:b._cursor}),b.cancelClick=this.hasDragged>10,b.mouseIsDown=this.hasDragged=this.hasPinched=!1,this.pinchDown=[]},onContainerMouseDown:function(a){a=this.normalize(a);a.preventDefault&&a.preventDefault();this.dragStart(a)},onDocumentMouseUp:function(a){this.drop(a)},onDocumentMouseMove:function(a){var b=this.chart,c=this.chartPosition,d=b.hoverSeries,a=Sb(a);c&&d&&!this.inClass(a.target,"highcharts-tracker")&&!b.isInsidePlot(a.pageX-
+c.left-b.plotLeft,a.pageY-c.top-b.plotTop)&&this.reset()},onContainerMouseLeave:function(){this.reset();this.chartPosition=null},onContainerMouseMove:function(a){var b=this.chart,a=this.normalize(a);a.returnValue=!1;b.mouseIsDown==="mousedown"&&this.drag(a);(this.inClass(a.target,"highcharts-tracker")||b.isInsidePlot(a.chartX-b.plotLeft,a.chartY-b.plotTop))&&!b.openMenu&&this.runPointActions(a)},inClass:function(a,b){for(var c;a;){if(c=w(a,"class"))if(c.indexOf(b)!==-1)return!0;else if(c.indexOf("highcharts-container")!==
+-1)return!1;a=a.parentNode}},onTrackerMouseOut:function(a){var b=this.chart.hoverSeries;if(b&&!b.options.stickyTracking&&!this.inClass(a.toElement||a.relatedTarget,"highcharts-tooltip"))b.onMouseOut()},onContainerClick:function(a){var b=this.chart,c=b.hoverPoint,d=b.plotLeft,e=b.plotTop,f=b.inverted,g,h,i,a=this.normalize(a);a.cancelBubble=!0;if(!b.cancelClick)c&&this.inClass(a.target,"highcharts-tracker")?(g=this.chartPosition,h=c.plotX,i=c.plotY,s(c,{pageX:g.left+d+(f?b.plotWidth-i:h),pageY:g.top+
+e+(f?b.plotHeight-h:i)}),K(c.series,"click",s(a,{point:c})),b.hoverPoint&&c.firePointEvent("click",a)):(s(a,this.getCoordinates(a)),b.isInsidePlot(a.chartX-d,a.chartY-e)&&K(b,"click",a))},onContainerTouchStart:function(a){var b=this.chart;a.touches.length===1?(a=this.normalize(a),b.isInsidePlot(a.chartX-b.plotLeft,a.chartY-b.plotTop)?(this.runPointActions(a),this.pinch(a)):this.reset()):a.touches.length===2&&this.pinch(a)},onContainerTouchMove:function(a){(a.touches.length===1||a.touches.length===
+2)&&this.pinch(a)},onDocumentTouchEnd:function(a){this.drop(a)},setDOMEvents:function(){var a=this,b=a.chart.container,c;this._events=c=[[b,"onmousedown","onContainerMouseDown"],[b,"onmousemove","onContainerMouseMove"],[b,"onclick","onContainerClick"],[b,"mouseleave","onContainerMouseLeave"],[z,"mousemove","onDocumentMouseMove"],[z,"mouseup","onDocumentMouseUp"]];hb&&c.push([b,"ontouchstart","onContainerTouchStart"],[b,"ontouchmove","onContainerTouchMove"],[z,"touchend","onDocumentTouchEnd"]);n(c,
+function(b){a["_"+b[2]]=function(c){a[b[2]](c)};b[1].indexOf("on")===0?b[0][b[1]]=a["_"+b[2]]:J(b[0],b[1],a["_"+b[2]])})},destroy:function(){var a=this;n(a._events,function(b){b[1].indexOf("on")===0?b[0][b[1]]=null:ba(b[0],b[1],a["_"+b[2]])});delete a._events;clearInterval(a.tooltipTimeout)}};wb.prototype={init:function(a,b){var c=this,d=b.itemStyle,e=p(b.padding,8),f=b.itemMarginTop||0;this.options=b;if(b.enabled)c.baseline=A(d.fontSize)+3+f,c.itemStyle=d,c.itemHiddenStyle=x(d,b.itemHiddenStyle),
+c.itemMarginTop=f,c.padding=e,c.initialItemX=e,c.initialItemY=e-5,c.maxItemWidth=0,c.chart=a,c.itemHeight=0,c.lastLineHeight=0,c.render(),J(c.chart,"endResize",function(){c.positionCheckboxes()})},colorizeItem:function(a,b){var c=this.options,d=a.legendItem,e=a.legendLine,f=a.legendSymbol,g=this.itemHiddenStyle.color,c=b?c.itemStyle.color:g,h=b?a.color:g,g=a.options&&a.options.marker,i={stroke:h,fill:h},j;d&&d.css({fill:c,color:c});e&&e.attr({stroke:h});if(f){if(g&&f.isMarker)for(j in g=a.convertAttribs(g),
+g)d=g[j],d!==v&&(i[j]=d);f.attr(i)}},positionItem:function(a){var b=this.options,c=b.symbolPadding,b=!b.rtl,d=a._legendItemPos,e=d[0],d=d[1],f=a.checkbox;a.legendGroup&&a.legendGroup.translate(b?e:this.legendWidth-e-2*c-4,d);if(f)f.x=e,f.y=d},destroyItem:function(a){var b=a.checkbox;n(["legendItem","legendLine","legendSymbol","legendGroup"],function(b){a[b]&&(a[b]=a[b].destroy())});b&&Ta(a.checkbox)},destroy:function(){var a=this.group,b=this.box;if(b)this.box=b.destroy();if(a)this.group=a.destroy()},
+positionCheckboxes:function(a){var b=this.group.alignAttr,c,d=this.clipHeight||this.legendHeight;if(b)c=b.translateY,n(this.allItems,function(e){var f=e.checkbox,g;f&&(g=c+f.y+(a||0)+3,L(f,{left:b.translateX+e.legendItemWidth+f.x-20+"px",top:g+"px",display:g>c-6&&g<c+d-6?"":S}))})},renderTitle:function(){var a=this.padding,b=this.options.title,c=0;if(b.text){if(!this.title)this.title=this.chart.renderer.label(b.text,a-3,a-4,null,null,null,null,null,"legend-title").attr({zIndex:1}).css(b.style).add(this.group);
+a=this.title.getBBox();c=a.height;this.offsetWidth=a.width;this.contentGroup.attr({translateY:c})}this.titleHeight=c},renderItem:function(a){var B;var b=this,c=b.chart,d=c.renderer,e=b.options,f=e.layout==="horizontal",g=e.symbolWidth,h=e.symbolPadding,i=b.itemStyle,j=b.itemHiddenStyle,k=b.padding,l=f?p(e.itemDistance,8):0,m=!e.rtl,o=e.width,q=e.itemMarginBottom||0,n=b.itemMarginTop,y=b.initialItemX,u=a.legendItem,t=a.series||a,s=t.options,w=s.showCheckbox,v=e.useHTML;if(!u&&(a.legendGroup=d.g("legend-item").attr({zIndex:1}).add(b.scrollGroup),
+t.drawLegendSymbol(b,a),a.legendItem=u=d.text(e.labelFormat?Ba(e.labelFormat,a):e.labelFormatter.call(a),m?g+h:-h,b.baseline,v).css(x(a.visible?i:j)).attr({align:m?"left":"right",zIndex:2}).add(a.legendGroup),(v?u:a.legendGroup).on("mouseover",function(){a.setState("hover");u.css(b.options.itemHoverStyle)}).on("mouseout",function(){u.css(a.visible?i:j);a.setState()}).on("click",function(b){var c=function(){a.setVisible()},b={browserEvent:b};a.firePointEvent?a.firePointEvent("legendItemClick",b,c):
+K(a,"legendItemClick",b,c)}),b.colorizeItem(a,a.visible),s&&w))a.checkbox=U("input",{type:"checkbox",checked:a.selected,defaultChecked:a.selected},e.itemCheckboxStyle,c.container),J(a.checkbox,"click",function(b){K(a,"checkboxClick",{checked:b.target.checked},function(){a.select()})});d=u.getBBox();B=a.legendItemWidth=e.itemWidth||g+h+d.width+l+(w?20:0),e=B;b.itemHeight=g=d.height;if(f&&b.itemX-y+e>(o||c.chartWidth-2*k-y))b.itemX=y,b.itemY+=n+b.lastLineHeight+q,b.lastLineHeight=0;b.maxItemWidth=r(b.maxItemWidth,
+e);b.lastItemY=n+b.itemY+q;b.lastLineHeight=r(g,b.lastLineHeight);a._legendItemPos=[b.itemX,b.itemY];f?b.itemX+=e:(b.itemY+=n+g+q,b.lastLineHeight=g);b.offsetWidth=o||r((f?b.itemX-y-l:e)+k,b.offsetWidth)},render:function(){var a=this,b=a.chart,c=b.renderer,d=a.group,e,f,g,h,i=a.box,j=a.options,k=a.padding,l=j.borderWidth,m=j.backgroundColor;a.itemX=a.initialItemX;a.itemY=a.initialItemY;a.offsetWidth=0;a.lastItemY=0;if(!d)a.group=d=c.g("legend").attr({zIndex:7}).add(),a.contentGroup=c.g().attr({zIndex:1}).add(d),
+a.scrollGroup=c.g().add(a.contentGroup);a.renderTitle();e=[];n(b.series,function(a){var b=a.options;b.showInLegend&&!t(b.linkedTo)&&(e=e.concat(a.legendItems||(b.legendType==="point"?a.data:a)))});Kb(e,function(a,b){return(a.options&&a.options.legendIndex||0)-(b.options&&b.options.legendIndex||0)});j.reversed&&e.reverse();a.allItems=e;a.display=f=!!e.length;n(e,function(b){a.renderItem(b)});g=j.width||a.offsetWidth;h=a.lastItemY+a.lastLineHeight+a.titleHeight;h=a.handleOverflow(h);if(l||m){g+=k;h+=
+k;if(i){if(g>0&&h>0)i[i.isNew?"attr":"animate"](i.crisp(null,null,null,g,h)),i.isNew=!1}else a.box=i=c.rect(0,0,g,h,j.borderRadius,l||0).attr({stroke:j.borderColor,"stroke-width":l||0,fill:m||S}).add(d).shadow(j.shadow),i.isNew=!0;i[f?"show":"hide"]()}a.legendWidth=g;a.legendHeight=h;n(e,function(b){a.positionItem(b)});f&&d.align(s({width:g,height:h},j),!0,"spacingBox");b.isResizing||this.positionCheckboxes()},handleOverflow:function(a){var b=this,c=this.chart,d=c.renderer,e=this.options,f=e.y,f=
+c.spacingBox.height+(e.verticalAlign==="top"?-f:f)-this.padding,g=e.maxHeight,h=this.clipRect,i=e.navigation,j=p(i.animation,!0),k=i.arrowSize||12,l=this.nav;e.layout==="horizontal"&&(f/=2);g&&(f=C(f,g));if(a>f&&!e.useHTML){this.clipHeight=c=f-20-this.titleHeight;this.pageCount=wa(a/c);this.currentPage=p(this.currentPage,1);this.fullHeight=a;if(!h)h=b.clipRect=d.clipRect(0,0,9999,0),b.contentGroup.clip(h);h.attr({height:c});if(!l)this.nav=l=d.g().attr({zIndex:1}).add(this.group),this.up=d.symbol("triangle",
+0,0,k,k).on("click",function(){b.scroll(-1,j)}).add(l),this.pager=d.text("",15,10).css(i.style).add(l),this.down=d.symbol("triangle-down",0,0,k,k).on("click",function(){b.scroll(1,j)}).add(l);b.scroll(0);a=f}else if(l)h.attr({height:c.chartHeight}),l.hide(),this.scrollGroup.attr({translateY:1}),this.clipHeight=0;return a},scroll:function(a,b){var c=this.pageCount,d=this.currentPage+a,e=this.clipHeight,f=this.options.navigation,g=f.activeColor,h=f.inactiveColor,f=this.pager,i=this.padding;d>c&&(d=
+c);if(d>0)b!==v&&Ka(b,this.chart),this.nav.attr({translateX:i,translateY:e+7+this.titleHeight,visibility:"visible"}),this.up.attr({fill:d===1?h:g}).css({cursor:d===1?"default":"pointer"}),f.attr({text:d+"/"+this.pageCount}),this.down.attr({x:18+this.pager.getBBox().width,fill:d===c?h:g}).css({cursor:d===c?"default":"pointer"}),e=-C(e*(d-1),this.fullHeight-e+i)+1,this.scrollGroup.animate({translateY:e}),f.attr({text:d+"/"+c}),this.currentPage=d,this.positionCheckboxes(e)}};xb.prototype={init:function(a,
+b){var c,d=a.series;a.series=null;c=x(M,a);c.series=a.series=d;var d=c.chart,e=d.margin,e=T(e)?e:[e,e,e,e];this.optionsMarginTop=p(d.marginTop,e[0]);this.optionsMarginRight=p(d.marginRight,e[1]);this.optionsMarginBottom=p(d.marginBottom,e[2]);this.optionsMarginLeft=p(d.marginLeft,e[3]);e=d.events;this.bounds={h:{},v:{}};this.callback=b;this.isResizing=0;this.options=c;this.axes=[];this.series=[];this.hasCartesianSeries=d.showAxes;var f=this,g;f.index=Fa.length;Fa.push(f);d.reflow!==!1&&J(f,"load",
+function(){f.initReflow()});if(e)for(g in e)J(f,g,e[g]);f.xAxis=[];f.yAxis=[];f.animation=$?!1:p(d.animation,!0);f.pointCount=0;f.counters=new Jb;f.firstRender()},initSeries:function(a){var b=this.options.chart;(b=aa[a.type||b.type||b.defaultSeriesType])||ja(17,!0);b=new b;b.init(this,a);return b},addSeries:function(a,b,c){var d,e=this;a&&(b=p(b,!0),K(e,"addSeries",{options:a},function(){d=e.initSeries(a);e.isDirtyLegend=!0;e.linkSeries();b&&e.redraw(c)}));return d},addAxis:function(a,b,c,d){var e=
+b?"xAxis":"yAxis",f=this.options;new db(this,x(a,{index:this[e].length,isX:b}));f[e]=ia(f[e]||{});f[e].push(a);p(c,!0)&&this.redraw(d)},isInsidePlot:function(a,b,c){var d=c?b:a,a=c?a:b;return d>=0&&d<=this.plotWidth&&a>=0&&a<=this.plotHeight},adjustTickAmounts:function(){this.options.chart.alignTicks!==!1&&n(this.axes,function(a){a.adjustTickAmount()});this.maxTicks=null},redraw:function(a){var b=this.axes,c=this.series,d=this.pointer,e=this.legend,f=this.isDirtyLegend,g,h,i=this.isDirtyBox,j=c.length,
+k=j,l=this.renderer,m=l.isHidden(),o=[];Ka(a,this);m&&this.cloneRenderTo();for(this.layOutTitles();k--;)if(a=c[k],a.options.stacking&&(g=!0,a.isDirty)){h=!0;break}if(h)for(k=j;k--;)if(a=c[k],a.options.stacking)a.isDirty=!0;n(c,function(a){a.isDirty&&a.options.legendType==="point"&&(f=!0)});if(f&&e.options.enabled)e.render(),this.isDirtyLegend=!1;g&&this.getStacks();if(this.hasCartesianSeries){if(!this.isResizing)this.maxTicks=null,n(b,function(a){a.setScale()});this.adjustTickAmounts();this.getMargins();
+n(b,function(a){a.isDirty&&(i=!0)});n(b,function(a){if(a.isDirtyExtremes)a.isDirtyExtremes=!1,o.push(function(){K(a,"afterSetExtremes",a.getExtremes())});(i||g)&&a.redraw()})}i&&this.drawChartBox();n(c,function(a){a.isDirty&&a.visible&&(!a.isCartesian||a.xAxis)&&a.redraw()});d&&d.reset&&d.reset(!0);l.draw();K(this,"redraw");m&&this.cloneRenderTo(!0);n(o,function(a){a.call()})},showLoading:function(a){var b=this.options,c=this.loadingDiv,d=b.loading;if(!c)this.loadingDiv=c=U(Ca,{className:"highcharts-loading"},
+s(d.style,{zIndex:10,display:S}),this.container),this.loadingSpan=U("span",null,d.labelStyle,c);this.loadingSpan.innerHTML=a||b.lang.loading;if(!this.loadingShown)L(c,{opacity:0,display:"",left:this.plotLeft+"px",top:this.plotTop+"px",width:this.plotWidth+"px",height:this.plotHeight+"px"}),Ab(c,{opacity:d.style.opacity},{duration:d.showDuration||0}),this.loadingShown=!0},hideLoading:function(){var a=this.options,b=this.loadingDiv;b&&Ab(b,{opacity:0},{duration:a.loading.hideDuration||100,complete:function(){L(b,
+{display:S})}});this.loadingShown=!1},get:function(a){var b=this.axes,c=this.series,d,e;for(d=0;d<b.length;d++)if(b[d].options.id===a)return b[d];for(d=0;d<c.length;d++)if(c[d].options.id===a)return c[d];for(d=0;d<c.length;d++){e=c[d].points||[];for(b=0;b<e.length;b++)if(e[b].id===a)return e[b]}return null},getAxes:function(){var a=this,b=this.options,c=b.xAxis=ia(b.xAxis||{}),b=b.yAxis=ia(b.yAxis||{});n(c,function(a,b){a.index=b;a.isX=!0});n(b,function(a,b){a.index=b});c=c.concat(b);n(c,function(b){new db(a,
+b)});a.adjustTickAmounts()},getSelectedPoints:function(){var a=[];n(this.series,function(b){a=a.concat(sb(b.points||[],function(a){return a.selected}))});return a},getSelectedSeries:function(){return sb(this.series,function(a){return a.selected})},getStacks:function(){var a=this;n(a.yAxis,function(a){if(a.stacks&&a.hasVisibleSeries)a.oldStacks=a.stacks});n(a.series,function(b){if(b.options.stacking&&(b.visible===!0||a.options.chart.ignoreHiddenSeries===!1))b.stackKey=b.type+p(b.options.stack,"")})},
+showResetZoom:function(){var a=this,b=M.lang,c=a.options.chart.resetZoomButton,d=c.theme,e=d.states,f=c.relativeTo==="chart"?null:"plotBox";this.resetZoomButton=a.renderer.button(b.resetZoom,null,null,function(){a.zoomOut()},d,e&&e.hover).attr({align:c.position.align,title:b.resetZoomTitle}).add().align(c.position,!1,f)},zoomOut:function(){var a=this;K(a,"selection",{resetSelection:!0},function(){a.zoom()})},zoom:function(a){var b,c=this.pointer,d=!1,e;!a||a.resetSelection?n(this.axes,function(a){b=
+a.zoom()}):n(a.xAxis.concat(a.yAxis),function(a){var e=a.axis,h=e.isXAxis;if(c[h?"zoomX":"zoomY"]||c[h?"pinchX":"pinchY"])b=e.zoom(a.min,a.max),e.displayBtn&&(d=!0)});e=this.resetZoomButton;if(d&&!e)this.showResetZoom();else if(!d&&T(e))this.resetZoomButton=e.destroy();b&&this.redraw(p(this.options.chart.animation,a&&a.animation,this.pointCount<100))},pan:function(a,b){var c=this,d=c.hoverPoints,e;d&&n(d,function(a){a.setState()});n(b==="xy"?[1,0]:[1],function(b){var d=a[b?"chartX":"chartY"],h=c[b?
+"xAxis":"yAxis"][0],i=c[b?"mouseDownX":"mouseDownY"],j=(h.pointRange||0)/2,k=h.getExtremes(),l=h.toValue(i-d,!0)+j,i=h.toValue(i+c[b?"plotWidth":"plotHeight"]-d,!0)-j;h.series.length&&l>C(k.dataMin,k.min)&&i<r(k.dataMax,k.max)&&(h.setExtremes(l,i,!1,!1,{trigger:"pan"}),e=!0);c[b?"mouseDownX":"mouseDownY"]=d});e&&c.redraw(!1);L(c.container,{cursor:"move"})},setTitle:function(a,b){var f;var c=this,d=c.options,e;e=d.title=x(d.title,a);f=d.subtitle=x(d.subtitle,b),d=f;n([["title",a,e],["subtitle",b,d]],
+function(a){var b=a[0],d=c[b],e=a[1],a=a[2];d&&e&&(c[b]=d=d.destroy());a&&a.text&&!d&&(c[b]=c.renderer.text(a.text,0,0,a.useHTML).attr({align:a.align,"class":"highcharts-"+b,zIndex:a.zIndex||4}).css(a.style).add())});c.layOutTitles()},layOutTitles:function(){var a=0,b=this.title,c=this.subtitle,d=this.options,e=d.title,d=d.subtitle,f=this.spacingBox.width-44;if(b&&(b.css({width:(e.width||f)+"px"}).align(s({y:15},e),!1,"spacingBox"),!e.floating&&!e.verticalAlign))a=b.getBBox().height,a>=18&&a<=25&&
+(a=15);c&&(c.css({width:(d.width||f)+"px"}).align(s({y:a+e.margin},d),!1,"spacingBox"),!d.floating&&!d.verticalAlign&&(a=wa(a+c.getBBox().height)));this.titleOffset=a},getChartSize:function(){var a=this.options.chart,b=this.renderToClone||this.renderTo;this.containerWidth=ib(b,"width");this.containerHeight=ib(b,"height");this.chartWidth=r(0,a.width||this.containerWidth||600);this.chartHeight=r(0,p(a.height,this.containerHeight>19?this.containerHeight:400))},cloneRenderTo:function(a){var b=this.renderToClone,
+c=this.container;a?b&&(this.renderTo.appendChild(c),Ta(b),delete this.renderToClone):(c&&c.parentNode===this.renderTo&&this.renderTo.removeChild(c),this.renderToClone=b=this.renderTo.cloneNode(0),L(b,{position:"absolute",top:"-9999px",display:"block"}),z.body.appendChild(b),c&&b.appendChild(c))},getContainer:function(){var a,b=this.options.chart,c,d,e;this.renderTo=a=b.renderTo;e="highcharts-"+yb++;if(ea(a))this.renderTo=a=z.getElementById(a);a||ja(13,!0);c=A(w(a,"data-highcharts-chart"));!isNaN(c)&&
+Fa[c]&&Fa[c].destroy();w(a,"data-highcharts-chart",this.index);a.innerHTML="";a.offsetWidth||this.cloneRenderTo();this.getChartSize();c=this.chartWidth;d=this.chartHeight;this.container=a=U(Ca,{className:"highcharts-container"+(b.className?" "+b.className:""),id:e},s({position:"relative",overflow:"hidden",width:c+"px",height:d+"px",textAlign:"left",lineHeight:"normal",zIndex:0,"-webkit-tap-highlight-color":"rgba(0,0,0,0)"},b.style),this.renderToClone||a);this._cursor=a.style.cursor;this.renderer=
+b.forExport?new Ga(a,c,d,!0):new Va(a,c,d);$&&this.renderer.create(this,a,c,d)},getMargins:function(){var a=this.options.chart,b=a.spacingTop,c=a.spacingRight,d=a.spacingBottom,a=a.spacingLeft,e,f=this.legend,g=this.optionsMarginTop,h=this.optionsMarginLeft,i=this.optionsMarginRight,j=this.optionsMarginBottom,k=this.options.legend,l=p(k.margin,10),m=k.x,o=k.y,q=k.align,u=k.verticalAlign,y=this.titleOffset;this.resetMargins();e=this.axisOffset;if(y&&!t(g))this.plotTop=r(this.plotTop,y+this.options.title.margin+
+b);if(f.display&&!k.floating)if(q==="right"){if(!t(i))this.marginRight=r(this.marginRight,f.legendWidth-m+l+c)}else if(q==="left"){if(!t(h))this.plotLeft=r(this.plotLeft,f.legendWidth+m+l+a)}else if(u==="top"){if(!t(g))this.plotTop=r(this.plotTop,f.legendHeight+o+l+b)}else if(u==="bottom"&&!t(j))this.marginBottom=r(this.marginBottom,f.legendHeight-o+l+d);this.extraBottomMargin&&(this.marginBottom+=this.extraBottomMargin);this.extraTopMargin&&(this.plotTop+=this.extraTopMargin);this.hasCartesianSeries&&
+n(this.axes,function(a){a.getOffset()});t(h)||(this.plotLeft+=e[3]);t(g)||(this.plotTop+=e[0]);t(j)||(this.marginBottom+=e[2]);t(i)||(this.marginRight+=e[1]);this.setChartSize()},initReflow:function(){function a(a){var g=c.width||ib(d,"width"),h=c.height||ib(d,"height"),a=a?a.target:N;if(!b.hasUserSize&&g&&h&&(a===N||a===z)){if(g!==b.containerWidth||h!==b.containerHeight)clearTimeout(e),b.reflowTimeout=e=setTimeout(function(){if(b.container)b.setSize(g,h,!1),b.hasUserSize=null},100);b.containerWidth=
+g;b.containerHeight=h}}var b=this,c=b.options.chart,d=b.renderTo,e;J(N,"resize",a);J(b,"destroy",function(){ba(N,"resize",a)})},setSize:function(a,b,c){var d=this,e,f,g;d.isResizing+=1;g=function(){d&&K(d,"endResize",null,function(){d.isResizing-=1})};Ka(c,d);d.oldChartHeight=d.chartHeight;d.oldChartWidth=d.chartWidth;if(t(a))d.chartWidth=e=r(0,u(a)),d.hasUserSize=!!e;if(t(b))d.chartHeight=f=r(0,u(b));L(d.container,{width:e+"px",height:f+"px"});d.setChartSize(!0);d.renderer.setSize(e,f,c);d.maxTicks=
+null;n(d.axes,function(a){a.isDirty=!0;a.setScale()});n(d.series,function(a){a.isDirty=!0});d.isDirtyLegend=!0;d.isDirtyBox=!0;d.getMargins();d.redraw(c);d.oldChartHeight=null;K(d,"resize");Da===!1?g():setTimeout(g,Da&&Da.duration||500)},setChartSize:function(a){var b=this.inverted,c=this.renderer,d=this.chartWidth,e=this.chartHeight,f=this.options.chart,g=f.spacingTop,h=f.spacingRight,i=f.spacingBottom,j=f.spacingLeft,k=this.clipOffset,l,m,o,q;this.plotLeft=l=u(this.plotLeft);this.plotTop=m=u(this.plotTop);
+this.plotWidth=o=r(0,u(d-l-this.marginRight));this.plotHeight=q=r(0,u(e-m-this.marginBottom));this.plotSizeX=b?q:o;this.plotSizeY=b?o:q;this.plotBorderWidth=f.plotBorderWidth||0;this.spacingBox=c.spacingBox={x:j,y:g,width:d-j-h,height:e-g-i};this.plotBox=c.plotBox={x:l,y:m,width:o,height:q};d=2*P(this.plotBorderWidth/2);b=wa(r(d,k[3])/2);c=wa(r(d,k[0])/2);this.clipBox={x:b,y:c,width:P(this.plotSizeX-r(d,k[1])/2-b),height:P(this.plotSizeY-r(d,k[2])/2-c)};a||n(this.axes,function(a){a.setAxisSize();
+a.setAxisTranslation()})},resetMargins:function(){var a=this.options.chart,b=a.spacingRight,c=a.spacingBottom,d=a.spacingLeft;this.plotTop=p(this.optionsMarginTop,a.spacingTop);this.marginRight=p(this.optionsMarginRight,b);this.marginBottom=p(this.optionsMarginBottom,c);this.plotLeft=p(this.optionsMarginLeft,d);this.axisOffset=[0,0,0,0];this.clipOffset=[0,0,0,0]},drawChartBox:function(){var a=this.options.chart,b=this.renderer,c=this.chartWidth,d=this.chartHeight,e=this.chartBackground,f=this.plotBackground,
+g=this.plotBorder,h=this.plotBGImage,i=a.borderWidth||0,j=a.backgroundColor,k=a.plotBackgroundColor,l=a.plotBackgroundImage,m=a.plotBorderWidth||0,o,q=this.plotLeft,p=this.plotTop,n=this.plotWidth,r=this.plotHeight,u=this.plotBox,t=this.clipRect,s=this.clipBox;o=i+(a.shadow?8:0);if(i||j)if(e)e.animate(e.crisp(null,null,null,c-o,d-o));else{e={fill:j||S};if(i)e.stroke=a.borderColor,e["stroke-width"]=i;this.chartBackground=b.rect(o/2,o/2,c-o,d-o,a.borderRadius,i).attr(e).add().shadow(a.shadow)}if(k)f?
+f.animate(u):this.plotBackground=b.rect(q,p,n,r,0).attr({fill:k}).add().shadow(a.plotShadow);if(l)h?h.animate(u):this.plotBGImage=b.image(l,q,p,n,r).add();t?t.animate({width:s.width,height:s.height}):this.clipRect=b.clipRect(s);if(m)g?g.animate(g.crisp(null,q,p,n,r)):this.plotBorder=b.rect(q,p,n,r,0,-m).attr({stroke:a.plotBorderColor,"stroke-width":m,zIndex:1}).add();this.isDirtyBox=!1},propFromSeries:function(){var a=this,b=a.options.chart,c,d=a.options.series,e,f;n(["inverted","angular","polar"],
+function(g){c=aa[b.type||b.defaultSeriesType];f=a[g]||b[g]||c&&c.prototype[g];for(e=d&&d.length;!f&&e--;)(c=aa[d[e].type])&&c.prototype[g]&&(f=!0);a[g]=f})},linkSeries:function(){var a=this,b=a.series;n(b,function(a){a.linkedSeries.length=0});n(b,function(b){var d=b.options.linkedTo;if(ea(d)&&(d=d===":previous"?a.series[b.index-1]:a.get(d)))d.linkedSeries.push(b),b.linkedParent=d})},render:function(){var a=this,b=a.axes,c=a.renderer,d=a.options,e=d.labels,f=d.credits,g;a.setTitle();a.legend=new wb(a,
+d.legend);a.getStacks();n(b,function(a){a.setScale()});a.getMargins();a.maxTicks=null;n(b,function(a){a.setTickPositions(!0);a.setMaxTicks()});a.adjustTickAmounts();a.getMargins();a.drawChartBox();a.hasCartesianSeries&&n(b,function(a){a.render()});if(!a.seriesGroup)a.seriesGroup=c.g("series-group").attr({zIndex:3}).add();n(a.series,function(a){a.translate();a.setTooltipPoints();a.render()});e.items&&n(e.items,function(b){var d=s(e.style,b.style),f=A(d.left)+a.plotLeft,g=A(d.top)+a.plotTop+12;delete d.left;
+delete d.top;c.text(b.html,f,g).attr({zIndex:2}).css(d).add()});if(f.enabled&&!a.credits)g=f.href,a.credits=c.text(f.text,0,0).on("click",function(){if(g)location.href=g}).attr({align:f.position.align,zIndex:8}).css(f.style).add().align(f.position);a.hasRendered=!0},destroy:function(){var a=this,b=a.axes,c=a.series,d=a.container,e,f=d&&d.parentNode;K(a,"destroy");Fa[a.index]=v;a.renderTo.removeAttribute("data-highcharts-chart");ba(a);for(e=b.length;e--;)b[e]=b[e].destroy();for(e=c.length;e--;)c[e]=
+c[e].destroy();n("title,subtitle,chartBackground,plotBackground,plotBGImage,plotBorder,seriesGroup,clipRect,credits,pointer,scroller,rangeSelector,legend,resetZoomButton,tooltip,renderer".split(","),function(b){var c=a[b];c&&c.destroy&&(a[b]=c.destroy())});if(d)d.innerHTML="",ba(d),f&&Ta(d);for(e in a)delete a[e]},isReadyToRender:function(){var a=this;return!Z&&N==N.top&&z.readyState!=="complete"||$&&!N.canvg?($?Tb.push(function(){a.firstRender()},a.options.global.canvasToolsURL):z.attachEvent("onreadystatechange",
+function(){z.detachEvent("onreadystatechange",a.firstRender);z.readyState==="complete"&&a.firstRender()}),!1):!0},firstRender:function(){var a=this,b=a.options,c=a.callback;if(a.isReadyToRender())a.getContainer(),K(a,"init"),a.resetMargins(),a.setChartSize(),a.propFromSeries(),a.getAxes(),n(b.series||[],function(b){a.initSeries(b)}),a.linkSeries(),K(a,"beforeRender"),a.pointer=new vb(a,b),a.render(),a.renderer.draw(),c&&c.apply(a,[a]),n(a.callbacks,function(b){b.apply(a,[a])}),a.cloneRenderTo(!0),
+K(a,"load")}};xb.prototype.callbacks=[];var Pa=function(){};Pa.prototype={init:function(a,b,c){this.series=a;this.applyOptions(b,c);this.pointAttr={};if(a.options.colorByPoint&&(b=a.options.colors||a.chart.options.colors,this.color=this.color||b[a.colorCounter++],a.colorCounter===b.length))a.colorCounter=0;a.chart.pointCount++;return this},applyOptions:function(a,b){var c=this.series,d=c.pointValKey,a=Pa.prototype.optionsToObject.call(this,a);s(this,a);this.options=this.options?s(this.options,a):
+a;if(d)this.y=this[d];if(this.x===v&&c)this.x=b===v?c.autoIncrement():b;return this},optionsToObject:function(a){var b,c=this.series,d=c.pointArrayMap||["y"],e=d.length,f=0,g=0;if(typeof a==="number"||a===null)b={y:a};else if(Ha(a)){b={};if(a.length>e){c=typeof a[0];if(c==="string")b.name=a[0];else if(c==="number")b.x=a[0];f++}for(;g<e;)b[d[g++]]=a[f++]}else if(typeof a==="object"){b=a;if(a.dataLabels)c._hasPointLabels=!0;if(a.marker)c._hasPointMarkers=!0}return b},destroy:function(){var a=this.series.chart,
+b=a.hoverPoints,c;a.pointCount--;if(b&&(this.setState(),ga(b,this),!b.length))a.hoverPoints=null;if(this===a.hoverPoint)this.onMouseOut();if(this.graphic||this.dataLabel)ba(this),this.destroyElements();this.legendItem&&a.legend.destroyItem(this);for(c in this)this[c]=null},destroyElements:function(){for(var a="graphic,dataLabel,dataLabelUpper,group,connector,shadowGroup".split(","),b,c=6;c--;)b=a[c],this[b]&&(this[b]=this[b].destroy())},getLabelConfig:function(){return{x:this.category,y:this.y,key:this.name||
+this.category,series:this.series,point:this,percentage:this.percentage,total:this.total||this.stackTotal}},select:function(a,b){var c=this,d=c.series,e=d.chart,a=p(a,!c.selected);c.firePointEvent(a?"select":"unselect",{accumulate:b},function(){c.selected=c.options.selected=a;d.options.data[oa(c,d.data)]=c.options;c.setState(a&&"select");b||n(e.getSelectedPoints(),function(a){if(a.selected&&a!==c)a.selected=a.options.selected=!1,d.options.data[oa(a,d.data)]=a.options,a.setState(""),a.firePointEvent("unselect")})})},
+onMouseOver:function(a){var b=this.series,c=b.chart,d=c.tooltip,e=c.hoverPoint;if(e&&e!==this)e.onMouseOut();this.firePointEvent("mouseOver");d&&(!d.shared||b.noSharedTooltip)&&d.refresh(this,a);this.setState("hover");c.hoverPoint=this},onMouseOut:function(){var a=this.series.chart,b=a.hoverPoints;if(!b||oa(this,b)===-1)this.firePointEvent("mouseOut"),this.setState(),a.hoverPoint=null},tooltipFormatter:function(a){var b=this.series,c=b.tooltipOptions,d=p(c.valueDecimals,""),e=c.valuePrefix||"",f=
+c.valueSuffix||"";n(b.pointArrayMap||["y"],function(b){b="{point."+b;if(e||f)a=a.replace(b+"}",e+b+"}"+f);a=a.replace(b+"}",b+":,."+d+"f}")});return Ba(a,{point:this,series:this.series})},update:function(a,b,c){var d=this,e=d.series,f=d.graphic,g,h=e.data,i=e.chart,j=e.options,b=p(b,!0);d.firePointEvent("update",{options:a},function(){d.applyOptions(a);if(T(a)&&(e.getAttribs(),f))a.marker&&a.marker.symbol?d.graphic=f.destroy():f.attr(d.pointAttr[e.state]);g=oa(d,h);e.xData[g]=d.x;e.yData[g]=e.toYData?
+e.toYData(d):d.y;e.zData[g]=d.z;j.data[g]=d.options;e.isDirty=e.isDirtyData=i.isDirtyBox=!0;j.legendType==="point"&&i.legend.destroyItem(d);b&&i.redraw(c)})},remove:function(a,b){var c=this,d=c.series,e=d.chart,f,g=d.data;Ka(b,e);a=p(a,!0);c.firePointEvent("remove",null,function(){f=oa(c,g);g.splice(f,1);d.options.data.splice(f,1);d.xData.splice(f,1);d.yData.splice(f,1);d.zData.splice(f,1);c.destroy();d.isDirty=!0;d.isDirtyData=!0;a&&e.redraw()})},firePointEvent:function(a,b,c){var d=this,e=this.series.options;
+(e.point.events[a]||d.options&&d.options.events&&d.options.events[a])&&this.importEvents();a==="click"&&e.allowPointSelect&&(c=function(a){d.select(null,a.ctrlKey||a.metaKey||a.shiftKey)});K(this,a,b,c)},importEvents:function(){if(!this.hasImportedEvents){var a=x(this.series.options.point,this.options).events,b;this.events=a;for(b in a)J(this,b,a[b]);this.hasImportedEvents=!0}},setState:function(a){var b=this.plotX,c=this.plotY,d=this.series,e=d.options.states,f=Y[d.type].marker&&d.options.marker,
+g=f&&!f.enabled,h=f&&f.states[a],i=h&&h.enabled===!1,j=d.stateMarkerGraphic,k=this.marker||{},l=d.chart,m=this.pointAttr,a=a||"";if(!(a===this.state||this.selected&&a!=="select"||e[a]&&e[a].enabled===!1||a&&(i||g&&!h.enabled))){if(this.graphic)e=f&&this.graphic.symbolName&&m[a].r,this.graphic.attr(x(m[a],e?{x:b-e,y:c-e,width:2*e,height:2*e}:{}));else{if(a&&h)e=h.radius,k=k.symbol||d.symbol,j&&j.currentSymbol!==k&&(j=j.destroy()),j?j.attr({x:b-e,y:c-e}):(d.stateMarkerGraphic=j=l.renderer.symbol(k,
+b-e,c-e,2*e,2*e).attr(m[a]).add(d.markerGroup),j.currentSymbol=k);if(j)j[a&&l.isInsidePlot(b,c)?"show":"hide"]()}this.state=a}}};var Q=function(){};Q.prototype={isCartesian:!0,type:"line",pointClass:Pa,sorted:!0,requireSorting:!0,pointAttrToOptions:{stroke:"lineColor","stroke-width":"lineWidth",fill:"fillColor",r:"radius"},colorCounter:0,init:function(a,b){var c,d,e=a.series;this.chart=a;this.options=b=this.setOptions(b);this.linkedSeries=[];this.bindAxes();s(this,{name:b.name,state:"",pointAttr:{},
+visible:b.visible!==!1,selected:b.selected===!0});if($)b.animation=!1;d=b.events;for(c in d)J(this,c,d[c]);if(d&&d.click||b.point&&b.point.events&&b.point.events.click||b.allowPointSelect)a.runTrackerClick=!0;this.getColor();this.getSymbol();this.setData(b.data,!1);if(this.isCartesian)a.hasCartesianSeries=!0;e.push(this);this._i=e.length-1;Kb(e,function(a,b){return p(a.options.index,a._i)-p(b.options.index,a._i)});n(e,function(a,b){a.index=b;a.name=a.name||"Series "+(b+1)})},bindAxes:function(){var a=
+this,b=a.options,c=a.chart,d;a.isCartesian&&n(["xAxis","yAxis"],function(e){n(c[e],function(c){d=c.options;if(b[e]===d.index||b[e]!==v&&b[e]===d.id||b[e]===v&&d.index===0)c.series.push(a),a[e]=c,c.isDirty=!0});a[e]||ja(18,!0)})},autoIncrement:function(){var a=this.options,b=this.xIncrement,b=p(b,a.pointStart,0);this.pointInterval=p(this.pointInterval,a.pointInterval,1);this.xIncrement=b+this.pointInterval;return b},getSegments:function(){var a=-1,b=[],c,d=this.points,e=d.length;if(e)if(this.options.connectNulls){for(c=
+e;c--;)d[c].y===null&&d.splice(c,1);d.length&&(b=[d])}else n(d,function(c,g){c.y===null?(g>a+1&&b.push(d.slice(a+1,g)),a=g):g===e-1&&b.push(d.slice(a+1,g+1))});this.segments=b},setOptions:function(a){var b=this.chart.options,c=b.plotOptions,d=c[this.type];this.userOptions=a;a=x(d,c.series,a);this.tooltipOptions=x(b.tooltip,a.tooltip);d.marker===null&&delete a.marker;return a},getColor:function(){var a=this.options,b=this.userOptions,c=this.chart.options.colors,d=this.chart.counters,e;e=a.color||Y[this.type].color;
+if(!e&&!a.colorByPoint)t(b._colorIndex)?a=b._colorIndex:(b._colorIndex=d.color,a=d.color++),e=c[a];this.color=e;d.wrapColor(c.length)},getSymbol:function(){var a=this.userOptions,b=this.options.marker,c=this.chart,d=c.options.symbols,c=c.counters;this.symbol=b.symbol;if(!this.symbol)t(a._symbolIndex)?a=a._symbolIndex:(a._symbolIndex=c.symbol,a=c.symbol++),this.symbol=d[a];if(/^url/.test(this.symbol))b.radius=0;c.wrapSymbol(d.length)},drawLegendSymbol:function(a){var b=this.options,c=b.marker,d=a.options,
+e;e=d.symbolWidth;var f=this.chart.renderer,g=this.legendGroup,a=a.baseline-u(f.fontMetrics(d.itemStyle.fontSize).b*0.3);if(b.lineWidth){d={"stroke-width":b.lineWidth};if(b.dashStyle)d.dashstyle=b.dashStyle;this.legendLine=f.path(["M",0,a,"L",e,a]).attr(d).add(g)}if(c&&c.enabled)b=c.radius,this.legendSymbol=e=f.symbol(this.symbol,e/2-b,a-b,2*b,2*b).add(g),e.isMarker=!0},addPoint:function(a,b,c,d){var e=this.options,f=this.data,g=this.graph,h=this.area,i=this.chart,j=this.xData,k=this.yData,l=this.zData,
+m=this.names,o=g&&g.shift||0,q=e.data;Ka(d,i);c&&n([g,h,this.graphNeg,this.areaNeg],function(a){if(a)a.shift=o+1});if(h)h.isArea=!0;b=p(b,!0);d={series:this};this.pointClass.prototype.applyOptions.apply(d,[a]);j.push(d.x);k.push(this.toYData?this.toYData(d):d.y);l.push(d.z);if(m)m[d.x]=d.name;q.push(a);e.legendType==="point"&&this.generatePoints();c&&(f[0]&&f[0].remove?f[0].remove(!1):(f.shift(),j.shift(),k.shift(),l.shift(),q.shift()));this.isDirtyData=this.isDirty=!0;b&&(this.getAttribs(),i.redraw())},
+setData:function(a,b){var c=this.points,d=this.options,e=this.chart,f=null,g=this.xAxis,h=g&&g.categories&&!g.categories.length?[]:null,i;this.xIncrement=null;this.pointRange=g&&g.categories?1:d.pointRange;this.colorCounter=0;var j=[],k=[],l=[],m=a?a.length:[];i=p(d.turboThreshold,1E3);var o=this.pointArrayMap,o=o&&o.length,q=!!this.toYData;if(i&&m>i){for(i=0;f===null&&i<m;)f=a[i],i++;if(qa(f)){f=p(d.pointStart,0);d=p(d.pointInterval,1);for(i=0;i<m;i++)j[i]=f,k[i]=a[i],f+=d;this.xIncrement=f}else if(Ha(f))if(o)for(i=
+0;i<m;i++)d=a[i],j[i]=d[0],k[i]=d.slice(1,o+1);else for(i=0;i<m;i++)d=a[i],j[i]=d[0],k[i]=d[1];else ja(12)}else for(i=0;i<m;i++)if(a[i]!==v&&(d={series:this},this.pointClass.prototype.applyOptions.apply(d,[a[i]]),j[i]=d.x,k[i]=q?this.toYData(d):d.y,l[i]=d.z,h&&d.name))h[d.x]=d.name;ea(k[0])&&ja(14,!0);this.data=[];this.options.data=a;this.xData=j;this.yData=k;this.zData=l;this.names=h;for(i=c&&c.length||0;i--;)c[i]&&c[i].destroy&&c[i].destroy();if(g)g.minRange=g.userMinRange;this.isDirty=this.isDirtyData=
+e.isDirtyBox=!0;p(b,!0)&&e.redraw(!1)},remove:function(a,b){var c=this,d=c.chart,a=p(a,!0);if(!c.isRemoving)c.isRemoving=!0,K(c,"remove",null,function(){c.destroy();d.isDirtyLegend=d.isDirtyBox=!0;d.linkSeries();a&&d.redraw(b)});c.isRemoving=!1},processData:function(a){var b=this.xData,c=this.yData,d=b.length,e;e=0;var f,g,h=this.xAxis,i=this.options,j=i.cropThreshold,k=this.isCartesian;if(k&&!this.isDirty&&!h.isDirty&&!this.yAxis.isDirty&&!a)return!1;if(k&&this.sorted&&(!j||d>j||this.forceCrop))if(a=
+h.min,h=h.max,b[d-1]<a||b[0]>h)b=[],c=[];else if(b[0]<a||b[d-1]>h)e=this.cropData(this.xData,this.yData,a,h),b=e.xData,c=e.yData,e=e.start,f=!0;for(h=b.length-1;h>=0;h--)d=b[h]-b[h-1],d>0&&(g===v||d<g)?g=d:d<0&&this.requireSorting&&ja(15);this.cropped=f;this.cropStart=e;this.processedXData=b;this.processedYData=c;if(i.pointRange===null)this.pointRange=g||1;this.closestPointRange=g},cropData:function(a,b,c,d){var e=a.length,f=0,g=e,h;for(h=0;h<e;h++)if(a[h]>=c){f=r(0,h-1);break}for(;h<e;h++)if(a[h]>
+d){g=h+1;break}return{xData:a.slice(f,g),yData:b.slice(f,g),start:f,end:g}},generatePoints:function(){var a=this.options.data,b=this.data,c,d=this.processedXData,e=this.processedYData,f=this.pointClass,g=d.length,h=this.cropStart||0,i,j=this.hasGroupedData,k,l=[],m;if(!b&&!j)b=[],b.length=a.length,b=this.data=b;for(m=0;m<g;m++)i=h+m,j?l[m]=(new f).init(this,[d[m]].concat(ia(e[m]))):(b[i]?k=b[i]:a[i]!==v&&(b[i]=k=(new f).init(this,a[i],d[m])),l[m]=k);if(b&&(g!==(c=b.length)||j))for(m=0;m<c;m++)if(m===
+h&&!j&&(m+=g),b[m])b[m].destroyElements(),b[m].plotX=v;this.data=b;this.points=l},setStackedPoints:function(){if(this.options.stacking&&!(this.visible!==!0&&this.chart.options.chart.ignoreHiddenSeries!==!1)){var a=this.processedXData,b=this.processedYData,c=b.length,d=this.options,e=d.threshold,f=d.stack,d=d.stacking,g=this.stackKey,h="-"+g,i=this.yAxis,j=i.stacks,k=i.oldStacks,l=i.stackExtremes,m,o,q,p,n;for(q=0;q<c;q++){p=a[q];n=b[q];o=(m=this.negStacks&&n<e)?h:g;typeof n==="number"&&!l[g]&&(l[g]=
+{dataMin:n,dataMax:n});j[o]||(j[o]={});if(!j[o][p])k[o]&&k[o][p]?(j[o][p]=k[o][p],j[o][p].total=null):j[o][p]=new Mb(i,i.options.stackLabels,m,p,f,d);o=j[o][p];m=o.total;o.addValue(n||0);o.cacheExtremes(this,[m,m+(n||0)]);if(typeof n==="number")l[g].dataMin=C(l[g].dataMin,o.total,n),l[g].dataMax=r(l[g].dataMax,o.total,n)}i.oldStacks={}}},getExtremes:function(){var a=this.yAxis,b=this.stackKey,c,d,e=this.options,f=a.isLog?null:e.threshold,g=this.processedXData,h=this.processedYData,i=h.length,j=[],
+k=0,l=this.xAxis.getExtremes(),m=l.min,l=l.max,o;if(e.stacking)c=a.stackExtremes[b],d=c.dataMin,c=c.dataMax,d=C(d,p(f,d)),c=r(c,p(f,c));if(!t(d)||!t(c)){for(b=0;b<i;b++)if(o=g[b],f=h[b],e=f!==null&&f!==v&&(!a.isLog||f.length||f>0),o=this.getExtremesFromAll||this.cropped||(g[b+1]||o)>=m&&(g[b-1]||o)<=l,e&&o)if(e=f.length)for(;e--;)f[e]!==null&&(j[k++]=f[e]);else j[k++]=f;d=p(d,Ia(j));c=p(c,ua(j))}this.dataMin=d;this.dataMax=c},translate:function(){this.processedXData||this.processData();this.generatePoints();
+for(var a=this.options,b=a.stacking,c=this.xAxis,d=c.categories,e=this.yAxis,f=this.points,g=f.length,h=!!this.modifyValue,i=a.pointPlacement,j=i==="between"||qa(i),k=a.threshold,a=0;a<g;a++){var l=f[a],m=l.x,o=l.y,q=l.low,n=e.stacks[(this.negStacks&&o<k?"-":"")+this.stackKey],r;if(e.isLog&&o<=0)l.y=o=null;l.plotX=c.translate(m,0,0,0,1,i);if(b&&this.visible&&n&&n[m])n=n[m],r=n.total,n.cum=q=n.cum-o,o=q+o,n.cum===0&&(q=p(k,e.min)),e.isLog&&q<=0&&(q=null),b==="percent"&&(q=r?q*100/r:0,o=r?o*100/r:0),
+l.percentage=r?l.y*100/r:0,l.total=l.stackTotal=r,l.stackY=o,n.setOffset(this.pointXOffset||0,this.barW||0);l.yBottom=t(q)?e.translate(q,0,1,0,1):null;h&&(o=this.modifyValue(o,l));l.plotY=typeof o==="number"&&o!==Infinity?e.translate(o,0,1,0,1):v;l.clientX=j?c.translate(m,0,0,0,1):l.plotX;l.negative=l.y<(k||0);l.category=d&&d[l.x]!==v?d[l.x]:l.x}this.getSegments()},setTooltipPoints:function(a){var b=[],c,d,e=(c=this.xAxis)?c.tooltipLen||c.len:this.chart.plotSizeX,f,g,h,i=[];if(this.options.enableMouseTracking!==
+!1){if(a)this.tooltipPoints=null;n(this.segments||this.points,function(a){b=b.concat(a)});c&&c.reversed&&(b=b.reverse());this.orderTooltipPoints&&this.orderTooltipPoints(b);a=b.length;for(h=0;h<a;h++){f=b[h];g=b[h+1];c=b[h-1]?d+1:0;for(d=b[h+1]?C(r(0,P((f.clientX+(g?g.wrappedClientX||g.clientX:e))/2)),e):e;c>=0&&c<=d;)i[c++]=f}this.tooltipPoints=i}},tooltipHeaderFormatter:function(a){var b=this.tooltipOptions,c=b.xDateFormat,d=b.dateTimeLabelFormats,e=this.xAxis,f=e&&e.options.type==="datetime",b=
+b.headerFormat,e=e&&e.closestPointRange,g;if(f&&!c)if(e)for(g in H){if(H[g]>=e){c=d[g];break}}else c=d.day;f&&c&&qa(a.key)&&(b=b.replace("{point.key}","{point.key:"+c+"}"));return Ba(b,{point:a,series:this})},onMouseOver:function(){var a=this.chart,b=a.hoverSeries;if(b&&b!==this)b.onMouseOut();this.options.events.mouseOver&&K(this,"mouseOver");this.setState("hover");a.hoverSeries=this},onMouseOut:function(){var a=this.options,b=this.chart,c=b.tooltip,d=b.hoverPoint;if(d)d.onMouseOut();this&&a.events.mouseOut&&
+K(this,"mouseOut");c&&!a.stickyTracking&&(!c.shared||this.noSharedTooltip)&&c.hide();this.setState();b.hoverSeries=null},animate:function(a){var b=this,c=b.chart,d=c.renderer,e;e=b.options.animation;var f=c.clipBox,g=c.inverted,h;if(e&&!T(e))e=Y[b.type].animation;h="_sharedClip"+e.duration+e.easing;if(a)a=c[h],e=c[h+"m"],a||(c[h]=a=d.clipRect(s(f,{width:0})),c[h+"m"]=e=d.clipRect(-99,g?-c.plotLeft:-c.plotTop,99,g?c.chartWidth:c.chartHeight)),b.group.clip(a),b.markerGroup.clip(e),b.sharedClipKey=h;
+else{if(a=c[h])a.animate({width:c.plotSizeX},e),c[h+"m"].animate({width:c.plotSizeX+99},e);b.animate=null;b.animationTimeout=setTimeout(function(){b.afterAnimate()},e.duration)}},afterAnimate:function(){var a=this.chart,b=this.sharedClipKey,c=this.group;c&&this.options.clip!==!1&&(c.clip(a.clipRect),this.markerGroup.clip());setTimeout(function(){b&&a[b]&&(a[b]=a[b].destroy(),a[b+"m"]=a[b+"m"].destroy())},100)},drawPoints:function(){var a,b=this.points,c=this.chart,d,e,f,g,h,i,j,k,l=this.options.marker,
+m,o=this.markerGroup;if(l.enabled||this._hasPointMarkers)for(f=b.length;f--;)if(g=b[f],d=P(g.plotX),e=g.plotY,k=g.graphic,i=g.marker||{},a=l.enabled&&i.enabled===v||i.enabled,m=c.isInsidePlot(u(d),e,c.inverted),a&&e!==v&&!isNaN(e)&&g.y!==null)if(a=g.pointAttr[g.selected?"select":""],h=a.r,i=p(i.symbol,this.symbol),j=i.indexOf("url")===0,k)k.attr({visibility:m?Z?"inherit":"visible":"hidden"}).animate(s({x:d-h,y:e-h},k.symbolName?{width:2*h,height:2*h}:{}));else{if(m&&(h>0||j))g.graphic=c.renderer.symbol(i,
+d-h,e-h,2*h,2*h).attr(a).add(o)}else if(k)g.graphic=k.destroy()},convertAttribs:function(a,b,c,d){var e=this.pointAttrToOptions,f,g,h={},a=a||{},b=b||{},c=c||{},d=d||{};for(f in e)g=e[f],h[f]=p(a[g],b[f],c[f],d[f]);return h},getAttribs:function(){var a=this,b=a.options,c=Y[a.type].marker?b.marker:b,d=c.states,e=d.hover,f,g=a.color,h={stroke:g,fill:g},i=a.points||[],j=[],k,l=a.pointAttrToOptions,m=b.negativeColor,o;b.marker?(e.radius=e.radius||c.radius+2,e.lineWidth=e.lineWidth||c.lineWidth+1):e.color=
+e.color||pa(e.color||g).brighten(e.brightness).get();j[""]=a.convertAttribs(c,h);n(["hover","select"],function(b){j[b]=a.convertAttribs(d[b],j[""])});a.pointAttr=j;for(g=i.length;g--;){h=i[g];if((c=h.options&&h.options.marker||h.options)&&c.enabled===!1)c.radius=0;if(h.negative&&m)h.color=h.fillColor=m;f=b.colorByPoint||h.color;if(h.options)for(o in l)t(c[l[o]])&&(f=!0);if(f){c=c||{};k=[];d=c.states||{};f=d.hover=d.hover||{};if(!b.marker)f.color=pa(f.color||h.color).brighten(f.brightness||e.brightness).get();
+k[""]=a.convertAttribs(s({color:h.color},c),j[""]);k.hover=a.convertAttribs(d.hover,j.hover,k[""]);k.select=a.convertAttribs(d.select,j.select,k[""]);if(h.negative&&b.marker&&m)k[""].fill=k.hover.fill=k.select.fill=a.convertAttribs({fillColor:m}).fill}else k=j;h.pointAttr=k}},update:function(a,b){var c=this.chart,d=this.type,a=x(this.userOptions,{animation:!1,index:this.index,pointStart:this.xData[0]},{data:this.options.data},a);this.remove(!1);s(this,aa[a.type||d].prototype);this.init(c,a);p(b,!0)&&
+c.redraw(!1)},destroy:function(){var a=this,b=a.chart,c=/AppleWebKit\/533/.test(Ea),d,e,f=a.data||[],g,h,i;K(a,"destroy");ba(a);n(["xAxis","yAxis"],function(b){if(i=a[b])ga(i.series,a),i.isDirty=i.forceRedraw=!0});a.legendItem&&a.chart.legend.destroyItem(a);for(e=f.length;e--;)(g=f[e])&&g.destroy&&g.destroy();a.points=null;clearTimeout(a.animationTimeout);n("area,graph,dataLabelsGroup,group,markerGroup,tracker,graphNeg,areaNeg,posClip,negClip".split(","),function(b){a[b]&&(d=c&&b==="group"?"hide":
+"destroy",a[b][d]())});if(b.hoverSeries===a)b.hoverSeries=null;ga(b.series,a);for(h in a)delete a[h]},drawDataLabels:function(){var a=this,b=a.options.dataLabels,c=a.points,d,e,f,g;if(b.enabled||a._hasPointLabels)a.dlProcessOptions&&a.dlProcessOptions(b),g=a.plotGroup("dataLabelsGroup","data-labels",a.visible?"visible":"hidden",b.zIndex||6),e=b,n(c,function(c){var i,j=c.dataLabel,k,l,m=c.connector,o=!0;d=c.options&&c.options.dataLabels;i=e.enabled||d&&d.enabled;if(j&&!i)c.dataLabel=j.destroy();else if(i){b=
+x(e,d);i=b.rotation;k=c.getLabelConfig();f=b.format?Ba(b.format,k):b.formatter.call(k,b);b.style.color=p(b.color,b.style.color,a.color,"black");if(j)if(t(f))j.attr({text:f}),o=!1;else{if(c.dataLabel=j=j.destroy(),m)c.connector=m.destroy()}else if(t(f)){j={fill:b.backgroundColor,stroke:b.borderColor,"stroke-width":b.borderWidth,r:b.borderRadius||0,rotation:i,padding:b.padding,zIndex:1};for(l in j)j[l]===v&&delete j[l];j=c.dataLabel=a.chart.renderer[i?"text":"label"](f,0,-999,null,null,null,b.useHTML).attr(j).css(b.style).add(g).shadow(b.shadow)}j&&
+a.alignDataLabel(c,j,b,null,o)}})},alignDataLabel:function(a,b,c,d,e){var f=this.chart,g=f.inverted,h=p(a.plotX,-999),i=p(a.plotY,-999),a=b.getBBox(),d=s({x:g?f.plotWidth-i:h,y:u(g?f.plotHeight-h:i),width:0,height:0},d);s(c,{width:a.width,height:a.height});c.rotation?(d={align:c.align,x:d.x+c.x+d.width/2,y:d.y+c.y+d.height/2},b[e?"attr":"animate"](d)):(b.align(c,null,d),d=b.alignAttr);b.attr({visibility:c.crop===!1||f.isInsidePlot(d.x,d.y)&&f.isInsidePlot(d.x+a.width,d.y+a.height)?f.renderer.isSVG?
+"inherit":"visible":"hidden"})},getSegmentPath:function(a){var b=this,c=[],d=b.options.step;n(a,function(e,f){var g=e.plotX,h=e.plotY,i;b.getPointSpline?c.push.apply(c,b.getPointSpline(a,e,f)):(c.push(f?"L":"M"),d&&f&&(i=a[f-1],d==="right"?c.push(i.plotX,h):d==="center"?c.push((i.plotX+g)/2,i.plotY,(i.plotX+g)/2,h):c.push(g,i.plotY)),c.push(e.plotX,e.plotY))});return c},getGraphPath:function(){var a=this,b=[],c,d=[];n(a.segments,function(e){c=a.getSegmentPath(e);e.length>1?b=b.concat(c):d.push(e[0])});
+a.singlePoints=d;return a.graphPath=b},drawGraph:function(){var a=this,b=this.options,c=[["graph",b.lineColor||this.color]],d=b.lineWidth,e=b.dashStyle,f=this.getGraphPath(),g=b.negativeColor;g&&c.push(["graphNeg",g]);n(c,function(c,g){var j=c[0],k=a[j];if(k)Wa(k),k.animate({d:f});else if(d&&f.length){k={stroke:c[1],"stroke-width":d,zIndex:1};if(e)k.dashstyle=e;a[j]=a.chart.renderer.path(f).attr(k).add(a.group).shadow(!g&&b.shadow)}})},clipNeg:function(){var a=this.options,b=this.chart,c=b.renderer,
+d=a.negativeColor||a.negativeFillColor,e,f=this.graph,g=this.area,h=this.posClip,i=this.negClip;e=b.chartWidth;var j=b.chartHeight,k=r(e,j),l=this.yAxis;if(d&&(f||g)){d=u(l.toPixels(a.threshold||0,!0));a={x:0,y:0,width:k,height:d};k={x:0,y:d,width:k,height:k};if(b.inverted)a.height=k.y=b.plotWidth-d,c.isVML&&(a={x:b.plotWidth-d-b.plotLeft,y:0,width:e,height:j},k={x:d+b.plotLeft-e,y:0,width:b.plotLeft+d,height:e});l.reversed?(b=k,e=a):(b=a,e=k);h?(h.animate(b),i.animate(e)):(this.posClip=h=c.clipRect(b),
+this.negClip=i=c.clipRect(e),f&&this.graphNeg&&(f.clip(h),this.graphNeg.clip(i)),g&&(g.clip(h),this.areaNeg.clip(i)))}},invertGroups:function(){function a(){var a={width:b.yAxis.len,height:b.xAxis.len};n(["group","markerGroup"],function(c){b[c]&&b[c].attr(a).invert()})}var b=this,c=b.chart;if(b.xAxis)J(c,"resize",a),J(b,"destroy",function(){ba(c,"resize",a)}),a(),b.invertGroups=a},plotGroup:function(a,b,c,d,e){var f=this[a],g=!f;g&&(this[a]=f=this.chart.renderer.g(b).attr({visibility:c,zIndex:d||
+0.1}).add(e));f[g?"attr":"animate"](this.getPlotBox());return f},getPlotBox:function(){return{translateX:this.xAxis?this.xAxis.left:this.chart.plotLeft,translateY:this.yAxis?this.yAxis.top:this.chart.plotTop,scaleX:1,scaleY:1}},render:function(){var a=this.chart,b,c=this.options,d=c.animation&&!!this.animate&&a.renderer.isSVG,e=this.visible?"visible":"hidden",f=c.zIndex,g=this.hasRendered,h=a.seriesGroup;b=this.plotGroup("group","series",e,f,h);this.markerGroup=this.plotGroup("markerGroup","markers",
+e,f,h);d&&this.animate(!0);this.getAttribs();b.inverted=this.isCartesian?a.inverted:!1;this.drawGraph&&(this.drawGraph(),this.clipNeg());this.drawDataLabels();this.drawPoints();this.options.enableMouseTracking!==!1&&this.drawTracker();a.inverted&&this.invertGroups();c.clip!==!1&&!this.sharedClipKey&&!g&&b.clip(a.clipRect);d?this.animate():g||this.afterAnimate();this.isDirty=this.isDirtyData=!1;this.hasRendered=!0},redraw:function(){var a=this.chart,b=this.isDirtyData,c=this.group,d=this.xAxis,e=this.yAxis;
+c&&(a.inverted&&c.attr({width:a.plotWidth,height:a.plotHeight}),c.animate({translateX:p(d&&d.left,a.plotLeft),translateY:p(e&&e.top,a.plotTop)}));this.translate();this.setTooltipPoints(!0);this.render();b&&K(this,"updatedData")},setState:function(a){var b=this.options,c=this.graph,d=this.graphNeg,e=b.states,b=b.lineWidth,a=a||"";if(this.state!==a)this.state=a,e[a]&&e[a].enabled===!1||(a&&(b=e[a].lineWidth||b+1),c&&!c.dashstyle&&(a={"stroke-width":b},c.attr(a),d&&d.attr(a)))},setVisible:function(a,
+b){var c=this,d=c.chart,e=c.legendItem,f,g=d.options.chart.ignoreHiddenSeries,h=c.visible;f=(c.visible=a=c.userOptions.visible=a===v?!h:a)?"show":"hide";n(["group","dataLabelsGroup","markerGroup","tracker"],function(a){if(c[a])c[a][f]()});if(d.hoverSeries===c)c.onMouseOut();e&&d.legend.colorizeItem(c,a);c.isDirty=!0;c.options.stacking&&n(d.series,function(a){if(a.options.stacking&&a.visible)a.isDirty=!0});n(c.linkedSeries,function(b){b.setVisible(a,!1)});if(g)d.isDirtyBox=!0;b!==!1&&d.redraw();K(c,
+f)},show:function(){this.setVisible(!0)},hide:function(){this.setVisible(!1)},select:function(a){this.selected=a=a===v?!this.selected:a;if(this.checkbox)this.checkbox.checked=a;K(this,a?"select":"unselect")},drawTracker:function(){var a=this,b=a.options,c=b.trackByArea,d=[].concat(c?a.areaPath:a.graphPath),e=d.length,f=a.chart,g=f.pointer,h=f.renderer,i=f.options.tooltip.snap,j=a.tracker,k=b.cursor,l=k&&{cursor:k},k=a.singlePoints,m,o=function(){if(f.hoverSeries!==a)a.onMouseOver()};if(e&&!c)for(m=
+e+1;m--;)d[m]==="M"&&d.splice(m+1,0,d[m+1]-i,d[m+2],"L"),(m&&d[m]==="M"||m===e)&&d.splice(m,0,"L",d[m-2]+i,d[m-1]);for(m=0;m<k.length;m++)e=k[m],d.push("M",e.plotX-i,e.plotY,"L",e.plotX+i,e.plotY);j?j.attr({d:d}):(a.tracker=h.path(d).attr({"stroke-linejoin":"round",visibility:a.visible?"visible":"hidden",stroke:Qb,fill:c?Qb:S,"stroke-width":b.lineWidth+(c?0:2*i),zIndex:2}).add(a.group),n([a.tracker,a.markerGroup],function(a){a.addClass("highcharts-tracker").on("mouseover",o).on("mouseout",function(a){g.onTrackerMouseOut(a)}).css(l);
+if(hb)a.on("touchstart",o)}))}};F=ha(Q);aa.line=F;Y.area=x(X,{threshold:0});F=ha(Q,{type:"area",getSegments:function(){var a=[],b=[],c=[],d=this.xAxis,e=this.yAxis,f=e.stacks[this.stackKey],g={},h,i,j=this.points,k,l,m;if(this.options.stacking&&!this.cropped){for(l=0;l<j.length;l++)g[j[l].x]=j[l];for(m in f)c.push(+m);c.sort(function(a,b){return a-b});n(c,function(a){g[a]?b.push(g[a]):(h=d.translate(a),k=f[a].percent?f[a].total?f[a].cum*100/f[a].total:0:f[a].cum,i=e.toPixels(k,!0),b.push({y:null,
+plotX:h,clientX:h,plotY:i,yBottom:i,onMouseOver:ya}))});b.length&&a.push(b)}else Q.prototype.getSegments.call(this),a=this.segments;this.segments=a},getSegmentPath:function(a){var b=Q.prototype.getSegmentPath.call(this,a),c=[].concat(b),d,e=this.options;b.length===3&&c.push("L",b[1],b[2]);if(e.stacking&&!this.closedStacks)for(d=a.length-1;d>=0;d--)d<a.length-1&&e.step&&c.push(a[d+1].plotX,a[d].yBottom),c.push(a[d].plotX,a[d].yBottom);else this.closeSegment(c,a);this.areaPath=this.areaPath.concat(c);
+return b},closeSegment:function(a,b){var c=this.yAxis.getThreshold(this.options.threshold);a.push("L",b[b.length-1].plotX,c,"L",b[0].plotX,c)},drawGraph:function(){this.areaPath=[];Q.prototype.drawGraph.apply(this);var a=this,b=this.areaPath,c=this.options,d=c.negativeColor,e=c.negativeFillColor,f=[["area",this.color,c.fillColor]];(d||e)&&f.push(["areaNeg",d,e]);n(f,function(d){var e=d[0],f=a[e];f?f.animate({d:b}):a[e]=a.chart.renderer.path(b).attr({fill:p(d[2],pa(d[1]).setOpacity(p(c.fillOpacity,
+0.75)).get()),zIndex:0}).add(a.group)})},drawLegendSymbol:function(a,b){b.legendSymbol=this.chart.renderer.rect(0,a.baseline-11,a.options.symbolWidth,12,2).attr({zIndex:3}).add(b.legendGroup)}});aa.area=F;Y.spline=x(X);D=ha(Q,{type:"spline",getPointSpline:function(a,b,c){var d=b.plotX,e=b.plotY,f=a[c-1],g=a[c+1],h,i,j,k;if(f&&g){a=f.plotY;j=g.plotX;var g=g.plotY,l;h=(1.5*d+f.plotX)/2.5;i=(1.5*e+a)/2.5;j=(1.5*d+j)/2.5;k=(1.5*e+g)/2.5;l=(k-i)*(j-d)/(j-h)+e-k;i+=l;k+=l;i>a&&i>e?(i=r(a,e),k=2*e-i):i<
+a&&i<e&&(i=C(a,e),k=2*e-i);k>g&&k>e?(k=r(g,e),i=2*e-k):k<g&&k<e&&(k=C(g,e),i=2*e-k);b.rightContX=j;b.rightContY=k}c?(b=["C",f.rightContX||f.plotX,f.rightContY||f.plotY,h||d,i||e,d,e],f.rightContX=f.rightContY=null):b=["M",d,e];return b}});aa.spline=D;Y.areaspline=x(Y.area);ma=F.prototype;D=ha(D,{type:"areaspline",closedStacks:!0,getSegmentPath:ma.getSegmentPath,closeSegment:ma.closeSegment,drawGraph:ma.drawGraph,drawLegendSymbol:ma.drawLegendSymbol});aa.areaspline=D;Y.column=x(X,{borderColor:"#FFFFFF",
+borderWidth:1,borderRadius:0,groupPadding:0.2,marker:null,pointPadding:0.1,minPointLength:0,cropThreshold:50,pointRange:null,states:{hover:{brightness:0.1,shadow:!1},select:{color:"#C0C0C0",borderColor:"#000000",shadow:!1}},dataLabels:{align:null,verticalAlign:null,y:null},stickyTracking:!1,threshold:0});D=ha(Q,{type:"column",pointAttrToOptions:{stroke:"borderColor","stroke-width":"borderWidth",fill:"color",r:"borderRadius"},trackerGroups:["group","dataLabelsGroup"],negStacks:!0,init:function(){Q.prototype.init.apply(this,
+arguments);var a=this,b=a.chart;b.hasRendered&&n(b.series,function(b){if(b.type===a.type)b.isDirty=!0})},getColumnMetrics:function(){var a=this,b=a.options,c=a.xAxis,d=a.yAxis,e=c.reversed,f,g={},h,i=0;b.grouping===!1?i=1:n(a.chart.series,function(b){var c=b.options,e=b.yAxis;if(b.type===a.type&&b.visible&&d.len===e.len&&d.pos===e.pos)c.stacking?(f=b.stackKey,g[f]===v&&(g[f]=i++),h=g[f]):c.grouping!==!1&&(h=i++),b.columnIndex=h});var c=C(O(c.transA)*(c.ordinalSlope||b.pointRange||c.closestPointRange||
+1),c.len),j=c*b.groupPadding,k=(c-2*j)/i,l=b.pointWidth,b=t(l)?(k-l)/2:k*b.pointPadding,l=p(l,k-2*b);return a.columnMetrics={width:l,offset:b+(j+((e?i-(a.columnIndex||0):a.columnIndex)||0)*k-c/2)*(e?-1:1)}},translate:function(){var a=this.chart,b=this.options,c=b.borderWidth,d=this.yAxis,e=this.translatedThreshold=d.getThreshold(b.threshold),f=p(b.minPointLength,5),b=this.getColumnMetrics(),g=b.width,h=this.barW=wa(r(g,1+2*c)),i=this.pointXOffset=b.offset,j=-(c%2?0.5:0),k=c%2?0.5:1;a.renderer.isVML&&
+a.inverted&&(k+=1);Q.prototype.translate.apply(this);n(this.points,function(a){var b=C(r(-999,a.plotY),d.len+999),c=p(a.yBottom,e),n=a.plotX+i,t=h,s=C(b,c),w,b=r(b,c)-s;O(b)<f&&f&&(b=f,s=u(O(s-e)>f?c-f:e-(d.translate(a.y,0,1,0,1)<=e?f:0)));a.barX=n;a.pointWidth=g;c=O(n)<0.5;t=u(n+t)+j;n=u(n)+j;t-=n;w=O(s)<0.5;b=u(s+b)+k;s=u(s)+k;b-=s;c&&(n+=1,t-=1);w&&(s-=1,b+=1);a.shapeType="rect";a.shapeArgs={x:n,y:s,width:t,height:b}})},getSymbol:ya,drawLegendSymbol:F.prototype.drawLegendSymbol,drawGraph:ya,drawPoints:function(){var a=
+this,b=a.options,c=a.chart.renderer,d;n(a.points,function(e){var f=e.plotY,g=e.graphic;if(f!==v&&!isNaN(f)&&e.y!==null)d=e.shapeArgs,g?(Wa(g),g.animate(x(d))):e.graphic=c[e.shapeType](d).attr(e.pointAttr[e.selected?"select":""]).add(a.group).shadow(b.shadow,null,b.stacking&&!b.borderRadius);else if(g)e.graphic=g.destroy()})},drawTracker:function(){var a=this,b=a.chart,c=b.pointer,d=a.options.cursor,e=d&&{cursor:d},f=function(c){var d=c.target,e;if(b.hoverSeries!==a)a.onMouseOver();for(;d&&!e;)e=d.point,
+d=d.parentNode;if(e!==v&&e!==b.hoverPoint)e.onMouseOver(c)};n(a.points,function(a){if(a.graphic)a.graphic.element.point=a;if(a.dataLabel)a.dataLabel.element.point=a});if(!a._hasTracking)n(a.trackerGroups,function(b){if(a[b]&&(a[b].addClass("highcharts-tracker").on("mouseover",f).on("mouseout",function(a){c.onTrackerMouseOut(a)}).css(e),hb))a[b].on("touchstart",f)}),a._hasTracking=!0},alignDataLabel:function(a,b,c,d,e){var f=this.chart,g=f.inverted,h=a.dlBox||a.shapeArgs,i=a.below||a.plotY>p(this.translatedThreshold,
+f.plotSizeY),j=p(c.inside,!!this.options.stacking);if(h&&(d=x(h),g&&(d={x:f.plotWidth-d.y-d.height,y:f.plotHeight-d.x-d.width,width:d.height,height:d.width}),!j))g?(d.x+=i?0:d.width,d.width=0):(d.y+=i?d.height:0,d.height=0);c.align=p(c.align,!g||j?"center":i?"right":"left");c.verticalAlign=p(c.verticalAlign,g||j?"middle":i?"top":"bottom");Q.prototype.alignDataLabel.call(this,a,b,c,d,e)},animate:function(a){var b=this.yAxis,c=this.options,d=this.chart.inverted,e={};if(Z)a?(e.scaleY=0.001,a=C(b.pos+
+b.len,r(b.pos,b.toPixels(c.threshold))),d?e.translateX=a-b.len:e.translateY=a,this.group.attr(e)):(e.scaleY=1,e[d?"translateX":"translateY"]=b.pos,this.group.animate(e,this.options.animation),this.animate=null)},remove:function(){var a=this,b=a.chart;b.hasRendered&&n(b.series,function(b){if(b.type===a.type)b.isDirty=!0});Q.prototype.remove.apply(a,arguments)}});aa.column=D;Y.bar=x(Y.column);ma=ha(D,{type:"bar",inverted:!0});aa.bar=ma;Y.scatter=x(X,{lineWidth:0,tooltip:{headerFormat:'<span style="font-size: 10px; color:{series.color}">{series.name}</span><br/>',
+pointFormat:"x: <b>{point.x}</b><br/>y: <b>{point.y}</b><br/>",followPointer:!0},stickyTracking:!1});ma=ha(Q,{type:"scatter",sorted:!1,requireSorting:!1,noSharedTooltip:!0,trackerGroups:["markerGroup"],drawTracker:D.prototype.drawTracker,setTooltipPoints:ya});aa.scatter=ma;Y.pie=x(X,{borderColor:"#FFFFFF",borderWidth:1,center:[null,null],clip:!1,colorByPoint:!0,dataLabels:{distance:30,enabled:!0,formatter:function(){return this.point.name}},ignoreHiddenPoint:!0,legendType:"point",marker:null,size:null,
+showInLegend:!1,slicedOffset:10,states:{hover:{brightness:0.1,shadow:!1}},stickyTracking:!1,tooltip:{followPointer:!0}});X={type:"pie",isCartesian:!1,pointClass:ha(Pa,{init:function(){Pa.prototype.init.apply(this,arguments);var a=this,b;if(a.y<0)a.y=null;s(a,{visible:a.visible!==!1,name:p(a.name,"Slice")});b=function(b){a.slice(b.type==="select")};J(a,"select",b);J(a,"unselect",b);return a},setVisible:function(a){var b=this,c=b.series,d=c.chart,e;b.visible=b.options.visible=a=a===v?!b.visible:a;c.options.data[oa(b,
+c.data)]=b.options;e=a?"show":"hide";n(["graphic","dataLabel","connector","shadowGroup"],function(a){if(b[a])b[a][e]()});b.legendItem&&d.legend.colorizeItem(b,a);if(!c.isDirty&&c.options.ignoreHiddenPoint)c.isDirty=!0,d.redraw()},slice:function(a,b,c){var d=this.series;Ka(c,d.chart);p(b,!0);this.sliced=this.options.sliced=a=t(a)?a:!this.sliced;d.options.data[oa(this,d.data)]=this.options;a=a?this.slicedTranslation:{translateX:0,translateY:0};this.graphic.animate(a);this.shadowGroup&&this.shadowGroup.animate(a)}}),
+requireSorting:!1,noSharedTooltip:!0,trackerGroups:["group","dataLabelsGroup"],pointAttrToOptions:{stroke:"borderColor","stroke-width":"borderWidth",fill:"color"},getColor:ya,animate:function(a){var b=this,c=b.points,d=b.startAngleRad;if(!a)n(c,function(a){var c=a.graphic,a=a.shapeArgs;c&&(c.attr({r:b.center[3]/2,start:d,end:d}),c.animate({r:a.r,start:a.start,end:a.end},b.options.animation))}),b.animate=null},setData:function(a,b){Q.prototype.setData.call(this,a,!1);this.processData();this.generatePoints();
+p(b,!0)&&this.chart.redraw()},generatePoints:function(){var a,b=0,c,d,e,f=this.options.ignoreHiddenPoint;Q.prototype.generatePoints.call(this);c=this.points;d=c.length;for(a=0;a<d;a++)e=c[a],b+=f&&!e.visible?0:e.y;this.total=b;for(a=0;a<d;a++)e=c[a],e.percentage=b>0?e.y/b*100:0,e.total=b},getCenter:function(){var a=this.options,b=this.chart,c=2*(a.slicedOffset||0),d,e=b.plotWidth-2*c,f=b.plotHeight-2*c,b=a.center,a=[p(b[0],"50%"),p(b[1],"50%"),a.size||"100%",a.innerSize||0],g=C(e,f),h;return Na(a,
+function(a,b){h=/%$/.test(a);d=b<2||b===2&&h;return(h?[e,f,g,g][b]*A(a)/100:a)+(d?c:0)})},translate:function(a){this.generatePoints();var b=0,c=this.options,d=c.slicedOffset,e=d+c.borderWidth,f,g,h,i=this.startAngleRad=Ma/180*((c.startAngle||0)%360-90),j=this.points,k=2*Ma,l=c.dataLabels.distance,c=c.ignoreHiddenPoint,m,n=j.length,p;if(!a)this.center=a=this.getCenter();this.getX=function(b,c){h=R.asin((b-a[1])/(a[2]/2+l));return a[0]+(c?-1:1)*W(h)*(a[2]/2+l)};for(m=0;m<n;m++){p=j[m];f=u((i+b*k)*1E3)/
+1E3;if(!c||p.visible)b+=p.percentage/100;g=u((i+b*k)*1E3)/1E3;p.shapeType="arc";p.shapeArgs={x:a[0],y:a[1],r:a[2]/2,innerR:a[3]/2,start:f,end:g};h=(g+f)/2;h>0.75*k&&(h-=2*Ma);p.slicedTranslation={translateX:u(W(h)*d),translateY:u(ca(h)*d)};f=W(h)*a[2]/2;g=ca(h)*a[2]/2;p.tooltipPos=[a[0]+f*0.7,a[1]+g*0.7];p.half=h<k/4?0:1;p.angle=h;e=C(e,l/2);p.labelPos=[a[0]+f+W(h)*l,a[1]+g+ca(h)*l,a[0]+f+W(h)*e,a[1]+g+ca(h)*e,a[0]+f,a[1]+g,l<0?"center":p.half?"right":"left",h]}this.setTooltipPoints()},drawGraph:null,
+drawPoints:function(){var a=this,b=a.chart.renderer,c,d,e=a.options.shadow,f,g;if(e&&!a.shadowGroup)a.shadowGroup=b.g("shadow").add(a.group);n(a.points,function(h){d=h.graphic;g=h.shapeArgs;f=h.shadowGroup;if(e&&!f)f=h.shadowGroup=b.g("shadow").add(a.shadowGroup);c=h.sliced?h.slicedTranslation:{translateX:0,translateY:0};f&&f.attr(c);d?d.animate(s(g,c)):h.graphic=d=b.arc(g).setRadialReference(a.center).attr(h.pointAttr[h.selected?"select":""]).attr({"stroke-linejoin":"round"}).attr(c).add(a.group).shadow(e,
+f);h.visible===!1&&h.setVisible(!1)})},drawDataLabels:function(){var a=this,b=a.data,c,d=a.chart,e=a.options.dataLabels,f=p(e.connectorPadding,10),g=p(e.connectorWidth,1),h=d.plotWidth,d=d.plotHeight,i,j,k=p(e.softConnector,!0),l=e.distance,m=a.center,o=m[2]/2,q=m[1],t=l>0,s,w,v,x,A=[[],[]],z,E,G,I,B,C=[0,0,0,0],H=function(a,b){return b.y-a.y},K=function(a,b){a.sort(function(a,c){return a.angle!==void 0&&(c.angle-a.angle)*b})};if(a.visible&&(e.enabled||a._hasPointLabels)){Q.prototype.drawDataLabels.apply(a);
+n(b,function(a){a.dataLabel&&A[a.half].push(a)});for(I=0;!x&&b[I];)x=b[I]&&b[I].dataLabel&&(b[I].dataLabel.getBBox().height||21),I++;for(I=2;I--;){var b=[],L=[],F=A[I],J=F.length,D;K(F,I-0.5);if(l>0){for(B=q-o-l;B<=q+o+l;B+=x)b.push(B);w=b.length;if(J>w){c=[].concat(F);c.sort(H);for(B=J;B--;)c[B].rank=B;for(B=J;B--;)F[B].rank>=w&&F.splice(B,1);J=F.length}for(B=0;B<J;B++){c=F[B];v=c.labelPos;c=9999;var N,M;for(M=0;M<w;M++)N=O(b[M]-v[1]),N<c&&(c=N,D=M);if(D<B&&b[B]!==null)D=B;else for(w<J-B+D&&b[B]!==
+null&&(D=w-J+B);b[D]===null;)D++;L.push({i:D,y:b[D]});b[D]=null}L.sort(H)}for(B=0;B<J;B++){c=F[B];v=c.labelPos;s=c.dataLabel;G=c.visible===!1?"hidden":"visible";c=v[1];if(l>0){if(w=L.pop(),D=w.i,E=w.y,c>E&&b[D+1]!==null||c<E&&b[D-1]!==null)E=c}else E=c;z=e.justify?m[0]+(I?-1:1)*(o+l):a.getX(D===0||D===b.length-1?c:E,I);s._attr={visibility:G,align:v[6]};s._pos={x:z+e.x+({left:f,right:-f}[v[6]]||0),y:E+e.y-10};s.connX=z;s.connY=E;if(this.options.size===null)w=s.width,z-w<f?C[3]=r(u(w-z+f),C[3]):z+w>
+h-f&&(C[1]=r(u(z+w-h+f),C[1])),E-x/2<0?C[0]=r(u(-E+x/2),C[0]):E+x/2>d&&(C[2]=r(u(E+x/2-d),C[2]))}}if(ua(C)===0||this.verifyDataLabelOverflow(C))this.placeDataLabels(),t&&g&&n(this.points,function(b){i=b.connector;v=b.labelPos;if((s=b.dataLabel)&&s._pos)G=s._attr.visibility,z=s.connX,E=s.connY,j=k?["M",z+(v[6]==="left"?5:-5),E,"C",z,E,2*v[2]-v[4],2*v[3]-v[5],v[2],v[3],"L",v[4],v[5]]:["M",z+(v[6]==="left"?5:-5),E,"L",v[2],v[3],"L",v[4],v[5]],i?(i.animate({d:j}),i.attr("visibility",G)):b.connector=i=
+a.chart.renderer.path(j).attr({"stroke-width":g,stroke:e.connectorColor||b.color||"#606060",visibility:G}).add(a.group);else if(i)b.connector=i.destroy()})}},verifyDataLabelOverflow:function(a){var b=this.center,c=this.options,d=c.center,e=c=c.minSize||80,f;d[0]!==null?e=r(b[2]-r(a[1],a[3]),c):(e=r(b[2]-a[1]-a[3],c),b[0]+=(a[3]-a[1])/2);d[1]!==null?e=r(C(e,b[2]-r(a[0],a[2])),c):(e=r(C(e,b[2]-a[0]-a[2]),c),b[1]+=(a[0]-a[2])/2);e<b[2]?(b[2]=e,this.translate(b),n(this.points,function(a){if(a.dataLabel)a.dataLabel._pos=
+null}),this.drawDataLabels()):f=!0;return f},placeDataLabels:function(){n(this.points,function(a){var a=a.dataLabel,b;if(a)(b=a._pos)?(a.attr(a._attr),a[a.moved?"animate":"attr"](b),a.moved=!0):a&&a.attr({y:-999})})},alignDataLabel:ya,drawTracker:D.prototype.drawTracker,drawLegendSymbol:F.prototype.drawLegendSymbol,getSymbol:ya};X=ha(Q,X);aa.pie=X;s(Highcharts,{Axis:db,Chart:xb,Color:pa,Legend:wb,Pointer:vb,Point:Pa,Tick:La,Tooltip:ub,Renderer:Va,Series:Q,SVGElement:va,SVGRenderer:Ga,arrayMin:Ia,
+arrayMax:ua,charts:Fa,dateFormat:Xa,format:Ba,pathAnim:zb,getOptions:function(){return M},hasBidiBug:Ub,isTouchDevice:Ob,numberFormat:za,seriesTypes:aa,setOptions:function(a){M=x(M,a);Lb();return M},addEvent:J,removeEvent:ba,createElement:U,discardElement:Ta,css:L,each:n,extend:s,map:Na,merge:x,pick:p,splat:ia,extendClass:ha,pInt:A,wrap:Bb,svg:Z,canvas:$,vml:!Z&&!$,product:"Highcharts",version:"3.0.5"})})();