Skip to content

Commit aae05a0

Browse files
nagixetimberg
authored andcommitted
Improve tick generation for linear scales (#5938)
* Improve tick generation for linear scales * Simplify the tick generation code * Refactor getTickLimit
1 parent a8920f6 commit aae05a0

File tree

6 files changed

+145
-41
lines changed

6 files changed

+145
-41
lines changed

src/scales/scale.linear.js

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
'use strict';
22

3-
var defaults = require('../core/core.defaults');
43
var helpers = require('../helpers/index');
54
var scaleService = require('../core/core.scaleService');
65
var Ticks = require('../core/core.ticks');
@@ -133,20 +132,16 @@ module.exports = function(Chart) {
133132
// Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero
134133
this.handleTickRangeOptions();
135134
},
136-
getTickLimit: function() {
137-
var maxTicks;
135+
// Returns the maximum number of ticks based on the scale dimension
136+
_computeTickLimit: function() {
138137
var me = this;
139-
var tickOpts = me.options.ticks;
138+
var tickFont;
140139

141140
if (me.isHorizontal()) {
142-
maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.width / 50));
143-
} else {
144-
// The factor of 2 used to scale the font size has been experimentally determined.
145-
var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, defaults.global.defaultFontSize);
146-
maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.height / (2 * tickFontSize)));
141+
return Math.ceil(me.width / 40);
147142
}
148-
149-
return maxTicks;
143+
tickFont = helpers.options._parseFont(me.options.ticks);
144+
return Math.ceil(me.height / tickFont.lineHeight);
150145
},
151146
// Called after the ticks are built. We need
152147
handleDirectionalChanges: function() {

src/scales/scale.linearbase.js

Lines changed: 51 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,35 +16,41 @@ function generateTicks(generationOptions, dataRange) {
1616
// for details.
1717

1818
var stepSize = generationOptions.stepSize;
19+
var unit = stepSize || 1;
20+
var maxNumSpaces = generationOptions.maxTicks - 1;
1921
var min = generationOptions.min;
2022
var max = generationOptions.max;
21-
var spacing, precision, factor, niceRange, niceMin, niceMax, numSpaces;
22-
23-
if (stepSize && stepSize > 0) {
24-
spacing = stepSize;
25-
} else {
26-
niceRange = helpers.niceNum(dataRange.max - dataRange.min, false);
27-
spacing = helpers.niceNum(niceRange / (generationOptions.maxTicks - 1), true);
28-
29-
precision = generationOptions.precision;
30-
if (!helpers.isNullOrUndef(precision)) {
31-
// If the user specified a precision, round to that number of decimal places
32-
factor = Math.pow(10, precision);
33-
spacing = Math.ceil(spacing * factor) / factor;
34-
}
23+
var precision = generationOptions.precision;
24+
var spacing, factor, niceMin, niceMax, numSpaces;
25+
26+
// spacing is set to a nice number of the dataRange divided by maxNumSpaces.
27+
// stepSize is used as a minimum unit if it is specified.
28+
spacing = helpers.niceNum((dataRange.max - dataRange.min) / maxNumSpaces / unit) * unit;
29+
numSpaces = Math.ceil(dataRange.max / spacing) - Math.floor(dataRange.min / spacing);
30+
if (numSpaces > maxNumSpaces) {
31+
// If the calculated num of spaces exceeds maxNumSpaces, recalculate it
32+
spacing = helpers.niceNum(numSpaces * spacing / maxNumSpaces / unit) * unit;
3533
}
36-
// If a precision is not specified, calculate factor based on spacing
37-
if (!factor) {
34+
35+
if (stepSize || helpers.isNullOrUndef(precision)) {
36+
// If a precision is not specified, calculate factor based on spacing
3837
factor = Math.pow(10, helpers.decimalPlaces(spacing));
38+
} else {
39+
// If the user specified a precision, round to that number of decimal places
40+
factor = Math.pow(10, precision);
41+
spacing = Math.ceil(spacing * factor) / factor;
3942
}
43+
4044
niceMin = Math.floor(dataRange.min / spacing) * spacing;
4145
niceMax = Math.ceil(dataRange.max / spacing) * spacing;
4246

4347
// If min, max and stepSize is set and they make an evenly spaced scale use it.
44-
if (!helpers.isNullOrUndef(min) && !helpers.isNullOrUndef(max) && stepSize) {
48+
if (stepSize) {
4549
// If very close to our whole number, use it.
46-
if (helpers.almostWhole((max - min) / stepSize, spacing / 1000)) {
50+
if (!helpers.isNullOrUndef(min) && helpers.almostWhole(min / spacing, spacing / 1000)) {
4751
niceMin = min;
52+
}
53+
if (!helpers.isNullOrUndef(max) && helpers.almostWhole(max / spacing, spacing / 1000)) {
4854
niceMax = max;
4955
}
5056
}
@@ -146,7 +152,32 @@ module.exports = function(Chart) {
146152
}
147153
}
148154
},
149-
getTickLimit: noop,
155+
156+
getTickLimit: function() {
157+
var me = this;
158+
var tickOpts = me.options.ticks;
159+
var stepSize = tickOpts.stepSize;
160+
var maxTicksLimit = tickOpts.maxTicksLimit;
161+
var maxTicks;
162+
163+
if (stepSize) {
164+
maxTicks = Math.ceil(me.max / stepSize) - Math.floor(me.min / stepSize) + 1;
165+
} else {
166+
maxTicks = me._computeTickLimit();
167+
maxTicksLimit = maxTicksLimit || 11;
168+
}
169+
170+
if (maxTicksLimit) {
171+
maxTicks = Math.min(maxTicksLimit, maxTicks);
172+
}
173+
174+
return maxTicks;
175+
},
176+
177+
_computeTickLimit: function() {
178+
return Number.POSITIVE_INFINITY;
179+
},
180+
150181
handleDirectionalChanges: noop,
151182

152183
buildTicks: function() {
@@ -155,7 +186,7 @@ module.exports = function(Chart) {
155186
var tickOpts = opts.ticks;
156187

157188
// Figure out what the max number of ticks we can support it is based on the size of
158-
// the axis area. For now, we say that the minimum tick spacing in pixels must be 50
189+
// the axis area. For now, we say that the minimum tick spacing in pixels must be 40
159190
// We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
160191
// the graph. Make sure we always have at least 2 ticks
161192
var maxTicks = me.getTickLimit();

src/scales/scale.logarithmic.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,6 @@ function generateTicks(generationOptions, dataRange) {
1515
var ticks = [];
1616
var valueOrDefault = helpers.valueOrDefault;
1717

18-
// Figure out what the max number of ticks we can support it is based on the size of
19-
// the axis area. For now, we say that the minimum tick spacing in pixels must be 50
20-
// We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
21-
// the graph
2218
var tickVal = valueOrDefault(generationOptions.min, Math.pow(10, Math.floor(helpers.log10(dataRange.min))));
2319

2420
var endExp = Math.floor(helpers.log10(dataRange.max));

src/scales/scale.radialLinear.js

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -357,11 +357,9 @@ module.exports = function(Chart) {
357357
// Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero
358358
me.handleTickRangeOptions();
359359
},
360-
getTickLimit: function() {
361-
var opts = this.options;
362-
var tickOpts = opts.ticks;
363-
var tickBackdropHeight = getTickBackdropHeight(opts);
364-
return Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(this.drawingArea / tickBackdropHeight));
360+
// Returns the maximum number of ticks based on the scale dimension
361+
_computeTickLimit: function() {
362+
return Math.ceil(this.drawingArea / getTickBackdropHeight(this.options));
365363
},
366364
convertTicksToLabels: function() {
367365
var me = this;

test/specs/scale.linear.tests.js

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -570,7 +570,7 @@ describe('Linear Scale', function() {
570570
expect(chart.scales.yScale0).not.toEqual(undefined); // must construct
571571
expect(chart.scales.yScale0.min).toBe(1);
572572
expect(chart.scales.yScale0.max).toBe(11);
573-
expect(chart.scales.yScale0.ticks).toEqual(['11', '9', '7', '5', '3', '1']);
573+
expect(chart.scales.yScale0.ticks).toEqual(['11', '10', '8', '6', '4', '2', '1']);
574574
});
575575

576576
it('Should create decimal steps if stepSize is a decimal number', function() {
@@ -749,6 +749,53 @@ describe('Linear Scale', function() {
749749
expect(chart.scales.yScale0.ticks).toEqual(['0.06', '0.05', '0.04', '0.03', '0.02', '0.01', '0']);
750750
});
751751

752+
it('Should correctly limit the maximum number of ticks', function() {
753+
var chart = window.acquireChart({
754+
type: 'bar',
755+
data: {
756+
labels: ['a', 'b'],
757+
datasets: [{
758+
data: [0.5, 2.5]
759+
}]
760+
},
761+
options: {
762+
scales: {
763+
yAxes: [{
764+
id: 'yScale'
765+
}]
766+
}
767+
}
768+
});
769+
770+
expect(chart.scales.yScale.ticks).toEqual(['2.5', '2.0', '1.5', '1.0', '0.5']);
771+
772+
chart.options.scales.yAxes[0].ticks.maxTicksLimit = 11;
773+
chart.update();
774+
775+
expect(chart.scales.yScale.ticks).toEqual(['2.5', '2.0', '1.5', '1.0', '0.5']);
776+
777+
chart.options.scales.yAxes[0].ticks.maxTicksLimit = 21;
778+
chart.update();
779+
780+
expect(chart.scales.yScale.ticks).toEqual([
781+
'2.5', '2.4', '2.3', '2.2', '2.1', '2.0', '1.9', '1.8', '1.7', '1.6',
782+
'1.5', '1.4', '1.3', '1.2', '1.1', '1.0', '0.9', '0.8', '0.7', '0.6',
783+
'0.5'
784+
]);
785+
786+
chart.options.scales.yAxes[0].ticks.maxTicksLimit = 11;
787+
chart.options.scales.yAxes[0].ticks.stepSize = 0.01;
788+
chart.update();
789+
790+
expect(chart.scales.yScale.ticks).toEqual(['2.5', '2.0', '1.5', '1.0', '0.5']);
791+
792+
chart.options.scales.yAxes[0].ticks.min = 0.3;
793+
chart.options.scales.yAxes[0].ticks.max = 2.8;
794+
chart.update();
795+
796+
expect(chart.scales.yScale.ticks).toEqual(['2.8', '2.5', '2.0', '1.5', '1.0', '0.5', '0.3']);
797+
});
798+
752799
it('Should build labels using the user supplied callback', function() {
753800
var chart = window.acquireChart({
754801
type: 'bar',

test/specs/scale.radialLinear.tests.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,43 @@ describe('Test the radial linear scale', function() {
282282
expect(chart.scale.end).toBe(0);
283283
});
284284

285+
it('Should correctly limit the maximum number of ticks', function() {
286+
var chart = window.acquireChart({
287+
type: 'radar',
288+
data: {
289+
labels: ['label1', 'label2', 'label3'],
290+
datasets: [{
291+
data: [0.5, 1.5, 2.5]
292+
}]
293+
},
294+
options: {
295+
scale: {
296+
pointLabels: {
297+
display: false
298+
}
299+
}
300+
}
301+
});
302+
303+
expect(chart.scale.ticks).toEqual(['0.5', '1.0', '1.5', '2.0', '2.5']);
304+
305+
chart.options.scale.ticks.maxTicksLimit = 11;
306+
chart.update();
307+
308+
expect(chart.scale.ticks).toEqual(['0.5', '1.0', '1.5', '2.0', '2.5']);
309+
310+
chart.options.scale.ticks.stepSize = 0.01;
311+
chart.update();
312+
313+
expect(chart.scale.ticks).toEqual(['0.5', '1.0', '1.5', '2.0', '2.5']);
314+
315+
chart.options.scale.ticks.min = 0.3;
316+
chart.options.scale.ticks.max = 2.8;
317+
chart.update();
318+
319+
expect(chart.scale.ticks).toEqual(['0.3', '0.5', '1.0', '1.5', '2.0', '2.5', '2.8']);
320+
});
321+
285322
it('Should build labels using the user supplied callback', function() {
286323
var chart = window.acquireChart({
287324
type: 'radar',

0 commit comments

Comments
 (0)