app/assets/javascripts/highcharts.js in highcharts-rails-4.0.3 vs app/assets/javascripts/highcharts.js in highcharts-rails-4.0.4

- old
+ new

@@ -1,18 +1,18 @@ // ==ClosureCompiler== // @compilation_level SIMPLE_OPTIMIZATIONS /** - * @license Highcharts JS v4.0.3 (2014-07-03) + * @license Highcharts JS v4.0.4 (2014-09-02) * * (c) 2009-2014 Torstein Honsi * * License: www.highcharts.com/license */ // JSLint options: -/*global Highcharts, document, window, navigator, setInterval, clearInterval, clearTimeout, setTimeout, location, jQuery, $, console, each, grep */ +/*global Highcharts, HighchartsAdapter, document, window, navigator, setInterval, clearInterval, clearTimeout, setTimeout, location, jQuery, $, console, each, grep */ /*jslint ass: true, sloppy: true, forin: true, plusplus: true, nomen: true, vars: true, regexp: true, newcap: true, browser: true, continue: true, white: true */ (function () { // encapsulated variables var UNDEFINED, doc = document, @@ -55,11 +55,11 @@ error, noop = function () { return UNDEFINED; }, charts = [], chartCount = 0, PRODUCT = 'Highcharts', - VERSION = '4.0.3', + VERSION = '4.0.4', // some constants for frequently used strings DIV = 'div', ABSOLUTE = 'absolute', RELATIVE = 'relative', @@ -80,10 +80,11 @@ // constants for attributes STROKE_WIDTH = 'stroke-width', // time methods, changed based on whether or not UTC is used + Date, // Allow using a different Date class makeTime, timezoneOffset, getMinutes, getHours, getDay, @@ -361,11 +362,12 @@ * @param {Number} decimals The amount of decimals * @param {String} decPoint The decimal point, defaults to the one given in the lang options * @param {String} thousandsSep The thousands separator, defaults to the one given in the lang options */ function numberFormat(number, decimals, decPoint, thousandsSep) { - var lang = defaultOptions.lang, + var externalFn = Highcharts.numberFormat, + lang = defaultOptions.lang, // http://kevin.vanzonneveld.net/techblog/article/javascript_equivalent_for_phps_number_format/ n = +number || 0, c = decimals === -1 ? (n.toString().split('.')[1] || '').length : // preserve decimals (isNaN(decimals = mathAbs(decimals)) ? 2 : decimals), @@ -373,12 +375,14 @@ t = thousandsSep === undefined ? lang.thousandsSep : thousandsSep, s = n < 0 ? "-" : "", i = String(pInt(n = mathAbs(n).toFixed(c))), j = i.length > 3 ? i.length % 3 : 0; - return s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) + - (c ? d + mathAbs(n - i).toFixed(c).slice(2) : ""); + return externalFn !== numberFormat ? + externalFn(number, decimals, decPoint, thousandsSep) : + (s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) + + (c ? d + mathAbs(n - i).toFixed(c).slice(2) : "")); } /** * Pad a string to a given length by adding 0 to the beginning * @param {Number} number @@ -561,11 +565,11 @@ * @param {Number} interval * @param {Array} multiples * @param {Number} magnitude * @param {Object} options */ -function normalizeTickInterval(interval, multiples, magnitude, options) { +function normalizeTickInterval(interval, multiples, magnitude, allowDecimals) { var normalized, i; // round to a tenfold of 1, 2, 2.5 or 5 magnitude = pick(magnitude, 1); normalized = interval / magnitude; @@ -573,11 +577,11 @@ // multiples for a linear scale if (!multiples) { multiples = [1, 2, 2.5, 5, 10]; // the allowDecimals option - if (options && options.allowDecimals === false) { + if (allowDecimals === false) { if (magnitude === 1) { multiples = [1, 2, 5, 10]; } else if (magnitude <= 0.1) { multiples = [1 / magnitude]; } @@ -850,16 +854,11 @@ * Initialize the adapter by applying some extensions to jQuery */ init: function (pathAnim) { // extend the animate function to allow SVG animations - var Fx = $.fx, - Step = Fx.step, - dSetter, - Tween = $.Tween, - propHooks = Tween && Tween.propHooks, - opacityHook = $.cssHooks.opacity; + var Fx = $.fx; /*jslint unparam: true*//* allow unused param x in this function */ $.extend($.easing, { easeOutQuad: function (x, t, b, c, d) { return -c * (t /= d) * (t - 2) + b; @@ -867,19 +866,19 @@ }); /*jslint unparam: false*/ // extend some methods to check for elem.attr, which means it is a Highcharts SVG object $.each(['cur', '_default', 'width', 'height', 'opacity'], function (i, fn) { - var obj = Step, + var obj = Fx.step, base; // Handle different parent objects if (fn === 'cur') { obj = Fx.prototype; // 'cur', the getter, relates to Fx.prototype - } else if (fn === '_default' && Tween) { // jQuery 1.8 model - obj = propHooks[fn]; + } else if (fn === '_default' && $.Tween) { // jQuery 1.8 model + obj = $.Tween.propHooks[fn]; fn = 'set'; } // Overwrite the method base = obj[fn]; @@ -909,17 +908,16 @@ }; } }); // Extend the opacity getter, needed for fading opacity with IE9 and jQuery 1.10+ - wrap(opacityHook, 'get', function (proceed, elem, computed) { + wrap($.cssHooks.opacity, 'get', function (proceed, elem, computed) { return elem.attr ? (elem.opacity || 0) : proceed.call(this, elem, computed); }); - // Define the setter function for d (path definitions) - dSetter = function (fx) { + this.addAnimSetter('d', function (fx) { var elem = fx.elem, ends; // Normally start and end should be set in state == 0, but sometimes, // for reasons unknown, this doesn't happen. Perhaps state == 0 is skipped @@ -929,26 +927,14 @@ fx.start = ends[0]; fx.end = ends[1]; fx.started = true; } - - // interpolate each value of the path + // Interpolate each value of the path elem.attr('d', pathAnim.step(fx.start, fx.end, fx.pos, elem.toD)); - }; + }); - // jQuery 1.8 style - if (Tween) { - propHooks.d = { - set: dSetter - }; - // pre 1.8 - } else { - // animate paths - Step.d = dSetter; - } - /** * Utility for iterating over an array. Parameters are reversed compared to jQuery. * @param {Array} arr * @param {Function} fn */ @@ -1004,10 +990,24 @@ return ret; }; }, + /** + * Add an animation setter for a specific property + */ + addAnimSetter: function (prop, setter) { + // jQuery 1.8 style + if ($.Tween) { + $.Tween.propHooks[prop] = { + set: setter + }; + // pre 1.8 + } else { + $.fx.step[prop] = setter; + } + }, /** * Downloads a script and executes a callback when done. * @param {String} scriptLocation * @param {Function} callback @@ -1183,18 +1183,21 @@ $el.stop(); if (params.opacity !== UNDEFINED && el.attr) { params.opacity += 'px'; // force jQuery to use same logic as width and height (#2161) } + el.hasAnim = 1; // #3342 $el.animate(params, options); }, /** * Stop running animation */ stop: function (el) { - $(el).stop(); + if (el.hasAnim) { // #3342, memory leak on calling $(el) from destroy + $(el).stop(); + } } }); }(win.jQuery)); @@ -1265,12 +1268,12 @@ thousandsSep: ',' }, global: { useUTC: true, //timezoneOffset: 0, - canvasToolsURL: 'http://code.highcharts.com/4.0.3/modules/canvas-tools.js', - VMLRadialGradientURL: 'http://code.highcharts.com/4.0.3/gfx/vml-radial-gradient.png' + canvasToolsURL: 'http://code.highcharts.com/4.0.4/modules/canvas-tools.js', + VMLRadialGradientURL: 'http://code.highcharts.com/4.0.4/gfx/vml-radial-gradient.png' }, chart: { //animation: true, //alignTicks: false, //reflow: true, @@ -1590,10 +1593,11 @@ var useUTC = defaultOptions.global.useUTC, GET = useUTC ? 'getUTC' : 'get', SET = useUTC ? 'setUTC' : 'set'; + Date = defaultOptions.global.Date || window.Date; timezoneOffset = ((useUTC && defaultOptions.global.timezoneOffset) || 0) * 60000; makeTime = useUTC ? Date.UTC : function (year, month, date, hours, minutes, seconds) { return new Date( year, month, @@ -2434,14 +2438,14 @@ */ show: function (inherit) { // IE9-11 doesn't handle visibilty:inherit well, so we remove the attribute instead (#2881) if (inherit && this.element.namespaceURI === SVG_NS) { this.element.removeAttribute('visibility'); - return this; } else { - return this.attr({ visibility: inherit ? 'inherit' : VISIBLE }); + this.attr({ visibility: inherit ? 'inherit' : VISIBLE }); } + return this; }, /** * Hide the element */ @@ -2454,11 +2458,11 @@ elemWrapper.animate({ opacity: 0 }, { duration: duration || 150, complete: function () { - elemWrapper.hide(); + elemWrapper.attr({ y: -9999 }); // #3088, assuming we're only using this for tooltips } }); }, /** @@ -2699,18 +2703,18 @@ .replace('shortdash', '3,1,') .replace('longdash', '8,3,') .replace(/dot/g, '1,3,') .replace('dash', '4,3,') .replace(/,$/, '') - .replace('solid', 1) .split(','); // ending comma i = value.length; while (i--) { value[i] = pInt(value[i]) * this['stroke-width']; } - value = value.join(','); + value = value.join(',') + .replace('NaN', 'none'); // #3226 this.element.setAttribute('stroke-dasharray', value); } }, alignSetter: function (value) { this.element.setAttribute('text-anchor', { left: 'start', center: 'middle', right: 'end' }[value]); @@ -2723,11 +2727,11 @@ var titleNode = this.element.getElementsByTagName('title')[0]; if (!titleNode) { titleNode = doc.createElementNS(SVG_NS, 'title'); this.element.appendChild(titleNode); } - titleNode.textContent = value; + titleNode.textContent = pick(value, '').replace(/<[^>]*>/g, ''); // #3276 }, textSetter: function (value) { if (value !== this.textStr) { // Delete bBox memo when the text changes delete this.bBox; @@ -3518,11 +3522,11 @@ } } }; imageSrc = symbol.match(imageRegex)[1]; - imageSize = symbolSizes[imageSrc]; + imageSize = symbolSizes[imageSrc] || (options && options.width && options.height && [options.width, options.height]); // Ireate the image synchronously, add attribs async obj = this.image(imageSrc) .attr({ x: x, @@ -3532,11 +3536,10 @@ if (imageSize) { centerImage(obj, imageSize); } else { // Initialize image to be 0 size so export will still function if there's no cached sizes. - // obj.attr({ width: 0, height: 0 }); // Create a dummy JavaScript image to get the width and height. Due to a bug in IE < 8, // the created element must be assigned to a variable in order to load (#292). imageElement = createElement('img', { @@ -3936,11 +3939,11 @@ * and add it before the text in the DOM. */ wrapper.onAdd = function () { text.add(wrapper); wrapper.attr({ - text: str || '', // alignment is available now + text: (str || str === 0) ? str : '', // alignment is available now // #3295: 0 not rendered if given as a value x: x, y: y }); if (box && defined(anchorX)) { @@ -5593,12 +5596,11 @@ value: axis.isLog ? correctFloat(lin2log(value)) : value }); // prepare CSS css = width && { width: mathMax(1, mathRound(width - 2 * (labelOptions.padding || 10))) + PX }; - css = extend(css, labelOptions.style); - + // first call if (!defined(label)) { attr = { align: axis.labelAlign }; @@ -5617,11 +5619,11 @@ 0, labelOptions.useHTML ) .attr(attr) // without position absolute, IE export sometimes is wrong - .css(css) + .css(extend(css, labelOptions.style)) .add(axis.labelGroup) : null; // Set the tick baseline and correct for rotation (#1764) axis.tickBaseline = chart.renderer.fontMetrics(labelOptions.style.fontSize, label).b; @@ -5690,18 +5692,19 @@ rightSide = sides[1], axisLeft, axisRight, neighbour, neighbourEdge, - line = this.label.line || 0, + line = this.label.line, + lineIndex = line || 0, labelEdge = axis.labelEdge, justifyLabel = axis.justifyLabels && (isFirst || isLast), justifyToPlot; // Hide it if it now overlaps the neighbour label - if (labelEdge[line] === UNDEFINED || pxPos + leftSide > labelEdge[line]) { - labelEdge[line] = pxPos + rightSide; + if (labelEdge[lineIndex] === UNDEFINED || pxPos + leftSide > labelEdge[lineIndex]) { + labelEdge[lineIndex] = pxPos + rightSide; } else if (!justifyLabel) { show = false; } @@ -6471,11 +6474,12 @@ //axis.tickPositions = UNDEFINED; // array containing predefined positions // Tick intervals //axis.tickInterval = UNDEFINED; //axis.minorTickInterval = UNDEFINED; - axis.tickmarkOffset = (axis.categories && options.tickmarkPlacement === 'between') ? 0.5 : 0; + axis.tickmarkOffset = (axis.categories && options.tickmarkPlacement === 'between' && + pick(options.tickInterval, 1) === 1) ? 0.5 : 0; // #3202 // Major ticks axis.ticks = {}; axis.labelEdge = []; // Minor ticks @@ -6639,12 +6643,12 @@ var axis = this, chart = axis.chart; axis.hasVisibleSeries = false; - // reset dataMin and dataMax in case we're redrawing - axis.dataMin = axis.dataMax = null; + // Reset properties in case we're redrawing (#3353) + axis.dataMin = axis.dataMax = axis.ignoreMinPadding = axis.ignoreMaxPadding = null; if (axis.buildStacks) { axis.buildStacks(); } @@ -7202,11 +7206,18 @@ } // for linear axes, get magnitude and normalize the interval if (!isDatetimeAxis && !isLog) { // linear if (!tickIntervalOption) { - axis.tickInterval = normalizeTickInterval(axis.tickInterval, null, getMagnitude(axis.tickInterval), options); + axis.tickInterval = normalizeTickInterval( + axis.tickInterval, + null, + getMagnitude(axis.tickInterval), + // If the tick interval is between 1 and 5 and the axis max is in the order of + // thousands, chances are we are dealing with years. Don't allow decimals. #3363. + pick(options.allowDecimals, !(axis.tickInterval > 1 && axis.tickInterval < 5 && axis.max > 1000 && axis.max < 9999)) + ); } } // get minorTickInterval axis.minorTickInterval = options.minorTickInterval === 'auto' && axis.tickInterval ? @@ -7252,15 +7263,10 @@ var roundedMin = tickPositions[0], roundedMax = tickPositions[tickPositions.length - 1], minPointOffset = axis.minPointOffset || 0, singlePad; - // Prevent all ticks from being removed (#3195) - if (!startOnTick && !endOnTick && !categories && tickPositions.length === 2) { - tickPositions.splice(1, 0, (roundedMax + roundedMin) / 2); - } - if (startOnTick) { axis.min = roundedMin; } else if (axis.min - minPointOffset > roundedMin) { tickPositions.shift(); } @@ -7269,10 +7275,15 @@ axis.max = roundedMax; } else if (axis.max + minPointOffset < roundedMax) { 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); + } + // 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 (tickPositions.length === 1) { singlePad = mathAbs(axis.max) > 10e12 ? 1 : 0.001; // The lowest possible number to avoid extra padding on columns (#2619, #2846) @@ -8236,13 +8247,13 @@ } minYear = minDate[getFullYear](); var time = minDate.getTime(), minMonth = minDate[getMonth](), minDateDate = minDate[getDate](), - localTimezoneOffset = useUTC ? - timezoneOffset : - (24 * 3600 * 1000 + minDate.getTimezoneOffset() * 60 * 1000) % (24 * 3600 * 1000); // #950 + localTimezoneOffset = (timeUnits.day + + (useUTC ? timezoneOffset : minDate.getTimezoneOffset() * 60 * 1000) + ) % timeUnits.day; // #950, #3359 // iterate and add tick positions at appropriate values while (time < max) { tickPositions.push(time); @@ -8580,22 +8591,22 @@ }, /** * Hide the tooltip */ - hide: function () { + hide: function (delay) { var tooltip = this, hoverPoints; clearTimeout(this.hideTimer); // disallow duplicate timers (#1728, #1766) if (!this.isHidden) { hoverPoints = this.chart.hoverPoints; this.hideTimer = setTimeout(function () { tooltip.label.fadeOut(); tooltip.isHidden = true; - }, pick(this.options.hideDelay, 500)); + }, pick(delay, this.options.hideDelay, 500)); // hide previous hoverPoints and set new if (hoverPoints) { each(hoverPoints, function (point) { point.setState(); @@ -9163,11 +9174,11 @@ /** * Reset the tracking by hiding the tooltip, the hover series state and the hover point * * @param allowMove {Boolean} Instead of destroying the tooltip altogether, allow moving it if possible */ - reset: function (allowMove) { + reset: function (allowMove, delay) { var pointer = this, chart = pointer.chart, hoverSeries = chart.hoverSeries, hoverPoint = chart.hoverPoint, tooltip = chart.tooltip, @@ -9198,11 +9209,11 @@ if (hoverSeries) { hoverSeries.onMouseOut(); } if (tooltip) { - tooltip.hide(); + tooltip.hide(delay); } if (pointer._onDocumentMouseMove) { removeEvent(doc, 'mousemove', pointer._onDocumentMouseMove); pointer._onDocumentMouseMove = null; @@ -9714,11 +9725,11 @@ lastValidTouch = self.lastValidTouch, hasZoom = self.hasZoom, selectionMarker = self.selectionMarker, transform = {}, fireClickEvent = touchesLength === 1 && ((self.inClass(e.target, PREFIX + 'tracker') && - chart.runTrackerClick) || chart.runChartClick), + chart.runTrackerClick) || self.runChartClick), clip = {}; // On touch devices, only proceed to trigger click if a handler is defined if ((hasZoom || followTouchMove) && !fireClickEvent) { e.preventDefault(); @@ -9750,10 +9761,11 @@ // Store the bounds for use in the touchmove handler bounds.min = mathMin(axis.pos, absMin - minPixelPadding); bounds.max = mathMax(axis.pos + axis.len, absMax + minPixelPadding); } }); + self.res = true; // reset on next move // Event type is touchmove, handle panning and pinching } else if (pinchDown.length) { // can be 0 when releasing, if touchend fires first @@ -9772,10 +9784,13 @@ self.scaleGroups(transform, clip); // Optionally move the tooltip on touchmove if (!hasZoom && followTouchMove && touchesLength === 1) { this.runPointActions(self.normalize(e)); + } else if (self.res) { + self.res = false; + this.reset(false, 0); } } }, onContainerTouchStart: function (e) { @@ -11737,11 +11752,11 @@ zIndex: 1 }) .add(); } else { plotBorder.animate( - plotBorder.crisp({ x: plotLeft, y: plotTop, width: plotWidth, height: plotHeight }) + plotBorder.crisp({ x: plotLeft, y: plotTop, width: plotWidth, height: plotHeight, strokeWidth: -plotBorderWidth }) //#3282 plotBorder should be negative ); } } // reset @@ -12742,11 +12757,11 @@ // If the point count is the same as is was, just run Point.update which is // cheaper, allows animation, and keeps references to points. if (updatePoints !== false && dataLength && oldDataLength === dataLength && !series.cropped && !series.hasGroupedData) { each(data, function (point, i) { - oldData[i].update(point, false); + oldData[i].update(point, false, null, false); }); } else { // Reset properties @@ -12877,18 +12892,19 @@ // the message on to override methods like in data grouping. if (isCartesian && !series.isDirty && !xAxis.isDirty && !series.yAxis.isDirty && !force) { return false; } - - // optionally filter out points outside the plot area - if (isCartesian && series.sorted && (!cropThreshold || dataLength > cropThreshold || series.forceCrop)) { - + if (xAxis) { xExtremes = xAxis.getExtremes(); // corrected for log axis (#3053) min = xExtremes.min; max = xExtremes.max; + } + // optionally filter out points outside the plot area + if (isCartesian && series.sorted && (!cropThreshold || dataLength > cropThreshold || series.forceCrop)) { + // it's outside current extremes if (processedXData[dataLength - 1] < min || processedXData[0] > max) { processedXData = []; processedYData = []; @@ -12909,10 +12925,11 @@ distance = processedXData[i] - processedXData[i - 1]; if (!cropped && processedXData[i] > min && processedXData[i] < max) { activePointCount++; } + if (distance > 0 && (closestPointRange === UNDEFINED || distance < closestPointRange)) { closestPointRange = distance; // Unsorted data is not supported by the line tooltip, as well as data grouping and // navigation in Stock charts (#725) and width calculation of columns (#1900) @@ -13009,10 +13026,11 @@ points[i] = point; } else { // splat the y data in case of ohlc data array points[i] = (new pointClass()).init(series, [processedXData[i]].concat(splat(processedYData[i]))); } + points[i].index = cursor; // For faster access in Point.update } // Hide cropped-away points - this only runs when the number of points is above cropThreshold, or when // swithching view from non-grouped data to grouped data (#637) if (data && (processedDataLength !== (dataLength = data.length) || hasGroupedData)) { @@ -13119,10 +13137,11 @@ stackValues; // Discard disallowed y values for log axes if (yAxis.isLog && yValue <= 0) { point.y = yValue = null; + error(10); } // Get the plotX translation point.plotX = xAxis.translate(xValue, 0, 0, 0, 1, pointPlacement, this.type === 'flags'); // Math.round fixes #591 @@ -13293,16 +13312,17 @@ graphic, options = series.options, seriesMarkerOptions = options.marker, seriesPointAttr = series.pointAttr[''], pointMarkerOptions, + hasPointMarker, enabled, isInside, markerGroup = series.markerGroup, globallyEnabled = pick( seriesMarkerOptions.enabled, - series.activePointCount < (0.5 * series.xAxis.len / seriesMarkerOptions.radius) + !series.requireSorting || series.activePointCount < (0.5 * series.xAxis.len / seriesMarkerOptions.radius) ); if (seriesMarkerOptions.enabled !== false || series._hasPointMarkers) { i = points.length; @@ -13310,10 +13330,11 @@ point = points[i]; plotX = mathFloor(point.plotX); // #1843 plotY = point.plotY; graphic = point.graphic; pointMarkerOptions = point.marker || {}; + hasPointMarker = !!point.marker; enabled = (globallyEnabled && pointMarkerOptions.enabled === UNDEFINED) || pointMarkerOptions.enabled; isInside = chart.isInsidePlot(mathRound(plotX), plotY, chart.inverted); // #1858 // only draw the point if y is defined if (enabled && plotY !== UNDEFINED && !isNaN(plotY) && point.y !== null) { @@ -13337,11 +13358,12 @@ point.graphic = graphic = chart.renderer.symbol( symbol, plotX - radius, plotY - radius, 2 * radius, - 2 * radius + 2 * radius, + hasPointMarker ? pointMarkerOptions : seriesMarkerOptions ) .attr(pointAttr) .add(markerGroup); } @@ -13947,10 +13969,16 @@ if (series.drawGraph) { series.drawGraph(); series.clipNeg(); } + each(series.points, function (point) { + if (point.redraw) { + point.redraw(); + } + }); + // draw the data labels (inn pies they go before the points) if (series.drawDataLabels) { series.drawDataLabels(); } @@ -14474,43 +14502,44 @@ * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call * @param {Boolean|Object} animation Whether to apply animation, and optionally animation * configuration * */ - update: function (options, redraw, animation) { + update: function (options, redraw, animation, runEvent) { var point = this, series = point.series, graphic = point.graphic, i, - data = series.data, chart = series.chart, seriesOptions = series.options; redraw = pick(redraw, true); - // fire the event with a default handler of doing the update - point.firePointEvent('update', { options: options }, function () { + function update() { point.applyOptions(options); - // update visuals - if (isObject(options)) { - series.getAttribs(); - if (graphic) { - if (options && options.marker && options.marker.symbol) { - point.graphic = graphic.destroy(); - } else { - graphic.attr(point.pointAttr[point.state || '']); + // Update visuals + if (isObject(options) && !isArray(options)) { + // Defer the actual redraw until getAttribs has been called (#3260) + point.redraw = function () { + if (graphic) { + if (options && options.marker && options.marker.symbol) { + point.graphic = graphic.destroy(); + } else { + graphic.attr(point.pointAttr[point.state || '']); + } } - } - if (options && options.dataLabels && point.dataLabel) { // #2468 - point.dataLabel = point.dataLabel.destroy(); - } + if (options && options.dataLabels && point.dataLabel) { // #2468 + point.dataLabel = point.dataLabel.destroy(); + } + point.redraw = null; + }; } // record changes in the parallel arrays - i = inArray(point, data); + i = point.index; series.updateParallelArrays(point, i); seriesOptions.data[i] = point.options; // redraw @@ -14523,11 +14552,18 @@ chart.legend.destroyItem(point); } if (redraw) { chart.redraw(animation); } - }); + } + + // Fire the event with a default handler of doing the update + if (runEvent === false) { // When called from setData + update(); + } else { + point.firePointEvent('update', { options: options }, update); + } }, /** * Remove a point and optionally redraw the series and if necessary the axes * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call @@ -14629,11 +14665,11 @@ } series.updateParallelArrays(point, 'splice', i, 0, 0); // insert undefined item series.updateParallelArrays(point, i); // update it - if (names) { + if (names && point.name) { names[x] = point.name; } dataOptions.splice(i, 0, options); if (isInTheMiddle) { @@ -15393,12 +15429,14 @@ // Cache for access in polar point.barX = barX; point.pointWidth = pointWidth; - // Fix the tooltip on center of grouped columns (#1216) - point.tooltipPos = chart.inverted ? [yAxis.len - plotY, series.xAxis.len - barX - barW / 2] : [barX + barW / 2, plotY]; + // Fix the tooltip on center of grouped columns (#1216, #424) + point.tooltipPos = chart.inverted ? + [yAxis.len - plotY, series.xAxis.len - barX - barW / 2] : + [barX + barW / 2, plotY + yAxis.pos - chart.plotTop]; // Round off to obtain crisp edges and avoid overlapping with neighbours (#2694) right = mathRound(barX + barW) + xCrisp; barX = mathRound(barX) + xCrisp; barW = right - barX; @@ -16066,10 +16104,11 @@ cursor = seriesOptions.cursor, options = seriesOptions.dataLabels, points = series.points, pointOptions, generalOptions, + hasRendered = series.hasRendered || 0, str, dataLabelsGroup; if (options.enabled || series._hasPointLabels) { @@ -16084,17 +16123,19 @@ 'data-labels', options.defer ? HIDDEN : VISIBLE, options.zIndex || 6 ); - if (!series.hasRendered && pick(options.defer, true)) { - dataLabelsGroup.attr({ opacity: 0 }); - addEvent(series, 'afterAnimate', function () { - if (series.visible) { // #3023, #3024 - dataLabelsGroup.show(); - } - dataLabelsGroup[seriesOptions.animation ? 'animate' : 'attr']({ opacity: 1 }, { duration: 200 }); - }); + if (pick(options.defer, true)) { + dataLabelsGroup.attr({ opacity: +hasRendered }); // #3300 + if (!hasRendered) { + addEvent(series, 'afterAnimate', function () { + if (series.visible) { // #3023, #3024 + dataLabelsGroup.show(); + } + dataLabelsGroup[seriesOptions.animation ? 'animate' : 'attr']({ opacity: 1 }, { duration: 200 }); + }); + } } // Make the labels for each point generalOptions = options; each(points, function (point) {