From de9625eb52cd4833fce774735d238e51805175d6 Mon Sep 17 00:00:00 2001 From: Jukka Kurkela Date: Wed, 9 Jan 2019 08:58:13 +0200 Subject: [PATCH] Fix generateTicks when working with tiny numbers (#5948) --- src/scales/scale.linearbase.js | 32 ++++++++++++++++++++------------ test/specs/scale.linear.tests.js | 25 +++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/src/scales/scale.linearbase.js b/src/scales/scale.linearbase.js index 808f454407a..057e68ffc3d 100644 --- a/src/scales/scale.linearbase.js +++ b/src/scales/scale.linearbase.js @@ -4,6 +4,7 @@ var helpers = require('../helpers/index'); var Scale = require('../core/core.scale'); var noop = helpers.noop; +var isNullOrUndef = helpers.isNullOrUndef; /** * Generate a set of linear ticks @@ -17,24 +18,31 @@ function generateTicks(generationOptions, dataRange) { // "nice number" algorithm. See https://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks // for details. + var MIN_SPACING = 1e-14; var stepSize = generationOptions.stepSize; var unit = stepSize || 1; var maxNumSpaces = generationOptions.maxTicks - 1; var min = generationOptions.min; var max = generationOptions.max; var precision = generationOptions.precision; - var spacing, factor, niceMin, niceMax, numSpaces; + var rmin = dataRange.min; + var rmax = dataRange.max; + var spacing = helpers.niceNum((rmax - rmin) / maxNumSpaces / unit) * unit; + var factor, niceMin, niceMax, numSpaces; + + // Beyond MIN_SPACING floating point numbers being to lose precision + // such that we can't do the math necessary to generate ticks + if (spacing < MIN_SPACING && isNullOrUndef(min) && isNullOrUndef(max)) { + return [rmin, rmax]; + } - // spacing is set to a nice number of the dataRange divided by maxNumSpaces. - // stepSize is used as a minimum unit if it is specified. - spacing = helpers.niceNum((dataRange.max - dataRange.min) / maxNumSpaces / unit) * unit; - numSpaces = Math.ceil(dataRange.max / spacing) - Math.floor(dataRange.min / spacing); + numSpaces = Math.ceil(rmax / spacing) - Math.floor(rmin / spacing); if (numSpaces > maxNumSpaces) { // If the calculated num of spaces exceeds maxNumSpaces, recalculate it spacing = helpers.niceNum(numSpaces * spacing / maxNumSpaces / unit) * unit; } - if (stepSize || helpers.isNullOrUndef(precision)) { + if (stepSize || isNullOrUndef(precision)) { // If a precision is not specified, calculate factor based on spacing factor = Math.pow(10, helpers.decimalPlaces(spacing)); } else { @@ -43,16 +51,16 @@ function generateTicks(generationOptions, dataRange) { spacing = Math.ceil(spacing * factor) / factor; } - niceMin = Math.floor(dataRange.min / spacing) * spacing; - niceMax = Math.ceil(dataRange.max / spacing) * spacing; + niceMin = Math.floor(rmin / spacing) * spacing; + niceMax = Math.ceil(rmax / spacing) * spacing; // If min, max and stepSize is set and they make an evenly spaced scale use it. if (stepSize) { // If very close to our whole number, use it. - if (!helpers.isNullOrUndef(min) && helpers.almostWhole(min / spacing, spacing / 1000)) { + if (!isNullOrUndef(min) && helpers.almostWhole(min / spacing, spacing / 1000)) { niceMin = min; } - if (!helpers.isNullOrUndef(max) && helpers.almostWhole(max / spacing, spacing / 1000)) { + if (!isNullOrUndef(max) && helpers.almostWhole(max / spacing, spacing / 1000)) { niceMax = max; } } @@ -67,11 +75,11 @@ function generateTicks(generationOptions, dataRange) { niceMin = Math.round(niceMin * factor) / factor; niceMax = Math.round(niceMax * factor) / factor; - ticks.push(helpers.isNullOrUndef(min) ? niceMin : min); + ticks.push(isNullOrUndef(min) ? niceMin : min); for (var j = 1; j < numSpaces; ++j) { ticks.push(Math.round((niceMin + j * spacing) * factor) / factor); } - ticks.push(helpers.isNullOrUndef(max) ? niceMax : max); + ticks.push(isNullOrUndef(max) ? niceMax : max); return ticks; } diff --git a/test/specs/scale.linear.tests.js b/test/specs/scale.linear.tests.js index a186c6cc7fe..bdf8a9386f9 100644 --- a/test/specs/scale.linear.tests.js +++ b/test/specs/scale.linear.tests.js @@ -1170,4 +1170,29 @@ describe('Linear Scale', function() { expect(data[0]._model.base + minBarLength).toEqual(data[0]._model.x); expect(data[1]._model.base - minBarLength).toEqual(data[1]._model.x); }); + + it('Should generate max and min that are not equal when data contains values that are very close to each other', function() { + var chart = window.acquireChart({ + type: 'scatter', + data: { + datasets: [{ + data: [ + {x: 1, y: 1.8548483304974972}, + {x: 2, y: 1.8548483304974974}, + ] + }], + }, + options: { + scales: { + yAxes: [{ + id: 'yScale0', + type: 'linear', + }] + } + } + }); + + expect(chart.scales.yScale0).not.toEqual(undefined); // must construct + expect(chart.scales.yScale0.max).toBeGreaterThan(chart.scales.yScale0.min); + }); });