app/assets/javascripts/highcharts.js in highcharts-rails-4.2.4 vs app/assets/javascripts/highcharts.js in highcharts-rails-4.2.5

- old
+ new

@@ -1,10 +1,10 @@ // ==ClosureCompiler== // @compilation_level SIMPLE_OPTIMIZATIONS /** - * @license Highcharts JS v4.2.4 (2016-04-14) + * @license Highcharts JS v4.2.5 (2016-05-06) * * (c) 2009-2016 Torstein Honsi * * License: www.highcharts.com/license */ @@ -57,11 +57,11 @@ timeUnits, noop = function () {}, charts = [], chartCount = 0, PRODUCT = 'Highcharts', - VERSION = '4.2.4', + VERSION = '4.2.5', // some constants for frequently used strings DIV = 'div', ABSOLUTE = 'absolute', RELATIVE = 'relative', @@ -480,13 +480,13 @@ /** * Check for number * @param {Object} n */ - function isNumber(n) { - return typeof n === 'number'; - } + var isNumber = Highcharts.isNumber = function isNumber(n) { + return typeof n === 'number' && !isNaN(n); + }; /** * Remove last occurence of an item from an array * @param {Array} arr * @param {Mixed} item @@ -674,11 +674,11 @@ * @param {String} format * @param {Number} timestamp * @param {Boolean} capitalize */ dateFormat = function (format, timestamp, capitalize) { - if (!defined(timestamp) || isNaN(timestamp)) { + if (!isNumber(timestamp)) { return defaultOptions.lang.invalidDate || ''; } format = pick(format, '%Y-%m-%d %H:%M:%S'); var date = new Date(timestamp - getTZOffset(timestamp)), @@ -1017,10 +1017,11 @@ * @param {String} thousandsSep The thousands separator, defaults to the one given in the lang options */ Highcharts.numberFormat = function (number, decimals, decimalPoint, thousandsSep) { number = +number || 0; + decimals = +decimals; var lang = defaultOptions.lang, origDec = (number.toString().split('.')[1] || '').length, decimalComponent, strinteger, @@ -1028,11 +1029,11 @@ absNumber = Math.abs(number), ret; if (decimals === -1) { decimals = Math.min(origDec, 20); // Preserve decimals. Not huge numbers (#3793). - } else if (isNaN(decimals)) { + } else if (!isNumber(decimals)) { decimals = 2; } // A string containing the positive integer component of the number strinteger = String(pInt(absNumber.toFixed(decimals))); @@ -1053,11 +1054,11 @@ // Add the remaining thousands groups, joined by the thousands separator ret += strinteger.substr(thousands).replace(/(\d{3})(?=\d)/g, '$1' + thousandsSep); // Add the decimal point and the decimal component - if (+decimals) { + if (decimals) { // Get the decimal component, and add power to avoid rounding errors with float numbers (#4573) decimalComponent = Math.abs(absNumber - strinteger + Math.pow(10, -Math.max(decimals, origDec) - 1)); ret += decimalPoint + decimalComponent.toFixed(decimals).slice(2); } @@ -1532,11 +1533,11 @@ }, global: { useUTC: true, //timezoneOffset: 0, canvasToolsURL: 'http://code.highcharts.com/modules/canvas-tools.js', - VMLRadialGradientURL: 'http://code.highcharts.com/4.2.4/gfx/vml-radial-gradient.png' + VMLRadialGradientURL: 'http://code.highcharts.com/4.2.5/gfx/vml-radial-gradient.png' }, chart: { //animation: true, //alignTicks: false, //reflow: true, @@ -1591,11 +1592,12 @@ // verticalAlign: 'top', // y: null, style: { color: '#333333', fontSize: '18px' - } + }, + widthAdjust: -44 }, subtitle: { text: '', align: 'center', @@ -1603,11 +1605,12 @@ // x: 0, // verticalAlign: 'top', // y: null, style: { color: '#555555' - } + }, + widthAdjust: -44 }, plotOptions: { line: { // base series options allowPointSelect: false, @@ -2014,11 +2017,11 @@ each(this.stops, function (stop, i) { ret.stops[i] = [ret.stops[i][0], stop.get(format)]; }); // it's NaN if gradient colors on a column chart - } else if (rgba && !isNaN(rgba[0])) { + } else if (rgba && isNumber(rgba[0])) { if (format === 'rgb' || (!format && rgba[3] === 1)) { ret = 'rgb(' + rgba[0] + ',' + rgba[1] + ',' + rgba[2] + ')'; } else if (format === 'a') { ret = rgba[3]; } else { @@ -3177,10 +3180,16 @@ var titleNode = this.element.getElementsByTagName('title')[0]; if (!titleNode) { titleNode = doc.createElementNS(SVG_NS, 'title'); this.element.appendChild(titleNode); } + + // Remove text content if it exists + if (titleNode.firstChild) { + titleNode.removeChild(titleNode.firstChild); + } + titleNode.appendChild( doc.createTextNode( (String(pick(value), '')).replace(/<[^>]*>/g, '') // #3276, #3895 ) ); @@ -3472,10 +3481,11 @@ hasMarkup = textStr.indexOf('<') !== -1, lines, childNodes = textNode.childNodes, styleRegex, hrefRegex, + wasTooLong, parentX = attr(textNode, 'x'), textStyles = wrapper.styles, width = wrapper.textWidth, textLineHeight = textStyles && textStyles.lineHeight, textShadow = textStyles && textStyles.textShadow, @@ -3527,22 +3537,24 @@ } else { lines = [textStr]; } - // remove empty line at end - if (lines[lines.length - 1] === '') { - lines.pop(); - } + // Trim empty lines (#5261) + lines = grep(lines, function (line) { + return line !== ''; + }); // build the lines each(lines, function buildTextLines(line, lineNo) { var spans, spanNo = 0; - - line = line.replace(/<span/g, '|||<span').replace(/<\/span>/g, '</span>|||'); + line = line + .replace(/^\s+|\s+$/g, '') // Trim to prevent useless/costly process on the spaces (#5258) + .replace(/<span/g, '|||<span') + .replace(/<\/span>/g, '</span>|||'); spans = line.split('|||'); each(spans, function buildTextSpans(span) { if (span !== '' || spans.length === 1) { var attributes = {}, @@ -3603,11 +3615,10 @@ // Check width and apply soft breaks or ellipsis if (width) { var words = span.replace(/([^\^])-/g, '$1- ').split(' '), // #1273 hasWhiteSpace = spans.length > 1 || lineNo || (words.length > 1 && textStyles.whiteSpace !== 'nowrap'), tooLong, - wasTooLong, actualWidth, rest = [], dy = getLineHeight(tspan), softLineNo = 1, rotation = wrapper.rotation, @@ -3635,13 +3646,10 @@ cursor /= 2; if (wordStr === '' || (!tooLong && cursor < 0.5)) { words = []; // All ok, break out } else { - if (tooLong) { - wasTooLong = true; - } wordStr = span.substring(0, wordStr.length + (tooLong ? -1 : 1) * mathCeil(cursor)); words = [wordStr + (width > 3 ? '\u2026' : '')]; tspan.removeChild(tspan.firstChild); } @@ -3673,21 +3681,22 @@ } if (words.length) { tspan.appendChild(doc.createTextNode(words.join(' ').replace(/- /g, '-'))); } } - if (wasTooLong) { - wrapper.attr('title', wrapper.textStr); - } wrapper.rotation = rotation; } spanNo++; } } }); }); + + if (wasTooLong) { + wrapper.attr('title', wrapper.textStr); + } if (tempParent) { tempParent.removeChild(textNode); // attach it to the DOM to read offset width } // Apply the text shadow @@ -6574,11 +6583,11 @@ }).add(axis.axisGroup); } } // the label is created on init - now move it into place - if (label && !isNaN(x)) { + if (label && isNumber(x)) { 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)) || @@ -6595,11 +6604,11 @@ // show those indices dividable by step show = false; } // Set the new position, and show or hide - if (show && !isNaN(xy.y)) { + if (show && isNumber(xy.y)) { xy.opacity = opacity; label[tick.isNew ? 'attr' : 'animate'](xy); tick.isNew = false; } else { label.attr('y', -9999); // #1338 @@ -7345,12 +7354,24 @@ // Get dataMin and dataMax for X axes if (axis.isXAxis) { xData = series.xData; if (xData.length) { - axis.dataMin = mathMin(pick(axis.dataMin, xData[0]), arrayMin(xData)); + // If xData contains values which is not numbers, then filter them out. + // To prevent performance hit, we only do this after we have already + // found seriesDataMin because in most cases all data is valid. #5234. + seriesDataMin = arrayMin(xData); + if (!isNumber(seriesDataMin) && !(seriesDataMin instanceof Date)) { // Date for #5010 + xData = grep(xData, function (x) { + return isNumber(x); + }); + seriesDataMin = arrayMin(xData); // Do it again with valid data + } + + axis.dataMin = mathMin(pick(axis.dataMin, xData[0]), seriesDataMin); axis.dataMax = mathMax(pick(axis.dataMax, xData[0]), arrayMax(xData)); + } // Get dataMin and dataMax for Y axes, as well as handle stacking and processed data } else { @@ -7492,12 +7513,11 @@ }; translatedValue = pick(translatedValue, axis.translate(value, null, null, old)); x1 = x2 = mathRound(translatedValue + transB); y1 = y2 = mathRound(cHeight - translatedValue - transB); - - if (isNaN(translatedValue)) { // no min or max + if (!isNumber(translatedValue)) { // no min or max skip = true; } else if (axis.horiz) { y1 = axisTop; y2 = cHeight - axis.bottom; @@ -7860,10 +7880,13 @@ axis.userMax = hardMax = axis.max; axis.range = null; // don't use it when running setExtremes } + // Hook for Highstock Scroller. Consider combining with beforePadding. + fireEvent(axis, 'foundExtremes'); + // Hook for adjusting this.min and this.max. Used by bubble series. if (axis.beforePadding) { axis.beforePadding(); } @@ -8672,10 +8695,11 @@ axisOffset = chart.axisOffset, clipOffset = chart.clipOffset, clip, directionFactor = [-1, 1, 1, -1][side], n, + textAlign, axisParent = axis.axisParent, // Used in color axis lineHeightCorrection, tickSize = this.tickSize('tick'); // For reuse in Axis.render @@ -8739,25 +8763,32 @@ } } if (axisTitleOptions && axisTitleOptions.text && axisTitleOptions.enabled !== false) { if (!axis.axisTitle) { + textAlign = axisTitleOptions.textAlign; + if (!textAlign) { + textAlign = (horiz ? { + low: 'left', + middle: 'center', + high: 'right' + } : { + low: opposite ? 'right' : 'left', + middle: 'center', + high: opposite ? 'left' : 'right' + })[axisTitleOptions.align]; + } axis.axisTitle = renderer.text( axisTitleOptions.text, 0, 0, axisTitleOptions.useHTML ) .attr({ zIndex: 7, rotation: axisTitleOptions.rotation || 0, - align: - axisTitleOptions.textAlign || { - low: opposite ? 'right' : 'left', - middle: 'center', - high: opposite ? 'left' : 'right' - }[axisTitleOptions.align] + align: textAlign }) .addClass(PREFIX + this.coll.toLowerCase() + '-title') .css(axisTitleOptions.style) .add(axis.axisGroup); axis.axisTitle.isNew = true; @@ -8900,11 +8931,11 @@ alternateGridColor = options.alternateGridColor, tickmarkOffset = axis.tickmarkOffset, lineWidth = options.lineWidth, linePath, hasRendered = chart.hasRendered, - slideInTicks = hasRendered && defined(axis.oldMin) && !isNaN(axis.oldMin), + slideInTicks = hasRendered && isNumber(axis.oldMin), showAxis = axis.showAxis, animation = animObject(renderer.globalAnimation), from, to; @@ -9196,10 +9227,11 @@ visibility: 'visible', 'stroke-width': strokeWidth // #4737 }); } else { attribs = { + 'pointer-events': 'none', // #5259 'stroke-width': strokeWidth, stroke: options.color || (categorized ? 'rgba(155,200,255,0.2)' : '#C0C0C0'), zIndex: pick(options.zIndex, 2) }; if (options.dashStyle) { @@ -10231,11 +10263,11 @@ // Find absolute nearest point each(kdpoints, function (p) { if (p) { // Store both closest points, using point.dist and point.distX comparisons (#4645): each(['dist', 'distX'], function (dist, k) { - if (typeof p[dist] === 'number') { + if (isNumber(p[dist])) { var // It is closer than the reference point isCloser = p[dist] < distance[k], // It is equally close, but above the reference point (#4679) isAbove = p[dist] === distance[k] && p.series.group.zIndex >= kdpoint[k].series.group.zIndex; @@ -10302,11 +10334,11 @@ addEvent(doc, 'mousemove', pointer._onDocumentMouseMove); } // Crosshair. For each hover point, loop over axes and draw cross if that point // belongs to the axis (#4927). - each(shared ? kdpoints : [pick(kdpoint[1], hoverPoint)], function (point) { + each(shared ? kdpoints : [pick(hoverPoint, kdpoint[1])], function (point) { // #5269 each(chart.axes, function (axis) { // In case of snap = false, point is undefined, and we draw the crosshair anyway (#5066) if (!point || point.series[axis.coll] === axis) { axis.drawCrosshair(e, point); } @@ -10993,14 +11025,14 @@ // the touchmove doesn't fire unless the finger moves more than ~4px. // So we emulate this behaviour in Android by checking how much it // moved, and cancelling on small distances. #3450. if (e.type === 'touchmove') { pinchDown = this.pinchDown; - hasMoved = Math.sqrt( + hasMoved = pinchDown[0] ? Math.sqrt( // #5266 Math.pow(pinchDown[0].chartX - e.chartX, 2) + Math.pow(pinchDown[0].chartY - e.chartY, 2) - ) >= 4; + ) >= 4 : false; } if (pick(hasMoved, true)) { this.pinch(e); } @@ -12167,10 +12199,13 @@ serie.updateTotals(); } redrawLegend = true; } } + if (serie.isDirtyData) { + fireEvent(serie, 'updatedData'); + } }); // handle added or removed series if (redrawLegend && legend.options.enabled) { // series or pie points are added or removed // draw legend graphics @@ -12393,10 +12428,11 @@ 'class': PREFIX + name, zIndex: chartTitleOptions.zIndex || 4 }) .css(chartTitleOptions.style) .add(); + } }); chart.layOutTitles(redraw); }, @@ -12410,29 +12446,29 @@ options = this.options, titleOptions = options.title, subtitleOptions = options.subtitle, requiresDirtyBox, renderer = this.renderer, - autoWidth = this.spacingBox.width - 44; // 44 makes room for default context button + spacingBox = this.spacingBox; if (title) { title - .css({ width: (titleOptions.width || autoWidth) + PX }) + .css({ width: (titleOptions.width || spacingBox.width + titleOptions.widthAdjust) + PX }) .align(extend({ y: renderer.fontMetrics(titleOptions.style.fontSize, title).b - 3 - }, titleOptions), false, 'spacingBox'); + }, titleOptions), false, spacingBox); if (!titleOptions.floating && !titleOptions.verticalAlign) { titleOffset = title.getBBox().height; } } if (subtitle) { subtitle - .css({ width: (subtitleOptions.width || autoWidth) + PX }) + .css({ width: (subtitleOptions.width || spacingBox.width + subtitleOptions.widthAdjust) + PX }) .align(extend({ y: titleOffset + (titleOptions.margin - 13) + renderer.fontMetrics(subtitleOptions.style.fontSize, title).b - }, subtitleOptions), false, 'spacingBox'); + }, subtitleOptions), false, spacingBox); if (!subtitleOptions.floating && !subtitleOptions.verticalAlign) { titleOffset = mathCeil(titleOffset + subtitle.getBBox().height); } } @@ -12543,11 +12579,11 @@ // If the container already holds a chart, destroy it. The check for hasRendered is there // because web pages that are saved to disk from the browser, will preserve the data-highcharts-chart // attribute and the SVG contents, but not an interactive chart. So in this case, // charts[oldChartIndex] will point to the wrong chart if any (#2609). oldChartIndex = pInt(attr(renderTo, indexAttrName)); - if (!isNaN(oldChartIndex) && charts[oldChartIndex] && charts[oldChartIndex].hasRendered) { + if (isNumber(oldChartIndex) && charts[oldChartIndex] && charts[oldChartIndex].hasRendered) { charts[oldChartIndex].destroy(); } // Make a reference to the chart from the div attr(renderTo, indexAttrName, chart.index); @@ -13471,11 +13507,11 @@ // For higher dimension series types. For instance, for ranges, point.y is mapped to point.low. if (pointValKey) { point.y = point[pointValKey]; } - point.isNull = point.y === null; + point.isNull = point.x === null || point.y === null; // If no x is set by now, get auto incremented value. All points must have an // x value, however the y value can be null to create a gap in the series if (point.x === undefined && series) { point.x = x === undefined ? series.autoIncrement() : x; @@ -13495,11 +13531,11 @@ valueCount = pointArrayMap.length, firstItemType, i = 0, j = 0; - if (typeof options === 'number' || options === null) { + if (isNumber(options) || options === null) { ret[pointArrayMap[0]] = options; } else if (isArray(options)) { // with leading x value if (!keys && options.length > valueCount) { @@ -13825,11 +13861,11 @@ * adding to the series.parallelArrays array. */ updateParallelArrays: function (point, i) { var series = point.series, args = arguments, - fn = typeof i === 'number' ? + fn = isNumber(i) ? // Insert the value in the given position function (key) { var val = key === 'y' && series.toYData ? series.toYData(point) : point[key]; series[key + 'Data'][i] = val; } : @@ -14323,11 +14359,11 @@ x, y, i, j; - yData = yData || this.stackedYData || this.processedYData; + yData = yData || this.stackedYData || this.processedYData || []; yDataLength = yData.length; for (i = 0; i < yDataLength; i++) { x = xData[i]; @@ -14401,13 +14437,14 @@ point.y = yValue = null; error(10); } // Get the plotX translation - point.plotX = plotX = mathMin(mathMax(-1e5, xAxis.translate(xValue, 0, 0, 0, 1, pointPlacement, this.type === 'flags')), 1e5); // #3923 + point.plotX = plotX = correctFloat( // #5236 + 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 && !point.isNull && stack && stack[xValue]) { stackIndicator = series.getStackIndicator(stackIndicator, xValue, series.index); pointStack = stack[xValue]; stackValues = pointStack.points[stackIndicator.key]; @@ -14630,11 +14667,11 @@ hasPointMarker = !!point.marker; enabled = (globallyEnabled && pointMarkerOptions.enabled === UNDEFINED) || pointMarkerOptions.enabled; isInside = point.isInside; // only draw the point if y is defined - if (enabled && plotY !== UNDEFINED && !isNaN(plotY) && point.y !== null) { + if (enabled && isNumber(plotY) && point.y !== null) { // shortcuts pointAttr = point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE] || seriesPointAttr; radius = pointAttr.r; symbol = pick(pointMarkerOptions.symbol, series.symbol); @@ -14728,10 +14765,11 @@ defaultLineColor = normalOptions.lineColor, defaultFillColor = normalOptions.fillColor, turboThreshold = seriesOptions.turboThreshold, zones = series.zones, zoneAxis = series.zoneAxis || 'y', + zoneColor, attr, key; // series type specific modifications if (seriesOptions.marker) { // line, spline, area, areaspline, scatter @@ -14776,18 +14814,19 @@ normalOptions = (point.options && point.options.marker) || point.options; if (normalOptions && normalOptions.enabled === false) { normalOptions.radius = 0; } + zoneColor = null; if (zones.length) { j = 0; threshold = zones[j]; while (point[zoneAxis] >= threshold.value) { threshold = zones[++j]; } - point.color = point.fillColor = pick(threshold.color, series.color); // #3636, #4267, #4430 - inherit color from series, when color is undefined + point.color = point.fillColor = zoneColor = pick(threshold.color, series.color); // #3636, #4267, #4430 - inherit color from series, when color is undefined } hasPointSpecificOptions = seriesOptions.colorByPoint || point.color; // #868 @@ -14827,10 +14866,16 @@ } // Color is explicitly set to null or undefined (#1288, #4068) if (normalOptions.hasOwnProperty('color') && !normalOptions.color) { delete normalOptions.color; } + + // When zone is set, but series.states.hover.color is not set, apply zone color on hover, #4670: + if (zoneColor && !stateOptionsHover.fillColor) { + pointStateOptionsHover.fillColor = zoneColor; + } + pointAttr[NORMAL_STATE] = series.convertAttribs(extend(attr, normalOptions), seriesPointAttr[NORMAL_STATE]); // inherit from point normal and series hover pointAttr[HOVER_STATE] = series.convertAttribs( stateOptions[HOVER_STATE], @@ -15384,12 +15429,11 @@ * Redraw the series after an update in the axes. */ redraw: function () { var series = this, chart = series.chart, - wasDirtyData = series.isDirtyData, // cache it here as it is set to false in render, but used after - wasDirty = series.isDirty, + wasDirty = series.isDirty || series.isDirtyData, // cache it here as it is set to false in render, but used after group = series.group, xAxis = series.xAxis, yAxis = series.yAxis; // reposition on resize @@ -15407,16 +15451,13 @@ }); } series.translate(); series.render(); - if (wasDirtyData) { - fireEvent(series, 'updatedData'); + if (wasDirty) { // #3868, #3945 + delete this.kdTree; } - if (wasDirty || wasDirtyData) { // #3945 recalculate the kdtree when dirty - delete this.kdTree; // #3868 recalculate the kdtree with dirty data - } }, /** * KD Tree && PointSearching Implementation */ @@ -17205,11 +17246,11 @@ each(series.points, function (point) { var plotY = point.plotY, graphic = point.graphic, borderAttr; - if (plotY !== UNDEFINED && !isNaN(plotY) && point.y !== null) { + if (isNumber(plotY) && point.y !== null) { shapeArgs = point.shapeArgs; borderAttr = defined(series.borderWidth) ? { 'stroke-width': series.borderWidth } : {}; @@ -17262,11 +17303,11 @@ attr[inverted ? 'translateX' : 'translateY'] = yAxis.pos; series.group.animate(attr, extend(animObject(series.options.animation), { // Do the scale synchronously to ensure smooth updating (#5030) step: function (val, fx) { series.group.attr({ - scaleY: fx.pos + scaleY: mathMax(0.001, fx.pos) // #5250 }); } })); // delete this function to allow it only once @@ -18013,15 +18054,15 @@ if (rotation) { justify = false; // Not supported for rotated text rotCorr = chart.renderer.rotCorr(baseline, rotation); // #3723 alignAttr = { x: alignTo.x + options.x + alignTo.width / 2 + rotCorr.x, - y: alignTo.y + options.y + alignTo.height / 2 + y: alignTo.y + options.y + { top: 0, middle: 0.5, bottom: 1 }[options.verticalAlign] * alignTo.height }; dataLabel[isNew ? 'attr' : 'animate'](alignAttr) .attr({ // #3003 - align: options.align + align: align }); // Compensate for the rotated label sticking out on the sides normRotation = (rotation + 720) % 360; negRotation = normRotation > 180 && normRotation < 360; @@ -18244,10 +18285,10 @@ slots.forEach(function (pos, no) { var slotX = series.getX(pos, i) + chart.plotLeft - (i ? 100 : 0), slotY = pos + chart.plotTop; - if (!isNaN(slotX)) { + if (isNumber(slotX)) { series.slotElements.push(chart.renderer.rect(slotX, slotY - 7, 100, labelHeight, 1) .attr({ 'stroke-width': 1, stroke: 'silver', fill: 'rgba(0,0,255,0.1)'