app/assets/javascripts/highcharts.js in highcharts-rails-5.0.7 vs app/assets/javascripts/highcharts.js in highcharts-rails-5.0.8

- old
+ new

@@ -1,7 +1,7 @@ /** - * @license Highcharts JS v5.0.7 (2017-01-17) + * @license Highcharts JS v5.0.8 (2017-03-08) * * (c) 2009-2016 Torstein Honsi * * License: www.highcharts.com/license */ @@ -19,11 +19,10 @@ /** * (c) 2010-2016 Torstein Honsi * * License: www.highcharts.com/license */ - 'use strict'; /* global window */ var win = window, doc = win.document; var SVG_NS = 'http://www.w3.org/2000/svg', @@ -34,11 +33,11 @@ isFirefox = /Firefox/.test(userAgent), hasBidiBug = isFirefox && parseInt(userAgent.split('Firefox/')[1], 10) < 4; // issue #38 var Highcharts = win.Highcharts ? win.Highcharts.error(16, true) : { product: 'Highcharts', - version: '5.0.7', + version: '5.0.8', deg2rad: Math.PI * 2 / 360, doc: doc, hasBidiBug: hasBidiBug, hasTouch: doc && doc.documentElement.ontouchstart !== undefined, isMS: isMS, @@ -65,11 +64,10 @@ * (c) 2010-2016 Torstein Honsi * * License: www.highcharts.com/license */ /* eslint max-len: ["warn", 80, 4] */ - 'use strict'; /** * The Highcharts object is the placeholder for all other members, and various * utility functions. * @namespace Highcharts @@ -1595,13 +1593,14 @@ * Iterate over an array. * * @function #each * @memberOf Highcharts * @param {Array} arr - The array to iterate over. - * @param {Function} fn - The iterator callback. It passes two arguments: + * @param {Function} fn - The iterator callback. It passes three arguments: * * item - The array item. * * index - The item's index in the array. + * * arr - The array that each is being applied to. * @param {Object} [ctx] The context. */ H.each = function(arr, fn, ctx) { // modern browsers return Array.prototype.forEach.call(arr, fn, ctx); }; @@ -1888,11 +1887,11 @@ } if (!end) { end = params[prop]; } - if (end.match && end.match('px')) { + if (end && end.match && end.match('px')) { end = end.replace(/px/g, ''); // #4351 } fx.run(start, end, unit); } }; @@ -2094,11 +2093,10 @@ /** * (c) 2010-2016 Torstein Honsi * * License: www.highcharts.com/license */ - 'use strict'; var each = H.each, isNumber = H.isNumber, map = H.map, merge = H.merge, pInt = H.pInt; @@ -2132,16 +2130,10 @@ regex: /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]?(?:\.[0-9]+)?)\s*\)/, parse: function(result) { return [pInt(result[1]), pInt(result[2]), pInt(result[3]), parseFloat(result[4], 10)]; } }, { - // HEX color - regex: /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/, - parse: function(result) { - return [pInt(result[1], 16), pInt(result[2], 16), pInt(result[3], 16), 1]; - } - }, { // RGB color regex: /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/, parse: function(result) { return [pInt(result[1]), pInt(result[2]), pInt(result[3]), 1]; } @@ -2160,11 +2152,12 @@ */ init: function(input) { var result, rgba, i, - parser; + parser, + len; this.input = input = this.names[input] || input; // Gradients if (input && input.stops) { @@ -2172,18 +2165,52 @@ return new H.Color(stop[1]); }); // Solid colors } else { - i = this.parsers.length; - while (i-- && !rgba) { - parser = this.parsers[i]; - result = parser.regex.exec(input); - if (result) { - rgba = parser.parse(result); + + // Check if it's possible to do bitmasking instead of regex + if (input && input[0] === '#') { + + len = input.length; + input = parseInt(input.substr(1), 16); + + // Handle long-form, e.g. #AABBCC + if (len === 7) { + + rgba = [ + (input & 0xFF0000) >> 16, + (input & 0xFF00) >> 8, + (input & 0xFF), + 1 + ]; + + // Handle short-form, e.g. #ABC + // In short form, the value is assumed to be the same + // for both nibbles for each component. e.g. #ABC = #AABBCC + } else if (len === 4) { + + rgba = [ + ((input & 0xF00) >> 4) | (input & 0xF00) >> 8, + ((input & 0xF0) >> 4) | (input & 0xF0), + ((input & 0xF) << 4) | (input & 0xF), + 1 + ]; } } + + // Otherwise, check regex parsers + if (!rgba) { + i = this.parsers.length; + while (i-- && !rgba) { + parser = this.parsers[i]; + result = parser.regex.exec(input); + if (result) { + rgba = parser.parse(result); + } + } + } } this.rgba = rgba || []; }, /** @@ -2263,11 +2290,10 @@ /** * (c) 2010-2016 Torstein Honsi * * License: www.highcharts.com/license */ - 'use strict'; var SVGElement, SVGRenderer, addEvent = H.addEvent, animate = H.animate, @@ -2334,12 +2360,12 @@ /** * For labels, these CSS properties are applied to the `text` node directly. * @type {Array.<string>} */ textProps: ['direction', 'fontSize', 'fontWeight', 'fontFamily', - 'fontStyle', 'color', 'lineHeight', 'width', 'textDecoration', - 'textOverflow', 'textOutline' + 'fontStyle', 'color', 'lineHeight', 'width', 'textAlign', + 'textDecoration', 'textOverflow', 'textOutline' ], /** * Initialize the SVG renderer. This function only exists to make the * initiation process overridable. It should not be called directly. @@ -2383,10 +2409,13 @@ animOptions.complete = complete; } animate(this, params, animOptions); } else { this.attr(params, null, complete); + if (animOptions.step) { + animOptions.step.call(this); + } } return this; }, /** @@ -2944,14 +2973,13 @@ * elements. * @param {CSSObject} styles The new CSS styles. * @returns {SVGElement} Return the SVG element for chaining. */ css: function(styles) { - var elemWrapper = this, - oldStyles = elemWrapper.styles, + var oldStyles = this.styles, newStyles = {}, - elem = elemWrapper.element, + elem = this.element, textWidth, n, serializedCss = '', hyphenate, hasNew = !oldStyles, @@ -2974,32 +3002,38 @@ hasNew = true; } } } if (hasNew) { - textWidth = elemWrapper.textWidth = - (styles && styles.width && elem.nodeName.toLowerCase() === 'text' && pInt(styles.width)) || - elemWrapper.textWidth; // #3501 // Merge the new styles with the old ones if (oldStyles) { styles = extend( oldStyles, newStyles ); } + // Get the text width from style + textWidth = this.textWidth = ( + styles && + styles.width && + styles.width !== 'auto' && + elem.nodeName.toLowerCase() === 'text' && + pInt(styles.width) + ); + // store object - elemWrapper.styles = styles; + this.styles = styles; - if (textWidth && (!svg && elemWrapper.renderer.forExport)) { + if (textWidth && (!svg && this.renderer.forExport)) { delete styles.width; } // serialize and set style attribute if (isMS && !svg) { - css(elemWrapper.element, styles); + css(this.element, styles); } else { hyphenate = function(a, b) { return '-' + b.toLowerCase(); }; for (n in styles) { @@ -3013,24 +3047,26 @@ attr(elem, 'style', serializedCss); // #1881 } } - if (elemWrapper.added) { - // Rebuild text after added - if (textWidth) { - elemWrapper.renderer.buildText(elemWrapper); + if (this.added) { + + // Rebuild text after added. Cache mechanisms in the buildText + // will prevent building if there are no significant changes. + if (this.element.nodeName === 'text') { + this.renderer.buildText(this); } // Apply text outline after added if (styles && styles.textOutline) { - elemWrapper.applyTextOutline(styles.textOutline); + this.applyTextOutline(styles.textOutline); } } } - return elemWrapper; + return this; }, /** * Get the current stroke width. In classic mode, the setter registers it @@ -3984,11 +4020,11 @@ .attr({ 'version': '1.1', 'class': 'highcharts-root' }) - .css(this.getStyle(style)); + .css(this.getStyle(style)); element = boxWrapper.element; container.appendChild(element); // For browsers other than IE, add the namespace attribute (#1978) if (container.innerHTML.indexOf('xmlns') === -1) { @@ -4023,11 +4059,11 @@ .replace(/ /g, '%20') : // replace spaces (needed for Safari only) ''; // Add description desc = this.createElement('desc').add(); - desc.element.appendChild(doc.createTextNode('Created with Highcharts 5.0.7')); + desc.element.appendChild(doc.createTextNode('Created with Highcharts 5.0.8')); renderer.defs = this.createElement('defs').add(); renderer.allowHTML = allowHTML; renderer.forExport = forExport; @@ -4260,12 +4296,14 @@ tempParent.appendChild(textNode); // attach it to the DOM to read offset width } if (hasMarkup) { lines = textStr + .replace(/<(b|strong)>/g, '<span style="font-weight:bold">') .replace(/<(i|em)>/g, '<span style="font-style:italic">') + .replace(/<a/g, '<span') .replace(/<\/(b|strong|i|em|a)>/g, '</span>') .split(/<br.*?>/g); } else { @@ -4721,28 +4759,32 @@ * Draw and return an arc. Overloaded function that takes arguments object. * @param {SVGAttributes} attribs Initial SVG attributes. * @returns {SVGElement} The generated wrapper element. */ arc: function(x, y, r, innerR, start, end) { - var arc; + var arc, + options; if (isObject(x)) { - y = x.y; - r = x.r; - innerR = x.innerR; - start = x.start; - end = x.end; - x = x.x; + options = x; + y = options.y; + r = options.r; + innerR = options.innerR; + start = options.start; + end = options.end; + x = options.x; + } else { + options = { + innerR: innerR, + start: start, + end: end + }; } // Arcs are defined as symbols for the ability to set // attributes in attr and animate - arc = this.symbol('arc', x || 0, y || 0, r || 0, r || 0, { - innerR: innerR || 0, - start: start || 0, - end: end || 0 - }); + arc = this.symbol('arc', x, y, r, r, options); arc.r = r; // #959 return arc; }, /** @@ -5594,11 +5636,11 @@ * Add specific attribute setters. */ // only change local variables wrapper.widthSetter = function(value) { - width = value; + width = H.isNumber(value) ? value : null; // width:auto => null }; wrapper.heightSetter = function(value) { height = value; }; wrapper['text-alignSetter'] = function(value) { @@ -5767,11 +5809,10 @@ /** * (c) 2010-2016 Torstein Honsi * * License: www.highcharts.com/license */ - 'use strict'; var attr = H.attr, createElement = H.createElement, css = H.css, defined = H.defined, each = H.each, @@ -6130,11 +6171,10 @@ /** * (c) 2010-2016 Torstein Honsi * * License: www.highcharts.com/license */ - 'use strict'; var VMLRenderer, VMLRendererExtension, VMLElement, @@ -7282,11 +7322,10 @@ /** * (c) 2010-2016 Torstein Honsi * * License: www.highcharts.com/license */ - 'use strict'; var color = H.color, each = H.each, getTZOffset = H.getTZOffset, isTouchDevice = H.isTouchDevice, merge = H.merge, @@ -7325,11 +7364,11 @@ }, global: { useUTC: true, //timezoneOffset: 0, - VMLRadialGradientURL: 'http://code.highcharts.com/5.0.7/gfx/vml-radial-gradient.png' + VMLRadialGradientURL: 'http://code.highcharts.com/5.0.8/gfx/vml-radial-gradient.png' }, chart: { //animation: true, //alignTicks: false, @@ -7375,12 +7414,12 @@ // fontSize: '12px' //}, backgroundColor: '#ffffff', //plotBackgroundColor: null, plotBorderColor: '#cccccc' - //plotBorderWidth: 0, - //plotShadow: false + //plotBorderWidth: 0, + //plotShadow: false }, title: { text: 'Chart title', align: 'center', @@ -7671,11 +7710,10 @@ /** * (c) 2010-2016 Torstein Honsi * * License: www.highcharts.com/license */ - 'use strict'; var arrayMax = H.arrayMax, arrayMin = H.arrayMin, defined = H.defined, destroyObjectProperties = H.destroyObjectProperties, each = H.each, @@ -7998,11 +8036,10 @@ /** * (c) 2010-2016 Torstein Honsi * * License: www.highcharts.com/license */ - 'use strict'; var correctFloat = H.correctFloat, defined = H.defined, destroyObjectProperties = H.destroyObjectProperties, isNumber = H.isNumber, merge = H.merge, @@ -8082,14 +8119,14 @@ 0, 0, labelOptions.useHTML ) - // without position absolute, IE export sometimes is wrong - .css(merge(labelOptions.style)) + // without position absolute, IE export sometimes is wrong + .css(merge(labelOptions.style)) - .add(axis.labelGroup): + .add(axis.labelGroup) : null; tick.labelLength = label && label.getBBox().width; // Un-rotated length tick.rotation = 0; // Base value to detect change for new calls to getBBox // update @@ -8259,55 +8296,35 @@ y + (horiz ? tickLength : 0) ], tickWidth); }, /** - * Put everything in place - * - * @param index {Number} - * @param old {Boolean} Use old coordinates to prepare an animation into new position + * Renders the gridLine. + * @param {Boolean} old Whether or not the tick is old + * @param {number} opacity The opacity of the grid line + * @param {number} reverseCrisp Modifier for avoiding overlapping 1 or -1 + * @return {undefined} */ - render: function(index, old, opacity) { + renderGridLine: function(old, opacity, reverseCrisp) { var tick = this, axis = tick.axis, options = axis.options, - chart = axis.chart, - renderer = chart.renderer, - horiz = axis.horiz, - type = tick.type, - label = tick.label, - pos = tick.pos, - labelOptions = options.labels, gridLine = tick.gridLine, - tickPrefix = type ? type + 'Tick' : 'tick', - tickSize = axis.tickSize(tickPrefix), gridLinePath, - mark = tick.mark, - isNewMark = !mark, - step = labelOptions.step, attribs = {}, - show = true, + pos = tick.pos, + type = tick.type, tickmarkOffset = axis.tickmarkOffset, - xy = tick.getPosition(horiz, pos, tickmarkOffset, old), - x = xy.x, - y = xy.y, - reverseCrisp = ((horiz && x === axis.pos + axis.len) || - (!horiz && y === axis.pos)) ? -1 : 1; // #1480, #1687 + renderer = axis.chart.renderer; var gridPrefix = type ? type + 'Grid' : 'grid', gridLineWidth = options[gridPrefix + 'LineWidth'], gridLineColor = options[gridPrefix + 'LineColor'], - dashStyle = options[gridPrefix + 'LineDashStyle'], - tickWidth = pick(options[tickPrefix + 'Width'], !type && axis.isXAxis ? 1 : 0), // X axis defaults to 1 - tickColor = options[tickPrefix + 'Color']; + dashStyle = options[gridPrefix + 'LineDashStyle']; - opacity = pick(opacity, 1); - this.isActive = true; - - // Create the grid line if (!gridLine) { attribs.stroke = gridLineColor; attribs['stroke-width'] = gridLineWidth; if (dashStyle) { @@ -8320,27 +8337,62 @@ if (old) { attribs.opacity = 0; } tick.gridLine = gridLine = renderer.path() .attr(attribs) - .addClass('highcharts-' + (type ? type + '-' : '') + 'grid-line') + .addClass( + 'highcharts-' + (type ? type + '-' : '') + 'grid-line' + ) .add(axis.gridGroup); } // If the parameter 'old' is set, the current call will be followed // by another call, therefore do not do any animations this time if (!old && gridLine) { - gridLinePath = axis.getPlotLinePath(pos + tickmarkOffset, gridLine.strokeWidth() * reverseCrisp, old, true); + gridLinePath = axis.getPlotLinePath( + pos + tickmarkOffset, + gridLine.strokeWidth() * reverseCrisp, + old, true + ); if (gridLinePath) { gridLine[tick.isNew ? 'attr' : 'animate']({ d: gridLinePath, opacity: opacity }); } } + }, - // create the tick mark + /** + * Renders the tick mark. + * @param {Object} xy The position vector of the mark + * @param {number} xy.x The x position of the mark + * @param {number} xy.y The y position of the mark + * @param {number} opacity The opacity of the mark + * @param {number} reverseCrisp Modifier for avoiding overlapping 1 or -1 + * @return {undefined} + */ + renderMark: function(xy, opacity, reverseCrisp) { + var tick = this, + axis = tick.axis, + options = axis.options, + renderer = axis.chart.renderer, + type = tick.type, + tickPrefix = type ? type + 'Tick' : 'tick', + tickSize = axis.tickSize(tickPrefix), + mark = tick.mark, + isNewMark = !mark, + x = xy.x, + y = xy.y; + + + var tickWidth = pick( + options[tickPrefix + 'Width'], !type && axis.isXAxis ? 1 : 0 + ), // X axis defaults to 1 + tickColor = options[tickPrefix + 'Color']; + + if (tickSize) { // negate the length if (axis.opposite) { tickSize[0] = -tickSize[0]; @@ -8358,24 +8410,74 @@ 'stroke-width': tickWidth }); } mark[isNewMark ? 'attr' : 'animate']({ - d: tick.getMarkPath(x, y, tickSize[0], mark.strokeWidth() * reverseCrisp, horiz, renderer), + d: tick.getMarkPath( + x, + y, + tickSize[0], + mark.strokeWidth() * reverseCrisp, + axis.horiz, + renderer), opacity: opacity }); } + }, - // the label is created on init - now move it into place + /** + * Renders the tick label. + * Note: The label should already be created in init(), so it should only + * have to be moved into place. + * @param {Object} xy The position vector of the label + * @param {number} xy.x The x position of the label + * @param {number} xy.y The y position of the label + * @param {Boolean} old Whether or not the tick is old + * @param {number} opacity The opacity of the label + * @param {number} index The index of the tick + * @return {undefined} + */ + renderLabel: function(xy, old, opacity, index) { + var tick = this, + axis = tick.axis, + horiz = axis.horiz, + options = axis.options, + label = tick.label, + labelOptions = options.labels, + step = labelOptions.step, + tickmarkOffset = axis.tickmarkOffset, + show = true, + x = xy.x, + y = xy.y; if (label && isNumber(x)) { - label.xy = xy = tick.getLabelPosition(x, y, label, horiz, labelOptions, tickmarkOffset, index, step); + label.xy = xy = tick.getLabelPosition( + x, + y, + label, + horiz, + labelOptions, + tickmarkOffset, + index, + step + ); - // Apply show first and show last. If the tick is both first and last, it is - // a single centered tick, in which case we show the label anyway (#2100). - if ((tick.isFirst && !tick.isLast && !pick(options.showFirstLabel, 1)) || - (tick.isLast && !tick.isFirst && !pick(options.showLastLabel, 1))) { + // Apply show first and show last. If the tick is both first and + // last, it is a single centered tick, in which case we show the + // label anyway (#2100). + if ( + ( + tick.isFirst && + !tick.isLast && + !pick(options.showFirstLabel, 1) + ) || + ( + tick.isLast && + !tick.isFirst && + !pick(options.showLastLabel, 1) + ) + ) { show = false; // Handle label overflow and show or hide accordingly } else if (horiz && !axis.isRadial && !labelOptions.step && !labelOptions.rotation && !old && opacity !== 0) { @@ -8398,10 +8500,42 @@ tick.isNew = false; } }, /** + * Put everything in place + * + * @param index {Number} + * @param old {Boolean} Use old coordinates to prepare an animation into new + * position + */ + render: function(index, old, opacity) { + var tick = this, + axis = tick.axis, + horiz = axis.horiz, + pos = tick.pos, + tickmarkOffset = axis.tickmarkOffset, + xy = tick.getPosition(horiz, pos, tickmarkOffset, old), + x = xy.x, + y = xy.y, + reverseCrisp = ((horiz && x === axis.pos + axis.len) || + (!horiz && y === axis.pos)) ? -1 : 1; // #1480, #1687 + + opacity = pick(opacity, 1); + this.isActive = true; + + // Create the grid line + this.renderGridLine(old, opacity, reverseCrisp); + + // create the tick mark + this.renderMark(xy, opacity, reverseCrisp); + + // the label is created on init - now move it into place + this.renderLabel(xy, old, opacity, index); + }, + + /** * Destructor for the tick prototype */ destroy: function() { destroyObjectProperties(this, this.axis); } @@ -8412,11 +8546,10 @@ /** * (c) 2010-2016 Torstein Honsi * * License: www.highcharts.com/license */ - 'use strict'; var addEvent = H.addEvent, animObject = H.animObject, arrayMax = H.arrayMax, arrayMin = H.arrayMin, @@ -8489,14 +8622,14 @@ cursor: 'default', fontSize: '11px' }, x: 0 - //y: undefined - /*formatter: function () { - return this.value; - },*/ + //y: undefined + /*formatter: function () { + return this.value; + },*/ }, //linkedTo: null, //max: undefined, //min: undefined, minPadding: 0.01, @@ -8554,11 +8687,11 @@ lineWidth: 1, gridLineColor: '#e6e6e6', // gridLineDashStyle: 'solid', // gridLineWidth: 0, tickColor: '#ccd6eb' - // tickWidth: 1 + // tickWidth: 1 }, /** * This options set extends the defaultOptions for Y axes @@ -8598,11 +8731,11 @@ }, gridLineWidth: 1, lineWidth: 0 - // tickWidth: 0 + // tickWidth: 0 }, /** * These options extend the defaultOptions for left axes @@ -8633,12 +8766,12 @@ */ defaultBottomAxisOptions: { labels: { autoRotation: [-45], x: 0 - // overflow: undefined, - // staggerLines: null + // overflow: undefined, + // staggerLines: null }, title: { rotation: 0 } }, @@ -8647,12 +8780,12 @@ */ defaultTopAxisOptions: { labels: { autoRotation: [-45], x: 0 - // overflow: undefined - // staggerLines: null + // overflow: undefined + // staggerLines: null }, title: { rotation: 0 } }, @@ -8712,10 +8845,11 @@ //axis.axisLine = undefined; // Shorthand types axis.isLog = type === 'logarithmic'; axis.isDatetimeAxis = isDatetimeAxis; + axis.positiveValuesOnly = axis.isLog && !axis.allowNegativeLog; // Flag, if axis is linked to another axis axis.isLinked = defined(options.linkedTo); // Linked axis. //axis.linkedParent = undefined; @@ -8810,10 +8944,11 @@ for (eventType in events) { addEvent(axis, eventType, events[eventType]); } // extend logarithmic axis + axis.lin2log = options.linearToLogConverter || axis.lin2log; if (axis.isLog) { axis.val2lin = axis.log2lin; axis.lin2val = axis.lin2log; } }, @@ -8849,11 +8984,11 @@ multi, ret, formatOption = axis.options.labels.format, // make sure the same symbol is added for all labels on a linear axis - numericSymbolDetector = axis.isLog ? value : axis.tickInterval; + numericSymbolDetector = axis.isLog ? Math.abs(value) : axis.tickInterval; if (formatOption) { ret = format(formatOption, this); } else if (categories) { @@ -8913,11 +9048,11 @@ seriesDataMax; axis.hasVisibleSeries = true; // Validate threshold in logarithmic axes - if (axis.isLog && threshold <= 0) { + if (axis.positiveValuesOnly && threshold <= 0) { threshold = null; } // Get dataMin and dataMax for X axes if (axis.isXAxis) { @@ -8958,11 +9093,11 @@ // Adjust to threshold if (defined(threshold)) { axis.threshold = threshold; } // If any series has a hard threshold, it takes precedence - if (!seriesOptions.softThreshold || axis.isLog) { + if (!seriesOptions.softThreshold || axis.positiveValuesOnly) { axis.softThreshold = false; } } } }); @@ -9105,12 +9240,13 @@ lastPos, roundedMin = correctFloat(Math.floor(min / tickInterval) * tickInterval), roundedMax = correctFloat(Math.ceil(max / tickInterval) * tickInterval), tickPositions = []; - // For single points, add a tick regardless of the relative position (#2662) - if (min === max && isNumber(min)) { + // For single points, add a tick regardless of the relative position + // (#2662, #6274) + if (this.single) { return [min]; } // Populate the intermediate values pos = roundedMin; @@ -9143,27 +9279,35 @@ options = axis.options, tickPositions = axis.tickPositions, minorTickInterval = axis.minorTickInterval, minorTickPositions = [], pos, - i, pointRangePadding = axis.pointRangePadding || 0, min = axis.min - pointRangePadding, // #1498 max = axis.max + pointRangePadding, // #1498 - range = max - min, - len; + range = max - min; // If minor ticks get too dense, they are hard to read, and may cause long running script. So we don't draw them. if (range && range / minorTickInterval < axis.len / 3) { // #3875 if (axis.isLog) { - len = tickPositions.length; - for (i = 1; i < len; i++) { - minorTickPositions = minorTickPositions.concat( - axis.getLogTickPositions(minorTickInterval, tickPositions[i - 1], tickPositions[i], true) - ); - } + // For each interval in the major ticks, compute the minor ticks + // separately. + each(this.paddedTicks, function(pos, i, paddedTicks) { + if (i) { + minorTickPositions.push.apply( + minorTickPositions, + axis.getLogTickPositions( + minorTickInterval, + paddedTicks[i - 1], + paddedTicks[i], + true + ) + ); + } + }); + } else if (axis.isDatetimeAxis && options.minorTickInterval === 'auto') { // #1314 minorTickPositions = minorTickPositions.concat( axis.getTimeTicks( axis.normalizeTimeTickInterval(minorTickInterval), min, @@ -9182,12 +9326,12 @@ minorTickPositions.push(pos); } } } - if (minorTickPositions.length !== 0) { // don't change the extremes, when there is no minor ticks - axis.trimTicks(minorTickPositions, options.startOnTick, options.endOnTick); // #3652 #3743 #1498 + if (minorTickPositions.length !== 0) { + axis.trimTicks(minorTickPositions); // #3652 #3743 #1498 #6330 } return minorTickPositions; }, /** @@ -9433,11 +9577,13 @@ // Secondary values if (saveOld) { axis.oldTransA = transA; } - axis.translationSlope = axis.transA = transA = axis.len / ((range + pointRangePadding) || 1); + axis.translationSlope = axis.transA = transA = + axis.options.staticScale || + axis.len / ((range + pointRangePadding) || 1); axis.transB = axis.horiz ? axis.left : axis.bottom; // translation addend axis.minPixelPadding = transA * minPointOffset; }, minFromRange: function() { @@ -9508,11 +9654,15 @@ axis.max = pick(hardMax, thresholdMax, axis.dataMax); } if (isLog) { - if (!secondPass && Math.min(axis.min, pick(axis.dataMin, axis.min)) <= 0) { // #978 + if ( + axis.positiveValuesOnly && + !secondPass && + Math.min(axis.min, pick(axis.dataMin, axis.min)) <= 0 + ) { // #978 H.error(10, 1); // Can't plot negative values on log axis } // The correctFloat cures #934, float errors on full tens. But it // was too aggressive for #4360 because of conversion back to lin, // therefore use precision 15. @@ -9657,22 +9807,28 @@ var options = this.options, tickPositions, tickPositionsOption = options.tickPositions, tickPositioner = options.tickPositioner, startOnTick = options.startOnTick, - endOnTick = options.endOnTick, - single; + endOnTick = options.endOnTick; // Set the tickmarkOffset this.tickmarkOffset = (this.categories && options.tickmarkPlacement === 'between' && this.tickInterval === 1) ? 0.5 : 0; // #3202 // get minorTickInterval this.minorTickInterval = options.minorTickInterval === 'auto' && this.tickInterval ? this.tickInterval / 5 : options.minorTickInterval; + // 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. + this.single = this.min === this.max && defined(this.min) && + !this.tickAmount && options.allowDecimals !== false; + // Find the tick positions this.tickPositions = tickPositions = tickPositionsOption && tickPositionsOption.slice(); // Work on a copy (#1565) if (!tickPositions) { if (this.isDatetimeAxis) { @@ -9706,23 +9862,20 @@ } } } - // reset min/max or remove extremes based on start/end on tick + // Reset min/max or remove extremes based on start/end on tick + this.paddedTicks = tickPositions.slice(0); // Used for logarithmic minor this.trimTicks(tickPositions, startOnTick, endOnTick); if (!this.isLinked) { - // When there is only one point, or all points have the same value on this axis, then min - // and max are equal and tickPositions.length is 0 or 1. In this case, add some padding - // in order to center the point, but leave it with one tick. #1337. - if (this.min === this.max && defined(this.min) && !this.tickAmount) { - // Substract half a unit (#2619, #2846, #2515, #3390) - single = true; + + // Substract half a unit (#2619, #2846, #2515, #3390) + if (this.single) { this.min -= 0.5; this.max += 0.5; } - this.single = single; if (!tickPositionsOption && !tickPositioner) { this.adjustTickAmount(); } } }, @@ -10390,13 +10543,13 @@ rotation: axisTitleOptions.rotation || 0, align: textAlign }) .addClass('highcharts-axis-title') - .css(axisTitleOptions.style) + .css(axisTitleOptions.style) - .add(axis.axisGroup); + .add(axis.axisGroup); axis.axisTitle.isNew = true; } // hide or show the title depending on whether showEmpty is set axis.axisTitle[display ? 'show' : 'hide'](true); @@ -10555,11 +10708,13 @@ axisOffset[side] = Math.max( axisOffset[side], axis.axisTitleMargin + titleOffset + directionFactor * axis.offset, labelOffsetPadded, // #3027 - hasData && tickPositions.length && tickSize ? tickSize[0] : 0 // #4866 + hasData && tickPositions.length && tickSize ? + tickSize[0] + directionFactor * axis.offset : + 0 // #4866 ); // Decide the clipping needed to keep the graph inside the plot area and axis lines clip = options.offset ? 0 : Math.floor(axis.axisLine.strokeWidth() / 2) * 2; // #4308, #4371 clipOffset[invertedSide] = Math.max(clipOffset[invertedSide], clip); @@ -11050,11 +11205,10 @@ /** * (c) 2010-2016 Torstein Honsi * * License: www.highcharts.com/license */ - 'use strict'; var Axis = H.Axis, Date = H.Date, dateFormat = H.dateFormat, defaultOptions = H.defaultOptions, defined = H.defined, @@ -11080,11 +11234,11 @@ var tickPositions = [], i, higherRanks = {}, useUTC = defaultOptions.global.useUTC, minYear, // used in months and years as a basis for Date.UTC() - minDate = new Date(min - getTZOffset(min)), + minDate = new Date(min - Math.abs(getTZOffset(min))), // #6278 makeTime = Date.hcMakeTime, interval = normalizedInterval.unitRange, count = normalizedInterval.count, variableDayLength; @@ -11311,11 +11465,10 @@ /** * (c) 2010-2016 Torstein Honsi * * License: www.highcharts.com/license */ - 'use strict'; var Axis = H.Axis, getMagnitude = H.getMagnitude, map = H.map, normalizeTickInterval = H.normalizeTickInterval, pick = H.pick; @@ -11435,11 +11588,10 @@ /** * (c) 2010-2016 Torstein Honsi * * License: www.highcharts.com/license */ - 'use strict'; var dateFormat = H.dateFormat, each = H.each, extend = H.extend, format = H.format, isNumber = H.isNumber, @@ -11826,25 +11978,24 @@ return s; }, /** * Refresh the tooltip's text and position. - * @param {Object} point + * @param {Object|Array} pointOrPoints Rither a point or an array of points */ - refresh: function(point, mouseEvent) { + refresh: function(pointOrPoints, mouseEvent) { var tooltip = this, - chart = tooltip.chart, label, options = tooltip.options, x, y, + point = pointOrPoints, anchor, textConfig = {}, text, pointConfig = [], formatter = options.formatter || tooltip.defaultFormatter, - hoverPoints = chart.hoverPoints, shared = tooltip.shared, currentSeries; clearTimeout(this.hideTimer); @@ -11854,20 +12005,10 @@ x = anchor[0]; y = anchor[1]; // shared tooltip, array is sent over if (shared && !(point.series && point.series.noSharedTooltip)) { - - // hide previous hoverPoints and set new - - chart.hoverPoints = point; - if (hoverPoints) { - each(hoverPoints, function(point) { - point.setState(); - }); - } - each(point, function(item) { item.setState('hover'); pointConfig.push(item.getLabelConfig()); }); @@ -11904,11 +12045,11 @@ }).show(); } // update text if (tooltip.split) { - this.renderSplit(text, chart.hoverPoints); + this.renderSplit(text, pointOrPoints); } else { label.attr({ text: text && text.join ? text.join('') : text }); @@ -12199,11 +12340,10 @@ /** * (c) 2010-2016 Torstein Honsi * * License: www.highcharts.com/license */ - 'use strict'; var addEvent = H.addEvent, attr = H.attr, charts = H.charts, color = H.color, css = H.css, @@ -12336,156 +12476,238 @@ value: axis.toValue(e[axis.horiz ? 'chartX' : 'chartY']) }); }); return coordinates; }, - /** - * With line type charts with a single tracker, get the point closest to the mouse. - * Run Point.onMouseOver and display tooltip for the point or points. + * Collects the points closest to a mouseEvent + * @param {Array} series Array of series to gather points from + * @param {Boolean} shared True if shared tooltip, otherwise false + * @param {Object} e Mouse event which possess a position to compare against + * @return {Array} KDPoints sorted by distance */ - runPointActions: function(e) { - - var pointer = this, - chart = pointer.chart, - series = chart.series, - tooltip = chart.tooltip, - shared = tooltip ? tooltip.shared : false, - followPointer, - updatePosition = true, - hoverPoint = chart.hoverPoint, - hoverSeries = chart.hoverSeries, - i, - anchor, + getKDPoints: function(series, shared, e) { + var kdpoints = [], noSharedTooltip, - stickToHoverSeries, directTouch, - kdpoints = [], - kdpointT; + kdpointT, + i; - // 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) { - for (i = 0; i < series.length; i++) { - if (series[i].directTouch || !series[i].options.stickyTracking) { - series = []; + // Find nearest points on all series + each(series, function(s) { + // Skip hidden series + noSharedTooltip = s.noSharedTooltip && shared; + 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 && kdpointT.series) { // Point.series becomes null when reset and before redraw (#5197) + kdpoints.push(kdpointT); } } - } + }); - // 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) { - kdpoints = [hoverPoint]; + // Sort kdpoints by distance to mouse pointer + kdpoints.sort(function(p1, p2) { + var isCloserX = p1.distX - p2.distX, + isCloser = p1.dist - p2.dist, + isAbove = + (p2.series.group && p2.series.group.zIndex) - + (p1.series.group && p1.series.group.zIndex), + result; - // 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; - if (s.visible && !noSharedTooltip && !directTouch && pick(s.options.enableMouseTracking, true)) { // #3821 - kdpointT = s.searchPoint(e, !noSharedTooltip && s.kdDimensions === 1); // #3828 - if (kdpointT && kdpointT.series) { // Point.series becomes null when reset and before redraw (#5197) - kdpoints.push(kdpointT); - } - } - }); - - // Sort kdpoints by distance to mouse pointer - kdpoints.sort(function(p1, p2) { - var isCloserX = p1.distX - p2.distX, - isCloser = p1.dist - p2.dist, - isAbove = (p2.series.group && p2.series.group.zIndex) - - (p1.series.group && p1.series.group.zIndex); - - // We have two points which are not in the same place on xAxis and shared tooltip: - if (isCloserX !== 0 && shared) { // #5721 - return isCloserX; - } + // We have two points which are not in the same place on xAxis and shared tooltip: + if (isCloserX !== 0 && shared) { // #5721 + result = isCloserX; // Points are not exactly in the same place on x/yAxis: - if (isCloser !== 0) { - return isCloser; - } + } else if (isCloser !== 0) { + result = isCloser; // The same xAxis and yAxis position, sort by z-index: - if (isAbove !== 0) { - return isAbove; - } - + } else if (isAbove !== 0) { + result = isAbove; // The same zIndex, sort by array index: - return p1.series.index > p2.series.index ? -1 : 1; - }); - } + } else { + result = p1.series.index > p2.series.index ? -1 : 1; + } + return result; + }); // Remove points with different x-positions, required for shared tooltip and crosshairs (#4645): if (shared) { i = kdpoints.length; while (i--) { if (kdpoints[i].x !== kdpoints[0].x || kdpoints[i].series.noSharedTooltip) { kdpoints.splice(i, 1); } } } + return kdpoints; + }, + getPointFromEvent: function(e) { + var target = e.target, + point; - // Refresh tooltip for kdpoint if new hover point or tooltip was hidden // #3926, #4200 - if (kdpoints[0] && (kdpoints[0] !== this.prevKDPoint || (tooltip && tooltip.isHidden))) { - // Draw tooltip if necessary - if (shared && !kdpoints[0].series.noSharedTooltip) { - // Do mouseover on all points (#3919, #3985, #4410, #5622) - for (i = 0; i < kdpoints.length; i++) { - kdpoints[i].onMouseOver(e, kdpoints[i] !== ((hoverSeries && hoverSeries.directTouch && hoverPoint) || kdpoints[0])); - } + while (target && !point) { + point = target.point; + target = target.parentNode; + } + return point; + }, - if (kdpoints.length && tooltip) { - // Keep the order of series in tooltip: - tooltip.refresh(kdpoints.sort(function(p1, p2) { - return p1.series.index - p2.series.index; - }), e); + getHoverData: function(existingHoverPoint, existingHoverSeries, series, isDirectTouch, shared, e) { + var i, + hoverPoint = existingHoverPoint, + hoverSeries = existingHoverSeries, + hoverPoints; + + // 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. + // Handle shared tooltip or cases where a series is not yet hovered + if (isDirectTouch) { + if (shared) { + hoverPoints = []; + each(series, function(s) { + // Skip hidden series + var noSharedTooltip = s.noSharedTooltip && shared, + directTouch = !shared && s.directTouch, + kdpointT; + if (s.visible && !noSharedTooltip && !directTouch && pick(s.options.enableMouseTracking, true)) { // #3821 + kdpointT = s.searchKDTree({ + clientX: hoverPoint.clientX, + plotY: hoverPoint.plotY + }, !noSharedTooltip && s.kdDimensions === 1); + if (kdpointT && kdpointT.series) { // Point.series becomes null when reset and before redraw (#5197) + hoverPoints.push(kdpointT); + } + } + }); + // If kdTree is not built + if (hoverPoints.length === 0) { + hoverPoints = [hoverPoint]; } } else { - if (tooltip) { - tooltip.refresh(kdpoints[0], e); + hoverPoints = [hoverPoint]; + } + } else if (hoverSeries && !hoverSeries.options.stickyTracking) { + hoverPoints = this.getKDPoints([hoverSeries], shared, e); + hoverPoint = hoverPoints[0]; + hoverSeries = hoverPoint && hoverPoint.series; + } else { + if (!shared) { + // 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 (!hoverSeries) { + for (i = 0; i < series.length; i++) { + if (series[i].directTouch || !series[i].options.stickyTracking) { + series = []; + } + } + // When we have non-shared tooltip and sticky tracking is disabled, + // search for the closest point only on hovered series: #5533, #5476 + } else if (!hoverSeries.options.stickyTracking) { + series = [hoverSeries]; } - if (!hoverSeries || !hoverSeries.directTouch) { // #4448 - kdpoints[0].onMouseOver(e); - } } - this.prevKDPoint = kdpoints[0]; - updatePosition = false; + hoverPoints = this.getKDPoints(series, shared, e); + hoverPoint = hoverPoints[0]; + hoverSeries = hoverPoint && hoverPoint.series; } - // Update positions (regardless of kdpoint or hoverPoint) - if (updatePosition) { - followPointer = hoverSeries && hoverSeries.tooltipOptions.followPointer; - if (tooltip && followPointer && !tooltip.isHidden) { - anchor = tooltip.getAnchor([{}], e); - tooltip.updatePosition({ - plotX: anchor[0], - plotY: anchor[1] - }); + // Keep the order of series in tooltip + // Must be done after assigning of hoverPoint + hoverPoints.sort(function(p1, p2) { + return p1.series.index - p2.series.index; + }); + + return { + hoverPoint: hoverPoint, + hoverSeries: hoverSeries, + hoverPoints: hoverPoints + }; + }, + /** + * With line type charts with a single tracker, get the point closest to the mouse. + * Run Point.onMouseOver and display tooltip for the point or points. + */ + runPointActions: function(e, p) { + var pointer = this, + chart = pointer.chart, + series = chart.series, + tooltip = chart.tooltip, + shared = tooltip ? tooltip.shared : false, + hoverPoint = p || chart.hoverPoint, + hoverSeries = hoverPoint && hoverPoint.series || chart.hoverSeries, + // onMouseOver or already hovering a series with directTouch + isDirectTouch = !!p || (hoverSeries && hoverSeries.directTouch), + hoverData = this.getHoverData(hoverPoint, hoverSeries, series, isDirectTouch, shared, e), + useSharedTooltip, + followPointer, + anchor, + points; + + // Update variables from hoverData. + hoverPoint = hoverData.hoverPoint; + hoverSeries = hoverData.hoverSeries; + followPointer = hoverSeries && hoverSeries.tooltipOptions.followPointer; + useSharedTooltip = shared && hoverPoint && !hoverPoint.series.noSharedTooltip; + points = useSharedTooltip ? hoverData.hoverPoints : [hoverPoint]; + + // Refresh tooltip for kdpoint if new hover point or tooltip was hidden // #3926, #4200 + if ( + hoverPoint && + // !(hoverSeries && hoverSeries.directTouch) && + (hoverPoint !== chart.hoverPoint || (tooltip && tooltip.isHidden)) + ) { + each(chart.hoverPoints || [], function(p) { + if (H.inArray(p, points) === -1) { + p.setState(); + } + }); + // Do mouseover on all points (#3919, #3985, #4410, #5622) + each(points || [], function(p) { + p.setState('hover'); + }); + // set normal state to previous series + if (chart.hoverSeries !== hoverSeries) { + hoverSeries.onMouseOver(); } + + // If tracking is on series in stead of on each point, + // fire mouseOver on hover point. + if (hoverSeries && !hoverSeries.directTouch) { // #4448 + if (chart.hoverPoint) { + chart.hoverPoint.firePointEvent('mouseOut'); + } + hoverPoint.firePointEvent('mouseOver'); + } + chart.hoverPoints = points; + chart.hoverPoint = hoverPoint; + // Draw tooltip if necessary + if (tooltip) { + tooltip.refresh(useSharedTooltip ? points : hoverPoint, e); + } + // Update positions (regardless of kdpoint or hoverPoint) + } else if (followPointer && tooltip && !tooltip.isHidden) { + anchor = tooltip.getAnchor([{}], e); + tooltip.updatePosition({ + plotX: anchor[0], + plotY: anchor[1] + }); } // Start the event listener to pick up the tooltip and crosshairs if (!pointer.unDocMouseMove) { pointer.unDocMouseMove = addEvent(doc, 'mousemove', function(e) { - if (charts[H.hoverChartIndex]) { - charts[H.hoverChartIndex].pointer.onDocumentMouseMove(e); + var chart = charts[H.hoverChartIndex]; + if (chart) { + chart.pointer.onDocumentMouseMove(e); } }); } // Crosshair. For each hover point, loop over axes and draw cross if that point // belongs to the axis (#4927). - each(shared ? kdpoints : [pick(hoverPoint, kdpoints[0])], function drawPointCrosshair(point) { // #5269 + each(points, 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 && point.series[axis.coll] === axis) { // #5658 axis.drawCrosshair(e, point); } @@ -12558,11 +12780,11 @@ // Remove crosshairs each(chart.axes, function(axis) { axis.hideCrosshair(); }); - pointer.hoverX = pointer.prevKDPoint = chart.hoverPoints = chart.hoverPoint = null; + pointer.hoverX = chart.hoverPoints = chart.hoverPoint = null; } }, /** * Scale series groups to a certain scale and translation @@ -12979,11 +13201,10 @@ /** * (c) 2010-2016 Torstein Honsi * * License: www.highcharts.com/license */ - 'use strict'; var charts = H.charts, each = H.each, extend = H.extend, map = H.map, noop = H.noop, @@ -13257,11 +13478,10 @@ /** * (c) 2010-2016 Torstein Honsi * * License: www.highcharts.com/license */ - 'use strict'; var addEvent = H.addEvent, charts = H.charts, css = H.css, doc = H.doc, extend = H.extend, @@ -13377,19 +13597,17 @@ /** * (c) 2010-2016 Torstein Honsi * * License: www.highcharts.com/license */ - 'use strict'; var Legend, addEvent = H.addEvent, css = H.css, discardElement = H.discardElement, defined = H.defined, each = H.each, - extend = H.extend, isFirefox = H.isFirefox, marginNames = H.marginNames, merge = H.merge, pick = H.pick, setAnimation = H.setAnimation, @@ -13578,11 +13796,21 @@ // Destroy items each(this.getAllItems(), function(item) { each(['legendItem', 'legendGroup'], destroyItems, item); }); - each(['box', 'title', 'group'], destroyItems, this); + // Destroy legend elements + each([ + 'clipRect', + 'up', + 'down', + 'pager', + 'nav', + 'box', + 'title', + 'group' + ], destroyItems, this); this.display = null; // Reset in .render on update. }, /** * Position the checkboxes after the width is determined @@ -13626,13 +13854,13 @@ this.title = this.chart.renderer.label(titleOptions.text, padding - 3, padding - 4, null, null, null, null, null, 'legend-title') .attr({ zIndex: 1 }) - .css(titleOptions.style) + .css(titleOptions.style) - .add(this.group); + .add(this.group); } bBox = this.title.getBBox(); titleHeight = bBox.height; this.offsetWidth = bBox.width; // #1717 this.contentGroup.attr({ @@ -13700,19 +13928,19 @@ }) .add(legend.scrollGroup); // Generate the list item text and add it to the group item.legendItem = li = renderer.text( - '', - ltr ? symbolWidth + symbolPadding : -symbolPadding, - legend.baseline || 0, - useHTML - ) + '', + ltr ? symbolWidth + symbolPadding : -symbolPadding, + legend.baseline || 0, + useHTML + ) - .css(merge(item.visible ? itemStyle : itemHiddenStyle)) // merge to prevent modifying original (#1021) + .css(merge(item.visible ? itemStyle : itemHiddenStyle)) // merge to prevent modifying original (#1021) - .attr({ + .attr({ align: ltr ? 'left' : 'right', zIndex: 2 }) .add(item.legendGroup); @@ -13980,14 +14208,14 @@ options[i < 2 ? 'x' : 'y'] = pInt(options.style[prop]) * (i % 2 ? -1 : 1); } }*/ if (display) { - legendGroup.align(extend({ + legendGroup.align(merge(options, { width: legendWidth, height: legendHeight - }, options), true, 'spacingBox'); + }), true, 'spacingBox'); } if (!chart.isResizing) { this.positionCheckboxes(); } @@ -14091,13 +14319,13 @@ }) .add(nav); this.pager = renderer.text('', 15, 10) .addClass('highcharts-legend-navigation') - .css(navOptions.style) + .css(navOptions.style) - .add(nav); + .add(nav); this.down = renderer.symbol('triangle-down', 0, 0, arrowSize, arrowSize) .on('click', function() { legend.scroll(1, animation); }) .add(nav); @@ -14109,11 +14337,11 @@ legendHeight = spaceHeight; // Reset } else if (nav) { clipToHeight(); - nav.hide(); + this.nav = nav.destroy(); // #6322 this.scrollGroup.attr({ translateY: 1 }); this.clipHeight = 0; // #1379 } @@ -14327,11 +14555,10 @@ /** * (c) 2010-2016 Torstein Honsi * * License: www.highcharts.com/license */ - 'use strict'; var addEvent = H.addEvent, animate = H.animate, animObject = H.animObject, attr = H.attr, doc = H.doc, @@ -14414,11 +14641,10 @@ userOptions.series = null; options = merge(defaultOptions, userOptions); // do the merge options.series = userOptions.series = seriesOptions; // set back the series data this.userOptions = userOptions; - this.respRules = []; var optionsChart = options.chart; var chartEvents = optionsChart.events; @@ -14563,12 +14789,11 @@ redrawLegend = chart.isDirtyLegend, hasStackedSeries, hasDirtyStacks, hasCartesianSeries = chart.hasCartesianSeries, isDirtyBox = chart.isDirtyBox, - seriesLength = series.length, - i = seriesLength, + i, serie, renderer = chart.renderer, isHiddenChart = renderer.isHidden(), afterRedraw = []; @@ -14585,10 +14810,11 @@ // Adjust title layout (reflow multiline text) chart.layOutTitles(); // link stacked series + i = series.length; while (i--) { serie = series[i]; if (serie.options.stacking) { hasStackedSeries = true; @@ -14598,11 +14824,11 @@ break; } } } if (hasDirtyStacks) { // mark others as dirty - i = seriesLength; + i = series.length; while (i--) { serie = series[i]; if (serie.options.stacking) { serie.isDirty = true; } @@ -14946,11 +15172,14 @@ 0, widthOption || chart.containerWidth || 600 // #1460 ); chart.chartHeight = Math.max( 0, - heightOption || chart.containerHeight || 400 + H.relativeLength( + heightOption, + chart.chartWidth + ) || chart.containerHeight || 400 ); }, /** * Create a clone of the chart's renderTo div and place it outside the viewport to allow @@ -15733,13 +15962,13 @@ .attr({ align: credits.position.align, zIndex: 8 }) - .css(credits.style) + .css(credits.style) - .add() + .add() .align(credits.position); // Dynamically update this.credits.update = function(options) { chart.credits = chart.credits.destroy(); @@ -15929,11 +16158,10 @@ /** * (c) 2010-2016 Torstein Honsi * * License: www.highcharts.com/license */ - 'use strict'; var Point, each = H.each, extend = H.extend, erase = H.erase, @@ -16280,11 +16508,10 @@ /** * (c) 2010-2016 Torstein Honsi * * License: www.highcharts.com/license */ - 'use strict'; var addEvent = H.addEvent, animObject = H.animObject, arrayMax = H.arrayMax, arrayMin = H.arrayMin, correctFloat = H.correctFloat, @@ -16413,10 +16640,13 @@ //showInLegend: null, // auto: true for standalone series, false for linked series softThreshold: true, states: { // states for the entire series hover: { //enabled: false, + animation: { + duration: 50 + }, lineWidthPlus: 1, marker: { // lineWidth: base + 1, // radius: base + 1 }, @@ -16438,11 +16668,11 @@ //xDateFormat: '%A, %b %e, %Y', //valuePrefix: '', //ySuffix: '' //} turboThreshold: 1000 - // zIndex: null + // zIndex: null }, /** @lends Series.prototype */ { isCartesian: true, pointClass: Point, @@ -17120,11 +17350,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 = (isNumber(y, true) || isArray(y)) && (!yAxis.isLog || (y.length || y > 0)); + validValue = (isNumber(y, true) || isArray(y)) && (!yAxis.positiveValuesOnly || (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) { @@ -17194,11 +17424,11 @@ 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) { + if (yAxis.positiveValuesOnly && yValue !== null && yValue <= 0) { point.isNull = true; } // Get the plotX translation point.plotX = plotX = correctFloat( // #5236 @@ -17222,11 +17452,11 @@ yValue = stackValues[1]; if (yBottom === stackThreshold && stackIndicator.key === stack[xValue].base) { yBottom = pick(threshold, yAxis.min); } - if (yAxis.isLog && yBottom <= 0) { // #1200, #1232 + if (yAxis.positiveValuesOnly && yBottom <= 0) { // #1200, #1232 yBottom = null; } point.total = point.stackTotal = pointStack.total; point.percentage = pointStack.total && (point.y / pointStack.total * 100); @@ -17429,11 +17659,12 @@ xAxis = series.xAxis, markerAttribs, globallyEnabled = pick( seriesMarkerOptions.enabled, xAxis.isRadial ? true : null, - series.closestPointRangePx > 2 * seriesMarkerOptions.radius + // Use larger or equal as radius is null in bubbles (#6321) + series.closestPointRangePx >= 2 * seriesMarkerOptions.radius ); if (seriesMarkerOptions.enabled !== false || series._hasPointMarkers) { for (i = 0; i < points.length; i++) { @@ -17980,10 +18211,19 @@ remover; function setInvert() { each(['group', 'markerGroup'], function(groupName) { if (series[groupName]) { + + // VML/HTML needs explicit attributes for flipping + if (chart.renderer.isVML) { + series[groupName].attr({ + width: series.yAxis.len, + height: series.xAxis.len + }); + } + series[groupName].width = series.yAxis.len; series[groupName].height = series.xAxis.len; series[groupName].invert(inverted); } }); @@ -18327,11 +18567,10 @@ /** * (c) 2010-2016 Torstein Honsi * * License: www.highcharts.com/license */ - 'use strict'; var Axis = H.Axis, Chart = H.Chart, correctFloat = H.correctFloat, defined = H.defined, destroyObjectProperties = H.destroyObjectProperties, @@ -18813,11 +19052,10 @@ /** * (c) 2010-2016 Torstein Honsi * * License: www.highcharts.com/license */ - 'use strict'; var addEvent = H.addEvent, animate = H.animate, Axis = H.Axis, Chart = H.Chart, createElement = H.createElement, @@ -19061,10 +19299,11 @@ // options.credits => chart.credits // options.legend => chart.legend // options.title => chart.title // options.tooltip => chart.tooltip // options.subtitle => chart.subtitle + // options.mapNavigation => chart.mapNavigation // options.navigator => chart.navigator // options.scrollbar => chart.scrollbar for (key in options) { if (this[key] && typeof this[key].update === 'function') { this[key].update(options[key], false); @@ -19092,11 +19331,10 @@ // Setters for collections. For axes and series, each item is referred // by an id. If the id is not found, it defaults to the corresponding // item in the collection, so setting one series without an id, will // update the first series in the chart. Setting two series without // an id will update the first and the second respectively (#6019) - // // docs: New behaviour for unidentified items, add it to docs for // chart.update and responsive. each(['xAxis', 'yAxis', 'series'], function(coll) { if (options[coll]) { each(splat(options[coll]), function(newOptions, i) { var item = ( @@ -19192,13 +19430,19 @@ // record changes in the parallel arrays i = point.index; series.updateParallelArrays(point, i); - // 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; + // Record the options to options.data. If the old or the new config + // is an object, use point options, otherwise use raw options + // (#4701, #4916). + seriesOptions.data[i] = ( + isObject(seriesOptions.data[i], true) || + isObject(options, true) + ) ? + point.options : + options; // redraw series.isDirty = series.isDirtyData = true; if (!series.fixedBox && series.hasCartesianSeries) { // #1906, #2320 chart.isDirtyBox = true; @@ -19401,11 +19645,11 @@ var series = this, chart = this.chart, // must use user options when changing type because this.options is merged // in with type specific plotOptions oldOptions = this.userOptions, - oldType = this.type, + oldType = this.oldType || this.type, newType = newOptions.type || oldOptions.type || chart.options.chart.type, proto = seriesTypes[oldType].prototype, preserve = ['group', 'markerGroup', 'dataLabelsGroup'], n; @@ -19441,10 +19685,11 @@ each(preserve, function(prop) { series[prop] = preserve[prop]; }); this.init(chart, newOptions); + this.oldType = oldType; chart.linkSeries(); // Links are lost in this.remove (#3028) if (pick(redraw, true)) { chart.redraw(false); } } @@ -19531,11 +19776,10 @@ /** * (c) 2010-2016 Torstein Honsi * * License: www.highcharts.com/license */ - 'use strict'; var color = H.color, each = H.each, LegendSymbolMixin = H.LegendSymbolMixin, map = H.map, pick = H.pick, @@ -19548,14 +19792,14 @@ * @extends {Series} */ seriesType('area', 'line', { softThreshold: false, threshold: 0 - // trackByArea: false, - // lineColor: null, // overrides color, but lets fillColor be unaltered - // fillOpacity: 0.75, - // fillColor: null + // trackByArea: false, + // lineColor: null, // overrides color, but lets fillColor be unaltered + // fillOpacity: 0.75, + // fillColor: null }, /** @lends seriesTypes.area.prototype */ { singleStacks: false, /** * Return an array of stacked points, where null and missing points are replaced by * dummy points in order for gaps to be drawn correctly in stacks. @@ -19656,15 +19900,15 @@ break; } // When reversedStacks is true, loop up, else loop down i += upOrDown; } - - y = yAxis.toPixels(y, true); + y = yAxis.translate(y, 0, 1, 0, 1); // #6272 segment.push({ isNull: true, - plotX: xAxis.toPixels(x, true), + plotX: xAxis.translate(x, 0, 0, 0, 1), // #6272 + x: x, plotY: y, yBottom: y }); } }); @@ -19721,11 +19965,12 @@ // Add to the top and bottom line of the area if (top !== undefined) { graphPoints.push({ plotX: plotX, plotY: top === null ? translatedThreshold : yAxis.getThreshold(top), - isNull: isNull + isNull: isNull, + isCliff: true }); bottomPoints.push({ plotX: plotX, plotY: bottom === null ? translatedThreshold : yAxis.getThreshold(bottom), doCurve: false // #1041, gaps in areaspline areas @@ -19778,10 +20023,11 @@ areaPath = topPath.concat(bottomPath); graphPath = getGraphPath.call(this, graphPoints, false, connectNulls); // TODO: don't set leftCliff and rightCliff when connectNulls? areaPath.xMap = topPath.xMap; this.areaPath = areaPath; + return graphPath; }, /** * Draw the graph and the underlying area. This method calls the Series base @@ -19861,11 +20107,10 @@ /** * (c) 2010-2016 Torstein Honsi * * License: www.highcharts.com/license */ - 'use strict'; var pick = H.pick, seriesType = H.seriesType; /** * Spline series type. @@ -19888,11 +20133,14 @@ rightContX, rightContY, ret; function doCurve(otherPoint) { - return otherPoint && !otherPoint.isNull && otherPoint.doCurve !== false; + return otherPoint && + !otherPoint.isNull && + otherPoint.doCurve !== false && + !point.isCliff; // #6387, area splines next to null } // Find control points if (doCurve(lastPoint) && doCurve(nextPoint)) { var lastX = lastPoint.plotX, @@ -19997,11 +20245,10 @@ /** * (c) 2010-2016 Torstein Honsi * * License: www.highcharts.com/license */ - 'use strict'; var areaProto = H.seriesTypes.area.prototype, defaultPlotOptions = H.defaultPlotOptions, LegendSymbolMixin = H.LegendSymbolMixin, seriesType = H.seriesType; /** @@ -20020,11 +20267,10 @@ /** * (c) 2010-2016 Torstein Honsi * * License: www.highcharts.com/license */ - 'use strict'; var animObject = H.animObject, color = H.color, each = H.each, extend = H.extend, isNumber = H.isNumber, @@ -20080,11 +20326,11 @@ distance: 6 }, threshold: 0, borderColor: '#ffffff' - // borderWidth: 1 + // borderWidth: 1 }, /** @lends seriesTypes.column.prototype */ { cropShoulder: 0, directTouch: true, // When tooltip is not shared, this series (and derivatives) requires direct touch/hover. KD-tree does not apply. @@ -20341,11 +20587,14 @@ fill = (zone && zone.color) || point.options.color || this.color; // When zones are present, don't use point.color (#4267) } // Select or hover states if (state) { - stateOptions = options.states[state]; + stateOptions = merge( + options.states[state], + point.options.states && point.options.states[state] || {} // #6401 + ); brightness = stateOptions.brightness; fill = stateOptions.color || (brightness !== undefined && color(fill).brighten(stateOptions.brightness).get()) || fill; stroke = stateOptions[strokeOption] || stroke; @@ -20396,23 +20645,23 @@ merge(shapeArgs) ); } else { point.graphic = graphic = renderer[point.shapeType](shapeArgs) - .attr({ - 'class': point.getClassName() - }) .add(point.group || series.group); } // Presentational graphic .attr(series.pointAttribs(point, point.selected && 'select')) .shadow(options.shadow, null, options.stacking && !options.borderRadius); + graphic.addClass(point.getClassName(), true); + + } else if (graphic) { point.graphic = graphic.destroy(); // #1269 } }); }, @@ -20484,11 +20733,10 @@ /** * (c) 2010-2016 Torstein Honsi * * License: www.highcharts.com/license */ - 'use strict'; var seriesType = H.seriesType; /** * The Bar series class @@ -20502,11 +20750,10 @@ /** * (c) 2010-2016 Torstein Honsi * * License: www.highcharts.com/license */ - 'use strict'; var Series = H.Series, seriesType = H.seriesType; /** * The scatter series type */ @@ -20543,11 +20790,10 @@ /** * (c) 2010-2016 Torstein Honsi * * License: www.highcharts.com/license */ - 'use strict'; var pick = H.pick, relativeLength = H.relativeLength; H.CenteredSeriesMixin = { /** @@ -20593,11 +20839,10 @@ /** * (c) 2010-2016 Torstein Honsi * * License: www.highcharts.com/license */ - 'use strict'; var addEvent = H.addEvent, CenteredSeriesMixin = H.CenteredSeriesMixin, defined = H.defined, each = H.each, extend = H.extend, @@ -20631,11 +20876,11 @@ formatter: function() { // #2945 return this.y === null ? undefined : this.point.name; }, // softConnector: true, x: 0 - // y: 0 + // y: 0 }, ignoreHiddenPoint: true, //innerSize: 0, legendType: 'point', marker: null, // point options are specified in the base options @@ -20878,12 +21123,13 @@ if (point.y !== null) { graphic = point.graphic; shapeArgs = point.shapeArgs; - // if the point is sliced, use special translation, else use plot area traslation - groupTranslation = point.sliced ? point.slicedTranslation : {}; + // If the point is sliced, use special translation, else use + // plot area traslation + groupTranslation = point.getTranslate(); // Put the shadow behind all points var shadowGroup = point.shadowGroup; if (shadow && !shadowGroup) { @@ -20900,17 +21146,16 @@ // Draw the slice if (graphic) { graphic .setRadialReference(series.center) - .attr(pointAttr) + .attr(pointAttr) - .animate(extend(shapeArgs, groupTranslation)); + .animate(extend(shapeArgs, groupTranslation)); } else { point.graphic = graphic = renderer[point.shapeType](shapeArgs) - .addClass(point.getClassName()) .setRadialReference(series.center) .attr(groupTranslation) .add(series.group); if (!point.visible) { @@ -20926,10 +21171,13 @@ 'stroke-linejoin': 'round' }) .shadow(shadow, shadowGroup); } + + graphic.addClass(point.getClassName()); + } }); }, @@ -21041,36 +21289,37 @@ * @param {Boolean} redraw Whether to redraw the chart. True by default. */ slice: function(sliced, redraw, animation) { var point = this, series = point.series, - chart = series.chart, - translation; + chart = series.chart; setAnimation(animation, chart); // redraw is true by default redraw = pick(redraw, true); // if called without an argument, toggle point.sliced = point.options.sliced = sliced = defined(sliced) ? sliced : !point.sliced; series.options.data[inArray(point, series.data)] = point.options; // update userOptions.data - translation = sliced ? point.slicedTranslation : { - translateX: 0, - translateY: 0 - }; + point.graphic.animate(this.getTranslate()); - point.graphic.animate(translation); - if (point.shadowGroup) { - point.shadowGroup.animate(translation); + point.shadowGroup.animate(this.getTranslate()); } }, + getTranslate: function() { + return this.sliced ? this.slicedTranslation : { + translateX: 0, + translateY: 0 + }; + }, + haloPath: function(size) { var shapeArgs = this.shapeArgs; return this.sliced || !this.visible ? [] : this.series.chart.renderer.symbols.arc( @@ -21091,11 +21340,10 @@ /** * (c) 2010-2016 Torstein Honsi * * License: www.highcharts.com/license */ - 'use strict'; var addEvent = H.addEvent, arrayMax = H.arrayMax, defined = H.defined, each = H.each, extend = H.extend, @@ -21582,10 +21830,25 @@ // get out if not enabled if (!series.visible || (!options.enabled && !series._hasPointLabels)) { return; } + // Reset all labels that have been shortened + each(data, function(point) { + if (point.dataLabel && point.visible && point.dataLabel.shortened) { + point.dataLabel + .attr({ + width: 'auto' + }).css({ + width: 'auto', + textOverflow: 'clip' + }); + point.dataLabel.shortened = false; + } + }); + + // run parent method Series.prototype.drawDataLabels.apply(series); each(data, function(point) { if (point.dataLabel && point.visible) { // #407, #2510 @@ -21606,10 +21869,11 @@ var top, bottom, length = points.length, positions, naturalY, + sideOverflow, size; if (!length) { return; } @@ -21681,28 +21945,43 @@ labelPos.y = y; // Detect overflowing data labels if (series.options.size === null) { - dataLabelWidth = dataLabel.width; + dataLabelWidth = dataLabel.getBBox().width; + + sideOverflow = null; // Overflow left if (x - dataLabelWidth < connectorPadding) { - overflow[3] = Math.max(Math.round(dataLabelWidth - x + connectorPadding), overflow[3]); + sideOverflow = Math.round( + dataLabelWidth - x + connectorPadding + ); + overflow[3] = Math.max(sideOverflow, overflow[3]); // Overflow right } else if (x + dataLabelWidth > plotWidth - connectorPadding) { - overflow[1] = Math.max(Math.round(x + dataLabelWidth - plotWidth + connectorPadding), overflow[1]); + sideOverflow = Math.round( + x + dataLabelWidth - plotWidth + connectorPadding + ); + overflow[1] = Math.max(sideOverflow, overflow[1]); } // Overflow top if (y - labelHeight / 2 < 0) { - overflow[0] = Math.max(Math.round(-y + labelHeight / 2), overflow[0]); + overflow[0] = Math.max( + Math.round(-y + labelHeight / 2), + overflow[0] + ); // Overflow left } else if (y + labelHeight / 2 > plotHeight) { - overflow[2] = Math.max(Math.round(y + labelHeight / 2 - plotHeight), overflow[2]); + overflow[2] = Math.max( + Math.round(y + labelHeight / 2 - plotHeight), + overflow[2] + ); } + dataLabel.sideOverflow = sideOverflow; } } // for each point }); // for each half // Do not apply the final placement and draw the connectors until we have verified @@ -21782,24 +22061,36 @@ */ seriesTypes.pie.prototype.placeDataLabels = function() { each(this.points, function(point) { var dataLabel = point.dataLabel, _pos; - if (dataLabel && point.visible) { _pos = dataLabel._pos; if (_pos) { + + // Shorten data labels with ellipsis if they still overflow + // after the pie has reached minSize (#223). + if (dataLabel.sideOverflow) { + dataLabel._attr.width = + dataLabel.getBBox().width - dataLabel.sideOverflow; + dataLabel.css({ + width: dataLabel._attr.width + 'px', + textOverflow: 'ellipsis' + }); + dataLabel.shortened = true; + } + dataLabel.attr(dataLabel._attr); dataLabel[dataLabel.moved ? 'animate' : 'attr'](_pos); dataLabel.moved = true; } else if (dataLabel) { dataLabel.attr({ y: -9999 }); } } - }); + }, this); }; seriesTypes.pie.prototype.alignDataLabel = noop; /** @@ -21928,11 +22219,10 @@ /** * (c) 2009-2016 Torstein Honsi * * License: www.highcharts.com/license */ - 'use strict'; /** * Highcharts module to hide overlapping data labels. This module is included in Highcharts. */ var Chart = H.Chart, each = H.each, @@ -21943,11 +22233,11 @@ // considered because they are usually accompanied by data labels that lie inside the columns. Chart.prototype.callbacks.push(function(chart) { function collectAndHide() { var labels = []; - each(chart.series, function(series) { + each(chart.series || [], function(series) { var dlOptions = series.options.dataLabels, collections = series.dataLabelCollections || ['dataLabel']; // Range series have two collections if ((dlOptions.enabled || series._hasPointLabels) && !dlOptions.allowOverlap && series.visible) { // #3866 each(collections, function(coll) { each(series.points, function(point) { @@ -22077,11 +22367,10 @@ /** * (c) 2010-2016 Torstein Honsi * * License: www.highcharts.com/license */ - 'use strict'; var addEvent = H.addEvent, Chart = H.Chart, createElement = H.createElement, css = H.css, defaultOptions = H.defaultOptions, @@ -22114,19 +22403,14 @@ drawTrackerPoint: function() { var series = this, chart = series.chart, pointer = chart.pointer, onMouseOver = function(e) { - var target = e.target, - point; + var point = pointer.getPointFromEvent(e); - while (target && !point) { - point = target.point; - target = target.parentNode; - } - - if (point !== undefined && point !== chart.hoverPoint) { // undefined on graph in scatterchart + // undefined on graph in scatterchart + if (point !== undefined) { point.onMouseOver(e); } }; // Add reference to the point @@ -22297,19 +22581,19 @@ */ extend(Legend.prototype, { setItemEvents: function(item, legendItem, useHTML) { var legend = this, - chart = legend.chart, + boxWrapper = legend.chart.renderer.boxWrapper, activeClass = 'highcharts-legend-' + (item.series ? 'point' : 'series') + '-active'; // Set the events on the item group, or in case of useHTML, the item itself (#1249) (useHTML ? legendItem : item.legendGroup).on('mouseover', function() { item.setState('hover'); // A CSS class to dim or hide other than the hovered series - chart.seriesGroup.addClass(activeClass); + boxWrapper.addClass(activeClass); legendItem.css(legend.options.itemHoverStyle); }) @@ -22317,11 +22601,11 @@ legendItem.css(item.visible ? legend.itemStyle : legend.itemHiddenStyle); // A CSS class to dim or hide other than the hovered series - chart.seriesGroup.removeClass(activeClass); + boxWrapper.removeClass(activeClass); item.setState(); }) .on('click', function(event) { var strLegendItemClick = 'legendItemClick', @@ -22499,12 +22783,18 @@ panMax = axis.toValue(startPos + axis.len - mousePos, true) - halfPointRange, flipped = panMax < panMin, newMin = flipped ? panMax : panMin, newMax = flipped ? panMin : panMax, - distMin = Math.min(extremes.dataMin, extremes.min) - newMin, - distMax = newMax - Math.max(extremes.dataMax, extremes.max); + paddedMin = axis.toValue( + axis.toPixels(extremes.min) - axis.minPixelPadding + ), + paddedMax = axis.toValue( + axis.toPixels(extremes.max) + axis.minPixelPadding + ), + distMin = Math.min(extremes.dataMin, paddedMin) - newMin, + distMax = newMax - Math.max(extremes.dataMax, paddedMax); // Negative distMin and distMax means that we're still inside the // data range. if (axis.series.length && distMin < 0 && distMax < 0) { axis.setExtremes( @@ -22570,62 +22860,33 @@ }); }, /** * Runs on mouse over the point - * + * * @param {Object} e The event arguments - * @param {Boolean} byProximity Falsy for kd points that are closest to the mouse, or to - * actually hovered points. True for other points in shared tooltip. */ - onMouseOver: function(e, byProximity) { + onMouseOver: function(e) { var point = this, series = point.series, chart = series.chart, - tooltip = chart.tooltip, - hoverPoint = chart.hoverPoint; - - if (point.series) { // It may have been destroyed, #4130 - // In shared tooltip, call mouse over when point/series is actually hovered: (#5766) - if (!byProximity) { - // set normal state to previous series - if (hoverPoint && hoverPoint !== point) { - hoverPoint.onMouseOut(); - } - if (chart.hoverSeries !== series) { - series.onMouseOver(); - } - chart.hoverPoint = point; - } - - // update the tooltip - if (tooltip && (!tooltip.shared || series.noSharedTooltip)) { - // hover point only for non shared points: (#5766) - point.setState('hover'); - tooltip.refresh(point, e); - } else if (!tooltip) { - point.setState('hover'); - } - - // trigger the event - point.firePointEvent('mouseOver'); - } + pointer = chart.pointer; + point.firePointEvent('mouseOver'); + pointer.runPointActions(e, point); }, /** * Runs on mouse out from the point */ onMouseOut: function() { - var chart = this.series.chart, - hoverPoints = chart.hoverPoints; - - this.firePointEvent('mouseOut'); - - if (!hoverPoints || inArray(this, hoverPoints) === -1) { // #887, #2240 - this.setState(); - chart.hoverPoint = null; - } + var point = this, + chart = point.series.chart; + point.firePointEvent('mouseOut'); + each(chart.hoverPoints || [], function(p) { + p.setState(); + }); + chart.hoverPoints = chart.hoverPoint = null; }, /** * Import events from the series' and point's options. Only do it on * demand, to save processing time on hovering. @@ -22907,11 +23168,15 @@ state = state || ''; if (series.state !== state) { // Toggle class names - each([series.group, series.markerGroup], function(group) { + each([ + series.group, + series.markerGroup, + series.dataLabelsGroup + ], function(group) { if (group) { // Old state if (series.state) { group.removeClass('highcharts-series-' + series.state); } @@ -22936,12 +23201,20 @@ if (graph && !graph.dashstyle) { // hover is turned off for dashed lines in VML attribs = { 'stroke-width': lineWidth }; - // use attr because animate will cause any other animation on the graph to stop - graph.attr(attribs); + + // Animate the graph stroke-width. By default a quick animation + // to hover, slower to un-hover. + graph.animate( + attribs, + pick( + series.chart.options.chart.animation, + stateOptions[state] && stateOptions[state].animation + ) + ); while (series['zone-graph-' + i]) { series['zone-graph-' + i].attr(attribs); i = i + 1; } } @@ -23053,66 +23326,91 @@ /** * (c) 2010-2016 Torstein Honsi * * License: www.highcharts.com/license */ - 'use strict'; var Chart = H.Chart, each = H.each, inArray = H.inArray, + isArray = H.isArray, isObject = H.isObject, pick = H.pick, splat = H.splat; /** - * Update the chart based on the current chart/document size and options for responsiveness + * Update the chart based on the current chart/document size and options for + * responsiveness. */ Chart.prototype.setResponsive = function(redraw) { - var options = this.options.responsive; + var options = this.options.responsive, + ruleIds = [], + currentResponsive = this.currentResponsive, + currentRuleIds; if (options && options.rules) { each(options.rules, function(rule) { - this.matchResponsiveRule(rule, redraw); + if (rule._id === undefined) { + rule._id = H.uniqueKey(); + } + + this.matchResponsiveRule(rule, ruleIds, redraw); }, this); } + + // Merge matching rules + var mergedOptions = H.merge.apply(0, H.map(ruleIds, function(ruleId) { + return H.find(options.rules, function(rule) { + return rule._id === ruleId; + }).chartOptions; + })); + + // Stringified key for the rules that currently apply. + ruleIds = ruleIds.toString() || undefined; + currentRuleIds = currentResponsive && currentResponsive.ruleIds; + + + // Changes in what rules apply + if (ruleIds !== currentRuleIds) { + + // Undo previous rules. Before we apply a new set of rules, we need to + // roll back completely to base options (#6291). + if (currentResponsive) { + this.update(currentResponsive.undoOptions, redraw); + } + + if (ruleIds) { + // Get undo-options for matching rules + this.currentResponsive = { + ruleIds: ruleIds, + mergedOptions: mergedOptions, + undoOptions: this.currentOptions(mergedOptions) + }; + + this.update(mergedOptions, redraw); + + } else { + this.currentResponsive = undefined; + } + } }; /** * Handle a single responsiveness rule */ - Chart.prototype.matchResponsiveRule = function(rule, redraw) { - var respRules = this.respRules, - condition = rule.condition, - matches, + Chart.prototype.matchResponsiveRule = function(rule, matches) { + var condition = rule.condition, fn = condition.callback || function() { return this.chartWidth <= pick(condition.maxWidth, Number.MAX_VALUE) && this.chartHeight <= pick(condition.maxHeight, Number.MAX_VALUE) && this.chartWidth >= pick(condition.minWidth, 0) && this.chartHeight >= pick(condition.minHeight, 0); }; - - if (rule._id === undefined) { - rule._id = H.uniqueKey(); + if (fn.call(this)) { + matches.push(rule._id); } - matches = fn.call(this); - // Apply a rule - if (!respRules[rule._id] && matches) { - - // Store the current state of the options - if (rule.chartOptions) { - respRules[rule._id] = this.currentOptions(rule.chartOptions); - this.update(rule.chartOptions, redraw); - } - - // Unapply a rule based on the previous options before the rule - // was applied - } else if (respRules[rule._id] && !matches) { - this.update(respRules[rule._id], redraw); - delete respRules[rule._id]; - } }; /** * Get the current values for a given set of options. Used before we update * the chart with a new responsiveness rule. @@ -23131,20 +23429,25 @@ for (key in options) { if (!depth && inArray(key, ['series', 'xAxis', 'yAxis']) > -1) { options[key] = splat(options[key]); ret[key] = []; + + // Iterate over collections like series, xAxis or yAxis and map + // the items by index. for (i = 0; i < options[key].length; i++) { - ret[key][i] = {}; - getCurrent( - options[key][i], - curr[key][i], - ret[key][i], - depth + 1 - ); + if (curr[key][i]) { // Item exists in current data (#6347) + ret[key][i] = {}; + getCurrent( + options[key][i], + curr[key][i], + ret[key][i], + depth + 1 + ); + } } } else if (isObject(options[key])) { - ret[key] = {}; + ret[key] = isArray(options[key]) ? [] : {}; getCurrent( options[key], curr[key] || {}, ret[key], depth + 1