highcharts
Version:
JavaScript charting framework
1,594 lines (1,369 loc) • 740 kB
JavaScript
// ==ClosureCompiler==
// @compilation_level SIMPLE_OPTIMIZATIONS
/**
* @license Highcharts JS v4.2.7 (2016-09-21)
*
* (c) 2009-2016 Torstein Honsi
*
* License: www.highcharts.com/license
*/
(function (root, factory) {
if (typeof module === 'object' && module.exports) {
module.exports = root.document ?
factory(root) :
factory;
} else {
root.Highcharts = factory(root);
}
}(typeof window !== 'undefined' ? window : this, function (win) { // eslint-disable-line no-undef
// encapsulated variables
var UNDEFINED,
doc = win.document,
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 = (win.navigator && win.navigator.userAgent) || '',
isOpera = win.opera,
isMS = /(msie|trident|edge)/i.test(userAgent) && !isOpera,
docMode8 = doc && doc.documentMode === 8,
isWebKit = !isMS && /AppleWebKit/.test(userAgent),
isFirefox = /Firefox/.test(userAgent),
isTouchDevice = /(Mobile|Android|Windows Phone)/.test(userAgent),
SVG_NS = 'http://www.w3.org/2000/svg',
hasSVG = doc && doc.createElementNS && !!doc.createElementNS(SVG_NS, 'svg').createSVGRect,
hasBidiBug = isFirefox && parseInt(userAgent.split('Firefox/')[1], 10) < 4, // issue #38
useCanVG = doc && !hasSVG && !isMS && !!doc.createElement('canvas').getContext,
Renderer,
hasTouch,
symbolSizes = {},
idCounter = 0,
garbageBin,
defaultOptions,
dateFormat, // function
pathAnim,
timeUnits,
noop = function () {},
charts = [],
chartCount = 0,
PRODUCT = 'Highcharts',
VERSION = '4.2.7',
// 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',
numRegex = /[0-9]/g,
NORMAL_STATE = '',
HOVER_STATE = 'hover',
SELECT_STATE = 'select',
marginNames = ['plotTop', 'marginRight', 'marginBottom', 'plotLeft'],
// Object for extending Axis
AxisPlotLineOrBandExtension,
// constants for attributes
STROKE_WIDTH = 'stroke-width',
// time methods, changed based on whether or not UTC is used
Date, // Allow using a different Date class
makeTime,
timezoneOffset,
getTimezoneOffset,
getMinutes,
getHours,
getDay,
getDate,
getMonth,
getFullYear,
setMilliseconds,
setSeconds,
setMinutes,
setHours,
setDate,
setMonth,
setFullYear,
// lookup over the types and the associated classes
seriesTypes = {},
Highcharts;
/**
* 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 new Error(msg);
}
// else ...
if (win.console) {
console.log(msg); // eslint-disable-line no-console
}
}
// The Highcharts namespace
Highcharts = win.Highcharts ? error(16, true) : { win: win };
Highcharts.seriesTypes = seriesTypes;
var timers = [],
getStyle,
// Previous adapter functions
inArray,
each,
grep,
offset,
map,
addEvent,
removeEvent,
fireEvent,
animate,
stop;
/**
* An animator object. One instance applies to one property (attribute or style prop)
* on one element.
*
* @param {object} elem The element to animate. May be a DOM element or a Highcharts SVGElement wrapper.
* @param {object} options Animation options, including duration, easing, step and complete.
* @param {object} prop The property to animate.
*/
function Fx(elem, options, prop) {
this.options = options;
this.elem = elem;
this.prop = prop;
}
Fx.prototype = {
/**
* Animating a path definition on SVGElement
* @returns {undefined}
*/
dSetter: function () {
var start = this.paths[0],
end = this.paths[1],
ret = [],
now = this.now,
i = start.length,
startVal;
if (now === 1) { // land on the final path without adjustment points appended in the ends
ret = this.toD;
} else if (i === end.length && now < 1) {
while (i--) {
startVal = parseFloat(start[i]);
ret[i] =
isNaN(startVal) ? // a letter instruction like M or L
start[i] :
now * (parseFloat(end[i] - startVal)) + startVal;
}
} else { // if animation is finished or length not matching, land on right value
ret = end;
}
this.elem.attr('d', ret);
},
/**
* Update the element with the current animation step
* @returns {undefined}
*/
update: function () {
var elem = this.elem,
prop = this.prop, // if destroyed, it is null
now = this.now,
step = this.options.step;
// Animation setter defined from outside
if (this[prop + 'Setter']) {
this[prop + 'Setter']();
// Other animations on SVGElement
} else if (elem.attr) {
if (elem.element) {
elem.attr(prop, now);
}
// HTML styles, raw HTML content like container size
} else {
elem.style[prop] = now + this.unit;
}
if (step) {
step.call(elem, now, this);
}
},
/**
* Run an animation
*/
run: function (from, to, unit) {
var self = this,
timer = function (gotoEnd) {
return timer.stopped ? false : self.step(gotoEnd);
},
i;
this.startTime = +new Date();
this.start = from;
this.end = to;
this.unit = unit;
this.now = this.start;
this.pos = 0;
timer.elem = this.elem;
if (timer() && timers.push(timer) === 1) {
timer.timerId = setInterval(function () {
for (i = 0; i < timers.length; i++) {
if (!timers[i]()) {
timers.splice(i--, 1);
}
}
if (!timers.length) {
clearInterval(timer.timerId);
}
}, 13);
}
},
/**
* Run a single step in the animation
* @param {Boolean} gotoEnd Whether to go to then endpoint of the animation after abort
* @returns {Boolean} True if animation continues
*/
step: function (gotoEnd) {
var t = +new Date(),
ret,
done,
options = this.options,
elem = this.elem,
complete = options.complete,
duration = options.duration,
curAnim = options.curAnim,
i;
if (elem.attr && !elem.element) { // #2616, element including flag is destroyed
ret = false;
} else if (gotoEnd || t >= duration + this.startTime) {
this.now = this.end;
this.pos = 1;
this.update();
curAnim[this.prop] = true;
done = true;
for (i in curAnim) {
if (curAnim[i] !== true) {
done = false;
}
}
if (done && complete) {
complete.call(elem);
}
ret = false;
} else {
this.pos = options.easing((t - this.startTime) / duration);
this.now = this.start + ((this.end - this.start) * this.pos);
this.update();
ret = true;
}
return ret;
},
/**
* Prepare start and end values so that the path can be animated one to one
*/
initPath: function (elem, fromD, toD) {
fromD = fromD || '';
var shift,
startX = elem.startX,
endX = elem.endX,
bezier = fromD.indexOf('C') > -1,
numParams = bezier ? 7 : 3,
fullLength,
slice,
i,
start = fromD.split(' '),
end = toD.slice(), // copy
isArea = elem.isArea,
positionFactor = isArea ? 2 : 1,
reverse;
/**
* In splines make move points have six parameters like bezier curves
*/
function sixify(arr) {
i = arr.length;
while (i--) {
if (arr[i] === M || arr[i] === L) {
arr.splice(i + 1, 0, arr[i + 1], arr[i + 2], arr[i + 1], arr[i + 2]);
}
}
}
/**
* Insert an array at the given position of another array
*/
function insertSlice(arr, subArr, index) {
[].splice.apply(
arr,
[index, 0].concat(subArr)
);
}
/**
* If shifting points, prepend a dummy point to the end path.
*/
function prepend(arr, other) {
while (arr.length < fullLength) {
// Move to, line to or curve to?
arr[0] = other[fullLength - arr.length];
// Prepend a copy of the first point
insertSlice(arr, arr.slice(0, numParams), 0);
// For areas, the bottom path goes back again to the left, so we need
// to append a copy of the last point.
if (isArea) {
insertSlice(arr, arr.slice(arr.length - numParams), arr.length);
i--;
}
}
arr[0] = 'M';
}
/**
* Copy and append last point until the length matches the end length
*/
function append(arr, other) {
var i = (fullLength - arr.length) / numParams;
while (i > 0 && i--) {
// Pull out the slice that is going to be appended or inserted. In a line graph,
// the positionFactor is 1, and the last point is sliced out. In an area graph,
// the positionFactor is 2, causing the middle two points to be sliced out, since
// an area path starts at left, follows the upper path then turns and follows the
// bottom back.
slice = arr.slice().splice(
(arr.length / positionFactor) - numParams,
numParams * positionFactor
);
// Move to, line to or curve to?
slice[0] = other[fullLength - numParams - (i * numParams)];
// Disable first control point
if (bezier) {
slice[numParams - 6] = slice[numParams - 2];
slice[numParams - 5] = slice[numParams - 1];
}
// Now insert the slice, either in the middle (for areas) or at the end (for lines)
insertSlice(arr, slice, arr.length / positionFactor);
if (isArea) {
i--;
}
}
}
if (bezier) {
sixify(start);
sixify(end);
}
// For sideways animation, find out how much we need to shift to get the start path Xs
// to match the end path Xs.
if (startX && endX) {
for (i = 0; i < startX.length; i++) {
if (startX[i] === endX[0]) { // Moving left, new points coming in on right
shift = i;
break;
} else if (startX[0] === endX[endX.length - startX.length + i]) { // Moving right
shift = i;
reverse = true;
break;
}
}
if (shift === undefined) {
start = [];
}
}
if (start.length && Highcharts.isNumber(shift)) {
// The common target length for the start and end array, where both
// arrays are padded in opposite ends
fullLength = end.length + shift * positionFactor * numParams;
if (!reverse) {
prepend(end, start);
append(start, end);
} else {
prepend(start, end);
append(end, start);
}
}
return [start, end];
}
}; // End of Fx prototype
/**
* Extend an object with the members of another
* @param {Object} a The object to be extended
* @param {Object} b The object to add to the first one
*/
var extend = Highcharts.extend = function (a, b) {
var n;
if (!a) {
a = {};
}
for (n in b) {
a[n] = b[n];
}
return a;
};
/**
* Deep merge two or more objects and return a third object. If the first argument is
* true, the contents of the second object is copied into the first object.
* Previously this function redirected to jQuery.extend(true), but this had two limitations.
* First, it deep merged arrays, which lead to workarounds in Highcharts. Second,
* it copied properties from extended prototypes.
*/
function merge() {
var i,
args = arguments,
len,
ret = {},
doCopy = function (copy, original) {
var value, key;
// An object is replacing a primitive
if (typeof copy !== 'object') {
copy = {};
}
for (key in original) {
if (original.hasOwnProperty(key)) {
value = original[key];
// Copy the contents of objects, but not arrays or DOM nodes
if (Highcharts.isObject(value, true) &&
key !== 'renderTo' && typeof value.nodeType !== 'number') {
copy[key] = doCopy(copy[key] || {}, value);
// Primitives and arrays are copied over directly
} else {
copy[key] = original[key];
}
}
}
return copy;
};
// If first argument is true, copy into the existing object. Used in setOptions.
if (args[0] === true) {
ret = args[1];
args = Array.prototype.slice.call(args, 2);
}
// For each argument, extend the return
len = args.length;
for (i = 0; i < len; i++) {
ret = doCopy(ret, args[i]);
}
return ret;
}
/**
* Shortcut for parseInt
* @param {Object} s
* @param {Number} mag Magnitude
*/
function pInt(s, mag) {
return parseInt(s, mag || 10);
}
/**
* Check for string
* @param {Object} s
*/
function isString(s) {
return typeof s === 'string';
}
/**
* Check for array
* @param {Object} obj
*/
function isArray(obj) {
var str = Object.prototype.toString.call(obj);
return str === '[object Array]' || str === '[object Array Iterator]';
}
/**
* Check for object
* @param {Object} obj
* @param {Boolean} strict Also checks that the object is not an array
*/
var isObject = Highcharts.isObject = function (obj, strict) {
//debugger;
return obj && typeof obj === 'object' && (!strict || !isArray(obj));
};
/**
* Check for number
* @param {Object} n
*/
var isNumber = Highcharts.isNumber = function isNumber(n) {
return typeof n === 'number' && !isNaN(n);
};
/**
* 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.
* @param {Object} obj
*/
function defined(obj) {
return obj !== UNDEFINED && obj !== null;
}
/**
* Set or get an attribute or an object of attributes. Can't use jQuery attr because
* it attempts to set expando properties on the SVG element, which is not allowed.
*
* @param {Object} elem The DOM element to receive the attribute(s)
* @param {String|Object} prop The property or an abject of key-value pairs
* @param {String} value The value if a single property is set
*/
function attr(elem, prop, value) {
var key,
ret;
// if the prop is a string
if (isString(prop)) {
// set the value
if (defined(value)) {
elem.setAttribute(prop, value);
// get the value
} else if (elem && elem.getAttribute) { // elem not defined when printing pie demo...
ret = elem.getAttribute(prop);
}
// else if prop is defined, it is a hash of key/value pairs
} else if (defined(prop) && isObject(prop)) {
for (key in prop) {
elem.setAttribute(key, prop[key]);
}
}
return ret;
}
/**
* Check if an element is an array, and if not, make it into an array.
*/
function splat(obj) {
return isArray(obj) ? obj : [obj];
}
/**
* Set a timeout if the delay is given, otherwise perform the function synchronously
* @param {Function} fn The function to perform
* @param {Number} delay Delay in milliseconds
* @param {Ojbect} context The context
* @returns {Nubmer} An identifier for the timeout
*/
function syncTimeout(fn, delay, context) {
if (delay) {
return setTimeout(fn, delay, context);
}
fn.call(0, context);
}
/**
* Return the first value that is defined.
*/
var pick = Highcharts.pick = function () {
var args = arguments,
i,
arg,
length = args.length;
for (i = 0; i < length; i++) {
arg = args[i];
if (arg !== UNDEFINED && arg !== null) {
return arg;
}
}
};
/**
* Set CSS on a given element
* @param {Object} el
* @param {Object} styles Style object with camel case property names
*/
function css(el, styles) {
if (isMS && !hasSVG) { // #2686
if (styles && styles.opacity !== UNDEFINED) {
styles.filter = 'alpha(opacity=' + (styles.opacity * 100) + ')';
}
}
extend(el.style, styles);
}
/**
* Utility function to create element with attributes and styles
* @param {Object} tag
* @param {Object} attribs
* @param {Object} styles
* @param {Object} parent
* @param {Object} nopad
*/
function createElement(tag, attribs, styles, parent, nopad) {
var el = doc.createElement(tag);
if (attribs) {
extend(el, attribs);
}
if (nopad) {
css(el, { padding: 0, border: 'none', margin: 0 });
}
if (styles) {
css(el, styles);
}
if (parent) {
parent.appendChild(el);
}
return el;
}
/**
* Extend a prototyped class by new members
* @param {Object} parent
* @param {Object} members
*/
function extendClass(Parent, members) {
var object = function () {
};
object.prototype = new Parent();
extend(object.prototype, members);
return object;
}
/**
* Pad a string to a given length by adding 0 to the beginning
* @param {Number} number
* @param {Number} length
*/
function pad(number, length, padder) {
return new Array((length || 2) + 1 - String(number).length).join(padder || 0) + number;
}
/**
* Return a length based on either the integer value, or a percentage of a base.
*/
function relativeLength(value, base) {
return (/%$/).test(value) ? base * parseFloat(value) / 100 : parseFloat(value);
}
/**
* Wrap a method with extended functionality, preserving the original function
* @param {Object} obj The context object that the method belongs to
* @param {String} method The name of the method to extend
* @param {Function} func A wrapper function callback. This function is called with the same arguments
* as the original function, except that the original function is unshifted and passed as the first
* argument.
*/
var wrap = Highcharts.wrap = function (obj, method, func) {
var proceed = obj[method];
obj[method] = function () {
var args = Array.prototype.slice.call(arguments);
args.unshift(proceed);
return func.apply(this, args);
};
};
function getTZOffset(timestamp) {
return ((getTimezoneOffset && getTimezoneOffset(timestamp)) || timezoneOffset || 0) * 60000;
}
/**
* Based on http://www.php.net/manual/en/function.strftime.php
* @param {String} format
* @param {Number} timestamp
* @param {Boolean} capitalize
*/
dateFormat = function (format, timestamp, capitalize) {
if (!defined(timestamp) || isNaN(timestamp)) {
return defaultOptions.lang.invalidDate || '';
}
format = pick(format, '%Y-%m-%d %H:%M:%S');
var date = new Date(timestamp - getTZOffset(timestamp)),
key, // used in for constuct below
// get the basic time values
hours = date[getHours](),
day = date[getDay](),
dayOfMonth = date[getDate](),
month = date[getMonth](),
fullYear = date[getFullYear](),
lang = defaultOptions.lang,
langWeekdays = lang.weekdays,
shortWeekdays = lang.shortWeekdays,
// List all format keys. Custom formats can be added from the outside.
replacements = extend({
// Day
'a': shortWeekdays ? shortWeekdays[day] : 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': pad(dayOfMonth, 2, ' '), // Day of the month, 1 through 31
'w': day,
// Week (none implemented)
//'W': weekNumber(),
// Month
'b': lang.shortMonths[month], // Short month, like 'Jan'
'B': lang.months[month], // Long month, like 'January'
'm': pad(month + 1), // Two digit month number, 01 through 12
// Year
'y': fullYear.toString().substr(2, 2), // Two digits year, like 09 for 2009
'Y': fullYear, // Four digits year, like 2009
// Time
'H': pad(hours), // Two digits hours in 24h format, 00 through 23
'k': hours, // Hours in 24h format, 0 through 23
'I': pad((hours % 12) || 12), // Two digits hours in 12h format, 00 through 11
'l': (hours % 12) || 12, // Hours in 12h format, 1 through 12
'M': pad(date[getMinutes]()), // Two digits minutes, 00 through 59
'p': hours < 12 ? 'AM' : 'PM', // Upper case AM or PM
'P': hours < 12 ? 'am' : 'pm', // Lower case AM or PM
'S': pad(date.getSeconds()), // Two digits seconds, 00 through 59
'L': pad(mathRound(timestamp % 1000), 3) // Milliseconds (naming from Ruby)
}, Highcharts.dateFormats);
// do the replaces
for (key in replacements) {
while (format.indexOf('%' + key) !== -1) { // regex would do it in one line, but this is faster
format = format.replace('%' + key, typeof replacements[key] === 'function' ? replacements[key](timestamp) : replacements[key]);
}
}
// Optionally capitalize the string and return
return capitalize ? format.substr(0, 1).toUpperCase() + format.substr(1) : format;
};
/**
* Format a single variable. Similar to sprintf, without the % prefix.
*/
function formatSingle(format, val) {
var floatRegex = /f$/,
decRegex = /\.([0-9])/,
lang = defaultOptions.lang,
decimals;
if (floatRegex.test(format)) { // float
decimals = format.match(decRegex);
decimals = decimals ? decimals[1] : -1;
if (val !== null) {
val = Highcharts.numberFormat(
val,
decimals,
lang.decimalPoint,
format.indexOf(',') > -1 ? lang.thousandsSep : ''
);
}
} else {
val = dateFormat(format, val);
}
return val;
}
/**
* Format a string according to a subset of the rules of Python's String.format method.
*/
function format(str, ctx) {
var splitter = '{',
isInside = false,
segment,
valueAndFormat,
path,
i,
len,
ret = [],
val,
index;
while ((index = str.indexOf(splitter)) !== -1) {
segment = str.slice(0, index);
if (isInside) { // we're on the closing bracket looking back
valueAndFormat = segment.split(':');
path = valueAndFormat.shift().split('.'); // get first and leave format
len = path.length;
val = ctx;
// Assign deeper paths
for (i = 0; i < len; i++) {
val = val[path[i]];
}
// Format the replacement
if (valueAndFormat.length) {
val = formatSingle(valueAndFormat.join(':'), val);
}
// Push the result and advance the cursor
ret.push(val);
} else {
ret.push(segment);
}
str = str.slice(index + 1); // the rest
isInside = !isInside; // toggle
splitter = isInside ? '}' : '{'; // now look for next matching bracket
}
ret.push(str);
return ret.join('');
}
/**
* Get the magnitude of a number
*/
function getMagnitude(num) {
return math.pow(10, mathFloor(math.log(num) / math.LN10));
}
/**
* Take an interval and normalize it to multiples of 1, 2, 2.5 and 5
* @param {Number} interval
* @param {Array} multiples
* @param {Number} magnitude
* @param {Object} options
*/
function normalizeTickInterval(interval, multiples, magnitude, allowDecimals, preventExceed) {
var normalized,
i,
retInterval = interval;
// round to a tenfold of 1, 2, 2.5 or 5
magnitude = pick(magnitude, 1);
normalized = interval / magnitude;
// multiples for a linear scale
if (!multiples) {
multiples = [1, 2, 2.5, 5, 10];
// the allowDecimals option
if (allowDecimals === false) {
if (magnitude === 1) {
multiples = [1, 2, 5, 10];
} else if (magnitude <= 0.1) {
multiples = [1 / magnitude];
}
}
}
// normalize the interval to the nearest multiple
for (i = 0; i < multiples.length; i++) {
retInterval = multiples[i];
if ((preventExceed && retInterval * magnitude >= interval) || // only allow tick amounts smaller than natural
(!preventExceed && (normalized <= (multiples[i] + (multiples[i + 1] || multiples[i])) / 2))) {
break;
}
}
// multiply back to the correct magnitude
retInterval *= magnitude;
return retInterval;
}
/**
* Utility method that sorts an object array and keeping the order of equal items.
* ECMA script standard does not specify the behaviour when items are equal.
*/
function stableSort(arr, sortFunction) {
var length = arr.length,
sortValue,
i;
// Add index to each item
for (i = 0; i < length; i++) {
arr[i].safeI = i; // stable sort index
}
arr.sort(function (a, b) {
sortValue = sortFunction(a, b);
return sortValue === 0 ? a.safeI - b.safeI : sortValue;
});
// Remove index from items
for (i = 0; i < length; i++) {
delete arr[i].safeI; // stable sort index
}
}
/**
* Non-recursive method to find the lowest member of an array. Math.min raises a maximum
* call stack size exceeded error in Chrome when trying to apply more than 150.000 points. This
* method is slightly slower, but safe.
*/
function arrayMin(data) {
var i = data.length,
min = data[0];
while (i--) {
if (data[i] < min) {
min = data[i];
}
}
return min;
}
/**
* Non-recursive method to find the lowest member of an array. Math.min raises a maximum
* call stack size exceeded error in Chrome when trying to apply more than 150.000 points. This
* method is slightly slower, but safe.
*/
function arrayMax(data) {
var i = data.length,
max = data[0];
while (i--) {
if (data[i] > max) {
max = data[i];
}
}
return max;
}
/**
* Utility method that destroys any SVGElement or VMLElement that are properties on the given object.
* It loops all properties and invokes destroy if there is a destroy method. The property is
* then delete'ed.
* @param {Object} The object to destroy properties on
* @param {Object} Exception, do not destroy this property, only delete it.
*/
function destroyObjectProperties(obj, except) {
var n;
for (n in obj) {
// If the object is non-null and destroy is defined
if (obj[n] && obj[n] !== except && obj[n].destroy) {
// Invoke the destroy
obj[n].destroy();
}
// Delete the property from the object.
delete obj[n];
}
}
/**
* Discard an element by moving it to the bin and delete
* @param {Object} The HTML node to discard
*/
function discardElement(element) {
// create a garbage bin element, not part of the DOM
if (!garbageBin) {
garbageBin = createElement(DIV);
}
// move the node and empty bin
if (element) {
garbageBin.appendChild(element);
}
garbageBin.innerHTML = '';
}
/**
* Fix JS round off float errors
* @param {Number} num
*/
function correctFloat(num, prec) {
return parseFloat(
num.toPrecision(prec || 14)
);
}
/**
* Set the global animation to either a given value, or fall back to the
* given chart's animation option
* @param {Object} animation
* @param {Object} chart
*/
function setAnimation(animation, chart) {
chart.renderer.globalAnimation = pick(animation, chart.animation);
}
/**
* Get the animation in object form, where a disabled animation is always
* returned with duration: 0
*/
function animObject(animation) {
return isObject(animation) ? merge(animation) : { duration: animation ? 500 : 0 };
}
/**
* The time unit lookup
*/
timeUnits = {
millisecond: 1,
second: 1000,
minute: 60000,
hour: 3600000,
day: 24 * 3600000,
week: 7 * 24 * 3600000,
month: 28 * 24 * 3600000,
year: 364 * 24 * 3600000
};
/**
* Format a number and return a string based on input settings
* @param {Number} number The input number to format
* @param {Number} decimals The amount of decimals
* @param {String} decimalPoint The decimal point, defaults to the one given in the lang options
* @param {String} thousandsSep The thousands separator, defaults to the one given in the lang options
*/
Highcharts.numberFormat = function (number, decimals, decimalPoint, thousandsSep) {
number = +number || 0;
decimals = +decimals;
var lang = defaultOptions.lang,
origDec = (number.toString().split('.')[1] || '').length,
decimalComponent,
strinteger,
thousands,
absNumber = Math.abs(number),
ret;
if (decimals === -1) {
decimals = Math.min(origDec, 20); // Preserve decimals. Not huge numbers (#3793).
} else if (!isNumber(decimals)) {
decimals = 2;
}
// A string containing the positive integer component of the number
strinteger = String(pInt(absNumber.toFixed(decimals)));
// Leftover after grouping into thousands. Can be 0, 1 or 3.
thousands = strinteger.length > 3 ? strinteger.length % 3 : 0;
// Language
decimalPoint = pick(decimalPoint, lang.decimalPoint);
thousandsSep = pick(thousandsSep, lang.thousandsSep);
// Start building the return
ret = number < 0 ? '-' : '';
// Add the leftover after grouping into thousands. For example, in the number 42 000 000,
// this line adds 42.
ret += thousands ? strinteger.substr(0, thousands) + thousandsSep : '';
// Add the remaining thousands groups, joined by the thousands separator
ret += strinteger.substr(thousands).replace(/(\d{3})(?=\d)/g, '$1' + thousandsSep);
// Add the decimal point and the decimal component
if (decimals) {
// Get the decimal component, and add power to avoid rounding errors with float numbers (#4573)
decimalComponent = Math.abs(absNumber - strinteger + Math.pow(10, -Math.max(decimals, origDec) - 1));
ret += decimalPoint + decimalComponent.toFixed(decimals).slice(2);
}
return ret;
};
/**
* Easing definition
* @param {Number} pos Current position, ranging from 0 to 1
*/
Math.easeInOutSine = function (pos) {
return -0.5 * (Math.cos(Math.PI * pos) - 1);
};
/**
* Internal method to return CSS value for given element and property
*/
getStyle = function (el, prop) {
var style;
// For width and height, return the actual inner pixel size (#4913)
if (prop === 'width') {
return Math.min(el.offsetWidth, el.scrollWidth) - getStyle(el, 'padding-left') - getStyle(el, 'padding-right');
} else if (prop === 'height') {
return Math.min(el.offsetHeight, el.scrollHeight) - getStyle(el, 'padding-top') - getStyle(el, 'padding-bottom');
}
// Otherwise, get the computed style
style = win.getComputedStyle(el, undefined);
return style && pInt(style.getPropertyValue(prop));
};
/**
* Return the index of an item in an array, or -1 if not found
*/
inArray = function (item, arr) {
return arr.indexOf ? arr.indexOf(item) : [].indexOf.call(arr, item);
};
/**
* Filter an array
*/
grep = function (elements, callback) {
return [].filter.call(elements, callback);
};
/**
* Map an array
*/
map = function (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 element's offset position, corrected by overflow:auto.
*/
offset = function (el) {
var docElem = doc.documentElement,
box = el.getBoundingClientRect();
return {
top: box.top + (win.pageYOffset || docElem.scrollTop) - (docElem.clientTop || 0),
left: box.left + (win.pageXOffset || docElem.scrollLeft) - (docElem.clientLeft || 0)
};
};
/**
* Stop running animation.
* A possible extension to this would be to stop a single property, when
* we want to continue animating others. Then assign the prop to the timer
* in the Fx.run method, and check for the prop here. This would be an improvement
* in all cases where we stop the animation from .attr. Instead of stopping
* everything, we can just stop the actual attributes we're setting.
*/
stop = function (el) {
var i = timers.length;
// Remove timers related to this element (#4519)
while (i--) {
if (timers[i].elem === el) {
timers[i].stopped = true; // #4667
}
}
};
/**
* Utility for iterating over an array.
* @param {Array} arr
* @param {Function} fn
*/
each = function (arr, fn) { // modern browsers
return Array.prototype.forEach.call(arr, fn);
};
/**
* Add an event listener
*/
addEvent = function (el, type, fn) {
var events = el.hcEvents = el.hcEvents || {};
function wrappedFn(e) {
e.target = e.srcElement || win; // #2820
fn.call(el, e);
}
// Handle DOM events in modern browsers
if (el.addEventListener) {
el.addEventListener(type, fn, false);
// Handle old IE implementation
} else if (el.attachEvent) {
if (!el.hcEventsIE) {
el.hcEventsIE = {};
}
// Link wrapped fn with original fn, so we can get this in removeEvent
el.hcEventsIE[fn.toString()] = wrappedFn;
el.attachEvent('on' + type, wrappedFn);
}
if (!events[type]) {
events[type] = [];
}
events[type].push(fn);
};
/**
* Remove event added with addEvent
*/
removeEvent = function (el, type, fn) {
var events,
hcEvents = el.hcEvents,
index;
function removeOneEvent(type, fn) {
if (el.removeEventListener) {
el.removeEventListener(type, fn, false);
} else if (el.attachEvent) {
fn = el.hcEventsIE[fn.toString()];
el.detachEvent('on' + type, fn);
}
}
function removeAllEvents() {
var types,
len,
n;
if (!el.nodeName) {
return; // break on non-DOM events
}
if (type) {
types = {};
types[type] = true;
} else {
types = hcEvents;
}
for (n in types) {
if (hcEvents[n]) {
len = hcEvents[n].length;
while (len--) {
removeOneEvent(n, hcEvents[n][len]);
}
}
}
}
if (hcEvents) {
if (type) {
events = hcEvents[type] || [];
if (fn) {
index = inArray(fn, events);
if (index > -1) {
events.splice(index, 1);
hcEvents[type] = events;
}
removeOneEvent(type, fn);
} else {
removeAllEvents();
hcEvents[type] = [];
}
} else {
removeAllEvents();
el.hcEvents = {};
}
}
};
/**
* Fire an event on a custom object
*/
fireEvent = function (el, type, eventArguments, defaultFunction) {
var e,
hcEvents = el.hcEvents,
events,
len,
i,
fn;
eventArguments = eventArguments || {};
if (doc.createEvent && (el.dispatchEvent || el.fireEvent)) {
e = doc.createEvent('Events');
e.initEvent(type, true, true);
e.target = el;
extend(e, eventArguments);
if (el.dispatchEvent) {
el.dispatchEvent(e);
} else {
el.fireEvent(type, e);
}
} else if (hcEvents) {
events = hcEvents[type] || [];
len = events.length;
// Attach a simple preventDefault function to skip default handler if called.
// The built-in defaultPrevented property is not overwritable (#5112)
if (!eventArguments.preventDefault) {
eventArguments.preventDefault = function () {
eventArguments.defaultPrevented = true;
};
}
eventArguments.target = el;
// If the type is not set, we're running a custom event (#2297). If it is set,
// we're running a browser event, and setting it will cause en error in
// IE8 (#2465).
if (!eventArguments.type) {
eventArguments.type = type;
}
for (i = 0; i < len; i++) {
fn = events[i];
// If the event handler return false, prevent the default handler from executing
if (fn && fn.call(el, eventArguments) === false) {
eventArguments.preventDefault();
}
}
}
// Run the default if not prevented
if (defaultFunction && !eventArguments.defaultPrevented) {
defaultFunction(eventArguments);
}
};
/**
* The global animate method, which uses Fx to create individual animators.
*/
animate = function (el, params, opt) {
var start,
unit = '',
end,
fx,
args,
prop;
if (!isObject(opt)) { // Number or undefined/null
args = arguments;
opt = {
duration: args[2],
easing: args[3],
complete: args[4]
};
}
if (!isNumber(opt.duration)) {
opt.duration = 400;
}
opt.easing = typeof opt.easing === 'function' ? opt.easing : (Math[opt.easing] || Math.easeInOutSine);
opt.curAnim = merge(params);
for (prop in params) {
fx = new Fx(el, opt, prop);
end = null;
if (prop === 'd') {
fx.paths = fx.initPath(
el,
el.d,
params.d
);
fx.toD = params.d;
start = 0;
end = 1;
} else if (el.attr) {
start = el.attr(prop);
} else {
start = parseFloat(getStyle(el, prop)) || 0;
if (prop !== 'opacity') {
unit = 'px';
}
}
if (!end) {
end = params[prop];
}
if (end.match && end.match('px')) {
end = end.replace(/px/g, ''); // #4351
}
fx.run(start, end, unit);
}
};
/**
* Register Highcharts as a plugin in jQuery
*/
if (win.jQuery) {
win.jQuery.fn.highcharts = function () {
var args = [].slice.call(arguments);
if (this[0]) { // this[0] is the renderTo div
// Create the chart
if (args[0]) {
new Highcharts[ // eslint-disable-line no-new
isString(args[0]) ? args.shift() : 'Chart' // Constructor defaults to Chart
](this[0], args[0], args[1]);
return this;
}
// When called without parameters or with the return argument, return an existing chart
return charts[attr(this[0], 'data-highcharts-chart')];
}
};
}
/**
* Compatibility section to add support for legacy IE. This can be removed if old IE
* support is not needed.
*/
if (doc && !doc.defaultView) {
getStyle = function (el, prop) {
var val,
alias = { width: 'clientWidth', height: 'clientHeight' }[prop];
if (el.style[prop]) {
return pInt(el.style[prop]);
}
if (prop === 'opacity') {
prop = 'filter';
}
// Getting the rendered width and height
if (alias) {
el.style.zoom = 1;
return Math.max(el[alias] - 2 * getStyle(el, 'padding'), 0);
}
val = el.currentStyle[prop.replace(/\-(\w)/g, function (a, b) {
return b.toUpperCase();
})];
if (prop === 'filter') {
val = val.replace(
/alpha\(opacity=([0-9]+)\)/,
function (a, b) {
return b / 100;
}
);
}
return val === '' ? 1 : pInt(val);
};
}
if (!Array.prototype.forEach) {
each = 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;
}
}
};
}
if (!Array.prototype.indexOf) {
inArray = function (item, arr) {
var len,
i = 0;
if (arr) {
len = arr.length;
for (; i < len; i++) {
if (arr[i] === item) {
return i;
}
}
}
return -1;
};
}
if (!Array.prototype.filter) {
grep = function (elements, fn) {
var ret = [],
i = 0,
length = elements.length;
for (; i < length; i++) {
if (fn(elements[i], i)) {
ret.push(elements[i]);
}
}
return ret;
};
}
//--- End compatibility section ---
// Expose utilities
Highcharts.Fx = Fx;
Highcharts.inArray = inArray;
Highcharts.each = each;
Highcharts.grep = grep;
Highcharts.offset = offset;
Highcharts.map = map;
Highcharts.addEvent = addEvent;
Highcharts.removeEvent = removeEvent;
Highcharts.fireEvent = fireEvent;
Highcharts.animate = animate;
Highcharts.animObject = animObject;
Highcharts.stop = stop;
/* ****************************************************************************
* Handle the options *
*****************************************************************************/
defaultOptions = {
colors: ['#7cb5ec', '#434348', '#90ed7d', '#f7a35c',
'#8085e9', '#f15c80', '#e4d354', '#2b908f', '#f45b5b', '#91e8e1'],
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'],