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]) {