Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve tick generation for linear scales #5938

Merged
merged 3 commits into from Jan 1, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
21 changes: 13 additions & 8 deletions src/scales/scale.linear.js
@@ -1,6 +1,5 @@
'use strict';

var defaults = require('../core/core.defaults');
var helpers = require('../helpers/index');
var scaleService = require('../core/core.scaleService');
var Ticks = require('../core/core.ticks');
Expand Down Expand Up @@ -134,16 +133,22 @@ module.exports = function(Chart) {
this.handleTickRangeOptions();
},
getTickLimit: function() {
simonbrunel marked this conversation as resolved.
Show resolved Hide resolved
var maxTicks;
var me = this;
var tickOpts = me.options.ticks;

if (me.isHorizontal()) {
maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.width / 50));
var stepSize = tickOpts.stepSize;
var maxTicksLimit = tickOpts.maxTicksLimit;
var maxTicks, tickFont;

if (stepSize > 0) {
maxTicks = Math.ceil(me.max / stepSize) - Math.floor(me.min / stepSize) + 1;
} else if (me.isHorizontal()) {
maxTicks = 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)));
tickFont = helpers.options._parseFont(tickOpts);
maxTicks = Math.ceil(me.height / tickFont.lineHeight);
}
if (maxTicksLimit || !(stepSize > 0)) {
maxTicks = Math.min(maxTicksLimit || 11, maxTicks);
}

return maxTicks;
Expand Down
42 changes: 24 additions & 18 deletions src/scales/scale.linearbase.js
Expand Up @@ -16,35 +16,41 @@ function generateTicks(generationOptions, dataRange) {
// for details.

var stepSize = generationOptions.stepSize;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stepSize < 0 doesn't seems a valid value, so maybe we can simplify all these checks by clamping this value:

var stepSize = Math.max(0, generationOptions.stepSize);
var unit = stepSize || 1;
//...

var unit = stepSize > 0 ? stepSize : 1;
var maxNumSpaces = generationOptions.maxTicks - 1;
var min = generationOptions.min;
var max = generationOptions.max;
var spacing, precision, factor, niceRange, niceMin, niceMax, numSpaces;
var precision = generationOptions.precision;
var spacing, factor, niceMin, niceMax, numSpaces;

// 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);
if (numSpaces > maxNumSpaces) {
// If the calculated num of spaces exceeds maxNumSpaces, recalculate it
spacing = helpers.niceNum(numSpaces * spacing / maxNumSpaces / unit) * unit;
}

if (stepSize && stepSize > 0) {
spacing = stepSize;
if (!(stepSize > 0) && !helpers.isNullOrUndef(precision)) {
simonbrunel marked this conversation as resolved.
Show resolved Hide resolved
// If the user specified a precision, round to that number of decimal places
factor = Math.pow(10, precision);
spacing = Math.ceil(spacing * factor) / factor;
} else {
niceRange = helpers.niceNum(dataRange.max - dataRange.min, false);
spacing = helpers.niceNum(niceRange / (generationOptions.maxTicks - 1), true);

precision = generationOptions.precision;
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;
}
}
// If a precision is not specified, calculate factor based on spacing
if (!factor) {
// If a precision is not specified, calculate factor based on spacing
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(min) && !helpers.isNullOrUndef(max) && stepSize) {
if (stepSize > 0) {
simonbrunel marked this conversation as resolved.
Show resolved Hide resolved
// If very close to our whole number, use it.
if (helpers.almostWhole((max - min) / stepSize, spacing / 1000)) {
if (!helpers.isNullOrUndef(min) && helpers.almostWhole(min / spacing, spacing / 1000)) {
niceMin = min;
}
if (!helpers.isNullOrUndef(max) && helpers.almostWhole(max / spacing, spacing / 1000)) {
niceMax = max;
}
}
Expand Down Expand Up @@ -155,7 +161,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();
Expand Down
4 changes: 0 additions & 4 deletions src/scales/scale.logarithmic.js
Expand Up @@ -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));
Expand Down
19 changes: 16 additions & 3 deletions src/scales/scale.radialLinear.js
Expand Up @@ -358,10 +358,23 @@ module.exports = function(Chart) {
me.handleTickRangeOptions();
},
getTickLimit: function() {
var opts = this.options;
var me = this;
var opts = me.options;
var tickOpts = opts.ticks;
var tickBackdropHeight = getTickBackdropHeight(opts);
return Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(this.drawingArea / tickBackdropHeight));
var stepSize = tickOpts.stepSize;
var maxTicksLimit = tickOpts.maxTicksLimit;
var maxTicks;

if (stepSize > 0) {
maxTicks = Math.ceil(me.max / stepSize) - Math.floor(me.min / stepSize) + 1;
} else {
maxTicks = Math.ceil(me.drawingArea / getTickBackdropHeight(opts));
}
if (maxTicksLimit || !(stepSize > 0)) {
maxTicks = Math.min(maxTicksLimit || 11, maxTicks);
}

return maxTicks;
},
convertTicksToLabels: function() {
var me = this;
Expand Down
49 changes: 48 additions & 1 deletion test/specs/scale.linear.tests.js
Expand Up @@ -570,7 +570,7 @@ describe('Linear Scale', function() {
expect(chart.scales.yScale0).not.toEqual(undefined); // must construct
expect(chart.scales.yScale0.min).toBe(1);
expect(chart.scales.yScale0.max).toBe(11);
expect(chart.scales.yScale0.ticks).toEqual(['11', '9', '7', '5', '3', '1']);
expect(chart.scales.yScale0.ticks).toEqual(['11', '10', '8', '6', '4', '2', '1']);
});

it('Should create decimal steps if stepSize is a decimal number', function() {
Expand Down Expand Up @@ -749,6 +749,53 @@ 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']);

chart.options.scales.yAxes[0].ticks.min = 0.3;
chart.options.scales.yAxes[0].ticks.max = 2.8;
chart.update();

expect(chart.scales.yScale.ticks).toEqual(['2.8', '2.5', '2.0', '1.5', '1.0', '0.5', '0.3']);
});

it('Should build labels using the user supplied callback', function() {
var chart = window.acquireChart({
type: 'bar',
Expand Down
37 changes: 37 additions & 0 deletions test/specs/scale.radialLinear.tests.js
Expand Up @@ -282,6 +282,43 @@ 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']);

chart.options.scale.ticks.min = 0.3;
chart.options.scale.ticks.max = 2.8;
chart.update();

expect(chart.scale.ticks).toEqual(['0.3', '0.5', '1.0', '1.5', '2.0', '2.5', '2.8']);
});

it('Should build labels using the user supplied callback', function() {
var chart = window.acquireChart({
type: 'radar',
Expand Down