From a4a41c0620551257a22f989e8f2edbbb1fea6156 Mon Sep 17 00:00:00 2001 From: Jukka Kurkela Date: Tue, 16 Jul 2019 00:20:16 +0300 Subject: [PATCH] Handle reverse support in core.scale (#6343) * Move log10 from core.helpers to helpers.math * Refactor scales --- src/controllers/controller.bar.js | 11 +-- src/core/core.controller.js | 5 ++ src/core/core.helpers.js | 13 --- src/core/core.scale.js | 82 ++++++++++++------- src/helpers/helpers.math.js | 23 ++++++ src/scales/scale.category.js | 69 +++++++--------- src/scales/scale.linear.js | 21 +---- src/scales/scale.linearbase.js | 19 +++++ src/scales/scale.logarithmic.js | 113 +++++++++----------------- src/scales/scale.time.js | 20 ++--- test/specs/core.helpers.tests.js | 10 --- test/specs/core.scale.tests.js | 2 +- test/specs/helpers.math.tests.js | 14 +++- test/specs/scale.linear.tests.js | 82 +++++++++++++++++++ test/specs/scale.logarithmic.tests.js | 2 + test/specs/scale.time.tests.js | 4 +- 16 files changed, 278 insertions(+), 212 deletions(-) diff --git a/src/controllers/controller.bar.js b/src/controllers/controller.bar.js index aa8b06a2cbc..4c36c74a820 100644 --- a/src/controllers/controller.bar.js +++ b/src/controllers/controller.bar.js @@ -32,7 +32,7 @@ defaults._set('bar', { * @private */ function computeMinSampleSize(scale, pixels) { - var min = scale.isHorizontal() ? scale.width : scale.height; + var min = scale._length; var ticks = scale.getTicks(); var prev, curr, i, ilen; @@ -42,7 +42,7 @@ function computeMinSampleSize(scale, pixels) { for (i = 0, ilen = ticks.length; i < ilen; ++i) { curr = scale.getPixelForTick(i); - min = i > 0 ? Math.min(min, curr - prev) : min; + min = i > 0 ? Math.min(min, Math.abs(curr - prev)) : min; prev = curr; } @@ -262,9 +262,6 @@ module.exports = DatasetController.extend({ var scale = me._getIndexScale(); var stackCount = me.getStackCount(); var datasetIndex = me.index; - var isHorizontal = scale.isHorizontal(); - var start = isHorizontal ? scale.left : scale.top; - var end = start + (isHorizontal ? scale.width : scale.height); var pixels = []; var i, ilen, min; @@ -279,8 +276,8 @@ module.exports = DatasetController.extend({ return { min: min, pixels: pixels, - start: start, - end: end, + start: scale._startPixel, + end: scale._endPixel, stackCount: stackCount, scale: scale }; diff --git a/src/core/core.controller.js b/src/core/core.controller.js index 5349fb5f72d..694e7764611 100644 --- a/src/core/core.controller.js +++ b/src/core/core.controller.js @@ -546,6 +546,11 @@ helpers.extend(Chart.prototype, /** @lends Chart */ { me._layers = []; helpers.each(me.boxes, function(box) { + // _configure is called twice, once in core.scale.update and once here. + // Here the boxes are fully updated and at their final positions. + if (box._configure) { + box._configure(); + } me._layers.push.apply(me._layers, box._layers()); }, me); diff --git a/src/core/core.helpers.js b/src/core/core.helpers.js index 31748758090..33c94799e15 100644 --- a/src/core/core.helpers.js +++ b/src/core/core.helpers.js @@ -100,19 +100,6 @@ module.exports = function() { } return x > 0 ? 1 : -1; }; - helpers.log10 = Math.log10 ? - function(x) { - return Math.log10(x); - } : - function(x) { - var exponent = Math.log(x) * Math.LOG10E; // Math.LOG10E = 1 / Math.LN10. - // Check for whole powers of 10, - // which due to floating point rounding error should be corrected. - var powerOf10 = Math.round(exponent); - var isPowerOf10 = x === Math.pow(10, powerOf10); - - return isPowerOf10 ? powerOf10 : exponent; - }; helpers.toRadians = function(degrees) { return degrees * (Math.PI / 180); }; diff --git a/src/core/core.scale.js b/src/core/core.scale.js index 5eed97b9c7b..4cfe0a064bd 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -70,9 +70,7 @@ function getPixelForGridLine(scale, index, offsetGridLines) { if (offsetGridLines) { if (scale.getTicks().length === 1) { - lineValue -= scale.isHorizontal() ? - Math.max(lineValue - scale.left, scale.right - lineValue) : - Math.max(lineValue - scale.top, scale.bottom - lineValue); + lineValue -= Math.max(lineValue - scale._startPixel, scale._endPixel - lineValue); } else if (index === 0) { lineValue -= (scale.getPixelForTick(1) - lineValue) / 2; } else { @@ -318,6 +316,12 @@ var Scale = Element.extend({ me._ticks = ticks; + // _configure is called twice, once here, once from core.controller.updateLayout. + // Here we haven't been positioned yet, but dimensions are correct. + // Variables set in _configure are needed for calculateTickRotation, and + // it's ok that coordinates are not correct there, only dimensions matter. + me._configure(); + // Tick Rotation me.beforeCalculateTickRotation(); me.calculateTickRotation(); @@ -332,6 +336,30 @@ var Scale = Element.extend({ return me.minSize; }, + + /** + * @private + */ + _configure: function() { + var me = this; + var reversePixels = me.options.ticks.reverse; + var startPixel, endPixel; + + if (me.isHorizontal()) { + startPixel = me.left; + endPixel = me.right; + } else { + startPixel = me.top; + endPixel = me.bottom; + // by default vertical scales are from bottom to top, so pixels are reversed + reversePixels = !reversePixels; + } + me._startPixel = startPixel; + me._endPixel = endPixel; + me._reversePixels = reversePixels; + me._length = endPixel - startPixel; + }, + afterUpdate: function() { helpers.callback(this.options.afterUpdate, [this]); }, @@ -576,10 +604,11 @@ var Scale = Element.extend({ // Shared Methods isHorizontal: function() { - return this.options.position === 'top' || this.options.position === 'bottom'; + var pos = this.options.position; + return pos === 'top' || pos === 'bottom'; }, isFullWidth: function() { - return (this.options.fullWidth); + return this.options.fullWidth; }, // Get the correct value. NaN bad inputs, If the value type is object get the x or y based on whether we are horizontal or not @@ -693,20 +722,11 @@ var Scale = Element.extend({ var me = this; var offset = me.options.offset; var numTicks = me._ticks.length; - if (index < 0 || index > numTicks - 1) { - return null; - } - if (me.isHorizontal()) { - var tickWidth = me.width / Math.max((numTicks - (offset ? 0 : 1)), 1); - var pixel = (tickWidth * index); + var tickWidth = 1 / Math.max(numTicks - (offset ? 0 : 1), 1); - if (offset) { - pixel += tickWidth / 2; - } - - return me.left + pixel; - } - return me.top + (index * (me.height / (numTicks - 1))); + return index < 0 || index > numTicks - 1 + ? null + : me.getPixelForDecimal(index * tickWidth + (offset ? tickWidth / 2 : 0)); }, /** @@ -715,9 +735,17 @@ var Scale = Element.extend({ */ getPixelForDecimal: function(decimal) { var me = this; - return me.isHorizontal() - ? me.left + decimal * me.width - : me.top + decimal * me.height; + + if (me._reversePixels) { + decimal = 1 - decimal; + } + + return me._startPixel + decimal * me._length; + }, + + getDecimalForPixel: function(pixel) { + var decimal = (pixel - this._startPixel) / this._length; + return Math.min(1, Math.max(0, this._reversePixels ? 1 - decimal : decimal)); }, /** @@ -745,7 +773,6 @@ var Scale = Element.extend({ */ _autoSkip: function(ticks) { var me = this; - var isHorizontal = me.isHorizontal(); var optionTicks = me.options.ticks; var tickCount = ticks.length; var skipRatio = false; @@ -755,9 +782,7 @@ var Scale = Element.extend({ // drawn as their center at end of axis, so tickCount-1 var ticksLength = me._tickSize() * (tickCount - 1); - // Axis length - var axisLength = isHorizontal ? me.width : me.height; - + var axisLength = me._length; var result = []; var i, tick; @@ -788,7 +813,6 @@ var Scale = Element.extend({ */ _tickSize: function() { var me = this; - var isHorizontal = me.isHorizontal(); var optionTicks = me.options.ticks; // Calculate space needed by label in axis direction. @@ -802,7 +826,7 @@ var Scale = Element.extend({ var h = labelSizes ? labelSizes.highest.height + padding : 0; // Calculate space needed for 1 tick in axis direction. - return isHorizontal + return me.isHorizontal() ? h * cos > w * sin ? w / cos : h / sin : h * sin < w * cos ? h / cos : w / sin; }, @@ -1130,7 +1154,7 @@ var Scale = Element.extend({ var scaleLabelX, scaleLabelY; if (me.isHorizontal()) { - scaleLabelX = me.left + ((me.right - me.left) / 2); // midpoint of the width + scaleLabelX = me.left + me.width / 2; // midpoint of the width scaleLabelY = position === 'bottom' ? me.bottom - halfLineHeight - scaleLabelPadding.bottom : me.top + halfLineHeight + scaleLabelPadding.top; @@ -1139,7 +1163,7 @@ var Scale = Element.extend({ scaleLabelX = isLeft ? me.left + halfLineHeight + scaleLabelPadding.top : me.right - halfLineHeight - scaleLabelPadding.top; - scaleLabelY = me.top + ((me.bottom - me.top) / 2); + scaleLabelY = me.top + me.height / 2; rotation = isLeft ? -0.5 * Math.PI : 0.5 * Math.PI; } diff --git a/src/helpers/helpers.math.js b/src/helpers/helpers.math.js index 9ed8114eab9..f2b2b6f0148 100644 --- a/src/helpers/helpers.math.js +++ b/src/helpers/helpers.math.js @@ -1,5 +1,7 @@ 'use strict'; +var helpers = require('./helpers.core'); + /** * @alias Chart.helpers.math * @namespace @@ -28,7 +30,28 @@ var exports = { return a - b; }).pop(); return result; + }, + + log10: Math.log10 || function(x) { + var exponent = Math.log(x) * Math.LOG10E; // Math.LOG10E = 1 / Math.LN10. + // Check for whole powers of 10, + // which due to floating point rounding error should be corrected. + var powerOf10 = Math.round(exponent); + var isPowerOf10 = x === Math.pow(10, powerOf10); + + return isPowerOf10 ? powerOf10 : exponent; } }; module.exports = exports; + +// DEPRECATIONS + +/** + * Provided for backward compatibility, use Chart.helpers.math.log10 instead. + * @namespace Chart.helpers.log10 + * @deprecated since version 2.9.0 + * @todo remove at version 3 + * @private + */ +helpers.log10 = exports.log10; diff --git a/src/scales/scale.category.js b/src/scales/scale.category.js index 4cb9422230d..bb74d40db3a 100644 --- a/src/scales/scale.category.js +++ b/src/scales/scale.category.js @@ -63,17 +63,30 @@ module.exports = Scale.extend({ return me.ticks[index - me.minIndex]; }, - // Used to get data value locations. Value can either be an index or a numerical value - getPixelForValue: function(value, index, datasetIndex) { + _configure: function() { var me = this; var offset = me.options.offset; + var ticks = me.ticks; + + Scale.prototype._configure.call(me); + + if (!me.isHorizontal()) { + // For backward compatibility, vertical category scale reverse is inverted. + me._reversePixels = !me._reversePixels; + } + + if (!ticks) { + return; + } - // 1 is added because we need the length but we have the indexes - var offsetAmt = Math.max(me.maxIndex + 1 - me.minIndex - (offset ? 0 : 1), 1); + me._startValue = me.minIndex - (offset ? 0.5 : 0); + me._valueRange = Math.max(ticks.length - (offset ? 0 : 1), 1); + }, - var isHorizontal = me.isHorizontal(); - var valueDimension = (isHorizontal ? me.width : me.height) / offsetAmt; - var valueCategory, labels, idx, pixel; + // Used to get data value locations. Value can either be an index or a numerical value + getPixelForValue: function(value, index, datasetIndex) { + var me = this; + var valueCategory, labels, idx; if (!isNullOrUndef(index) && !isNullOrUndef(datasetIndex)) { value = me.chart.data.datasets[datasetIndex].data[index]; @@ -82,53 +95,31 @@ module.exports = Scale.extend({ // If value is a data object, then index is the index in the data array, // not the index of the scale. We need to change that. if (!isNullOrUndef(value)) { - valueCategory = isHorizontal ? value.x : value.y; + valueCategory = me.isHorizontal() ? value.x : value.y; } if (valueCategory !== undefined || (value !== undefined && isNaN(index))) { labels = me._getLabels(); value = helpers.valueOrDefault(valueCategory, value); idx = labels.indexOf(value); index = idx !== -1 ? idx : index; + if (isNaN(index)) { + index = value; + } } - - pixel = valueDimension * (index - me.minIndex); - - if (offset) { - pixel += valueDimension / 2; - } - - return (isHorizontal ? me.left : me.top) + pixel; + return me.getPixelForDecimal((index - me._startValue) / me._valueRange); }, getPixelForTick: function(index) { var ticks = this.ticks; - if (index < 0 || index > ticks.length - 1) { - return null; - } - return this.getPixelForValue(ticks[index], index + this.minIndex); + return index < 0 || index > ticks.length - 1 + ? null + : this.getPixelForValue(ticks[index], index + this.minIndex); }, getValueForPixel: function(pixel) { var me = this; - var offset = me.options.offset; - var offsetAmt = Math.max(me._ticks.length - (offset ? 0 : 1), 1); - var isHorizontal = me.isHorizontal(); - var valueDimension = (isHorizontal ? me.width : me.height) / offsetAmt; - var value; - - pixel -= isHorizontal ? me.left : me.top; - - if (offset) { - pixel -= valueDimension / 2; - } - - if (pixel <= 0) { - value = 0; - } else { - value = Math.round(pixel / valueDimension); - } - - return value + me.minIndex; + var value = Math.round(me._startValue + me.getDecimalForPixel(pixel) * me._valueRange); + return Math.min(Math.max(value, 0), me.ticks.length - 1); }, getBasePixel: function() { diff --git a/src/scales/scale.linear.js b/src/scales/scale.linear.js index 7db13eb46cf..004b7d8e6ea 100644 --- a/src/scales/scale.linear.js +++ b/src/scales/scale.linear.js @@ -149,29 +149,12 @@ module.exports = LinearScaleBase.extend({ // Utils getPixelForValue: function(value) { - // This must be called after fit has been run so that - // this.left, this.top, this.right, and this.bottom have been defined var me = this; - var start = me.start; - - var rightValue = +me.getRightValue(value); - var pixel; - var range = me.end - start; - - if (me.isHorizontal()) { - pixel = me.left + (me.width / range * (rightValue - start)); - } else { - pixel = me.bottom - (me.height / range * (rightValue - start)); - } - return pixel; + return me.getPixelForDecimal((+me.getRightValue(value) - me._startValue) / me._valueRange); }, getValueForPixel: function(pixel) { - var me = this; - var isHorizontal = me.isHorizontal(); - var innerDimension = isHorizontal ? me.width : me.height; - var offset = (isHorizontal ? pixel - me.left : me.bottom - pixel) / innerDimension; - return me.start + ((me.end - me.start) * offset); + return this._startValue + this.getDecimalForPixel(pixel) * this._valueRange; }, getPixelForTick: function(index) { diff --git a/src/scales/scale.linearbase.js b/src/scales/scale.linearbase.js index 7f68279db6c..95ba9bebe1f 100644 --- a/src/scales/scale.linearbase.js +++ b/src/scales/scale.linearbase.js @@ -231,5 +231,24 @@ module.exports = Scale.extend({ me.zeroLineIndex = me.ticks.indexOf(0); Scale.prototype.convertTicksToLabels.call(me); + }, + + _configure: function() { + var me = this; + var ticks = me.getTicks(); + var start = me.min; + var end = me.max; + var offset; + + Scale.prototype._configure.call(me); + + if (me.options.offset && ticks.length) { + offset = (end - start) / Math.max(ticks.length - 1, 1) / 2; + start -= offset; + end += offset; + } + me._startValue = start; + me._endValue = end; + me._valueRange = end - start; } }); diff --git a/src/scales/scale.logarithmic.js b/src/scales/scale.logarithmic.js index 5def62aac55..6a6553ab8a9 100644 --- a/src/scales/scale.logarithmic.js +++ b/src/scales/scale.logarithmic.js @@ -6,6 +6,7 @@ var Scale = require('../core/core.scale'); var Ticks = require('../core/core.ticks'); var valueOrDefault = helpers.valueOrDefault; +var log10 = helpers.math.log10; /** * Generate a set of logarithmic ticks @@ -16,20 +17,20 @@ var valueOrDefault = helpers.valueOrDefault; function generateTicks(generationOptions, dataRange) { var ticks = []; - var tickVal = valueOrDefault(generationOptions.min, Math.pow(10, Math.floor(helpers.log10(dataRange.min)))); + var tickVal = valueOrDefault(generationOptions.min, Math.pow(10, Math.floor(log10(dataRange.min)))); - var endExp = Math.floor(helpers.log10(dataRange.max)); + var endExp = Math.floor(log10(dataRange.max)); var endSignificand = Math.ceil(dataRange.max / Math.pow(10, endExp)); var exp, significand; if (tickVal === 0) { - exp = Math.floor(helpers.log10(dataRange.minNotZero)); + exp = Math.floor(log10(dataRange.minNotZero)); significand = Math.floor(dataRange.minNotZero / Math.pow(10, exp)); ticks.push(tickVal); tickVal = significand * Math.pow(10, exp); } else { - exp = Math.floor(helpers.log10(tickVal)); + exp = Math.floor(log10(tickVal)); significand = Math.floor(tickVal / Math.pow(10, exp)); } var precision = exp < 0 ? Math.pow(10, Math.abs(exp)) : 1; @@ -178,26 +179,26 @@ module.exports = Scale.extend({ if (me.min === me.max) { if (me.min !== 0 && me.min !== null) { - me.min = Math.pow(10, Math.floor(helpers.log10(me.min)) - 1); - me.max = Math.pow(10, Math.floor(helpers.log10(me.max)) + 1); + me.min = Math.pow(10, Math.floor(log10(me.min)) - 1); + me.max = Math.pow(10, Math.floor(log10(me.max)) + 1); } else { me.min = DEFAULT_MIN; me.max = DEFAULT_MAX; } } if (me.min === null) { - me.min = Math.pow(10, Math.floor(helpers.log10(me.max)) - 1); + me.min = Math.pow(10, Math.floor(log10(me.max)) - 1); } if (me.max === null) { me.max = me.min !== 0 - ? Math.pow(10, Math.floor(helpers.log10(me.min)) + 1) + ? Math.pow(10, Math.floor(log10(me.min)) + 1) : DEFAULT_MAX; } if (me.minNotZero === null) { if (me.min > 0) { me.minNotZero = me.min; } else if (me.max < 1) { - me.minNotZero = Math.pow(10, Math.floor(helpers.log10(me.max))); + me.minNotZero = Math.pow(10, Math.floor(log10(me.max))); } else { me.minNotZero = DEFAULT_MIN; } @@ -259,87 +260,47 @@ module.exports = Scale.extend({ * @private */ _getFirstTickValue: function(value) { - var exp = Math.floor(helpers.log10(value)); + var exp = Math.floor(log10(value)); var significand = Math.floor(value / Math.pow(10, exp)); return significand * Math.pow(10, exp); }, - getPixelForValue: function(value) { + _configure: function() { var me = this; - var tickOpts = me.options.ticks; - var reverse = tickOpts.reverse; - var log10 = helpers.log10; - var firstTickValue = me._getFirstTickValue(me.minNotZero); + var start = me.min; var offset = 0; - var innerDimension, pixel, start, end, sign; - value = +me.getRightValue(value); - if (reverse) { - start = me.end; - end = me.start; - sign = -1; - } else { - start = me.start; - end = me.end; - sign = 1; - } - if (me.isHorizontal()) { - innerDimension = me.width; - pixel = reverse ? me.right : me.left; - } else { - innerDimension = me.height; - sign *= -1; // invert, since the upper-left corner of the canvas is at pixel (0, 0) - pixel = reverse ? me.top : me.bottom; - } - if (value !== start) { - if (start === 0) { // include zero tick - offset = valueOrDefault(tickOpts.fontSize, defaults.global.defaultFontSize); - innerDimension -= offset; - start = firstTickValue; - } - if (value !== 0) { - offset += innerDimension / (log10(end) - log10(start)) * (log10(value) - log10(start)); - } - pixel += sign * offset; + Scale.prototype._configure.call(me); + + if (start === 0) { + start = me._getFirstTickValue(me.minNotZero); + offset = valueOrDefault(me.options.ticks.fontSize, defaults.global.defaultFontSize) / me._length; } - return pixel; + + me._startValue = log10(start); + me._valueOffset = offset; + me._valueRange = (log10(me.max) - log10(start)) / (1 - offset); }, - getValueForPixel: function(pixel) { + getPixelForValue: function(value) { var me = this; - var tickOpts = me.options.ticks; - var reverse = tickOpts.reverse; - var log10 = helpers.log10; - var firstTickValue = me._getFirstTickValue(me.minNotZero); - var innerDimension, start, end, value; + var decimal = 0; - if (reverse) { - start = me.end; - end = me.start; - } else { - start = me.start; - end = me.end; - } - if (me.isHorizontal()) { - innerDimension = me.width; - value = reverse ? me.right - pixel : pixel - me.left; - } else { - innerDimension = me.height; - value = reverse ? pixel - me.top : me.bottom - pixel; - } - if (value !== start) { - if (start === 0) { // include zero tick - var offset = valueOrDefault(tickOpts.fontSize, defaults.global.defaultFontSize); - value -= offset; - innerDimension -= offset; - start = firstTickValue; - } - value *= log10(end) - log10(start); - value /= innerDimension; - value = Math.pow(10, log10(start) + value); + value = +me.getRightValue(value); + + if (value > me.min && value > 0) { + decimal = (log10(value) - me._startValue) / me._valueRange + me._valueOffset; } - return value; + return me.getPixelForDecimal(decimal); + }, + + getValueForPixel: function(pixel) { + var me = this; + var decimal = me.getDecimalForPixel(pixel); + return decimal === 0 && me.min === 0 + ? 0 + : Math.pow(10, me._startValue + (decimal - me._valueOffset) * me._valueRange); } }); diff --git a/src/scales/scale.time.js b/src/scales/scale.time.js index 88ccdf7a271..b766a16829d 100644 --- a/src/scales/scale.time.js +++ b/src/scales/scale.time.js @@ -596,7 +596,6 @@ module.exports = Scale.extend({ me.max = Math.max(min + 1, max); // PRIVATE - me._horizontal = me.isHorizontal(); me._table = []; me._timestamps = { data: timestamps, @@ -611,16 +610,16 @@ module.exports = Scale.extend({ var max = me.max; var options = me.options; var timeOpts = options.time; - var timestamps = []; + var timestamps = me._timestamps; var ticks = []; var i, ilen, timestamp; switch (options.ticks.source) { case 'data': - timestamps = me._timestamps.data; + timestamps = timestamps.data; break; case 'labels': - timestamps = me._timestamps.labels; + timestamps = timestamps.labels; break; case 'auto': default: @@ -725,13 +724,8 @@ module.exports = Scale.extend({ getPixelForOffset: function(time) { var me = this; var offsets = me._offsets; - var size = me._horizontal ? me.width : me.height; var pos = interpolate(me._table, 'time', time, 'pos'); - var offset = size * (offsets.start + pos) * offsets.factor; - - return me.options.ticks.reverse ? - (me._horizontal ? me.right : me.bottom) - offset : - (me._horizontal ? me.left : me.top) + offset; + return me.getPixelForDecimal((offsets.start + pos) * offsets.factor); }, getPixelForValue: function(value, index, datasetIndex) { @@ -761,11 +755,7 @@ module.exports = Scale.extend({ getValueForPixel: function(pixel) { var me = this; var offsets = me._offsets; - var size = me._horizontal ? me.width : me.height; - var offset = me.options.ticks.reverse ? - (me._horizontal ? me.right : me.bottom) - pixel : - pixel - (me._horizontal ? me.left : me.top); - var pos = offset / size / offsets.factor - offsets.start; + var pos = me.getDecimalForPixel(pixel) / offsets.factor - offsets.end; var time = interpolate(me._table, 'pos', pos, 'time'); // DEPRECATION, we should return time directly diff --git a/test/specs/core.helpers.tests.js b/test/specs/core.helpers.tests.js index 1f2bc29d5a1..e6ade2bd76a 100644 --- a/test/specs/core.helpers.tests.js +++ b/test/specs/core.helpers.tests.js @@ -26,16 +26,6 @@ describe('Core helper tests', function() { expect(helpers.sign(-5)).toBe(-1); }); - it('should do a log10 operation', function() { - expect(helpers.log10(0)).toBe(-Infinity); - - // Check all allowed powers of 10, which should return integer values - var maxPowerOf10 = Math.floor(helpers.log10(Number.MAX_VALUE)); - for (var i = 0; i < maxPowerOf10; i += 1) { - expect(helpers.log10(Math.pow(10, i))).toBe(i); - } - }); - it('should correctly determine if two numbers are essentially equal', function() { expect(helpers.almostEquals(0, Number.EPSILON, 2 * Number.EPSILON)).toBe(true); expect(helpers.almostEquals(1, 1.1, 0.0001)).toBe(false); diff --git a/test/specs/core.scale.tests.js b/test/specs/core.scale.tests.js index 605707eb3c1..f293eb2aaa3 100644 --- a/test/specs/core.scale.tests.js +++ b/test/specs/core.scale.tests.js @@ -94,7 +94,7 @@ describe('Core.scale', function() { labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5'], offsetGridLines: true, offset: true, - expected: [-0.5, 102.5, 204.5, 307.5, 409.5] + expected: [0.5, 102.5, 204.5, 307.5, 409.5] }, { labels: ['tick1'], offsetGridLines: false, diff --git a/test/specs/helpers.math.tests.js b/test/specs/helpers.math.tests.js index c26435f65a1..0006a3dd164 100644 --- a/test/specs/helpers.math.tests.js +++ b/test/specs/helpers.math.tests.js @@ -1,7 +1,9 @@ 'use strict'; + describe('Chart.helpers.math', function() { - var factorize = Chart.helpers.math._factorize; + var math = Chart.helpers.math; + var factorize = math._factorize; it('should factorize', function() { expect(factorize(1000)).toEqual([1, 2, 4, 5, 8, 10, 20, 25, 40, 50, 100, 125, 200, 250, 500]); @@ -13,4 +15,14 @@ describe('Chart.helpers.math', function() { expect(factorize(-1)).toEqual([]); expect(factorize(2.76)).toEqual([]); }); + + it('should do a log10 operation', function() { + expect(math.log10(0)).toBe(-Infinity); + + // Check all allowed powers of 10, which should return integer values + var maxPowerOf10 = Math.floor(math.log10(Number.MAX_VALUE)); + for (var i = 0; i < maxPowerOf10; i += 1) { + expect(math.log10(Math.pow(10, i))).toBe(i); + } + }); }); diff --git a/test/specs/scale.linear.tests.js b/test/specs/scale.linear.tests.js index 9af5c9cb950..30f35cbaf98 100644 --- a/test/specs/scale.linear.tests.js +++ b/test/specs/scale.linear.tests.js @@ -1196,4 +1196,86 @@ describe('Linear Scale', function() { expect(chart.scales.yScale0).not.toEqual(undefined); // must construct expect(chart.scales.yScale0.max).toBeGreaterThan(chart.scales.yScale0.min); }); + + it('Should get correct pixel values when horizontal', function() { + var chart = window.acquireChart({ + type: 'horizontalBar', + data: { + datasets: [{ + data: [0.05, -25, 10, 15, 20, 25, 30, 35] + }] + }, + options: { + scales: { + xAxes: [{ + id: 'x', + type: 'linear', + }] + } + } + }); + + var start = chart.chartArea.left; + var end = chart.chartArea.right; + var min = -30; + var max = 40; + var scale = chart.scales.x; + + expect(scale.getPixelForValue(max)).toBeCloseToPixel(end); + expect(scale.getPixelForValue(min)).toBeCloseToPixel(start); + expect(scale.getValueForPixel(end)).toBeCloseTo(max, 4); + expect(scale.getValueForPixel(start)).toBeCloseTo(min, 4); + + scale.options.ticks.reverse = true; + chart.update(); + + start = chart.chartArea.left; + end = chart.chartArea.right; + + expect(scale.getPixelForValue(max)).toBeCloseToPixel(start); + expect(scale.getPixelForValue(min)).toBeCloseToPixel(end); + expect(scale.getValueForPixel(end)).toBeCloseTo(min, 4); + expect(scale.getValueForPixel(start)).toBeCloseTo(max, 4); + }); + + it('Should get correct pixel values when vertical', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + data: [0.05, -25, 10, 15, 20, 25, 30, 35] + }] + }, + options: { + scales: { + yAxes: [{ + id: 'y', + type: 'linear', + }] + } + } + }); + + var start = chart.chartArea.bottom; + var end = chart.chartArea.top; + var min = -30; + var max = 40; + var scale = chart.scales.y; + + expect(scale.getPixelForValue(max)).toBeCloseToPixel(end); + expect(scale.getPixelForValue(min)).toBeCloseToPixel(start); + expect(scale.getValueForPixel(end)).toBeCloseTo(max, 4); + expect(scale.getValueForPixel(start)).toBeCloseTo(min, 4); + + scale.options.ticks.reverse = true; + chart.update(); + + start = chart.chartArea.bottom; + end = chart.chartArea.top; + + expect(scale.getPixelForValue(max)).toBeCloseToPixel(start); + expect(scale.getPixelForValue(min)).toBeCloseToPixel(end); + expect(scale.getValueForPixel(end)).toBeCloseTo(min, 4); + expect(scale.getValueForPixel(start)).toBeCloseTo(max, 4); + }); }); diff --git a/test/specs/scale.logarithmic.tests.js b/test/specs/scale.logarithmic.tests.js index dd7c7cce94a..c1c048c7c01 100644 --- a/test/specs/scale.logarithmic.tests.js +++ b/test/specs/scale.logarithmic.tests.js @@ -862,6 +862,8 @@ describe('Logarithmic Scale tests', function() { type: 'logarithmic' }]; Chart.helpers.extend(scaleConfig, setup.scale); + scaleConfig[setup.axis + 'Axes'][0].type = 'logarithmic'; + var description = 'dataset has stack option and ' + setup.describe + ' and axis is "' + setup.axis + '";'; describe(description, function() { diff --git a/test/specs/scale.time.tests.js b/test/specs/scale.time.tests.js index 38b838726f1..6b8514ae4ac 100755 --- a/test/specs/scale.time.tests.js +++ b/test/specs/scale.time.tests.js @@ -1551,11 +1551,11 @@ describe('Time scale tests', function() { var firstTickInterval = scale.getPixelForTick(1) - scale.getPixelForTick(0); var lastTickInterval = scale.getPixelForTick(numTicks - 1) - scale.getPixelForTick(numTicks - 2); - expect(scale.getValueForPixel(scale.left + firstTickInterval / 2)).toBeCloseToTime({ + expect(scale.getValueForPixel(scale.left + lastTickInterval / 2)).toBeCloseToTime({ value: moment('2042-01-01T00:00:00'), unit: 'hour', }); - expect(scale.getValueForPixel(scale.left + scale.width - lastTickInterval / 2)).toBeCloseToTime({ + expect(scale.getValueForPixel(scale.left + scale.width - firstTickInterval / 2)).toBeCloseToTime({ value: moment('2017-01-01T00:00:00'), unit: 'hour', });