app/assets/javascripts/highcharts.js in highcharts-rails-5.0.6 vs app/assets/javascripts/highcharts.js in highcharts-rails-5.0.7
- old
+ new
@@ -1,7 +1,7 @@
/**
- * @license Highcharts JS v5.0.6 (2016-12-07)
+ * @license Highcharts JS v5.0.7 (2017-01-17)
*
* (c) 2009-2016 Torstein Honsi
*
* License: www.highcharts.com/license
*/
@@ -34,11 +34,11 @@
isFirefox = /Firefox/.test(userAgent),
hasBidiBug = isFirefox && parseInt(userAgent.split('Firefox/')[1], 10) < 4; // issue #38
var Highcharts = win.Highcharts ? win.Highcharts.error(16, true) : {
product: 'Highcharts',
- version: '5.0.6',
+ version: '5.0.7',
deg2rad: Math.PI * 2 / 360,
doc: doc,
hasBidiBug: hasBidiBug,
hasTouch: doc && doc.documentElement.ontouchstart !== undefined,
isMS: isMS,
@@ -1163,12 +1163,15 @@
(multiples[i + 1] || multiples[i])) / 2))) {
break;
}
}
- // multiply back to the correct magnitude
- retInterval *= magnitude;
+ // Multiply back to the correct magnitude. Correct floats to appropriate
+ // precision (#6085).
+ retInterval = H.correctFloat(
+ retInterval * magnitude, -Math.round(Math.log(0.001) / Math.LN10)
+ );
return retInterval;
};
@@ -1368,39 +1371,44 @@
* Format a number and return a string based on input settings.
*
* @function #numberFormat
* @memberOf Highcharts
* @param {Number} number - The input number to format.
- * @param {Number} decimals - The amount of decimals.
+ * @param {Number} decimals - The amount of decimals. A value of -1 preserves
+ * the amount in the input number.
* @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.
* @returns {String} The formatted number.
*/
H.numberFormat = function(number, decimals, decimalPoint, thousandsSep) {
-
number = +number || 0;
decimals = +decimals;
var lang = H.defaultOptions.lang,
origDec = (number.toString().split('.')[1] || '').length,
- decimalComponent,
strinteger,
thousands,
- absNumber = Math.abs(number),
- ret;
+ ret,
+ roundedNumber;
if (decimals === -1) {
// Preserve decimals. Not huge numbers (#3793).
decimals = Math.min(origDec, 20);
} else if (!H.isNumber(decimals)) {
decimals = 2;
}
+ // Add another decimal to avoid rounding errors of float numbers. (#4573)
+ // Then use toFixed to handle rounding.
+ roundedNumber = (
+ Math.abs(number) + Math.pow(10, -Math.max(decimals, origDec) - 1)
+ ).toFixed(decimals);
+
// A string containing the positive integer component of the number
- strinteger = String(H.pInt(absNumber.toFixed(decimals)));
+ strinteger = String(H.pInt(roundedNumber));
// Leftover after grouping into thousands. Can be 0, 1 or 3.
thousands = strinteger.length > 3 ? strinteger.length % 3 : 0;
// Language
@@ -1419,15 +1427,12 @@
.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);
+ // Get the decimal component
+ ret += decimalPoint + roundedNumber.slice(-decimals);
}
return ret;
};
@@ -2278,10 +2283,11 @@
each = H.each,
extend = H.extend,
erase = H.erase,
grep = H.grep,
hasTouch = H.hasTouch,
+ inArray = H.inArray,
isArray = H.isArray,
isFirefox = H.isFirefox,
isMS = H.isMS,
isObject = H.isObject,
isString = H.isString,
@@ -2946,11 +2952,16 @@
elem = elemWrapper.element,
textWidth,
n,
serializedCss = '',
hyphenate,
- hasNew = !oldStyles;
+ hasNew = !oldStyles,
+ // These CSS properties are interpreted internally by the SVG
+ // renderer, but are not supported by SVG and should not be added to
+ // the DOM. In styled mode, no CSS should find its way to the DOM
+ // whatsoever (#6173).
+ svgPseudoProps = ['textOverflow', 'width'];
// convert legacy
if (styles && styles.color) {
styles.fill = styles.color;
}
@@ -2990,13 +3001,19 @@
} else {
hyphenate = function(a, b) {
return '-' + b.toLowerCase();
};
for (n in styles) {
- serializedCss += n.replace(/([A-Z])/g, hyphenate) + ':' + styles[n] + ';';
+ if (inArray(n, svgPseudoProps) === -1) {
+ serializedCss +=
+ n.replace(/([A-Z])/g, hyphenate) + ':' +
+ styles[n] + ';';
+ }
}
- attr(elem, 'style', serializedCss); // #1881
+ if (serializedCss) {
+ attr(elem, 'style', serializedCss); // #1881
+ }
}
if (elemWrapper.added) {
// Rebuild text after added
@@ -3136,12 +3153,12 @@
element = wrapper.element,
transform;
// flipping affects translate as adjustment for flipping around the group's axis
if (inverted) {
- translateX += wrapper.attr('width');
- translateY += wrapper.attr('height');
+ translateX += wrapper.width;
+ translateY += wrapper.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 + ')'];
@@ -3324,12 +3341,12 @@
// Properties that affect bounding box
cacheKey += [
'',
rotation || 0,
fontSize,
- element.style.width,
- element.style['text-overflow'] // #5968
+ styles && styles.width,
+ styles && styles.textOverflow // #5968
]
.join(',');
}
@@ -3358,13 +3375,13 @@
}
bBox = element.getBBox ?
// SVG: use extend because IE9 is not allowed to change width and height in case
// of rotation (below)
- extend({}, element.getBBox()) :
- // Legacy IE in export mode
- {
+ extend({}, element.getBBox()) : {
+
+ // Legacy IE in export mode
width: element.offsetWidth,
height: element.offsetHeight
};
// #3842
@@ -3394,12 +3411,23 @@
// 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, #2568)
- if (isMS && styles && styles.fontSize === '11px' && height.toPrecision(3) === '16.9') {
+ // Workaround for wrong bounding box in IE, Edge and Chrome on
+ // Windows. With Highcharts' default font, IE and Edge report
+ // a box height of 16.899 and Chrome rounds it to 17. If this
+ // stands uncorrected, it results in more padding added below
+ // the text than above when adding a label border or background.
+ // Also vertical positioning is affected.
+ // http://jsfiddle.net/highcharts/em37nvuj/
+ // (#1101, #1505, #1669, #2568, #6213).
+ if (
+ styles &&
+ styles.fontSize === '11px' &&
+ Math.round(height) === 17
+ ) {
bBox.height = height = 14;
}
// Adjust for rotated text
if (rotation) {
@@ -3988,17 +4016,18 @@
*/
// #24, #672, #1070
this.url = (isFirefox || isWebKit) && doc.getElementsByTagName('base').length ?
win.location.href
.replace(/#.*?$/, '') // remove the hash
+ .replace(/<[^>]*>/g, '') // wing cut HTML
.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 Highcharts 5.0.6'));
+ desc.element.appendChild(doc.createTextNode('Created with Highcharts 5.0.7'));
renderer.defs = this.createElement('defs').add();
renderer.allowHTML = allowHTML;
renderer.forExport = forExport;
@@ -4167,18 +4196,21 @@
textStyles = wrapper.styles,
width = wrapper.textWidth,
textLineHeight = textStyles && textStyles.lineHeight,
textOutline = textStyles && textStyles.textOutline,
ellipsis = textStyles && textStyles.textOverflow === 'ellipsis',
+ noWrap = textStyles && textStyles.whiteSpace === 'nowrap',
+ fontSize = textStyles && textStyles.fontSize,
+ textCache,
i = childNodes.length,
tempParent = width && !wrapper.added && this.box,
getLineHeight = function(tspan) {
var fontSizeStyle;
fontSizeStyle = /(px|em)$/.test(tspan && tspan.style.fontSize) ?
tspan.style.fontSize :
- ((textStyles && textStyles.fontSize) || renderer.style.fontSize || 12);
+ (fontSize || renderer.style.fontSize || 12);
return textLineHeight ?
pInt(textLineHeight) :
renderer.fontMetrics(
@@ -4189,10 +4221,26 @@
},
unescapeAngleBrackets = function(inputStr) {
return inputStr.replace(/</g, '<').replace(/>/g, '>');
};
+ // The buildText code is quite heavy, so if we're not changing something
+ // that affects the text, skip it (#6113).
+ textCache = [
+ textStr,
+ ellipsis,
+ noWrap,
+ textLineHeight,
+ textOutline,
+ fontSize,
+ width
+ ].join(',');
+ if (textCache === wrapper.textCache) {
+ return;
+ }
+ wrapper.textCache = textCache;
+
/// remove old text
while (i--) {
textNode.removeChild(childNodes[i]);
}
@@ -4308,11 +4356,10 @@
}*/
// Check width and apply soft breaks or ellipsis
if (width) {
var words = span.replace(/([^\^])-/g, '$1- ').split(' '), // #1273
- noWrap = textStyles.whiteSpace === 'nowrap',
hasWhiteSpace = spans.length > 1 || lineNo || (words.length > 1 && !noWrap),
tooLong,
actualWidth,
rest = [],
dy = getLineHeight(tspan),
@@ -4440,10 +4487,19 @@
* @param {ColorString} rgba - The color to get the contrast for.
* @returns {string} The contrast color, either `#000000` or `#FFFFFF`.
*/
getContrast: function(rgba) {
rgba = color(rgba).rgba;
+
+ // The threshold may be discussed. Here's a proposal for adding
+ // different weight to the color channels (#6216)
+ /*
+ rgba[0] *= 1; // red
+ rgba[1] *= 1.2; // green
+ rgba[2] *= 0.7; // blue
+ */
+
return rgba[0] + rgba[1] + rgba[2] > 2 * 255 ? '#000000' : '#FFFFFF';
},
/**
* Create a button with preset states.
@@ -4857,11 +4913,11 @@
// get the symbol definition function
symbolFn = this.symbols[symbol],
// check if there's a path defined for this symbol
- path = defined(x) && symbolFn && symbolFn(
+ path = defined(x) && symbolFn && this.symbols[symbol](
Math.round(x),
Math.round(y),
width,
height,
options
@@ -5021,17 +5077,16 @@
/**
* 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'
- ];
+ // Return a full arc
+ return this.arc(x + w / 2, y + h / 2, w / 2, h / 2, {
+ start: 0,
+ end: Math.PI * 2,
+ open: false
+ });
},
'square': function(x, y, w, h) {
return [
'M', x, y,
@@ -5068,46 +5123,54 @@
'Z'
];
},
'arc': function(x, y, w, h, options) {
var start = options.start,
- radius = options.r || w || h,
+ rx = options.r || w,
+ ry = options.r || h || w,
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 = Math.cos(start),
sinStart = Math.sin(start),
cosEnd = Math.cos(end),
sinEnd = Math.sin(end),
- longArc = options.end - start < Math.PI ? 0 : 1;
+ longArc = options.end - start < Math.PI ? 0 : 1,
+ arc;
- return [
+ arc = [
'M',
- x + radius * cosStart,
- y + radius * sinStart,
+ x + rx * cosStart,
+ y + ry * sinStart,
'A', // arcTo
- radius, // x radius
- radius, // y radius
+ rx, // x radius
+ ry, // 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
+ x + rx * cosEnd,
+ y + ry * sinEnd
];
+
+ if (defined(innerRadius)) {
+ arc.push(
+ 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
+ );
+ }
+
+ arc.push(open ? '' : 'Z'); // close
+ return arc;
},
/**
* Callout shape used for default tooltips, also used for rounded rectangles in VML
*/
@@ -7239,15 +7302,22 @@
colors: '#7cb5ec #434348 #90ed7d #f7a35c #8085e9 #f15c80 #e4d354 #2b908f #f45b5b #91e8e1'.split(' '),
symbols: ['circle', 'diamond', 'square', 'triangle', 'triangle-down'],
lang: {
loading: 'Loading...',
- months: ['January', 'February', 'March', 'April', 'May', 'June', 'July',
+ 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'],
+ shortMonths: [
+ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul',
+ 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
+ ],
+ weekdays: [
+ 'Sunday', 'Monday', 'Tuesday', 'Wednesday',
+ 'Thursday', 'Friday', 'Saturday'
+ ],
// invalidDate: '',
decimalPoint: '.',
numericSymbols: ['k', 'M', 'G', 'T', 'P', 'E'], // SI prefixes used in axis labels
resetZoom: 'Reset zoom',
resetZoomTitle: 'Reset zoom level 1:1',
@@ -7255,11 +7325,11 @@
},
global: {
useUTC: true,
//timezoneOffset: 0,
- VMLRadialGradientURL: 'http://code.highcharts.com/5.0.6/gfx/vml-radial-gradient.png'
+ VMLRadialGradientURL: 'http://code.highcharts.com/5.0.7/gfx/vml-radial-gradient.png'
},
chart: {
//animation: true,
//alignTicks: false,
@@ -7492,10 +7562,41 @@
};
/**
+ * Sets the getTimezoneOffset function. If the timezone option is set, a default
+ * getTimezoneOffset function with that timezone is returned. If not, the
+ * specified getTimezoneOffset function is returned. If neither are specified,
+ * undefined is returned.
+ * @return {function} a getTimezoneOffset function or undefined
+ */
+ function getTimezoneOffsetOption() {
+ var globalOptions = H.defaultOptions.global,
+ moment = win.moment;
+
+ if (globalOptions.timezone) {
+ if (!moment) {
+ // getTimezoneOffset-function stays undefined because it depends on
+ // Moment.js
+ H.error(25);
+
+ } else {
+ return function(timestamp) {
+ return -moment.tz(
+ timestamp,
+ globalOptions.timezone
+ ).utcOffset();
+ };
+ }
+ }
+
+ // If not timezone is set, look for the getTimezoneOffset callback
+ return globalOptions.useUTC && globalOptions.getTimezoneOffset;
+ }
+
+ /**
* Set the time methods globally based on the useUTC option. Time method can be
* either local time or UTC (default). It is called internally on initiating
* Highcharts and after running `Highcharts.setOptions`.
*
* @private
@@ -7507,11 +7608,11 @@
GET = useUTC ? 'getUTC' : 'get',
SET = useUTC ? 'setUTC' : 'set';
H.Date = Date = globalOptions.Date || win.Date; // Allow using a different Date class
Date.hcTimezoneOffset = useUTC && globalOptions.timezoneOffset;
- Date.hcGetTimezoneOffset = useUTC && globalOptions.getTimezoneOffset;
+ Date.hcGetTimezoneOffset = getTimezoneOffsetOption();
Date.hcMakeTime = function(year, month, date, hours, minutes, seconds) {
var d;
if (useUTC) {
d = Date.UTC.apply(0, arguments);
d += getTZOffset(d);
@@ -7684,10 +7785,11 @@
path = axis.getPlotBandPath(from, to, options);
} else {
return;
}
+
// common for lines and bands
if (isNew && path && path.length) {
svgElem.attr({
d: path
});
@@ -9244,11 +9346,11 @@
series.generatePoints();
}
each(series.points, function(point, i) {
var x;
- if (point.options && point.options.x === undefined) {
+ if (point.options) {
x = axis.nameToX(point);
if (x !== point.x) {
point.x = x;
series.xData[i] = x;
}
@@ -9604,26 +9706,23 @@
}
}
}
+ // reset min/max or remove extremes based on start/end on tick
+ this.trimTicks(tickPositions, startOnTick, endOnTick);
if (!this.isLinked) {
-
- // reset min/max or remove extremes based on start/end on tick
- this.trimTicks(tickPositions, startOnTick, endOnTick);
-
// 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 0 or 1. In this case, add some padding
// in order to center the point, but leave it with one tick. #1337.
if (this.min === this.max && defined(this.min) && !this.tickAmount) {
// Substract half a unit (#2619, #2846, #2515, #3390)
single = true;
this.min -= 0.5;
this.max += 0.5;
}
this.single = single;
-
if (!tickPositionsOption && !tickPositioner) {
this.adjustTickAmount();
}
}
},
@@ -9634,29 +9733,31 @@
trimTicks: function(tickPositions, startOnTick, endOnTick) {
var roundedMin = tickPositions[0],
roundedMax = tickPositions[tickPositions.length - 1],
minPointOffset = this.minPointOffset || 0;
- if (startOnTick) {
- this.min = roundedMin;
- } else {
- while (this.min - minPointOffset > tickPositions[0]) {
- tickPositions.shift();
+ if (!this.isLinked) {
+ if (startOnTick) {
+ this.min = roundedMin;
+ } else {
+ while (this.min - minPointOffset > tickPositions[0]) {
+ tickPositions.shift();
+ }
}
- }
- if (endOnTick) {
- this.max = roundedMax;
- } else {
- while (this.max + minPointOffset < tickPositions[tickPositions.length - 1]) {
- tickPositions.pop();
+ if (endOnTick) {
+ this.max = roundedMax;
+ } else {
+ while (this.max + minPointOffset < tickPositions[tickPositions.length - 1]) {
+ tickPositions.pop();
+ }
}
- }
- // If no tick are left, set one tick in the middle (#3195)
- if (tickPositions.length === 0 && defined(roundedMin)) {
- tickPositions.push((roundedMax + roundedMin) / 2);
+ // If no tick are left, set one tick in the middle (#3195)
+ if (tickPositions.length === 0 && defined(roundedMin)) {
+ tickPositions.push((roundedMax + roundedMin) / 2);
+ }
}
},
/**
* Check if there are multiple axes in the same pane
@@ -9919,17 +10020,16 @@
* Update the axis metrics
*/
setAxisSize: function() {
var chart = this.chart,
options = this.options,
- offsetLeft = options.offsetLeft || 0,
- offsetRight = options.offsetRight || 0,
+ offsets = options.offsets || [0, 0, 0, 0], // top / right / bottom / left
horiz = this.horiz,
- width = pick(options.width, chart.plotWidth - offsetLeft + offsetRight),
- height = pick(options.height, chart.plotHeight),
- top = pick(options.top, chart.plotTop),
- left = pick(options.left, chart.plotLeft + offsetLeft),
+ width = pick(options.width, chart.plotWidth - offsets[3] + offsets[1]),
+ height = pick(options.height, chart.plotHeight - offsets[0] + offsets[2]),
+ top = pick(options.top, chart.plotTop + offsets[0]),
+ left = pick(options.left, chart.plotLeft + offsets[3]),
percentRegex = /%$/;
// Check for percentage based input values. Rounding fixes problems with
// column overflow and plot line filtering (#4898, #4899)
if (percentRegex.test(height)) {
@@ -10111,13 +10211,19 @@
horiz = this.horiz,
labelOptions = this.options.labels,
slotCount = Math.max(this.tickPositions.length - (this.categories ? 0 : 1), 1),
marginLeft = chart.margin[3];
- return (horiz && (labelOptions.step || 0) < 2 && !labelOptions.rotation && // #4415
- ((this.staggerLines || 1) * chart.plotWidth) / slotCount) ||
- (!horiz && ((marginLeft && (marginLeft - chart.spacing[3])) || chart.chartWidth * 0.33)); // #1580, #1931
+ return (
+ horiz &&
+ (labelOptions.step || 0) < 2 &&
+ !labelOptions.rotation && // #4415
+ ((this.staggerLines || 1) * this.len) / slotCount
+ ) || (!horiz && (
+ (marginLeft && (marginLeft - chart.spacing[3])) ||
+ chart.chartWidth * 0.33
+ )); // #1580, #1931
},
/**
* Render the axis labels and determine whether ellipsis or rotation need to be applied
@@ -10295,10 +10401,25 @@
// hide or show the title depending on whether showEmpty is set
axis.axisTitle[display ? 'show' : 'hide'](true);
},
/**
+ * Generates a tick for initial positioning.
+ * @param {number} pos - The tick position in axis values.
+ * @param {number} i - The index of the tick in axis.tickPositions.
+ */
+ generateTick: function(pos) {
+ var ticks = this.ticks;
+
+ if (!ticks[pos]) {
+ ticks[pos] = new Tick(this, pos);
+ } else {
+ ticks[pos].addLabel(); // update labels depending on tick interval
+ }
+ },
+
+ /**
* Render the tick labels to a preliminary position to get their sizes
*/
getOffset: function() {
var axis = this,
chart = axis.chart,
@@ -10358,16 +10479,13 @@
}
if (hasData || axis.isLinked) {
// Generate ticks
- each(tickPositions, function(pos) {
- if (!ticks[pos]) {
- ticks[pos] = new Tick(axis, pos);
- } else {
- ticks[pos].addLabel(); // update labels depending on tick interval
- }
+ each(tickPositions, function(pos, i) {
+ // i is not used here, but may be used in overrides
+ axis.generateTick(pos, i);
});
axis.renderUnsquish();
@@ -10538,10 +10656,58 @@
offAxis + yOption - (opposite ? this.height : 0) + offset : alongAxis + yOption
};
},
/**
+ * Render a minor tick into the given position. If a minor tick already
+ * exists in this position, move it.
+ * @param {number} pos - The position in axis values.
+ */
+ renderMinorTick: function(pos) {
+ var slideInTicks = this.chart.hasRendered && isNumber(this.oldMin),
+ minorTicks = this.minorTicks;
+
+ if (!minorTicks[pos]) {
+ minorTicks[pos] = new Tick(this, pos, 'minor');
+ }
+
+ // Render new ticks in old position
+ if (slideInTicks && minorTicks[pos].isNew) {
+ minorTicks[pos].render(null, true);
+ }
+
+ minorTicks[pos].render(null, false, 1);
+ },
+
+ /**
+ * Render a major tick into the given position. If a tick already exists
+ * in this position, move it.
+ * @param {number} pos - The position in axis values
+ * @param {number} i - The tick index
+ */
+ renderTick: function(pos, i) {
+ var isLinked = this.isLinked,
+ ticks = this.ticks,
+ slideInTicks = this.chart.hasRendered && isNumber(this.oldMin);
+
+ // Linked axes need an extra check to find out if
+ if (!isLinked || (pos >= this.min && pos <= this.max)) {
+
+ if (!ticks[pos]) {
+ ticks[pos] = new Tick(this, pos);
+ }
+
+ // render new ticks in old position
+ if (slideInTicks && ticks[pos].isNew) {
+ ticks[pos].render(i, true, 0.1);
+ }
+
+ ticks[pos].render(i);
+ }
+ },
+
+ /**
* Render the axis
*/
render: function() {
var axis = this,
chart = axis.chart,
@@ -10557,12 +10723,10 @@
alternateBands = axis.alternateBands,
stackLabelOptions = options.stackLabels,
alternateGridColor = options.alternateGridColor,
tickmarkOffset = axis.tickmarkOffset,
axisLine = axis.axisLine,
- hasRendered = chart.hasRendered,
- slideInTicks = hasRendered && isNumber(axis.oldMin),
showAxis = axis.showAxis,
animation = animObject(renderer.globalAnimation),
from,
to;
@@ -10583,43 +10747,19 @@
if (axis.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);
+ axis.renderMinorTick(pos);
});
}
// 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, function(pos, i) {
-
- // 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, 0.1);
- }
-
- ticks[pos].render(i);
- }
-
+ axis.renderTick(pos, i);
});
// 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 || axis.single)) {
if (!ticks[-1]) {
@@ -11056,14 +11196,21 @@
// push the last time
tickPositions.push(time);
// Handle higher ranks. Mark new days if the time is on midnight
- // (#950, #1649, #1760, #3349)
- if (interval <= timeUnits.hour) {
+ // (#950, #1649, #1760, #3349). Use a reasonable dropout threshold to
+ // prevent looping over dense data grouping (#6156).
+ if (interval <= timeUnits.hour && tickPositions.length < 10000) {
each(tickPositions, function(time) {
- if (dateFormat('%H%M%S%L', time) === '000000000') {
+ if (
+ // Speed optimization, no need to run dateFormat unless
+ // we're on a full or half hour
+ time % 1800000 === 0 &&
+ // Check for local or global midnight
+ dateFormat('%H%M%S%L', time) === '000000000'
+ ) {
higherRanks[time] = 'day';
}
});
}
}
@@ -11728,17 +11875,17 @@
textConfig = {
x: point[0].category,
y: point[0].y
};
textConfig.points = pointConfig;
- this.len = pointConfig.length;
point = point[0];
// single point tooltip
} else {
textConfig = point.getLabelConfig();
}
+ this.len = pointConfig.length; // #6128
text = formatter.call(textConfig, tooltip);
// register the current series
currentSeries = point.series;
this.distance = pick(currentSeries.tooltipOptions.distance, 16);
@@ -11801,12 +11948,12 @@
rightAligned = true,
options = this.options,
headerHeight,
tooltipLabel = this.getLabel();
- // Create the individual labels
- each(labels.slice(0, labels.length - 1), function(str, i) {
+ // Create the individual labels for header and points, ignore footer
+ each(labels.slice(0, points.length + 1), function(str, i) {
var point = points[i - 1] ||
// Item 0 is the header. Instead of this, we could also use the crosshair label
{
isHeader: true,
plotX: points[0].plotX
@@ -11925,60 +12072,80 @@
point.plotY + chart.plotTop
);
},
/**
- * Get the best X date format based on the closest point range on the axis.
+ * Get the optimal date format for a point, based on a range.
+ * @param {number} range - The time range
+ * @param {number|Date} date - The date of the point in question
+ * @param {number} startOfWeek - An integer representing the first day of
+ * the week, where 0 is Sunday
+ * @param {Object} dateTimeLabelFormats - A map of time units to formats
+ * @return {string} - the optimal date format for a point
*/
- getXDateFormat: function(point, options, xAxis) {
- var xDateFormat,
- dateTimeLabelFormats = options.dateTimeLabelFormats,
- closestPointRange = xAxis && xAxis.closestPointRange,
+ getDateFormat: function(range, date, startOfWeek, dateTimeLabelFormats) {
+ var dateStr = dateFormat('%m-%d %H:%M:%S.%L', date),
+ format,
n,
blank = '01-01 00:00:00.000',
strpos = {
millisecond: 15,
second: 12,
minute: 9,
hour: 6,
day: 3
},
- date,
lastN = 'millisecond'; // for sub-millisecond data, #4223
+ for (n in timeUnits) {
- if (closestPointRange) {
- date = dateFormat('%m-%d %H:%M:%S.%L', point.x);
- for (n in timeUnits) {
+ // If the range is exactly one week and we're looking at a Sunday/Monday, go for the week format
+ if (range === timeUnits.week && +dateFormat('%w', date) === startOfWeek &&
+ dateStr.substr(6) === blank.substr(6)) {
+ n = 'week';
+ break;
+ }
- // If the range is exactly one week and we're looking at a Sunday/Monday, go for the week format
- if (closestPointRange === timeUnits.week && +dateFormat('%w', point.x) === xAxis.options.startOfWeek &&
- date.substr(6) === blank.substr(6)) {
- n = 'week';
- break;
- }
+ // The first format that is too great for the range
+ if (timeUnits[n] > range) {
+ n = lastN;
+ break;
+ }
- // The first format that is too great for the range
- if (timeUnits[n] > closestPointRange) {
- n = lastN;
- break;
- }
-
- // If the point is placed every day at 23:59, we need to show
- // the minutes as well. #2637.
- if (strpos[n] && date.substr(strpos[n]) !== blank.substr(strpos[n])) {
- break;
- }
-
- // Weeks are outside the hierarchy, only apply them on Mondays/Sundays like in the first condition
- if (n !== 'week') {
- lastN = n;
- }
+ // If the point is placed every day at 23:59, we need to show
+ // the minutes as well. #2637.
+ if (strpos[n] && dateStr.substr(strpos[n]) !== blank.substr(strpos[n])) {
+ break;
}
- if (n) {
- xDateFormat = dateTimeLabelFormats[n];
+ // Weeks are outside the hierarchy, only apply them on Mondays/Sundays like in the first condition
+ if (n !== 'week') {
+ lastN = n;
}
+ }
+
+ if (n) {
+ format = dateTimeLabelFormats[n];
+ }
+
+ return format;
+ },
+
+ /**
+ * Get the best X date format based on the closest point range on the axis.
+ */
+ getXDateFormat: function(point, options, xAxis) {
+ var xDateFormat,
+ dateTimeLabelFormats = options.dateTimeLabelFormats,
+ closestPointRange = xAxis && xAxis.closestPointRange;
+
+ if (closestPointRange) {
+ xDateFormat = this.getDateFormat(
+ closestPointRange,
+ point.x,
+ xAxis.options.startOfWeek,
+ dateTimeLabelFormats
+ );
} else {
xDateFormat = dateTimeLabelFormats.day;
}
return xDateFormat || dateTimeLabelFormats.year; // #2546, 2581
@@ -12234,11 +12401,12 @@
// Sort kdpoints by distance to mouse pointer
kdpoints.sort(function(p1, p2) {
var isCloserX = p1.distX - p2.distX,
isCloser = p1.dist - p2.dist,
- isAbove = p2.series.group.zIndex - p1.series.group.zIndex;
+ isAbove = (p2.series.group && p2.series.group.zIndex) -
+ (p1.series.group && p1.series.group.zIndex);
// We have two points which are not in the same place on xAxis and shared tooltip:
if (isCloserX !== 0 && shared) { // #5721
return isCloserX;
}
@@ -13560,10 +13728,11 @@
legend.baseline = legend.fontMetrics.f + 3 + itemMarginTop;
li.attr('y', legend.baseline);
}
// Draw the legend symbol inside the group box
+ legend.symbolHeight = options.symbolHeight || legend.fontMetrics.f;
series.drawLegendSymbol(legend, item);
if (legend.setItemEvents) {
legend.setItemEvents(item, li, useHTML);
}
@@ -14037,11 +14206,11 @@
* @param {Object} legend The legend object
* @param {Object} item The series (this) or point
*/
drawRectangle: function(legend, item) {
var options = legend.options,
- symbolHeight = options.symbolHeight || legend.fontMetrics.f,
+ symbolHeight = legend.symbolHeight,
square = options.squareSymbol,
symbolWidth = square ? symbolHeight : legend.symbolWidth;
item.legendSymbol = this.chart.renderer.rect(
square ? (legend.symbolWidth - symbolHeight) / 2 : 0,
@@ -14068,10 +14237,12 @@
var options = this.options,
markerOptions = options.marker,
radius,
legendSymbol,
symbolWidth = legend.symbolWidth,
+ symbolHeight = legend.symbolHeight,
+ generalRadius = symbolHeight / 2,
renderer = this.chart.renderer,
legendItemGroup = this.legendGroup,
verticalCenter = legend.baseline - Math.round(legend.fontMetrics.b * 0.3),
attr = {};
@@ -14097,11 +14268,26 @@
.attr(attr)
.add(legendItemGroup);
// Draw the marker
if (markerOptions && markerOptions.enabled !== false) {
- radius = this.symbol.indexOf('url') === 0 ? 0 : markerOptions.radius;
+
+ // Do not allow the marker to be larger than the symbolHeight
+ radius = Math.min(
+ pick(markerOptions.radius, generalRadius),
+ generalRadius
+ );
+
+ // Restrict symbol markers size
+ if (this.symbol.indexOf('url') === 0) {
+ markerOptions = merge(markerOptions, {
+ width: symbolHeight,
+ height: symbolHeight
+ });
+ radius = 0;
+ }
+
this.legendSymbol = legendSymbol = renderer.symbol(
this.symbol,
(symbolWidth / 2) - radius,
verticalCenter - radius,
2 * radius,
@@ -14324,10 +14510,30 @@
series.init(this, options);
return series;
},
/**
+ * Order all series above a given index. When series are added and ordered
+ * by configuration, only the last series is handled (#248, #1123, #2456,
+ * #6112). This function is called on series initialization and destroy.
+ *
+ * @param {number} fromIndex - If this is given, only the series above this
+ * index are handled.
+ */
+ orderSeries: function(fromIndex) {
+ var series = this.series,
+ i = fromIndex || 0;
+ for (; i < series.length; i++) {
+ if (series[i]) {
+ series[i].index = i;
+ series[i].name = series[i].name ||
+ 'Series ' + (series[i].index + 1);
+ }
+ }
+ },
+
+ /**
* 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
@@ -14364,10 +14570,15 @@
serie,
renderer = chart.renderer,
isHiddenChart = renderer.isHidden(),
afterRedraw = [];
+ // Handle responsive rules, not only on resize (#6130)
+ if (chart.setResponsive) {
+ chart.setResponsive(false);
+ }
+
H.setAnimation(animation, chart);
if (isHiddenChart) {
chart.cloneRenderTo();
}
@@ -14466,28 +14677,35 @@
// the plot areas size has changed
if (isDirtyBox) {
chart.drawChartBox();
}
+ // Fire an event before redrawing series, used by the boost module to
+ // clear previous series renderings.
+ fireEvent(chart, 'predraw');
// redraw affected series
each(series, function(serie) {
if ((isDirtyBox || serie.isDirty) && serie.visible) {
serie.redraw();
}
+ // Set it here, otherwise we will have unlimited 'updatedData' calls
+ // for a hidden series after setData(). Fixes #6012
+ serie.isDirtyData = false;
});
// move tooltip or reset
if (pointer) {
pointer.reset(true);
}
// redraw if canvas
renderer.draw();
- // fire the event
+ // Fire the events
fireEvent(chart, 'redraw');
+ fireEvent(chart, 'render');
if (isHiddenChart) {
chart.cloneRenderTo(true);
}
@@ -14506,11 +14724,11 @@
var ret,
series = this.series,
i;
function itemById(item) {
- return item.id === id || item.options.id === id;
+ return item.id === id || (item.options && item.options.id === id);
}
ret =
// Search axes
find(this.axes, itemById) ||
@@ -14722,14 +14940,18 @@
}
if (!defined(heightOption)) {
chart.containerHeight = getStyle(renderTo, 'height');
}
- chart.chartWidth = Math.max(0, widthOption || chart.containerWidth || 600); // #1393, 1460
- chart.chartHeight = Math.max(0, pick(heightOption,
- // the offsetHeight of an empty container is 0 in standard browsers, but 19 in IE7:
- chart.containerHeight > 19 ? chart.containerHeight : 400));
+ chart.chartWidth = Math.max( // #1393
+ 0,
+ widthOption || chart.containerWidth || 600 // #1460
+ );
+ chart.chartHeight = Math.max(
+ 0,
+ heightOption || 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
@@ -14905,12 +15127,12 @@
if (chart.legend.display) {
chart.legend.adjustMargins(margin, spacing);
}
// adjust for scroller
- if (chart.extraBottomMargin) {
- chart.marginBottom += chart.extraBottomMargin;
+ if (chart.extraMargin) {
+ chart[chart.extraMargin.type] = (chart[chart.extraMargin.type] || 0) + chart.extraMargin.value;
}
if (chart.extraTopMargin) {
chart.plotTop += chart.extraTopMargin;
}
if (!skipAxes) {
@@ -15046,13 +15268,10 @@
chart.isDirtyBox = true; // force redraw of plot and chart border
chart.layOutTitles(); // #2857
chart.getMargins();
- if (chart.setResponsive) {
- chart.setResponsive(false);
- }
chart.redraw(animation);
chart.oldChartHeight = null;
fireEvent(chart, 'resize');
@@ -15568,13 +15787,15 @@
while (i--) {
series[i] = series[i].destroy();
}
// ==== Destroy chart properties:
- each(['title', 'subtitle', 'chartBackground', 'plotBackground', 'plotBGImage',
- 'plotBorder', 'seriesGroup', 'clipRect', 'credits', 'pointer',
- 'rangeSelector', 'legend', 'resetZoomButton', 'tooltip', 'renderer'
+ each([
+ 'title', 'subtitle', 'chartBackground', 'plotBackground',
+ 'plotBGImage', 'plotBorder', 'seriesGroup', 'clipRect', 'credits',
+ 'pointer', 'rangeSelector', 'legend', 'resetZoomButton', 'tooltip',
+ 'renderer'
], function(name) {
var prop = chart[name];
if (prop && prop.destroy) {
chart[name] = prop.destroy();
@@ -15664,13 +15885,10 @@
chart.pointer = new Pointer(chart, options);
}
chart.render();
- // add canvas
- chart.renderer.draw();
-
// Fire the load event if there are no external images
if (!chart.renderer.imgCount && chart.onload) {
chart.onload();
}
@@ -15690,11 +15908,13 @@
fn.apply(this, [this]);
}
}, this);
fireEvent(this, 'load');
+ fireEvent(this, 'render');
+
// Set up auto resize, check for not destroyed (#6068)
if (defined(this.index) && this.options.chart.reflow !== false) {
this.initReflow();
}
@@ -15890,13 +16110,15 @@
getClassName: function() {
return 'highcharts-point' +
(this.selected ? ' highcharts-point-select' : '') +
(this.negative ? ' highcharts-negative' : '') +
(this.isNull ? ' highcharts-null-point' : '') +
- (this.colorIndex !== undefined ? ' highcharts-color-' + this.colorIndex : '') +
+ (this.colorIndex !== undefined ? ' highcharts-color-' +
+ this.colorIndex : '') +
(this.options.className ? ' ' + this.options.className : '') +
- (this.zone && this.zone.className ? ' ' + this.zone.className : '');
+ (this.zone && this.zone.className ? ' ' +
+ this.zone.className.replace('highcharts-negative', '') : '');
},
/**
* Return the zone that the point belongs to
*/
@@ -15982,10 +16204,11 @@
getLabelConfig: function() {
return {
x: this.category,
y: this.y,
color: this.color,
+ colorIndex: this.colorIndex,
key: this.name || this.category,
series: this.series,
point: this,
percentage: this.percentage,
total: this.total || this.stackTotal
@@ -16233,12 +16456,11 @@
init: function(chart, options) {
var series = this,
eventType,
events,
chartSeries = chart.series,
- lastSeries,
- i;
+ lastSeries;
series.chart = chart;
series.options = options = series.setOptions(options); // merge with plotOptions
series.linkedSeries = [];
@@ -16285,18 +16507,12 @@
if (chartSeries.length) {
lastSeries = chartSeries[chartSeries.length - 1];
}
series._i = pick(lastSeries && lastSeries._i, -1) + 1;
- // Insert the series and update the `index` property of all series
- // above this. Unless the `index` option is set, the new series is
- // inserted last. #248, #1123, #2456
- for (i = this.insert(chartSeries); i < chartSeries.length; i++) {
- chartSeries[i].index = i;
- chartSeries[i].name = chartSeries[i].name ||
- 'Series ' + (chartSeries[i].index + 1);
- }
+ // Insert the series and re-order all series above the insertion point.
+ chart.orderSeries(this.insert(chartSeries));
},
/**
* Insert the series in a collection with other series, either the chart
* series or yAxis series, in the correct order according to the index
@@ -16500,24 +16716,32 @@
return options;
},
getCyclic: function(prop, value, defaults) {
var i,
+ chart = this.chart,
userOptions = this.userOptions,
indexName = prop + 'Index',
counterName = prop + 'Counter',
- len = defaults ? defaults.length : pick(this.chart.options.chart[prop + 'Count'], this.chart[prop + 'Count']),
+ len = defaults ? defaults.length : pick(
+ chart.options.chart[prop + 'Count'],
+ chart[prop + 'Count']
+ ),
setting;
if (!value) {
// Pick up either the colorIndex option, or the _colorIndex after Series.update()
setting = pick(userOptions[indexName], userOptions['_' + indexName]);
if (defined(setting)) { // after Series.update()
i = setting;
} else {
- userOptions['_' + indexName] = i = this.chart[counterName] % len;
- this.chart[counterName] += 1;
+ // #6138
+ if (!chart.series.length) {
+ chart[counterName] = 0;
+ }
+ userOptions['_' + indexName] = i = chart[counterName] % len;
+ chart[counterName] += 1;
}
if (defaults) {
value = defaults[i];
}
}
@@ -17126,10 +17350,11 @@
if (clipRect.count.length === 0 && sharedClipKey && chart[sharedClipKey]) {
if (!seriesClipBox) {
chart[sharedClipKey] = chart[sharedClipKey].destroy();
}
if (chart[sharedClipKey + 'm']) {
+ this.markerGroup.clip();
chart[sharedClipKey + 'm'] = chart[sharedClipKey + 'm'].destroy();
}
}
}
},
@@ -17209,12 +17434,11 @@
series.closestPointRangePx > 2 * seriesMarkerOptions.radius
);
if (seriesMarkerOptions.enabled !== false || series._hasPointMarkers) {
- i = points.length;
- while (i--) {
+ for (i = 0; i < points.length; i++) {
point = points[i];
plotY = point.plotY;
graphic = point.graphic;
pointMarkerOptions = point.marker || {};
hasPointMarker = !!point.marker;
@@ -17271,12 +17495,11 @@
* Get non-presentational attributes for the point.
*/
markerAttribs: function(point, state) {
var seriesMarkerOptions = this.options.marker,
seriesStateOptions,
- pointOptions = point && point.options,
- pointMarkerOptions = (pointOptions && pointOptions.marker) || {},
+ pointMarkerOptions = point.marker || {},
pointStateOptions,
radius = pick(
pointMarkerOptions.radius,
seriesMarkerOptions.radius
),
@@ -17424,10 +17647,11 @@
// remove from hoverSeries
if (chart.hoverSeries === series) {
chart.hoverSeries = null;
}
erase(chart.series, series);
+ chart.orderSeries();
// clear all members
for (prop in series) {
delete series[prop];
}
@@ -17754,18 +17978,15 @@
var series = this,
chart = series.chart,
remover;
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(inverted);
+ series[groupName].width = series.yAxis.len;
+ series[groupName].height = series.xAxis.len;
+ series[groupName].invert(inverted);
}
});
}
// Pie, go away (#1736)
@@ -17921,11 +18142,11 @@
series.animationTimeout = syncTimeout(function() {
series.afterAnimate();
}, animDuration);
}
- series.isDirty = series.isDirtyData = false; // means data is in accordance with what you see
+ series.isDirty = 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;
},
/**
@@ -17978,11 +18199,21 @@
clientX: inverted ? xAxis.len - e.chartY + xAxis.pos : e.chartX - xAxis.pos,
plotY: inverted ? yAxis.len - e.chartX + yAxis.pos : e.chartY - yAxis.pos
}, compareX);
},
+ /**
+ * Build the k-d-tree that is used by mouse and touch interaction to get the
+ * closest point. Line-like series typically have a one-dimensional tree
+ * where points are searched along the X axis, while scatter-like series
+ * typically search in two dimensions, X and Y.
+ */
buildKDTree: function() {
+
+ // Prevent multiple k-d-trees from being built simultaneously (#6235)
+ this.buildingKdTree = true;
+
var series = this,
dimensions = series.kdDimensions;
// Internal function
function _kdtree(points, depth, dimensions) {
@@ -18019,10 +18250,11 @@
null, !series.directTouch // For line-type series restrict to plot area, but column-type series not (#3916, #4511)
),
dimensions,
dimensions
);
+ series.buildingKdTree = false;
}
delete series.kdTree;
// For testing tooltips, don't build async
syncTimeout(startRecursive, series.options.kdNow ? 0 : 1);
@@ -18076,11 +18308,11 @@
}
return ret;
}
- if (!this.kdTree) {
+ if (!this.kdTree && !this.buildingKdTree) {
this.buildKDTree();
}
if (this.kdTree) {
return _search(point,
@@ -18855,17 +19087,24 @@
if (options.plotOptions) {
merge(true, this.options.plotOptions, options.plotOptions);
}
- // Setters for collections. For axes and series, each item is referred by an id. If the
- // id is not found, it defaults to the first item in the collection, so setting series
- // without an id, will update the first series in the chart.
+ // Setters for collections. For axes and series, each item is referred
+ // by an id. If the id is not found, it defaults to the corresponding
+ // item in the collection, so setting one series without an id, will
+ // update the first series in the chart. Setting two series without
+ // an id will update the first and the second respectively (#6019)
+ // // docs: New behaviour for unidentified items, add it to docs for
+ // chart.update and responsive.
each(['xAxis', 'yAxis', 'series'], function(coll) {
if (options[coll]) {
- each(splat(options[coll]), function(newOptions) {
- var item = (defined(newOptions.id) && this.get(newOptions.id)) || this[coll][0];
+ each(splat(options[coll]), function(newOptions, i) {
+ var item = (
+ defined(newOptions.id) &&
+ this.get(newOptions.id)
+ ) || this[coll][i];
if (item && item.coll === coll) {
item.update(newOptions, false);
}
}, this);
}
@@ -19008,11 +19247,12 @@
addPoint: function(options, redraw, shift, animation) {
var series = this,
seriesOptions = series.options,
data = series.data,
chart = series.chart,
- names = series.xAxis && series.xAxis.names,
+ xAxis = series.xAxis,
+ names = xAxis && xAxis.hasNames && xAxis.names,
dataOptions = seriesOptions.data,
point,
isInTheMiddle,
xData = series.xData,
i,
@@ -19922,11 +20162,11 @@
Math.abs(xAxis.transA) * (xAxis.ordinalSlope || options.pointRange || xAxis.closestPointRange || xAxis.tickInterval || 1), // #2610
xAxis.len // #1535
),
groupPadding = categoryWidth * options.groupPadding,
groupWidth = categoryWidth - 2 * groupPadding,
- pointOffsetWidth = groupWidth / columnCount,
+ pointOffsetWidth = groupWidth / (columnCount || 1),
pointWidth = Math.min(
options.maxPointWidth || xAxis.len,
pick(options.pointWidth, pointOffsetWidth * (1 - 2 * options.pointPadding))
),
pointPadding = (pointOffsetWidth - pointWidth) / 2,
@@ -20274,11 +20514,14 @@
lineWidth: 0,
marker: {
enabled: true // Overrides auto-enabling in line series (#3647)
},
tooltip: {
- headerFormat: '<span style="color:{point.color}">\u25CF</span> <span style="font-size: 0.85em"> {series.name}</span><br/>',
+
+ headerFormat: '<span style="color:{point.color}">\u25CF</span> ' +
+ '<span style="font-size: 0.85em"> {series.name}</span><br/>',
+
pointFormat: 'x: <b>{point.x}</b><br/>y: <b>{point.y}</b><br/>'
}
// Prototype members
}, {
@@ -20967,11 +21210,10 @@
/**
* Draw the data labels
*/
Series.prototype.drawDataLabels = function() {
-
var series = this,
seriesOptions = series.options,
options = seriesOptions.dataLabels,
points = series.points,
pointOptions,
@@ -21016,142 +21258,108 @@
}
// 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,
- style,
- moreStyle = {};
-
+ isNew = !dataLabel,
+ style;
// Determine if each data label is enabled
+ // @note dataLabelAttribs (like pointAttribs) would eradicate
+ // the need for dlOptions, and simplify the section below.
pointOptions = point.dlOptions || (point.options && point.options.dataLabels); // dlOptions is used in treemaps
enabled = pick(pointOptions && pointOptions.enabled, generalOptions.enabled) && point.y !== null; // #2282, #4641
-
-
- // 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) {
-
+ if (enabled) {
// Create individual options structure that can be extended without
// affecting others
options = merge(generalOptions, pointOptions);
- style = options.style;
-
- rotation = options.rotation;
-
- // Get the string
labelConfig = point.getLabelConfig();
str = options.format ?
format(options.format, labelConfig) :
options.formatter.call(labelConfig, options);
+ style = options.style;
+ rotation = options.rotation;
-
// Determine the color
style.color = pick(options.color, style.color, series.color, '#000000');
+ // Get automated contrast color
+ if (style.color === 'contrast') {
+ style.color = options.inside || options.distance < 0 || !!seriesOptions.stacking ?
+ renderer.getContrast(point.color || series.color) :
+ '#000000';
+ }
+ if (seriesOptions.cursor) {
+ style.cursor = seriesOptions.cursor;
+ }
- // update existing label
- if (dataLabel) {
+ attr = {
+ //align: align,
- if (defined(str)) {
- dataLabel
- .attr({
- text: str
- });
- isNew = false;
+ fill: options.backgroundColor,
+ stroke: options.borderColor,
+ 'stroke-width': options.borderWidth,
- } else { // #1437 - the label is shown conditionally
- point.dataLabel = dataLabel = dataLabel.destroy();
- if (connector) {
- point.connector = connector.destroy();
- }
- }
+ r: options.borderRadius || 0,
+ rotation: rotation,
+ padding: options.padding,
+ zIndex: 1
+ };
- // 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
- };
-
-
- // Get automated contrast color
- if (style.color === 'contrast') {
- moreStyle.color = options.inside || options.distance < 0 || !!seriesOptions.stacking ?
- renderer.getContrast(point.color || series.color) :
- '#000000';
+ // Remove unused attributes (#947)
+ for (name in attr) {
+ if (attr[name] === undefined) {
+ delete attr[name];
}
-
- if (seriesOptions.cursor) {
- moreStyle.cursor = seriesOptions.cursor;
- }
-
-
-
- // Remove unused attributes (#947)
- for (name in attr) {
- if (attr[name] === undefined) {
- delete attr[name];
- }
- }
-
+ }
+ }
+ // If the point is outside the plot area, destroy it. #678, #820
+ if (dataLabel && (!enabled || !defined(str))) {
+ point.dataLabel = dataLabel = dataLabel.destroy();
+ if (connector) {
+ point.connector = connector.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 && defined(str)) {
+ // create new label
+ if (!dataLabel) {
dataLabel = point.dataLabel = renderer[rotation ? 'text' : 'label']( // labels don't support rotation
- str,
- 0, -9999,
- options.shape,
- null,
- null,
- options.useHTML,
- null,
- 'data-label'
- )
- .attr(attr);
-
+ str,
+ 0, -9999,
+ options.shape,
+ null,
+ null,
+ options.useHTML,
+ null,
+ 'data-label'
+ );
dataLabel.addClass(
'highcharts-data-label-color-' + point.colorIndex +
' ' + (options.className || '') +
(options.useHTML ? 'highcharts-tracker' : '') // #3398
);
+ } else {
+ attr.text = str;
+ }
+ dataLabel.attr(attr);
+ // Styles must be applied before add in order to read text bounding box
+ dataLabel.css(style).shadow(options.shadow);
- // Styles must be applied before add in order to read text bounding box
- dataLabel.css(extend(style, moreStyle));
-
+ if (!dataLabel.added) {
dataLabel.add(dataLabelsGroup);
-
-
- dataLabel.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);
- }
+ // Now the data label is created and placed at 0,0, so we need to align it
+ series.alignDataLabel(point, dataLabel, options, null, isNew);
}
});
}
};
@@ -22916,30 +23124,40 @@
/**
* Recurse over a set of options and its current values,
* and store the current values in the ret object.
*/
- function getCurrent(options, curr, ret) {
+ function getCurrent(options, curr, ret, depth) {
var key, i;
for (key in options) {
- if (inArray(key, ['series', 'xAxis', 'yAxis']) > -1) {
+ if (!depth && inArray(key, ['series', 'xAxis', 'yAxis']) > -1) {
options[key] = splat(options[key]);
ret[key] = [];
for (i = 0; i < options[key].length; i++) {
ret[key][i] = {};
- getCurrent(options[key][i], curr[key][i], ret[key][i]);
+ getCurrent(
+ options[key][i],
+ curr[key][i],
+ ret[key][i],
+ depth + 1
+ );
}
} else if (isObject(options[key])) {
ret[key] = {};
- getCurrent(options[key], curr[key] || {}, ret[key]);
+ getCurrent(
+ options[key],
+ curr[key] || {},
+ ret[key],
+ depth + 1
+ );
} else {
ret[key] = curr[key] || null;
}
}
}
- getCurrent(options, this.options, ret);
+ getCurrent(options, this.options, ret, 0);
return ret;
};
}(Highcharts));
return Highcharts