app/assets/javascripts/highcharts.js in highcharts-rails-4.2.6 vs app/assets/javascripts/highcharts.js in highcharts-rails-4.2.7
- old
+ new
@@ -1,10 +1,10 @@
// ==ClosureCompiler==
// @compilation_level SIMPLE_OPTIMIZATIONS
/**
- * @license Highcharts JS v4.2.6 (2016-08-02)
+ * @license Highcharts JS v4.2.7 (2016-09-21)
*
* (c) 2009-2016 Torstein Honsi
*
* License: www.highcharts.com/license
*/
@@ -57,11 +57,11 @@
timeUnits,
noop = function () {},
charts = [],
chartCount = 0,
PRODUCT = 'Highcharts',
- VERSION = '4.2.6',
+ VERSION = '4.2.7',
// some constants for frequently used strings
DIV = 'div',
ABSOLUTE = 'absolute',
RELATIVE = 'relative',
@@ -70,11 +70,11 @@
VISIBLE = 'visible',
PX = 'px',
NONE = 'none',
M = 'M',
L = 'L',
- numRegex = /^[0-9]+$/,
+ numRegex = /[0-9]/g,
NORMAL_STATE = '',
HOVER_STATE = 'hover',
SELECT_STATE = 'select',
marginNames = ['plotTop', 'marginRight', 'marginBottom', 'plotLeft'],
@@ -479,11 +479,11 @@
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]' &&
+ 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 {
@@ -529,11 +529,12 @@
/**
* Check for array
* @param {Object} obj
*/
function isArray(obj) {
- return Object.prototype.toString.call(obj) === '[object Array]';
+ var str = Object.prototype.toString.call(obj);
+ return str === '[object Array]' || str === '[object Array Iterator]';
}
/**
* Check for object
* @param {Object} obj
@@ -1599,11 +1600,11 @@
},
global: {
useUTC: true,
//timezoneOffset: 0,
canvasToolsURL: 'http://code.highcharts.com/modules/canvas-tools.js',
- VMLRadialGradientURL: 'http://code.highcharts.com/4.2.6/gfx/vml-radial-gradient.png'
+ VMLRadialGradientURL: 'http://code.highcharts.com/4.2.7/gfx/vml-radial-gradient.png'
},
chart: {
//animation: true,
//alignTicks: false,
//reflow: true,
@@ -2869,23 +2870,20 @@
rotation = pick(rot, wrapper.rotation);
rad = rotation * deg2rad;
if (textStr !== UNDEFINED) {
- // Properties that affect bounding box
- cacheKey = ['', rotation || 0, styles && styles.fontSize, element.style.width].join(',');
+ cacheKey =
- // Since numbers are monospaced, and numerical labels appear a lot in a chart,
- // we assume that a label of n characters has the same bounding box as others
- // of the same length.
- if (textStr === '' || numRegex.test(textStr)) {
- cacheKey = 'num:' + textStr.toString().length + cacheKey;
+ // Since numbers are monospaced, and numerical labels appear a lot in a chart,
+ // we assume that a label of n characters has the same bounding box as others
+ // of the same length.
+ textStr.toString().replace(numRegex, '0') +
- // Caching all strings reduces rendering time by 4-5%.
- } else {
- cacheKey = textStr + cacheKey;
- }
+ // Properties that affect bounding box
+ ['', rotation || 0, styles && styles.fontSize, element.style.width].join(',');
+
}
if (cacheKey && !reload) {
bBox = cache[cacheKey];
}
@@ -2961,12 +2959,13 @@
bBox.width = mathAbs(height * mathSin(rad)) + mathAbs(width * mathCos(rad));
bBox.height = mathAbs(height * mathCos(rad)) + mathAbs(width * mathSin(rad));
}
}
- // Cache it
- if (cacheKey) {
+ // Cache it. When loading a chart in a hidden iframe in Firefox and IE/Edge, the
+ // bounding box height is 0, so don't cache it (#5620).
+ if (cacheKey && bBox.height > 0) {
// Rotate (#4681)
while (cacheKeys.length > 250) {
delete cache[cacheKeys.shift()];
}
@@ -3238,14 +3237,10 @@
}
},
alignSetter: function (value) {
this.element.setAttribute('text-anchor', { left: 'start', center: 'middle', right: 'end' }[value]);
},
- opacitySetter: function (value, key, element) {
- this[key] = value;
- element.setAttribute(key, value);
- },
titleSetter: function (value) {
var titleNode = this.element.getElementsByTagName('title')[0];
if (!titleNode) {
titleNode = doc.createElementNS(SVG_NS, 'title');
this.element.appendChild(titleNode);
@@ -3353,10 +3348,17 @@
SVGElement.prototype.scaleXSetter = SVGElement.prototype.scaleYSetter = function (value, key) {
this[key] = value;
this.doTransform = true;
};
+ // These setters both set the key on the instance itself plus as an attribute
+ SVGElement.prototype.opacitySetter = SVGElement.prototype.displaySetter = function (value, key, element) {
+ this[key] = value;
+ element.setAttribute(key, value);
+ };
+
+
// WebKit and Batik have problems with a stroke-width of zero, so in this case we remove the
// stroke attribute altogether. #1270, #1369, #3065, #3072.
SVGElement.prototype['stroke-widthSetter'] = SVGElement.prototype.strokeSetter = function (value, key, element) {
this[key] = value;
// Only apply the stroke attribute if the stroke width is defined and larger than 0
@@ -3378,11 +3380,10 @@
var SVGRenderer = function () {
this.init.apply(this, arguments);
};
SVGRenderer.prototype = {
Element: SVGElement,
-
/**
* Initialize the SVGRenderer
* @param {Object} container
* @param {Number} width
* @param {Number} height
@@ -3681,11 +3682,12 @@
}*/
// Check width and apply soft breaks or ellipsis
if (width) {
var words = span.replace(/([^\^])-/g, '$1- ').split(' '), // #1273
- hasWhiteSpace = spans.length > 1 || lineNo || (words.length > 1 && textStyles.whiteSpace !== 'nowrap'),
+ noWrap = textStyles.whiteSpace === 'nowrap',
+ hasWhiteSpace = spans.length > 1 || lineNo || (words.length > 1 && !noWrap),
tooLong,
actualWidth,
rest = [],
dy = getLineHeight(tspan),
softLineNo = 1,
@@ -3725,11 +3727,11 @@
// so we can move on to build the next line.
} else if (!tooLong || words.length === 1) {
words = rest;
rest = [];
- if (words.length) {
+ if (words.length && !noWrap) {
softLineNo++;
tspan = doc.createElementNS(SVG_NS, 'tspan');
attr(tspan, {
dy: dy,
@@ -4156,11 +4158,10 @@
mathRound(y),
width,
height,
options
),
-
imageRegex = /^url\((.*?)\)$/,
imageSrc,
imageSize,
centerImage;
@@ -4220,10 +4221,12 @@
// 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).
createElement('img', {
onload: function () {
+ var chart = charts[ren.chartIndex];
+
// Special case for SVGs on IE11, the width is not accessible until the image is
// part of the DOM (#2854).
if (this.width === 0) {
css(this, {
position: ABSOLUTE,
@@ -4240,12 +4243,12 @@
this.parentNode.removeChild(this);
}
// Fire the load event when all external images are loaded
ren.imgCount--;
- if (!ren.imgCount && charts[ren.chartIndex].onload) {
- charts[ren.chartIndex].onload();
+ if (!ren.imgCount && chart && chart.onload) {
+ chart.onload();
}
},
src: imageSrc
});
this.imgCount++;
@@ -4562,11 +4565,12 @@
wrapperX,
wrapperY,
crispAdjust = 0,
deferredAttr = {},
baselineOffset,
- needsBox,
+ hasBGImage = /^url\((.*?)\)$/.test(shape),
+ needsBox = hasBGImage,
updateBoxSize,
updateTextPadding,
boxAttr;
/**
@@ -4592,11 +4596,11 @@
if (!box) {
// create the border box if it is not already present
boxX = crispAdjust;
boxY = (baseline ? -baselineOffset : 0) + crispAdjust;
- wrapper.box = box = renderer.symbols[shape] ? // Symbol definition exists (#5324)
+ wrapper.box = box = renderer.symbols[shape] || hasBGImage ? // Symbol definition exists (#5324)
renderer.symbol(shape, boxX, boxY, wrapper.width, wrapper.height, deferredAttr) :
renderer.rect(boxX, boxY, wrapper.width, wrapper.height, 0, deferredAttr[STROKE_WIDTH]);
if (!box.isImg) { // #4324, fill "none" causes it to be ignored by mouse events in IE
box.attr('fill', NONE);
@@ -5014,11 +5018,11 @@
renderer = wrapper.renderer,
isSVG = renderer.isSVG,
addSetters = function (element, style) {
// These properties are set as attributes on the SVG group, and as
// identical CSS properties on the div. (#3542)
- each(['opacity', 'visibility'], function (prop) {
+ each(['display', 'opacity', 'visibility'], function (prop) {
wrap(element, prop + 'Setter', function (proceed, value, key, elem) {
proceed.call(this, value, key, elem);
style[key] = value;
});
});
@@ -5105,11 +5109,13 @@
// the SVG group structure
htmlGroup = parentGroup.div = parentGroup.div || createElement(DIV, cls, {
position: ABSOLUTE,
left: (parentGroup.translateX || 0) + PX,
top: (parentGroup.translateY || 0) + PX,
- opacity: parentGroup.opacity // #5075
+ display: parentGroup.display,
+ opacity: parentGroup.opacity, // #5075
+ pointerEvents: parentGroup.styles && parentGroup.styles.pointerEvents // #5595
}, htmlGroup || container); // the top group is appended to container
// Shortcut
htmlGroupStyle = htmlGroup.style;
@@ -5617,10 +5623,13 @@
}
key = 'top';
}
element.style[key] = value;
},
+ displaySetter: function (value, key, element) {
+ element.style[key] = value;
+ },
xSetter: function (value, key, element) {
this[key] = value; // used in getter
if (key === 'x') {
key = 'left';
@@ -7172,11 +7181,11 @@
// Flag, is the axis horizontal
axis.horiz = chart.inverted ? !isXAxis : isXAxis;
// Flag, isXAxis
axis.isXAxis = isXAxis;
- axis.coll = isXAxis ? 'xAxis' : 'yAxis';
+ axis.coll = axis.coll || (isXAxis ? 'xAxis' : 'yAxis');
axis.opposite = userOptions.opposite; // needed in setOptions
axis.side = userOptions.side || (axis.horiz ?
(axis.opposite ? 0 : 2) : // top : bottom
(axis.opposite ? 1 : 3)); // right : left
@@ -7200,11 +7209,12 @@
axis.reversed = options.reversed;
axis.visible = options.visible !== false;
axis.zoomEnabled = options.zoomEnabled !== false;
// Initial categories
- axis.categories = options.categories || type === 'category';
+ axis.hasNames = type === 'category' || options.categories === true;
+ axis.categories = options.categories || axis.hasNames;
axis.names = axis.names || []; // Preserve on update (#3830)
// Elements
//axis.axisGroup = UNDEFINED;
//axis.gridGroup = UNDEFINED;
@@ -7284,11 +7294,11 @@
var eventType,
events = axis.options.events;
// Register
if (inArray(axis, chart.axes) === -1) { // don't add it again on Axis.update()
- if (isXAxis && !this.isColorAxis) { // #2713
+ if (isXAxis) { // #2713
chart.axes.splice(chart.xAxis.length, 0, axis);
} else {
chart.axes.push(axis);
}
@@ -7322,11 +7332,11 @@
* Merge and set options
*/
setOptions: function (userOptions) {
this.options = merge(
this.defaultOptions,
- this.isXAxis ? {} : this.defaultYAxisOptions,
+ this.coll === 'yAxis' && this.defaultYAxisOptions,
[this.defaultTopAxisOptions, this.defaultRightAxisOptions,
this.defaultBottomAxisOptions, this.defaultLeftAxisOptions][this.side],
merge(
defaultOptions[this.coll], // if set in setOptions (#1053)
userOptions
@@ -7738,17 +7748,17 @@
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;
+ minArgs[2] = axis.isLog ? axis.log2lin(axis.dataMin) : 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;
+ maxArgs[2] = axis.isLog ? axis.log2lin(axis.dataMax) : axis.dataMax;
}
max = arrayMin(maxArgs);
// now if the max is adjusted, adjust the min back
@@ -7784,10 +7794,72 @@
}
return ret;
},
/**
+ * When a point name is given and no x, search for the name in the existing categories,
+ * or if categories aren't provided, search names or create a new category (#2522).
+ */
+ nameToX: function (point) {
+ var explicitCategories = isArray(this.categories),
+ names = explicitCategories ? this.categories : this.names,
+ nameX = point.options.x,
+ x;
+
+ point.series.requireSorting = false;
+
+ if (!defined(nameX)) {
+ nameX = this.options.nameToX === false ?
+ point.series.autoIncrement() :
+ inArray(point.name, names);
+ }
+ if (nameX === -1) { // The name is not found in currenct categories
+ if (!explicitCategories) {
+ x = names.length;
+ }
+ } else {
+ x = nameX;
+ }
+
+ // Write the last point's name to the names array
+ this.names[x] = point.name;
+
+ return x;
+ },
+
+ /**
+ * When changes have been done to series data, update the axis.names.
+ */
+ updateNames: function () {
+ var axis = this;
+
+ if (this.names.length > 0) {
+ this.names.length = 0;
+ this.minRange = undefined;
+ each(this.series || [], function (series) {
+
+ // When adding a series, points are not yet generated
+ if (!series.processedXData) {
+ series.processData();
+ series.generatePoints();
+ }
+
+ each(series.points, function (point, i) {
+ var x;
+ if (point.options && point.options.x === undefined) {
+ x = axis.nameToX(point);
+ if (x !== point.x) {
+ point.x = x;
+ series.xData[i] = x;
+ }
+ }
+ });
+ });
+ }
+ },
+
+ /**
* Update translation information
*/
setAxisTranslation: function (saveOld) {
var axis = this,
range = axis.max - axis.min,
@@ -8063,11 +8135,11 @@
!!this.tickAmount
);
}
// Prevent ticks from getting so close that we can't draw the labels
- if (!this.tickAmount && this.len) { // Color axis with disabled legend has no length
+ if (!this.tickAmount) {
axis.tickInterval = axis.unsquish();
}
this.setTickPositions();
},
@@ -8635,34 +8707,36 @@
innerWidth = mathMax(1, mathRound(slotWidth - 2 * (labelOptions.padding || 5))),
attr = {},
labelMetrics = this.labelMetrics(),
textOverflowOption = labelOptions.style.textOverflow,
css,
- labelLength = 0,
+ maxLabelLength = 0,
label,
i,
pos;
// Set rotation option unless it is "auto", like in gauges
if (!isString(labelOptions.rotation)) {
attr.rotation = labelOptions.rotation || 0; // #4443
}
+ // Get the longest label length
+ each(tickPositions, function (tick) {
+ tick = ticks[tick];
+ if (tick && tick.labelLength > maxLabelLength) {
+ maxLabelLength = tick.labelLength;
+ }
+ });
+ this.maxLabelLength = maxLabelLength;
+
+
// Handle auto rotation on horizontal axis
if (this.autoRotation) {
- // Get the longest label length
- each(tickPositions, function (tick) {
- tick = ticks[tick];
- if (tick && tick.labelLength > labelLength) {
- labelLength = tick.labelLength;
- }
- });
-
// Apply rotation only if the label is too wide for the slot, and
// the label is wider than its height.
- if (labelLength > innerWidth && labelLength > labelMetrics.h) {
+ if (maxLabelLength > innerWidth && maxLabelLength > labelMetrics.h) {
attr.rotation = this.labelRotation;
} else {
this.labelRotation = 0;
}
@@ -8699,11 +8773,11 @@
// Add ellipsis if the label length is significantly longer than ideal
if (attr.rotation) {
css = {
- width: (labelLength > chart.chartHeight * 0.5 ? chart.chartHeight * 0.33 : chart.chartHeight) + PX
+ width: (maxLabelLength > chart.chartHeight * 0.5 ? chart.chartHeight * 0.33 : chart.chartHeight) + PX
};
if (!textOverflowOption) {
css.textOverflow = 'ellipsis';
}
}
@@ -9682,16 +9756,16 @@
.attr({
padding: padding,
fill: options.backgroundColor,
'stroke-width': borderWidth,
r: options.borderRadius,
- zIndex: 8
+ zIndex: 8,
+ display: 'none' // #2301, #2657, #3532, #5570
})
.css(style)
.css({ padding: 0 }) // Remove it from VML, the padding is applied as an attribute instead (#1117)
- .add()
- .attr({ y: -9e9 }); // #2301, #2657, #3532
+ .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);
@@ -10011,11 +10085,14 @@
} else {
// show it
if (tooltip.isHidden) {
stop(label);
- label.attr('opacity', 1).show();
+ label.attr({
+ opacity: 1,
+ display: 'block'
+ }).show();
}
// update text
label.attr({
text: text
@@ -10288,20 +10365,19 @@
chart = pointer.chart,
series = chart.series,
tooltip = chart.tooltip,
shared = tooltip ? tooltip.shared : false,
followPointer,
+ updatePosition = true,
hoverPoint = chart.hoverPoint,
hoverSeries = chart.hoverSeries,
i,
- distance = [Number.MAX_VALUE, Number.MAX_VALUE], // #4511
anchor,
noSharedTooltip,
stickToHoverSeries,
directTouch,
kdpoints = [],
- kdpoint = [],
kdpointT;
// For hovering over the empty parts of the plot area (hoverSeries is undefined).
// If there is one series with point tracking (combo chart), don't go to nearest neighbour.
if (!shared && !hoverSeries) {
@@ -10315,14 +10391,19 @@
// If it has a hoverPoint and that series requires direct touch (like columns, #3899), or we're on
// a noSharedTooltip series among shared tooltip series (#4546), use the hoverPoint . Otherwise,
// search the k-d tree.
stickToHoverSeries = hoverSeries && (shared ? hoverSeries.noSharedTooltip : hoverSeries.directTouch);
if (stickToHoverSeries && hoverPoint) {
- kdpoint = [hoverPoint];
+ kdpoints = [hoverPoint];
// Handle shared tooltip or cases where a series is not yet hovered
} else {
+ // When we have non-shared tooltip and sticky tracking is disabled,
+ // search for the closest point only on hovered series: #5533, #5476
+ if (!shared && hoverSeries && !hoverSeries.options.stickyTracking) {
+ series = [hoverSeries];
+ }
// Find nearest points on all series
each(series, function (s) {
// Skip hidden series
noSharedTooltip = s.noSharedTooltip && shared;
directTouch = !shared && s.directTouch;
@@ -10331,67 +10412,70 @@
if (kdpointT && kdpointT.series) { // Point.series becomes null when reset and before redraw (#5197)
kdpoints.push(kdpointT);
}
}
});
- // Find absolute nearest point
- each(kdpoints, function (p) {
- if (p) {
- // Store both closest points, using point.dist and point.distX comparisons (#4645):
- each(['dist', 'distX'], function (dist, k) {
- if (isNumber(p[dist])) {
- var
- // It is closer than the reference point
- isCloser = p[dist] < distance[k],
- // It is equally close, but above the reference point (#4679)
- isAbove = p[dist] === distance[k] && p.series.group.zIndex >= kdpoint[k].series.group.zIndex;
- if (isCloser || isAbove) {
- distance[k] = p[dist];
- kdpoint[k] = p;
- }
- }
- });
+ // Sort kdpoints by distance to mouse pointer
+ kdpoints.sort(function (p1, p2) {
+ var isCloserX = p1.distX - p2.distX,
+ isCloser = p1.dist - p2.dist,
+ isAbove = p1.series.group.zIndex > p2.series.group.zIndex ? -1 : 1;
+ // We have two points which are not in the same place on xAxis and shared tooltip:
+ if (isCloserX !== 0) {
+ return isCloserX;
}
+ // Points are not exactly in the same place on x/yAxis:
+ if (isCloser !== 0) {
+ return isCloser;
+ }
+ // The same xAxis and yAxis position, sort by z-index:
+ return isAbove;
});
}
// Remove points with different x-positions, required for shared tooltip and crosshairs (#4645):
if (shared) {
i = kdpoints.length;
while (i--) {
- if (kdpoints[i].clientX !== kdpoint[1].clientX || kdpoints[i].series.noSharedTooltip) {
+ if (kdpoints[i].clientX !== kdpoints[0].clientX || kdpoints[i].series.noSharedTooltip) {
kdpoints.splice(i, 1);
}
}
}
// Refresh tooltip for kdpoint if new hover point or tooltip was hidden // #3926, #4200
- if (kdpoint[0] && (kdpoint[0] !== this.prevKDPoint || (tooltip && tooltip.isHidden))) {
+ if (kdpoints[0] && (kdpoints[0] !== pointer.hoverPoint || (tooltip && tooltip.isHidden))) {
// Draw tooltip if necessary
- if (shared && !kdpoint[0].series.noSharedTooltip) {
+ if (shared && !kdpoints[0].series.noSharedTooltip) {
+ // Do mouseover on all points (#3919, #3985, #4410)
+ for (i = 0; i >= 0; i--) {
+ kdpoints[i].onMouseOver(e, kdpoints[i] !== ((hoverSeries && hoverSeries.directTouch && hoverPoint) || kdpoints[0]));
+ }
+ // Make sure that the hoverPoint and hoverSeries are stored for events (e.g. click), #5622
+ if (hoverSeries && hoverSeries.directTouch && hoverPoint && hoverPoint !== kdpoints[0]) {
+ hoverPoint.onMouseOver(e, false);
+ }
if (kdpoints.length && tooltip) {
- tooltip.refresh(kdpoints, e);
+ // Keep the order of series in tooltip:
+ tooltip.refresh(kdpoints.sort(function (p1, p2) {
+ return p1.series.index - p2.series.index;
+ }), e);
}
-
- // Do mouseover on all points (#3919, #3985, #4410)
- each(kdpoints, function (point) {
- point.onMouseOver(e, point !== ((hoverSeries && hoverSeries.directTouch && hoverPoint) || kdpoint[0]));
- });
- this.prevKDPoint = kdpoint[1];
} else {
if (tooltip) {
- tooltip.refresh(kdpoint[0], e);
+ tooltip.refresh(kdpoints[0], e);
}
if (!hoverSeries || !hoverSeries.directTouch) { // #4448
- kdpoint[0].onMouseOver(e);
+ kdpoints[0].onMouseOver(e);
}
- this.prevKDPoint = kdpoint[0];
}
-
+ pointer.prevKDPoint = kdpoints[0];
+ updatePosition = false;
+ }
// Update positions (regardless of kdpoint or hoverPoint)
- } else {
+ if (updatePosition) {
followPointer = hoverSeries && hoverSeries.tooltipOptions.followPointer;
if (tooltip && followPointer && !tooltip.isHidden) {
anchor = tooltip.getAnchor([{}], e);
tooltip.updatePosition({ plotX: anchor[0], plotY: anchor[1] });
}
@@ -10407,14 +10491,14 @@
addEvent(doc, 'mousemove', pointer._onDocumentMouseMove);
}
// Crosshair. For each hover point, loop over axes and draw cross if that point
// belongs to the axis (#4927).
- each(shared ? kdpoints : [pick(hoverPoint, kdpoint[1])], function (point) { // #5269
- each(chart.axes, function (axis) {
+ each(shared ? kdpoints : [pick(hoverPoint, kdpoints[0])], function drawPointCrosshair(point) { // #5269
+ each(chart.axes, function drawAxisCrosshair(axis) {
// In case of snap = false, point is undefined, and we draw the crosshair anyway (#5066)
- if (!point || point.series[axis.coll] === axis) {
+ if (!point || point.series && point.series[axis.coll] === axis) { // #5658
axis.drawCrosshair(e, point);
}
});
});
},
@@ -12295,10 +12379,11 @@
// reset maxTicks
chart.maxTicks = null;
// set axes scales
each(axes, function (axis) {
+ axis.updateNames();
axis.setScale();
});
}
}
@@ -12587,11 +12672,13 @@
container = this.container;
// Destroy the clone and bring the container back to the real renderTo div
if (revert) {
if (clone) {
- this.renderTo.appendChild(container);
+ while (clone.childNodes.length) { // #5231
+ this.renderTo.appendChild(clone.firstChild);
+ }
discardElement(clone);
delete this.renderToClone;
}
// Set up the clone
@@ -13141,11 +13228,11 @@
if (linkedTo === ':previous') {
linkedTo = chart.series[series.index - 1];
} else {
linkedTo = chart.get(linkedTo);
}
- if (linkedTo) {
+ if (linkedTo && linkedTo.linkedParent !== series) { // #3341 avoid mutual linking
linkedTo.linkedSeries.push(series);
series.linkedParent = linkedTo;
series.visible = pick(series.options.visible, linkedTo.options.visible, series.visible); // #3879
}
}
@@ -13572,31 +13659,37 @@
// copy options directly to point
extend(point, options);
point.options = point.options ? extend(point.options, options) : options;
+ // Since options are copied into the Point instance, some accidental options must be shielded (#5681)
+ if (options.group) {
+ delete point.group;
+ }
+
// For higher dimension series types. For instance, for ranges, point.y is mapped to point.low.
if (pointValKey) {
point.y = point[pointValKey];
}
- point.isNull = point.x === null || !isNumber(point.y, true); // #3571, check for NaN
+ point.isNull = pick(
+ point.isValid && !point.isValid(),
+ point.x === null || !isNumber(point.y, true)
+ ); // #3571, check for NaN
// 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 ('name' in point && x === undefined && series.xAxis && series.xAxis.hasNames) {
+ point.x = series.xAxis.nameToX(point);
+ }
if (point.x === undefined && series) {
if (x === undefined) {
point.x = series.autoIncrement(point);
} else {
point.x = x;
}
}
-
- // Write the last point's name to the names array
- if (series.xAxis && series.xAxis.names) {
- series.xAxis.names[point.x] = point.name;
- }
-
+
return point;
},
/**
* Transform number or array configs into objects
@@ -13778,11 +13871,12 @@
}
fireEvent(this, eventType, eventArgs, defaultFunction);
},
visible: true
- };/**
+ };
+ /**
* @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.
@@ -13957,42 +14051,22 @@
/**
* 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 (point) {
+ autoIncrement: function () {
var options = this.options,
xIncrement = this.xIncrement,
date,
pointInterval,
- pointIntervalUnit = options.pointIntervalUnit,
- xAxis = this.xAxis,
- explicitCategories,
- names,
- nameX;
+ pointIntervalUnit = options.pointIntervalUnit;
xIncrement = pick(xIncrement, options.pointStart, 0);
this.pointInterval = pointInterval = pick(this.pointInterval, options.pointInterval, 1);
- // When a point name is given and no x, search for the name in the existing categories,
- // or if categories aren't provided, search names or create a new category (#2522).
- if (xAxis && xAxis.categories && point.name) {
- this.requireSorting = false;
- explicitCategories = isArray(xAxis.categories);
- names = explicitCategories ? xAxis.categories : xAxis.names;
- nameX = inArray(point.name, names); // #2522
- if (nameX === -1) { // The name is not found in currenct categories
- if (!explicitCategories) {
- xIncrement = names.length;
- }
- } else {
- xIncrement = nameX;
- }
- }
-
// Added code for pointInterval strings
if (pointIntervalUnit) {
date = new Date(xIncrement);
if (pointIntervalUnit === 'day') {
@@ -14176,19 +14250,14 @@
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;
+ xData[i] = this.autoIncrement();
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];
@@ -14463,11 +14532,11 @@
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));
+ validValue = (isNumber(y, true) || isArray(y)) && (!yAxis.isLog || (y.length || y > 0));
withinRange = this.getExtremesFromAll || this.options.getExtremesFromAll || this.cropped ||
((xData[i + 1] || x) >= xMin && (xData[i - 1] || x) <= xMax);
if (validValue && withinRange) {
@@ -14526,12 +14595,11 @@
pointStack,
stackValues;
// Discard disallowed y values for log axes (#3434)
if (yAxis.isLog && yValue !== null && yValue <= 0) {
- point.y = yValue = null;
- error(10);
+ point.isNull = true;
}
// Get the plotX translation
point.plotX = plotX = correctFloat( // #5236
mathMin(mathMax(-1e5, xAxis.translate(xValue, 0, 0, 0, 1, pointPlacement, this.type === 'flags')), 1e5) // #3923
@@ -14578,11 +14646,11 @@
point.isInside = plotY !== UNDEFINED && plotY >= 0 && plotY <= yAxis.len && // #3519
plotX >= 0 && plotX <= xAxis.len;
// Set client related positions for mouse tracking
- point.clientX = dynamicallyPlaced ? correctFloat(xAxis.translate(xValue, 0, 0, 0, 1)) : plotX; // #1514
+ point.clientX = dynamicallyPlaced ? correctFloat(xAxis.translate(xValue, 0, 0, 0, 1, pointPlacement)) : plotX; // #1514, #5383, #5518
point.negative = point.y < (threshold || 0);
// some API data
point.category = categories && categories[point.x] !== UNDEFINED ?
@@ -14683,18 +14751,13 @@
*/
animate: function (init) {
var series = this,
chart = series.chart,
clipRect,
- animation = series.options.animation,
+ animation = animObject(series.options.animation),
sharedClipKey;
- // Animation option is set to true
- if (animation && !isObject(animation)) {
- animation = defaultPlotOptions[series.type].animation;
- }
-
// Initialize the animation. Set up the clipping rectangle.
if (init) {
series.setClip(animation);
@@ -15260,19 +15323,21 @@
graph = this.graph,
area = this.area,
chartSizeMax = mathMax(chart.chartWidth, chart.chartHeight),
axis = this[(this.zoneAxis || 'y') + 'Axis'],
extremes,
- reversed = axis.reversed,
+ reversed,
inverted = chart.inverted,
- horiz = axis.horiz,
+ horiz,
pxRange,
pxPosMin,
pxPosMax,
ignoreZones = false;
- if (zones.length && (graph || area) && axis.min !== UNDEFINED) {
+ if (zones.length && (graph || area) && axis && axis.min !== UNDEFINED) {
+ reversed = axis.reversed;
+ horiz = axis.horiz;
// The use of the Color Threshold assumes there are no gaps
// so it is safe to hide the original graph and area
if (graph) {
graph.hide();
}
@@ -15318,11 +15383,11 @@
clipAttr.y = chart.plotWidth - clipAttr.y;
}
}
/// VML SUPPPORT
- if (chart.inverted && renderer.isVML) {
+ if (inverted && renderer.isVML) {
if (axis.isXAxis) {
clipAttr = {
x: 0,
y: reversed ? pxPosMin : pxPosMax,
height: clipAttr.width,
@@ -16266,12 +16331,11 @@
var point = this,
series = point.series,
graphic = point.graphic,
i,
chart = series.chart,
- seriesOptions = series.options,
- names = series.xAxis && series.xAxis.names;
+ seriesOptions = series.options;
redraw = pick(redraw, true);
function update() {
@@ -16297,14 +16361,11 @@
}
// record changes in the parallel arrays
i = point.index;
series.updateParallelArrays(point, i);
- if (names && point.name) {
- names[point.x] = point.name;
- }
-
+
// Record the options to options.data. If there is an object from before,
// use point options, otherwise use raw options. (#4701)
seriesOptions.data[i] = isObject(seriesOptions.data[i], true) ? point.options : options;
// redraw
@@ -16362,12 +16423,10 @@
isInTheMiddle,
xData = series.xData,
i,
x;
- setAnimation(animation, chart);
-
// 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.
@@ -16415,13 +16474,14 @@
}
// redraw
series.isDirty = true;
series.isDirtyData = true;
+
if (redraw) {
series.getAttribs(); // #1937
- chart.redraw();
+ chart.redraw(animation); // Animation is set anyway on redraw, #5665
}
},
/**
* Remove a point (rendered or not), by index
@@ -16470,16 +16530,15 @@
*
* @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) {
+ remove: function (redraw, animation, withEvent) {
var series = this,
chart = series.chart;
- // Fire the event with a default handler of removing the point
- fireEvent(series, 'remove', null, function () {
+ function remove() {
// Destroy elements
series.destroy();
// Redraw
@@ -16487,11 +16546,18 @@
chart.linkSeries();
if (pick(redraw, true)) {
chart.redraw(animation);
}
- });
+ }
+
+ // Fire the event with a default handler of removing the point
+ if (withEvent !== false) {
+ fireEvent(series, 'remove', null, remove);
+ } else {
+ remove();
+ }
},
/**
* Update the series with a new set of options
*/
@@ -16524,11 +16590,11 @@
pointStart: this.xData[0] // when updating after addPoint
}, { data: this.options.data }, newOptions);
// Destroy the series and delete all properties. Reinsert all methods
// and properties from the new type prototype (#2270, #3719)
- this.remove(false);
+ this.remove(false, null, false);
for (n in proto) {
this[n] = UNDEFINED;
}
extend(this, seriesTypes[newOptions.type || oldType].prototype);
@@ -16809,18 +16875,18 @@
plotY: top === null ? translatedThreshold : yAxis.getThreshold(top),
isNull: isNull
});
bottomPoints.push({
plotX: plotX,
- plotY: bottom === null ? translatedThreshold : yAxis.getThreshold(bottom)
+ plotY: bottom === null ? translatedThreshold : yAxis.getThreshold(bottom),
+ doCurve: false // #1041, gaps in areaspline areas
});
}
};
// Find what points to use
points = points || this.points;
-
// Fill in missing points
if (stacking) {
points = this.getStackPoints();
}
@@ -16947,12 +17013,16 @@
leftContY,
rightContX,
rightContY,
ret;
+ function doCurve(otherPoint) {
+ return otherPoint && !otherPoint.isNull && otherPoint.doCurve !== false;
+ }
+
// Find control points
- if (lastPoint && !lastPoint.isNull && nextPoint && !nextPoint.isNull) {
+ if (doCurve(lastPoint) && doCurve(nextPoint)) {
var lastX = lastPoint.plotX,
lastY = lastPoint.plotY,
nextX = nextPoint.plotX,
nextY = nextPoint.plotY,
correction = 0;
@@ -16999,33 +17069,39 @@
/*
if (leftContX) {
this.chart.renderer.circle(leftContX + this.chart.plotLeft, leftContY + this.chart.plotTop, 2)
.attr({
stroke: 'red',
- 'stroke-width': 1,
- fill: 'none'
+ 'stroke-width': 2,
+ fill: 'none',
+ zIndex: 9
})
.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
+ 'stroke-width': 2,
+ zIndex: 9
})
.add();
+ }
+ if (rightContX) {
this.chart.renderer.circle(rightContX + this.chart.plotLeft, rightContY + this.chart.plotTop, 2)
.attr({
stroke: 'green',
- 'stroke-width': 1,
- fill: 'none'
+ 'stroke-width': 2,
+ fill: 'none',
+ zIndex: 9
})
.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
+ 'stroke-width': 2,
+ zIndex: 9
})
.add();
}
// */
ret = [
@@ -17314,11 +17390,16 @@
[yAxis.len + yAxis.pos - chart.plotLeft - plotY, series.xAxis.len - barX - barW / 2, barH] :
[barX + barW / 2, plotY + yAxis.pos - chart.plotTop, barH];
// Register shape type and arguments to be used in drawPoints
point.shapeType = 'rect';
- point.shapeArgs = series.crispCol(barX, barY, barW, barH);
+ point.shapeArgs = series.crispCol.apply(
+ series,
+ point.isNull ?
+ [point.plotX, yAxis.len / 2, 0, 0] : // #3169, drilldown from null must have a position to work from
+ [barX, barY, barW, barH]
+ );
});
},
getSymbol: noop,
@@ -19641,10 +19722,10 @@
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;
+ series.visible = vis = series.options.visible = series.userOptions.visible = vis === undefined ? !oldVisibility : vis; // #5618
showOrHide = vis ? 'show' : 'hide';
// show or hide elements
each(['group', 'dataLabelsGroup', 'markerGroup', 'tracker'], function (key) {
if (series[key]) {