From 03cec644248731e0140e3055fcc3b16e4c20426f Mon Sep 17 00:00:00 2001 From: Akihiko Kusanagi Date: Tue, 13 Nov 2018 04:12:09 +0800 Subject: [PATCH] Support decimal stepSize (#5786) --- src/core/core.helpers.js | 20 ++++++++++++++ src/scales/scale.linearbase.js | 47 ++++++++++++++++---------------- test/specs/core.helpers.tests.js | 11 ++++++++ test/specs/scale.linear.tests.js | 29 ++++++++++++++++++++ 4 files changed, 84 insertions(+), 23 deletions(-) diff --git a/src/core/core.helpers.js b/src/core/core.helpers.js index 5cd1d8f6e91..d8175af50c5 100644 --- a/src/core/core.helpers.js +++ b/src/core/core.helpers.js @@ -174,6 +174,26 @@ module.exports = function() { helpers.toDegrees = function(radians) { return radians * (180 / Math.PI); }; + + /** + * Returns the number of decimal places + * i.e. the number of digits after the decimal point, of the value of this Number. + * @param {Number} x - A number. + * @returns {Number} The number of decimal places. + */ + helpers.decimalPlaces = function(x) { + if (!helpers.isFinite(x)) { + return; + } + var e = 1; + var p = 0; + while (Math.round(x * e) / e !== x) { + e *= 10; + p++; + } + return p; + }; + // Gets the angle from vertical upright to the point about a centre. helpers.getAngleFromPoint = function(centrePoint, anglePoint) { var distanceFromXCenter = anglePoint.x - centrePoint.x; diff --git a/src/scales/scale.linearbase.js b/src/scales/scale.linearbase.js index 7408eb9f989..0dc77fa36aa 100644 --- a/src/scales/scale.linearbase.js +++ b/src/scales/scale.linearbase.js @@ -15,36 +15,41 @@ function generateTicks(generationOptions, dataRange) { // "nice number" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks // for details. - var factor; - var precision; - var spacing; + var stepSize = generationOptions.stepSize; + var min = generationOptions.min; + var max = generationOptions.max; + var spacing, precision, factor, niceRange, niceMin, niceMax, numSpaces; - if (generationOptions.stepSize && generationOptions.stepSize > 0) { - spacing = generationOptions.stepSize; + if (stepSize && stepSize > 0) { + spacing = stepSize; } else { - var niceRange = helpers.niceNum(dataRange.max - dataRange.min, false); + niceRange = helpers.niceNum(dataRange.max - dataRange.min, false); spacing = helpers.niceNum(niceRange / (generationOptions.maxTicks - 1), true); precision = generationOptions.precision; - if (precision !== undefined) { + if (!helpers.isNullOrUndef(precision)) { // If the user specified a precision, round to that number of decimal places factor = Math.pow(10, precision); spacing = Math.ceil(spacing * factor) / factor; } } - var niceMin = Math.floor(dataRange.min / spacing) * spacing; - var niceMax = Math.ceil(dataRange.max / spacing) * spacing; + // If a precision is not specified, calculate factor based on spacing + if (!factor) { + factor = Math.pow(10, helpers.decimalPlaces(spacing)); + } + niceMin = Math.floor(dataRange.min / spacing) * spacing; + niceMax = Math.ceil(dataRange.max / spacing) * spacing; // If min, max and stepSize is set and they make an evenly spaced scale use it. - if (!helpers.isNullOrUndef(generationOptions.min) && !helpers.isNullOrUndef(generationOptions.max) && generationOptions.stepSize) { + if (!helpers.isNullOrUndef(min) && !helpers.isNullOrUndef(max) && stepSize) { // If very close to our whole number, use it. - if (helpers.almostWhole((generationOptions.max - generationOptions.min) / generationOptions.stepSize, spacing / 1000)) { - niceMin = generationOptions.min; - niceMax = generationOptions.max; + if (helpers.almostWhole((max - min) / stepSize, spacing / 1000)) { + niceMin = min; + niceMax = max; } } - var numSpaces = (niceMax - niceMin) / spacing; + numSpaces = (niceMax - niceMin) / spacing; // If very close to our rounded value, use it. if (helpers.almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) { numSpaces = Math.round(numSpaces); @@ -52,17 +57,13 @@ function generateTicks(generationOptions, dataRange) { numSpaces = Math.ceil(numSpaces); } - precision = 1; - if (spacing < 1) { - precision = Math.pow(10, 1 - Math.floor(helpers.log10(spacing))); - niceMin = Math.round(niceMin * precision) / precision; - niceMax = Math.round(niceMax * precision) / precision; - } - ticks.push(generationOptions.min !== undefined ? generationOptions.min : niceMin); + niceMin = Math.round(niceMin * factor) / factor; + niceMax = Math.round(niceMax * factor) / factor; + ticks.push(helpers.isNullOrUndef(min) ? niceMin : min); for (var j = 1; j < numSpaces; ++j) { - ticks.push(Math.round((niceMin + j * spacing) * precision) / precision); + ticks.push(Math.round((niceMin + j * spacing) * factor) / factor); } - ticks.push(generationOptions.max !== undefined ? generationOptions.max : niceMax); + ticks.push(helpers.isNullOrUndef(max) ? niceMax : max); return ticks; } diff --git a/test/specs/core.helpers.tests.js b/test/specs/core.helpers.tests.js index 30baaf5acc1..de6d0b41301 100644 --- a/test/specs/core.helpers.tests.js +++ b/test/specs/core.helpers.tests.js @@ -238,6 +238,17 @@ describe('Core helper tests', function() { expect(helpers.toDegrees(Math.PI * 3 / 2)).toBe(270); }); + it('should get the correct number of decimal places', function() { + expect(helpers.decimalPlaces(100)).toBe(0); + expect(helpers.decimalPlaces(1)).toBe(0); + expect(helpers.decimalPlaces(0)).toBe(0); + expect(helpers.decimalPlaces(0.01)).toBe(2); + expect(helpers.decimalPlaces(-0.01)).toBe(2); + expect(helpers.decimalPlaces('1')).toBe(undefined); + expect(helpers.decimalPlaces('')).toBe(undefined); + expect(helpers.decimalPlaces(undefined)).toBe(undefined); + }); + it('should get an angle from a point', function() { var center = { x: 0, diff --git a/test/specs/scale.linear.tests.js b/test/specs/scale.linear.tests.js index 959a76db01e..9ec21116e88 100644 --- a/test/specs/scale.linear.tests.js +++ b/test/specs/scale.linear.tests.js @@ -573,6 +573,35 @@ describe('Linear Scale', function() { expect(chart.scales.yScale0.ticks).toEqual(['11', '9', '7', '5', '3', '1']); }); + it('Should create decimal steps if stepSize is a decimal number', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + yAxisID: 'yScale0', + data: [10, 3, 6, 8, 3, 1] + }], + labels: ['a', 'b', 'c', 'd', 'e', 'f'] + }, + options: { + scales: { + yAxes: [{ + id: 'yScale0', + type: 'linear', + ticks: { + stepSize: 2.5 + } + }] + } + } + }); + + expect(chart.scales.yScale0).not.toEqual(undefined); // must construct + expect(chart.scales.yScale0.min).toBe(0); + expect(chart.scales.yScale0.max).toBe(10); + expect(chart.scales.yScale0.ticks).toEqual(['10', '7.5', '5', '2.5', '0']); + }); + describe('precision', function() { it('Should create integer steps if precision is 0', function() { var chart = window.acquireChart({