From 48aecb47449da883b29c1ad61ee4e1cfff1bd74d Mon Sep 17 00:00:00 2001 From: Akihiko Kusanagi Date: Thu, 27 Dec 2018 01:20:14 +0800 Subject: [PATCH] Improve tick generation for linear scales --- src/scales/scale.linear.js | 4 +-- src/scales/scale.linearbase.js | 26 +++++++++++++--- src/scales/scale.logarithmic.js | 4 --- test/specs/scale.linear.tests.js | 41 ++++++++++++++++++++++++++ test/specs/scale.radialLinear.tests.js | 31 +++++++++++++++++++ 5 files changed, 96 insertions(+), 10 deletions(-) diff --git a/src/scales/scale.linear.js b/src/scales/scale.linear.js index a980615c5d1..228ce099849 100644 --- a/src/scales/scale.linear.js +++ b/src/scales/scale.linear.js @@ -139,11 +139,11 @@ module.exports = function(Chart) { var tickOpts = me.options.ticks; if (me.isHorizontal()) { - maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.width / 50)); + maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.width / 40)); } else { // The factor of 2 used to scale the font size has been experimentally determined. var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, defaults.global.defaultFontSize); - maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.height / (2 * tickFontSize))); + maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.height / (1.5 * tickFontSize))); } return maxTicks; diff --git a/src/scales/scale.linearbase.js b/src/scales/scale.linearbase.js index e2898e1ea38..490bc2ab6b8 100644 --- a/src/scales/scale.linearbase.js +++ b/src/scales/scale.linearbase.js @@ -18,13 +18,30 @@ function generateTicks(generationOptions, dataRange) { var stepSize = generationOptions.stepSize; var min = generationOptions.min; var max = generationOptions.max; - var spacing, precision, factor, niceRange, niceMin, niceMax, numSpaces; + var spacing, precision, factor, niceMin, niceMax, numSpaces, maxNumSpaces; if (stepSize && stepSize > 0) { spacing = stepSize; + if (generationOptions.maxTicksLimit) { + maxNumSpaces = generationOptions.maxTicksLimit - 1; + // spacing is set to stepSize multiplied by a nice number of + // Math.ceil((max - min) / maxNumSpaces / stepSize) = num of steps that should be grouped + spacing *= helpers.niceNum(Math.ceil((dataRange.max - dataRange.min) / maxNumSpaces / stepSize)); + numSpaces = Math.ceil(dataRange.max / spacing) - Math.floor(dataRange.min / spacing); + if (numSpaces > maxNumSpaces) { + // If the calculated num of spaces exceeds maxNumSpaces, recalculate it + spacing = helpers.niceNum(Math.ceil(numSpaces * spacing / maxNumSpaces / stepSize)) * stepSize; + } + } } else { - niceRange = helpers.niceNum(dataRange.max - dataRange.min, false); - spacing = helpers.niceNum(niceRange / (generationOptions.maxTicks - 1), true); + maxNumSpaces = generationOptions.maxTicks - 1; + // spacing is set to a nice number of (max - min) / maxNumSpaces + spacing = helpers.niceNum((dataRange.max - dataRange.min) / maxNumSpaces); + numSpaces = Math.ceil(dataRange.max / spacing) - Math.floor(dataRange.min / spacing); + if (numSpaces > maxNumSpaces) { + // If the calculated num of spaces exceeds maxNumSpaces, recalculate it + spacing = helpers.niceNum(numSpaces * spacing / maxNumSpaces); + } precision = generationOptions.precision; if (!helpers.isNullOrUndef(precision)) { @@ -155,7 +172,7 @@ module.exports = function(Chart) { var tickOpts = opts.ticks; // Figure out what the max number of ticks we can support it is based on the size of - // the axis area. For now, we say that the minimum tick spacing in pixels must be 50 + // the axis area. For now, we say that the minimum tick spacing in pixels must be 40 // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on // the graph. Make sure we always have at least 2 ticks var maxTicks = me.getTickLimit(); @@ -163,6 +180,7 @@ module.exports = function(Chart) { var numericGeneratorOptions = { maxTicks: maxTicks, + maxTicksLimit: tickOpts.maxTicksLimit, min: tickOpts.min, max: tickOpts.max, precision: tickOpts.precision, diff --git a/src/scales/scale.logarithmic.js b/src/scales/scale.logarithmic.js index 0365568e772..240df0faf46 100644 --- a/src/scales/scale.logarithmic.js +++ b/src/scales/scale.logarithmic.js @@ -15,10 +15,6 @@ function generateTicks(generationOptions, dataRange) { var ticks = []; var valueOrDefault = helpers.valueOrDefault; - // Figure out what the max number of ticks we can support it is based on the size of - // the axis area. For now, we say that the minimum tick spacing in pixels must be 50 - // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on - // the graph var tickVal = valueOrDefault(generationOptions.min, Math.pow(10, Math.floor(helpers.log10(dataRange.min)))); var endExp = Math.floor(helpers.log10(dataRange.max)); diff --git a/test/specs/scale.linear.tests.js b/test/specs/scale.linear.tests.js index ad35e2a4347..9b866a5a762 100644 --- a/test/specs/scale.linear.tests.js +++ b/test/specs/scale.linear.tests.js @@ -749,6 +749,47 @@ describe('Linear Scale', function() { expect(chart.scales.yScale0.ticks).toEqual(['0.06', '0.05', '0.04', '0.03', '0.02', '0.01', '0']); }); + it('Should correctly limit the maximum number of ticks', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + labels: ['a', 'b'], + datasets: [{ + data: [0.5, 2.5] + }] + }, + options: { + scales: { + yAxes: [{ + id: 'yScale' + }] + } + } + }); + + expect(chart.scales.yScale.ticks).toEqual(['2.5', '2.0', '1.5', '1.0', '0.5']); + + chart.options.scales.yAxes[0].ticks.maxTicksLimit = 11; + chart.update(); + + expect(chart.scales.yScale.ticks).toEqual(['2.5', '2.0', '1.5', '1.0', '0.5']); + + chart.options.scales.yAxes[0].ticks.maxTicksLimit = 21; + chart.update(); + + expect(chart.scales.yScale.ticks).toEqual([ + '2.5', '2.4', '2.3', '2.2', '2.1', '2.0', '1.9', '1.8', '1.7', '1.6', + '1.5', '1.4', '1.3', '1.2', '1.1', '1.0', '0.9', '0.8', '0.7', '0.6', + '0.5' + ]); + + chart.options.scales.yAxes[0].ticks.maxTicksLimit = 11; + chart.options.scales.yAxes[0].ticks.stepSize = 0.01; + chart.update(); + + expect(chart.scales.yScale.ticks).toEqual(['2.5', '2.0', '1.5', '1.0', '0.5']); + }); + it('Should build labels using the user supplied callback', function() { var chart = window.acquireChart({ type: 'bar', diff --git a/test/specs/scale.radialLinear.tests.js b/test/specs/scale.radialLinear.tests.js index 49ef34b46d6..b564564ec66 100644 --- a/test/specs/scale.radialLinear.tests.js +++ b/test/specs/scale.radialLinear.tests.js @@ -282,6 +282,37 @@ describe('Test the radial linear scale', function() { expect(chart.scale.end).toBe(0); }); + it('Should correctly limit the maximum number of ticks', function() { + var chart = window.acquireChart({ + type: 'radar', + data: { + labels: ['label1', 'label2', 'label3'], + datasets: [{ + data: [0.5, 1.5, 2.5] + }] + }, + options: { + scale: { + pointLabels: { + display: false + } + } + } + }); + + expect(chart.scale.ticks).toEqual(['0.5', '1.0', '1.5', '2.0', '2.5']); + + chart.options.scale.ticks.maxTicksLimit = 11; + chart.update(); + + expect(chart.scale.ticks).toEqual(['0.5', '1.0', '1.5', '2.0', '2.5']); + + chart.options.scale.ticks.stepSize = 0.01; + chart.update(); + + expect(chart.scale.ticks).toEqual(['0.5', '1.0', '1.5', '2.0', '2.5']); + }); + it('Should build labels using the user supplied callback', function() { var chart = window.acquireChart({ type: 'radar',