app/assets/javascripts/highcharts.js in highcharts-rails-4.1.5 vs app/assets/javascripts/highcharts.js in highcharts-rails-4.1.6

- old
+ new

@@ -1,10 +1,10 @@ // ==ClosureCompiler== // @compilation_level SIMPLE_OPTIMIZATIONS /** - * @license Highcharts JS v4.1.5 (2015-04-13) + * @license Highcharts JS v4.1.6 (2015-06-12) * * (c) 2009-2014 Torstein Honsi * * License: www.highcharts.com/license */ @@ -54,11 +54,11 @@ timeUnits, noop = function () { return UNDEFINED; }, charts = [], chartCount = 0, PRODUCT = 'Highcharts', - VERSION = '4.1.5', + VERSION = '4.1.6', // some constants for frequently used strings DIV = 'div', ABSOLUTE = 'absolute', RELATIVE = 'relative', @@ -367,10 +367,17 @@ // Create an array of the remaining length +1 and join it with 0's return new Array((length || 2) + 1 - String(number).length).join(0) + number; } /** + * Return a length based on either the integer value, or a percentage of a base. + */ +function relativeLength (value, base) { + return (/%$/).test(value) ? base * parseFloat(value) / 100 : parseFloat(value); +} + +/** * Wrap a method with extended functionality, preserving the original function * @param {Object} obj The context object that the method belongs to * @param {String} method The name of the method to extend * @param {Function} func A wrapper function callback. This function is called with the same arguments * as the original function, except that the original function is unshifted and passed as the first @@ -1259,12 +1266,12 @@ thousandsSep: ' ' }, global: { useUTC: true, //timezoneOffset: 0, - canvasToolsURL: 'http://code.highcharts.com/4.1.5/modules/canvas-tools.js', - VMLRadialGradientURL: 'http://code.highcharts.com/4.1.5/gfx/vml-radial-gradient.png' + canvasToolsURL: 'http://code.highcharts.com/4.1.6/modules/canvas-tools.js', + VMLRadialGradientURL: 'http://code.highcharts.com/4.1.6/gfx/vml-radial-gradient.png' }, chart: { //animation: true, //alignTicks: false, //reflow: true, @@ -1942,33 +1949,36 @@ */ applyTextShadow: function (textShadow) { var elem = this.element, tspans, hasContrast = textShadow.indexOf('contrast') !== -1, + styles = {}, // IE10 and IE11 report textShadow in elem.style even though it doesn't work. Check // this again with new IE release. In exports, the rendering is passed to PhantomJS. supports = this.renderer.forExport || (elem.style.textShadow !== UNDEFINED && !isIE); // When the text shadow is set to contrast, use dark stroke for light text and vice versa if (hasContrast) { - textShadow = textShadow.replace(/contrast/g, this.renderer.getContrast(elem.style.fill)); + styles.textShadow = textShadow = textShadow.replace(/contrast/g, this.renderer.getContrast(elem.style.fill)); } + // Safari with retina displays as well as PhantomJS bug (#3974). Firefox does not tolerate this, + // it removes the text shadows. + if (isWebKit) { + styles.textRendering = 'geometricPrecision'; + } + /* Selective side-by-side testing in supported browser (http://jsfiddle.net/highcharts/73L1ptrh/) if (elem.textContent.indexOf('2.') === 0) { elem.style['text-shadow'] = 'none'; supports = false; } // */ // No reason to polyfill, we've got native support if (supports) { - if (hasContrast) { // Apply the altered style - css(elem, { - textShadow: textShadow - }); - } + css(elem, styles); // Apply altered textShadow or textRendering workaround } else { this.fakeTS = true; // Fake text shadow // In order to get the right y position of the clones, @@ -3267,11 +3277,11 @@ } else { if (tooLong) { wasTooLong = true; } wordStr = span.substring(0, wordStr.length + (tooLong ? -1 : 1) * mathCeil(cursor)); - words = [wordStr + '\u2026']; + words = [wordStr + (width > 3 ? '\u2026' : '')]; tspan.removeChild(tspan.firstChild); } // Looping down, this is the first word sequence that is not too long, // so we can move on to build the next line. @@ -3363,11 +3373,11 @@ /** * Returns white for dark colors and black for bright colors */ getContrast: function (color) { color = Color(color).rgba; - return color[0] + color[1] + color[2] > 384 ? '#000' : '#FFF'; + return color[0] + color[1] + color[2] > 384 ? '#000000' : '#FFFFFF'; }, /** * Create a button with preset states * @param {String} text @@ -3887,15 +3897,12 @@ halfDistance = 6, r = mathMin((options && options.r) || 0, w, h), safeDistance = r + halfDistance, anchorX = options && options.anchorX, anchorY = options && options.anchorY, - path, - normalizer = mathRound(options.strokeWidth || 0) % 2 / 2; // mathRound because strokeWidth can sometimes have roundoff errors; + path; - x += normalizer; - y += normalizer; path = [ 'M', x + r, y, 'L', x + w - r, y, // top side 'C', x + w, y, x + w, y, x + w, y + r, // top-right corner 'L', x + w, y + h - r, // right side @@ -4119,12 +4126,12 @@ if (needsBox) { // create the border box if it is not already present if (!box) { - boxX = mathRound(-alignFactor * padding); - boxY = baseline ? -baselineOffset : 0; + boxX = mathRound(-alignFactor * padding) + crispAdjust; + boxY = (baseline ? -baselineOffset : 0) + crispAdjust; wrapper.box = box = shape ? renderer.symbol(shape, boxX, boxY, wrapper.width, wrapper.height, deferredAttr) : renderer.rect(boxX, boxY, wrapper.width, wrapper.height, 0, deferredAttr[STROKE_WIDTH]); box.attr('fill', NONE).add(wrapper); @@ -4160,15 +4167,11 @@ // update if anything changed if (x !== text.x || y !== text.y) { text.attr('x', x); if (y !== UNDEFINED) { - // As a workaround for #3649, use translation instead of y attribute. #3649 - // is a rendering bug in WebKit for Retina (Mac, iOS, PhantomJS) that - // results in duplicated text when an y attribute is used in combination - // with a CSS text-style. - text.attr(text.element.nodeName === 'SPAN' ? 'y' : 'translateY', y); + text.attr('y', y); } } // record current values text.x = x; @@ -4261,11 +4264,11 @@ } boxAttr(key, value); }; wrapper.anchorXSetter = function (value, key) { anchorX = value; - boxAttr(key, value + crispAdjust - wrapperX); + boxAttr(key, mathRound(value) - crispAdjust - wrapperX); }; wrapper.anchorYSetter = function (value, key) { anchorY = value; boxAttr(key, value - wrapperY); }; @@ -5894,37 +5897,39 @@ label = this.label, rotation = this.rotation, factor = { left: 0, center: 0.5, right: 1 }[axis.labelAlign], labelWidth = label.getBBox().width, slotWidth = axis.slotWidth, + xCorrection = factor, + goRight = 1, leftPos, rightPos, textWidth; // Check if the label overshoots the chart spacing box. If it does, move it. // If it now overshoots the slotWidth, add ellipsis. if (!rotation) { leftPos = pxPos - factor * labelWidth; - rightPos = pxPos + factor * labelWidth; + rightPos = pxPos + (1 - factor) * labelWidth; if (leftPos < leftBound) { - slotWidth -= leftBound - leftPos; - xy.x = leftBound; - label.attr({ align: 'left' }); + slotWidth = xy.x + slotWidth * (1 - factor) - leftBound; } else if (rightPos > rightBound) { - slotWidth -= rightPos - rightBound; - xy.x = rightBound; - label.attr({ align: 'right' }); + slotWidth = rightBound - xy.x + slotWidth * factor; + goRight = -1; } + slotWidth = mathMin(axis.slotWidth, slotWidth); // #4177 + if (slotWidth < axis.slotWidth && axis.labelAlign === 'center') { + xy.x += goRight * (axis.slotWidth - slotWidth - xCorrection * (axis.slotWidth - mathMin(labelWidth, slotWidth))); + } // If the label width exceeds the available space, set a text width to be // picked up below. Also, if a width has been set before, we need to set a new // one because the reported labelWidth will be limited by the box (#3938). if (labelWidth > slotWidth || (axis.autoRotation && label.styles.width)) { textWidth = slotWidth; } - // Add ellipsis to prevent rotated labels to be clipped against the edge of the chart } else if (rotation < 0 && pxPos - factor * labelWidth < leftBound) { textWidth = mathRound(pxPos / mathCos(rotation * deg2rad) - leftBound); } else if (rotation > 0 && pxPos + factor * labelWidth > rightBound) { @@ -6907,11 +6912,11 @@ /** * Translate from axis value to pixel position on the chart, or back * */ translate: function (val, backwards, cvsCoord, old, handleLog, pointPlacement) { - var axis = this, + var axis = this.linkedParent || this, // #1417 sign = 1, cvsOffset = 0, localA = old ? axis.oldTransA : axis.transA, localMin = old ? axis.oldMin : axis.min, returnValue, @@ -7369,11 +7374,11 @@ // get tickInterval if (axis.min === axis.max || axis.min === undefined || axis.max === undefined) { axis.tickInterval = 1; } else if (isLinked && !tickIntervalOption && tickPixelIntervalOption === axis.linkedParent.options.tickPixelInterval) { - axis.tickInterval = axis.linkedParent.tickInterval; + axis.tickInterval = tickIntervalOption = axis.linkedParent.tickInterval; } else { axis.tickInterval = pick( tickIntervalOption, this.tickAmount ? ((axis.max - axis.min) / mathMax(this.tickAmount - 1, 1)) : undefined, categories ? // for categoried axis, 1 is default, for linear axis use tickPix @@ -7414,24 +7419,22 @@ if (!tickIntervalOption && axis.tickInterval < minTickInterval) { axis.tickInterval = minTickInterval; } // for linear axes, get magnitude and normalize the interval - if (!isDatetimeAxis && !isLog) { // linear - if (!tickIntervalOption) { - axis.tickInterval = normalizeTickInterval( - axis.tickInterval, - null, - getMagnitude(axis.tickInterval), - // If the tick interval is between 0.5 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 > 0.5 && axis.tickInterval < 5 && axis.max > 1000 && axis.max < 9999)), - !!this.tickAmount - ); - } + if (!isDatetimeAxis && !isLog && !tickIntervalOption) { + axis.tickInterval = normalizeTickInterval( + axis.tickInterval, + null, + getMagnitude(axis.tickInterval), + // If the tick interval is between 0.5 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 > 0.5 && axis.tickInterval < 5 && axis.max > 1000 && axis.max < 9999)), + !!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 axis.tickInterval = axis.unsquish(); } @@ -7459,11 +7462,11 @@ // get minorTickInterval this.minorTickInterval = options.minorTickInterval === 'auto' && this.tickInterval ? this.tickInterval / 5 : options.minorTickInterval; // Find the tick positions - this.tickPositions = tickPositions = options.tickPositions && options.tickPositions.slice(); // Work on a copy (#1565) + this.tickPositions = tickPositions = tickPositionsOption && tickPositionsOption.slice(); // Work on a copy (#1565) if (!tickPositions) { if (this.isDatetimeAxis) { tickPositions = this.getTimeTicks( this.normalizeTimeTickInterval(this.tickInterval, options.units), @@ -7561,11 +7564,13 @@ var options = axis.options, horiz = axis.horiz, key = [horiz ? options.left : options.top, horiz ? options.width : options.height, options.pane].join(','); if (others[key]) { - hasOther = true; + if (axis.series.length) { + hasOther = true; // #4201 + } } else { others[key] = 1; } }); @@ -7937,12 +7942,13 @@ tickPositions = this.tickPositions, ticks = this.ticks, labelOptions = this.options.labels, horiz = this.horiz, margin = chart.margin, + slotCount = this.categories ? tickPositions.length : tickPositions.length - 1, slotWidth = this.slotWidth = (horiz && !labelOptions.step && !labelOptions.rotation && - ((this.staggerLines || 1) * chart.plotWidth) / tickPositions.length) || + ((this.staggerLines || 1) * chart.plotWidth) / slotCount) || (!horiz && ((margin[3] && (margin[3] - chart.spacing[3])) || chart.chartWidth * 0.33)), // #1580, #1931, innerWidth = mathMax(1, mathRound(slotWidth - 2 * (labelOptions.padding || 5))), attr = {}, labelMetrics = renderer.fontMetrics(labelOptions.style.fontSize, ticks[0] && ticks[0].label), css, @@ -8206,10 +8212,12 @@ axisLength = this.len, axisTitleOptions = this.options.title, margin = horiz ? axisLeft : axisTop, opposite = this.opposite, offset = this.offset, + xOption = axisTitleOptions.x || 0, + yOption = axisTitleOptions.y || 0, fontSize = pInt(axisTitleOptions.style.fontSize || 12), // the position in the length direction of the axis alongAxis = { low: margin + (horiz ? 0 : axisLength), @@ -8224,16 +8232,15 @@ this.axisTitleMargin + (this.side === 2 ? fontSize : 0); return { x: horiz ? - alongAxis : - offAxis + (opposite ? this.width : 0) + offset + - (axisTitleOptions.x || 0), // x + alongAxis + xOption : + offAxis + (opposite ? this.width : 0) + offset + xOption, y: horiz ? - offAxis - (opposite ? this.height : 0) + offset : - alongAxis + (axisTitleOptions.y || 0) // y + offAxis + yOption - (opposite ? this.height : 0) + offset : + alongAxis + yOption }; }, /** * Render the axis @@ -8509,11 +8516,13 @@ if ( // Disabled in options !this.crosshair || // Snap - ((defined(point) || !pick(this.crosshair.snap, true)) === false) + ((defined(point) || !pick(this.crosshair.snap, true)) === false) || + // Not on this axis (#4095, #2888) + (point && point.series && point.series[this.coll] !== this) ) { this.hideCrosshair(); } else { @@ -8998,20 +9007,10 @@ this.hideTimer = setTimeout(function () { tooltip.label.fadeOut(); tooltip.isHidden = true; }, pick(delay, this.options.hideDelay, 500)); - - // hide previous hoverPoints and set new - if (hoverPoints) { - each(hoverPoints, function (point) { - point.setState(); - }); - } - - this.chart.hoverPoints = null; - this.chart.hoverSeries = null; } }, /** * Extendable method to get the anchor position of the tooltip @@ -9074,11 +9073,11 @@ getPosition: function (boxWidth, boxHeight, point) { var chart = this.chart, distance = this.distance, ret = {}, - h = point.h, + h = point.h || 0, // #4117 swapped, first = ['y', chart.chartHeight, boxHeight, point.plotY + chart.plotTop], second = ['x', chart.chartWidth, boxWidth, point.plotX + chart.plotLeft], // The far side is right or bottom preferFarSide = pick(point.ttBelow, (chart.inverted && !point.negative) || (!chart.inverted && point.negative)), @@ -9268,11 +9267,11 @@ tooltip.updatePosition({ plotX: x, plotY: y, negative: point.negative, ttBelow: point.ttBelow, - h: (point.shapeArgs && point.shapeArgs.height) || 0 + h: anchor[2] || 0 }); this.isHidden = false; } fireEvent(chart, 'tooltipRefresh', { @@ -9297,11 +9296,11 @@ ); // do the move this.move( mathRound(pos.x), - mathRound(pos.y), + mathRound(pos.y || 0), // can be undefined (#3977) point.plotX + chart.plotLeft, point.plotY + chart.plotTop ); }, @@ -9320,11 +9319,11 @@ minute: 9, hour: 6, day: 3 }, date, - lastN; + lastN = 'millisecond'; // for sub-millisecond data, #4223 if (closestPointRange) { date = dateFormat('%m-%d %H:%M:%S.%L', point.x); for (n in timeUnits) { @@ -9530,20 +9529,17 @@ chart = pointer.chart, series = chart.series, tooltip = chart.tooltip, shared = tooltip ? tooltip.shared : false, followPointer, - //point, - //points, hoverPoint = chart.hoverPoint, hoverSeries = chart.hoverSeries, i, - //j, distance = chart.chartWidth, - rdistance = chart.chartWidth, anchor, noSharedTooltip, + directTouch, kdpoints = [], kdpoint, kdpointT; // For hovering over the empty parts of the plot area (hoverSeries is undefined). @@ -9555,42 +9551,39 @@ } } } // If it has a hoverPoint and that series requires direct touch (like columns), - // use the hoverPoint (#3899). Otherwise, search the k-d tree. + // use the hoverPoint (#3899). Otherwise, search the k-d tree. if (!shared && hoverSeries && hoverSeries.directTouch && hoverPoint) { kdpoint = hoverPoint; // Handle shared tooltip or cases where a series is not yet hovered } else { // Find nearest points on all series each(series, function (s) { // Skip hidden series noSharedTooltip = s.noSharedTooltip && shared; - if (s.visible && !noSharedTooltip && pick(s.options.enableMouseTracking, true)) { // #3821 - kdpointT = s.searchPoint(e); // #3828 + directTouch = !shared && s.directTouch; + if (s.visible && !noSharedTooltip && !directTouch && pick(s.options.enableMouseTracking, true)) { // #3821 + kdpointT = s.searchPoint(e, !noSharedTooltip && s.kdDimensions === 1); // #3828 if (kdpointT) { kdpoints.push(kdpointT); } } }); // Find absolute nearest point each(kdpoints, function (p) { - if (p && defined(p.plotX) && defined(p.plotY)) { - if ((p.dist.distX < distance) || ((p.dist.distX === distance || p.series.kdDimensions > 1) && - p.dist.distR < rdistance)) { - distance = p.dist.distX; - rdistance = p.dist.distR; - kdpoint = p; - } + if (p && typeof p.dist === 'number' && p.dist < distance) { + distance = p.dist; + kdpoint = p; } }); } - // Refresh tooltip for kdpoint if new hover point or tooltip was hidden // #3926 - if (kdpoint && (kdpoint !== hoverPoint || (tooltip && tooltip.isHidden))) { + // Refresh tooltip for kdpoint if new hover point or tooltip was hidden // #3926, #4200 + if (kdpoint && (kdpoint !== this.prevKDPoint || (tooltip && tooltip.isHidden))) { // Draw tooltip if necessary if (shared && !kdpoint.series.noSharedTooltip) { i = kdpoints.length; while (i--) { if (kdpoints[i].clientX !== kdpoint.clientX || kdpoints[i].series.noSharedTooltip) { @@ -9613,10 +9606,11 @@ if (tooltip) { tooltip.refresh(kdpoint, e); } kdpoint.onMouseOver(e); } + this.prevKDPoint = kdpoint; // Update positions (regardless of kdpoint or hoverPoint) } else { followPointer = hoverSeries && hoverSeries.tooltipOptions.followPointer; if (tooltip && followPointer && !tooltip.isHidden) { @@ -9637,11 +9631,12 @@ // Crosshair each(chart.axes, function (axis) { axis.drawCrosshair(e, pick(kdpoint, hoverPoint)); }); - + + }, /** @@ -9652,12 +9647,13 @@ reset: function (allowMove, delay) { var pointer = this, chart = pointer.chart, hoverSeries = chart.hoverSeries, hoverPoint = chart.hoverPoint, + hoverPoints = chart.hoverPoints, tooltip = chart.tooltip, - tooltipPoints = tooltip && tooltip.shared ? chart.hoverPoints : hoverPoint; + tooltipPoints = tooltip && tooltip.shared ? hoverPoints : hoverPoint; // Narrow in allowMove allowMove = allowMove && tooltip && tooltipPoints; // Check if the points have moved outside the plot area, #1003 @@ -9669,11 +9665,11 @@ tooltip.refresh(tooltipPoints); if (hoverPoint) { // #2500 hoverPoint.setState(hoverPoint.state, true); each(chart.axes, function (axis) { if (pick(axis.options.crosshair && axis.options.crosshair.snap, true)) { - axis.drawCrosshair(null, allowMove); + axis.drawCrosshair(null, hoverPoint); } else { axis.hideCrosshair(); } }); @@ -9684,10 +9680,16 @@ if (hoverPoint) { hoverPoint.onMouseOut(); } + if (hoverPoints) { + each(hoverPoints, function (point) { + point.setState(); + }); + } + if (hoverSeries) { hoverSeries.onMouseOut(); } if (tooltip) { @@ -9702,11 +9704,11 @@ // Remove crosshairs each(chart.axes, function (axis) { axis.hideCrosshair(); }); - pointer.hoverX = null; + pointer.hoverX = chart.hoverPoints = chart.hoverPoint = null; } }, /** @@ -10005,11 +10007,10 @@ plotLeft = chart.plotLeft, plotTop = chart.plotTop; e = this.normalize(e); e.originalEvent = e; // #3913 - e.cancelBubble = true; // IE specific if (!chart.cancelClick) { // On tracker click, fire the series and point events. #783, #1583 if (hoverPoint && this.inClass(e.target, PREFIX + 'tracker')) { @@ -10206,12 +10207,18 @@ transform = {}, fireClickEvent = touchesLength === 1 && ((self.inClass(e.target, PREFIX + 'tracker') && chart.runTrackerClick) || self.runChartClick), clip = {}; + // Don't initiate panning until the user has pinched. This prevents us from + // blocking page scrolling as users scroll down a long page (#4210). + if (touchesLength > 1) { + self.initiated = true; + } + // On touch devices, only proceed to trigger click if a handler is defined - if (hasZoom && !fireClickEvent) { + if (hasZoom && self.initiated && !fireClickEvent) { e.preventDefault(); } // Normalize each touch map(touches, function (e) { @@ -10269,11 +10276,14 @@ this.reset(false, 0); } } }, - onContainerTouchStart: function (e) { + /** + * General touch handler shared by touchstart and touchmove. + */ + touch: function (e, start) { var chart = this.chart; hoverChartIndex = chart.index; if (e.touches.length === 1) { @@ -10281,28 +10291,32 @@ e = this.normalize(e); if (chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop) && !chart.openMenu) { // Run mouse events and display tooltip etc - this.runPointActions(e); + if (start) { + this.runPointActions(e); + } this.pinch(e); - } else { + } else if (start) { // Hide the tooltip on touching outside the plot area (#1203) this.reset(); } } else if (e.touches.length === 2) { this.pinch(e); - } + } }, + onContainerTouchStart: function (e) { + this.touch(e, true); + }, + onContainerTouchMove: function (e) { - if (e.touches.length === 1 || e.touches.length === 2) { - this.pinch(e); - } + this.touch(e); }, onDocumentTouchEnd: function (e) { if (charts[hoverChartIndex]) { charts[hoverChartIndex].pointer.drop(e); @@ -10503,14 +10517,15 @@ symbolPadding = options.symbolPadding, ltr = !options.rtl, legendItemPos = item._legendItemPos, itemX = legendItemPos[0], itemY = legendItemPos[1], - checkbox = item.checkbox; + checkbox = item.checkbox, + legendGroup = item.legendGroup; - if (item.legendGroup) { - item.legendGroup.translate( + if (legendGroup && legendGroup.element) { + legendGroup.translate( ltr ? itemX : legend.legendWidth - itemX - 2 * symbolPadding - 4, itemY ); } @@ -10538,20 +10553,10 @@ discardElement(item.checkbox); } }, /** - * Destroy all items. - */ - clearItems: function () { - var legend = this; - each(legend.getAllItems(), function (item) { - legend.destroyItem(item); - }); - }, - - /** * Destroys the legend. */ destroy: function () { var legend = this, legendGroup = legend.group, @@ -10616,10 +10621,20 @@ } this.titleHeight = titleHeight; }, /** + * Set the legend item text + */ + setText: function (item) { + var options = this.options; + item.legendItem.attr({ + text: options.labelFormat ? format(options.labelFormat, item) : options.labelFormatter.call(item) + }); + }, + + /** * Render a single specific legend item * @param {Object} item A series or point */ renderItem: function (item) { var legend = this, @@ -10655,11 +10670,11 @@ .attr({ zIndex: 1 }) .add(legend.scrollGroup); // Generate the list item text and add it to the group item.legendItem = li = renderer.text( - options.labelFormat ? format(options.labelFormat, item) : options.labelFormatter.call(item), + '', ltr ? symbolWidth + symbolPadding : -symbolPadding, legend.baseline || 0, useHTML ) .css(merge(item.visible ? itemStyle : itemHiddenStyle)) // merge to prevent modifying original (#1021) @@ -10690,10 +10705,13 @@ if (showCheckbox) { legend.createCheckboxForItem(item); } } + // Always update the text + legend.setText(item); + // calculate the positions for the next line bBox = li.getBBox(); itemWidth = item.checkboxOffset = options.itemWidth || @@ -11400,14 +11418,17 @@ serie.isDirty = true; } } } - // handle updated data in the series + // Handle updated data in the series each(series, function (serie) { - if (serie.isDirty) { // prepare the data so axis can read it + if (serie.isDirty) { if (serie.options.legendType === 'point') { + if (serie.updateTotals) { + serie.updateTotals(); + } redrawLegend = true; } } }); @@ -12636,26 +12657,24 @@ plotWidth = chart.plotWidth - 2 * slicingRoom, plotHeight = chart.plotHeight - 2 * slicingRoom, centerOption = options.center, positions = [pick(centerOption[0], '50%'), pick(centerOption[1], '50%'), options.size || '100%', options.innerSize || 0], smallestSize = mathMin(plotWidth, plotHeight), - isPercent, i, value; for (i = 0; i < 4; ++i) { value = positions[i]; - isPercent = /%$/.test(value); - handleSlicingRoom = i < 2 || (i === 2 && isPercent); - positions[i] = (isPercent ? - // i == 0: centerX, relative to width - // i == 1: centerY, relative to height - // i == 2: size, relative to smallestSize - // i == 3: innerSize, relative to size - [plotWidth, plotHeight, smallestSize, positions[2]][i] * - pInt(value) / 100 : - pInt(value)) + (handleSlicingRoom ? slicingRoom : 0); + handleSlicingRoom = i < 2 || (i === 2 && /%$/.test(value)); + + // i == 0: centerX, relative to width + // i == 1: centerY, relative to height + // i == 2: size, relative to smallestSize + // i == 3: innerSize, relative to size + positions[i] = relativeLength(value, [plotWidth, plotHeight, smallestSize, positions[2]][i]) + + (handleSlicingRoom ? slicingRoom : 0); + } return positions; } }; @@ -12726,11 +12745,11 @@ * Transform number or array configs into objects */ optionsToObject: function (options) { var ret = {}, series = this.series, - keys = series.options.keys, // docs: http://jsfiddle.net/ch4v7n8v/1 + keys = series.options.keys, pointArrayMap = keys || series.pointArrayMap || ['y'], valueCount = pointArrayMap.length, firstItemType, i = 0, j = 0; @@ -13589,12 +13608,12 @@ 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)); - withinRange = this.getExtremesFromAll || this.cropped || ((xData[i + 1] || x) >= xMin && - (xData[i - 1] || x) <= xMax); + withinRange = this.getExtremesFromAll || this.options.getExtremesFromAll || this.cropped || + ((xData[i + 1] || x) >= xMin && (xData[i - 1] || x) <= xMax); if (validValue && withinRange) { j = y.length; if (j) { // array, like ohlc or range data @@ -13632,10 +13651,11 @@ hasModifyValue = !!series.modifyValue, i, pointPlacement = options.pointPlacement, dynamicallyPlaced = pointPlacement === 'between' || isNumber(pointPlacement), threshold = options.threshold, + stackThreshold = options.startFromThreshold ? threshold : 0, plotX, plotY, lastPlotX, closestPointRangePx = Number.MAX_VALUE; @@ -13643,33 +13663,33 @@ for (i = 0; i < dataLength; i++) { var point = points[i], xValue = point.x, yValue = point.y, yBottom = point.low, - stack = stacking && yAxis.stacks[(series.negStacks && yValue < threshold ? '-' : '') + series.stackKey], + stack = stacking && yAxis.stacks[(series.negStacks && yValue < (stackThreshold ? 0 : threshold) ? '-' : '') + series.stackKey], pointStack, stackValues; // Discard disallowed y values for log axes (#3434) if (yAxis.isLog && yValue !== null && yValue <= 0) { point.y = yValue = null; error(10); } // Get the plotX translation - point.plotX = plotX = xAxis.translate(xValue, 0, 0, 0, 1, pointPlacement, this.type === 'flags'); // Math.round fixes #591 + point.plotX = plotX = mathMin(mathMax(-1e5, xAxis.translate(xValue, 0, 0, 0, 1, pointPlacement, this.type === 'flags')), 1e5); // #3923 // Calculate the bottom y value for stacked series if (stacking && series.visible && stack && stack[xValue]) { pointStack = stack[xValue]; stackValues = pointStack.points[series.index + ',' + i]; yBottom = stackValues[0]; yValue = stackValues[1]; - if (yBottom === 0) { + if (yBottom === stackThreshold) { yBottom = pick(threshold, yAxis.min); } if (yAxis.isLog && yBottom <= 0) { // #1200, #1232 yBottom = null; } @@ -14318,12 +14338,17 @@ graph = this.graph, area = this.area, chartSizeMax = mathMax(chart.chartWidth, chart.chartHeight), zoneAxis = this.zoneAxis || 'y', axis = this[zoneAxis + 'Axis'], + extremes, reversed = axis.reversed, + inverted = chart.inverted, horiz = axis.horiz, + pxRange, + pxPosMin, + pxPosMax, ignoreZones = false; if (zones.length && (graph || area)) { // The use of the Color Threshold assumes there are no gaps // so it is safe to hide the original graph and area @@ -14333,52 +14358,54 @@ if (area) { area.hide(); } // Create the clips + extremes = axis.getExtremes(); each(zones, function (threshold, i) { - translatedFrom = pick(translatedTo, (reversed ? (horiz ? chart.plotWidth : 0) : (horiz ? 0 : axis.toPixels(axis.min)))); - translatedTo = mathRound(axis.toPixels(pick(threshold.value, axis.max), true)); - if (axis.isXAxis) { - translatedFrom = translatedFrom > translatedTo ? translatedTo : translatedFrom; //#4006 from should be less or equal then to - } else { - translatedFrom = translatedFrom < translatedTo ? translatedTo : translatedFrom; //#4006 from should be less or equal then to - } - + translatedFrom = reversed ? + (horiz ? chart.plotWidth : 0) : + (horiz ? 0 : axis.toPixels(extremes.min)); + translatedFrom = mathMin(mathMax(pick(translatedTo, translatedFrom), 0), chartSizeMax); + translatedTo = mathMin(mathMax(mathRound(axis.toPixels(pick(threshold.value, extremes.max), true)), 0), chartSizeMax); + if (ignoreZones) { - translatedFrom = translatedTo = axis.toPixels(axis.max); + translatedFrom = translatedTo = axis.toPixels(extremes.max); } + pxRange = Math.abs(translatedFrom - translatedTo); + pxPosMin = mathMin(translatedFrom, translatedTo); + pxPosMax = mathMax(translatedFrom, translatedTo); if (axis.isXAxis) { clipAttr = { - x: reversed ? translatedTo : translatedFrom, + x: inverted ? pxPosMax : pxPosMin, y: 0, - width: Math.abs(translatedFrom - translatedTo), + width: pxRange, height: chartSizeMax }; if (!horiz) { clipAttr.x = chart.plotHeight - clipAttr.x; } } else { clipAttr = { x: 0, - y: reversed ? translatedFrom : translatedTo, + y: inverted ? pxPosMax : pxPosMin, width: chartSizeMax, - height: Math.abs(translatedFrom - translatedTo) - }; + height: pxRange + }; if (horiz) { clipAttr.y = chart.plotWidth - clipAttr.y; } - } + } /// VML SUPPPORT if (chart.inverted && renderer.isVML) { if (axis.isXAxis) { clipAttr = { x: 0, - y: reversed ? translatedFrom : translatedTo, + y: reversed ? pxPosMin : pxPosMax, height: clipAttr.width, width: chart.chartWidth }; } else { clipAttr = { @@ -14403,11 +14430,11 @@ if (area) { series['zoneArea' + i].clip(clips[i]); } } // if this zone extends out of the axis, ignore the others - ignoreZones = threshold.value > axis.max; + ignoreZones = threshold.value > extremes.max; }); this.clips = clips; } }, @@ -14637,24 +14664,22 @@ /** * KD Tree && PointSearching Implementation */ kdDimensions: 1, - kdTree: null, kdAxisArray: ['clientX', 'plotY'], - kdComparer: 'distX', - searchPoint: function (e) { + searchPoint: function (e, compareX) { var series = this, xAxis = series.xAxis, yAxis = series.yAxis, inverted = series.chart.inverted; return this.searchKDTree({ clientX: inverted ? xAxis.len - e.chartY + xAxis.pos : e.chartX - xAxis.pos, plotY: inverted ? yAxis.len - e.chartX + yAxis.pos : e.chartY - yAxis.pos - }); + }, compareX); }, buildKDTree: function () { var series = this, dimensions = series.kdDimensions; @@ -14673,11 +14698,11 @@ return a[axis] - b[axis]; }); median = Math.floor(length / 2); - // build and return node + // build and return nod return { point: points[median], left: _kdtree(points.slice(0, median), depth + 1, dimensions), right: _kdtree(points.slice(median + 1), depth + 1, dimensions) }; @@ -14688,70 +14713,68 @@ // Start the recursive build process with a clone of the points array and null points filtered out (#3873) function startRecursive() { var points = grep(series.points, function (point) { return point.y !== null; }); - series.kdTree = _kdtree(points, dimensions, dimensions); - } + series.kdTree = _kdtree(points, dimensions, dimensions); + } delete series.kdTree; if (series.options.kdSync) { // For testing tooltips, don't build async startRecursive(); } else { setTimeout(startRecursive); } }, - searchKDTree: function (point) { + searchKDTree: function (point, compareX) { var series = this, - kdComparer = this.kdComparer, kdX = this.kdAxisArray[0], - kdY = this.kdAxisArray[1]; + kdY = this.kdAxisArray[1], + kdComparer = compareX ? 'distX' : 'dist'; - // Internal function - function _distance(p1, p2) { + // Set the one and two dimensional distance on the point object + function setDistance(p1, p2) { var x = (defined(p1[kdX]) && defined(p2[kdX])) ? Math.pow(p1[kdX] - p2[kdX], 2) : null, y = (defined(p1[kdY]) && defined(p2[kdY])) ? Math.pow(p1[kdY] - p2[kdY], 2) : null, r = (x || 0) + (y || 0); - - return { - distX: defined(x) ? Math.sqrt(x) : Number.MAX_VALUE, - distY: defined(y) ? Math.sqrt(y) : Number.MAX_VALUE, - distR: defined(r) ? Math.sqrt(r) : Number.MAX_VALUE - }; + + p2.dist = defined(r) ? Math.sqrt(r) : Number.MAX_VALUE; + p2.distX = defined(x) ? Math.sqrt(x) : Number.MAX_VALUE; } function _search(search, tree, depth, dimensions) { var point = tree.point, axis = series.kdAxisArray[depth % dimensions], tdist, sideA, sideB, ret = point, nPoint1, nPoint2; - point.dist = _distance(search, point); + + setDistance(search, point); // Pick side based on distance to splitting point tdist = search[axis] - point[axis]; sideA = tdist < 0 ? 'left' : 'right'; + sideB = tdist < 0 ? 'right' : 'left'; // End of tree if (tree[sideA]) { nPoint1 =_search(search, tree[sideA], depth + 1, dimensions); - ret = (nPoint1.dist[kdComparer] < ret.dist[kdComparer] ? nPoint1 : point); - - sideB = tdist < 0 ? 'right' : 'left'; - if (tree[sideB]) { - // compare distance to current best to splitting point to decide wether to check side B or not - if (Math.sqrt(tdist*tdist) < ret.dist[kdComparer]) { - nPoint2 = _search(search, tree[sideB], depth + 1, dimensions); - ret = (nPoint2.dist[kdComparer] < ret.dist[kdComparer] ? nPoint2 : ret); - } + ret = (nPoint1[kdComparer] < ret[kdComparer] ? nPoint1 : point); + } + if (tree[sideB]) { + // compare distance to current best to splitting point to decide wether to check side B or not + if (Math.sqrt(tdist * tdist) < ret[kdComparer]) { + nPoint2 = _search(search, tree[sideB], depth + 1, dimensions); + ret = (nPoint2[kdComparer] < ret[kdComparer] ? nPoint2 : ret); } } + return ret; } if (!this.kdTree) { this.buildKDTree(); @@ -14945,10 +14968,11 @@ yData = series.processedYData, stackedYData = [], yDataLength = yData.length, seriesOptions = series.options, threshold = seriesOptions.threshold, + stackThreshold = seriesOptions.startFromThreshold ? threshold : 0, stackOption = seriesOptions.stack, stacking = seriesOptions.stacking, stackKey = series.stackKey, negKey = '-' + stackKey, negStacks = series.negStacks, @@ -14970,11 +14994,11 @@ y = yData[i]; pointKey = series.index + ',' + i; // Read stacked values into a stack based on the x value, // the sign of y and the stack key. Stacking is also handled for null values (#739) - isNegative = negStacks && y < threshold; + isNegative = negStacks && y < (stackThreshold ? 0 : threshold); key = isNegative ? negKey : stackKey; // Create empty object for this stack if it doesn't exist yet if (!stacks[key]) { stacks[key] = {}; @@ -14990,12 +15014,15 @@ } } // If the StackItem doesn't exist, create it first stack = stacks[key][x]; - stack.points[pointKey] = [stack.cum || 0]; + //stack.points[pointKey] = [stack.cum || stackThreshold]; + stack.points[pointKey] = [pick(stack.cum, stackThreshold)]; + + // Add value to the stack total if (stacking === 'percent') { // Percent stacked column, totals are the same for the positive and negative stacks other = isNegative ? stackKey : negKey; @@ -15009,11 +15036,11 @@ } } else { stack.total = correctFloat(stack.total + (y || 0)); } - stack.cum = (stack.cum || 0) + (y || 0); + stack.cum = pick(stack.cum, stackThreshold) + (y || 0); stack.points[pointKey].push(stack.cum); stackedYData[i] = stack.cum; } @@ -15221,18 +15248,21 @@ function update() { point.applyOptions(options); // Update visuals + if (point.y === null && graphic) { // #4146 + point.graphic = graphic.destroy(); + } 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 || '']); + graphic.attr(point.pointAttr[point.state || ''])[point.visible ? 'show' : 'hide'](true); // #2430 } } if (options && options.dataLabels && point.dataLabel) { // #2468 point.dataLabel = point.dataLabel.destroy(); } @@ -15253,13 +15283,12 @@ series.isDirty = series.isDirtyData = true; if (!series.fixedBox && series.hasCartesianSeries) { // #1906, #2320 chart.isDirtyBox = true; } - if (chart.legend.display && seriesOptions.legendType === 'point') { // #1831, #1885, #3934 - series.updateTotals(); - chart.legend.clearItems(); + if (seriesOptions.legendType === 'point') { // #1831, #1885 + chart.isDirtyLegend = true; } if (redraw) { chart.redraw(animation); } } @@ -15984,10 +16013,11 @@ dataLabels: { align: null, // auto verticalAlign: null, // auto y: null }, + startFromThreshold: true, // docs: http://jsfiddle.net/highcharts/hz8fopan/14/ stickyTracking: false, tooltip: { distance: 6 }, threshold: 0 @@ -16135,11 +16165,12 @@ Series.prototype.translate.apply(series); // Record the new values each(series.points, function (point) { var yBottom = pick(point.yBottom, translatedThreshold), - plotY = mathMin(mathMax(-999 - yBottom, point.plotY), yAxis.len + 999 + yBottom), // Don't draw too far outside plot area (#1303, #2241) + safeDistance = 999 + mathAbs(yBottom), + plotY = mathMin(mathMax(-safeDistance, point.plotY), yAxis.len + safeDistance), // Don't draw too far outside plot area (#1303, #2241, #4264) barX = point.plotX + pointXOffset, barW = seriesBarW, barY = mathMin(plotY, yBottom), right, bottom, @@ -16161,15 +16192,10 @@ // Cache for access in polar point.barX = barX; point.pointWidth = pointWidth; - // Fix the tooltip on center of grouped columns (#1216, #424, #3648) - point.tooltipPos = chart.inverted ? - [yAxis.len + yAxis.pos - chart.plotLeft - 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; @@ -16182,19 +16208,23 @@ if (fromTop) { barY -= 1; barH += 1; } + // Fix the tooltip on center of grouped columns (#1216, #424, #3648) + point.tooltipPos = chart.inverted ? + [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 = { x: barX, y: barY, width: barW, height: barH }; - }); }, getSymbol: noop, @@ -16349,11 +16379,10 @@ requireSorting: false, noSharedTooltip: true, trackerGroups: ['group', 'markerGroup', 'dataLabelsGroup'], takeOrdinalPosition: false, // #2342 kdDimensions: 2, - kdComparer: 'distR', drawGraph: function () { if (this.options.lineWidth) { Series.prototype.drawGraph.call(this); } } @@ -16435,44 +16464,42 @@ /** * Toggle the visibility of the pie slice * @param {Boolean} vis Whether to show the slice or not. If undefined, the * visibility is toggled */ - setVisible: function (vis, force) { + setVisible: function (vis, redraw) { var point = this, series = point.series, chart = series.chart, - doRedraw = !series.isDirty && series.options.ignoreHiddenPoint; + ignoreHiddenPoint = series.options.ignoreHiddenPoint; + + redraw = pick(redraw, ignoreHiddenPoint); - // Only if the value has changed - if (vis !== point.visible || force) { - + if (vis !== point.visible) { + // If called without an argument, toggle visibility point.visible = point.options.visible = vis = vis === UNDEFINED ? !point.visible : vis; series.options.data[inArray(point, series.data)] = point.options; // update userOptions.data - // Show and hide associated elements + // Show and hide associated elements. This is performed regardless of redraw or not, + // because chart.redraw only handles full series. each(['graphic', 'dataLabel', 'connector', 'shadowGroup'], function (key) { if (point[key]) { point[key][vis ? 'show' : 'hide'](true); } }); if (point.legendItem) { - if (chart.hasRendered) { - series.updateTotals(); - chart.legend.clearItems(); - if (!doRedraw) { - chart.legend.render(); - } - } chart.legend.colorizeItem(point, vis); } - + // Handle ignore hidden slices - if (doRedraw) { + if (ignoreHiddenPoint) { series.isDirty = true; + } + + if (redraw) { chart.redraw(); } } }, @@ -16527,10 +16554,11 @@ var PieSeries = { type: 'pie', isCartesian: false, pointClass: PiePoint, requireSorting: false, + directTouch: true, noSharedTooltip: true, trackerGroups: ['group', 'dataLabelsGroup'], axisTypes: [], pointAttrToOptions: { // mapping between SVG attributes and the corresponding options stroke: 'borderColor', @@ -16595,36 +16623,25 @@ * Recompute total chart sum and update percentages of points. */ updateTotals: function () { var i, total = 0, - points, - len, + points = this.points, + len = points.length, point, ignoreHiddenPoint = this.options.ignoreHiddenPoint; - // Populate local vars - points = this.points; - len = points.length; - // Get the total sum for (i = 0; i < len; i++) { point = points[i]; - - // Disallow negative values (#1530, #3623) - if (point.y < 0) { - point.y = null; - } - total += (ignoreHiddenPoint && !point.visible) ? 0 : point.y; } this.total = total; // Set each point's properties for (i = 0; i < len; i++) { point = points[i]; - //point.percentage = (total <= 0 || ignoreHiddenPoint && !point.visible) ? 0 : point.y / total * 100; point.percentage = (total > 0 && (point.visible || !ignoreHiddenPoint)) ? point.y / total * 100 : 0; point.total = total; } }, @@ -16760,22 +16777,20 @@ //center, graphic, //group, shadow = series.options.shadow, shadowGroup, - shapeArgs; + shapeArgs, + attr; if (shadow && !series.shadowGroup) { series.shadowGroup = renderer.g('shadow') .add(series.group); } // draw the slices each(series.points, function (point) { - - var visible = point.options.visible; - graphic = point.graphic; shapeArgs = point.shapeArgs; shadowGroup = point.shadowGroup; // put the shadow behind all points @@ -16795,31 +16810,28 @@ shadowGroup.attr(groupTranslation); } // draw the slice if (graphic) { - graphic.animate(extend(shapeArgs, groupTranslation)); + graphic.animate(extend(shapeArgs, groupTranslation)); } else { + attr = { 'stroke-linejoin': 'round' }; + if (!point.visible) { + attr.visibility = 'hidden'; + } + point.graphic = graphic = renderer[point.shapeType](shapeArgs) .setRadialReference(series.center) .attr( point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE] ) - .attr({ - 'stroke-linejoin': 'round' - //zIndex: 1 // #2722 (reversed) - }) + .attr(attr) .attr(groupTranslation) .add(series.group) .shadow(shadow, shadowGroup); } - // Detect point specific visibility (#2430) - if (visible !== undefined) { - point.setVisible(visible, true); - } - }); }, @@ -17368,11 +17380,10 @@ y = naturalY; } // get the x - use the natural x position for first and last slot, to prevent the top // and botton slice connectors from touching each other on either side - // Problem: Should check that it makes sense - http://jsfiddle.net/highcharts/n1y6ngxz/ x = options.justify ? seriesCenter[0] + (i ? -1 : 1) * (radius + distanceOption) : series.getX(y === centerY - radius - distanceOption || y === centerY + radius + distanceOption ? naturalY : y, i); @@ -17426,11 +17437,11 @@ each(this.points, function (point) { connector = point.connector; labelPos = point.labelPos; dataLabel = point.dataLabel; - if (dataLabel && dataLabel._pos) { + if (dataLabel && dataLabel._pos && point.visible) { visibility = dataLabel._attr.visibility; x = dataLabel.connX; y = dataLabel.connY; connectorPath = softConnector ? [ M, @@ -17477,11 +17488,11 @@ seriesTypes.pie.prototype.placeDataLabels = function () { each(this.points, function (point) { var dataLabel = point.dataLabel, _pos; - if (dataLabel) { + if (dataLabel && point.visible) { _pos = dataLabel._pos; if (_pos) { dataLabel.attr(dataLabel._attr); dataLabel[dataLabel.moved ? 'animate' : 'attr'](_pos); dataLabel.moved = true; @@ -17536,10 +17547,11 @@ } // If the size must be decreased, we need to run translate and drawDataLabels again if (newSize < center[2]) { center[2] = newSize; + center[3] = relativeLength(options.innerSize || 0, newSize); this.translate(center); each(this.points, function (point) { if (point.dataLabel) { point.dataLabel._pos = null; // reset } @@ -17563,11 +17575,11 @@ */ seriesTypes.column.prototype.alignDataLabel = function (point, dataLabel, options, alignTo, isNew) { var inverted = this.chart.inverted, series = point.series, dlBox = point.dlBox || point.shapeArgs, // data label box for alignment - below = point.below || (point.plotY > pick(this.translatedThreshold, series.yAxis.len)), + below = pick(point.below, point.plotY > pick(this.translatedThreshold, series.yAxis.len)), // point.below is used in range series inside = pick(options.inside, !!this.options.stacking); // draw it inside the box? // Align to the column itself, or the top of it if (dlBox) { // Area range uses this method but not alignTo alignTo = merge(dlBox); @@ -17611,11 +17623,11 @@ } /** - * Highcharts JS v4.1.5 (2015-04-13) + * Highcharts JS v4.1.6 (2015-06-12) * Highcharts module to hide overlapping data labels. This module is included by default in Highmaps. * * (c) 2010-2014 Torstein Honsi * * License: www.highcharts.com/license @@ -17623,10 +17635,11 @@ /*global Highcharts, HighchartsAdapter */ (function (H) { var Chart = H.Chart, each = H.each, + pick = H.pick, addEvent = HighchartsAdapter.addEvent; // Collect potensial overlapping data labels. Stack labels probably don't need to be // considered because they are usually accompanied by data labels that lie inside the columns. Chart.prototype.callbacks.push(function (chart) { @@ -17636,11 +17649,11 @@ each(chart.series, function (series) { var dlOptions = series.options.dataLabels; if ((dlOptions.enabled || series._hasPointLabels) && !dlOptions.allowOverlap && series.visible) { // #3866 each(series.points, function (point) { if (point.dataLabel) { - point.dataLabel.labelrank = point.labelrank; + point.dataLabel.labelrank = pick(point.labelrank, point.shapeArgs && point.shapeArgs.height); // #4118 labels.push(point.dataLabel); } }); } }); @@ -17683,10 +17696,17 @@ label.oldOpacity = label.opacity; label.newOpacity = 1; } } + // Prevent a situation in a gradually rising slope, that each label + // will hide the previous one because the previous one always has + // lower rank. + labels.sort(function (a, b) { + return b.labelrank - a.labelrank; + }); + // Detect overlapping labels for (i = 0; i < len; i++) { label1 = labels[i]; for (j = i + 1; j < len; ++j) { @@ -18351,10 +18371,12 @@ options = series.options, chart = series.chart, tooltip = chart.tooltip, hoverPoint = chart.hoverPoint; + chart.hoverSeries = null; // #182, set to null before the mouseOut event fires + // trigger mouse out on the point, which must be in this series if (hoverPoint) { hoverPoint.onMouseOut(); } @@ -18369,10 +18391,9 @@ tooltip.hide(); } // set normal state series.setState(); - chart.hoverSeries = null; }, /** * Set the state of the graph */