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

Linear Scale: Ability to specify a fixed number of ticks #8643

Merged
merged 7 commits into from
Mar 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/docs/axes/cartesian/linear.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Namespace: `options.scales[scaleId].ticks`

| Name | Type | Default | Description
| ---- | ---- | ------- | -----------
| `count` | `number` | `undefined` | The number of ticks to generate. If specified, this overrides the automatic generation.
| `format` | `object` | | The [`Intl.NumberFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat) options used by the default label formatter
| `maxTicksLimit` | `number` | `11` | Maximum number of ticks and gridlines to show.
| `precision` | `number` | | if defined and `stepSize` is not specified, the step size will be rounded to this many decimal places.
Expand Down
1 change: 1 addition & 0 deletions docs/docs/axes/radial/linear.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Namespace: `options.scales[scaleId].ticks`
| ---- | ---- | ------- | ------- | -----------
| `backdropColor` | [`Color`](../../general/colors.md) | Yes | `'rgba(255, 255, 255, 0.75)'` | Color of label backdrops.
| `backdropPadding` | [`Padding`](../../general/padding.md) | Yes | `2` | Padding of label backdrop.
| `count` | `number` | Yes | `undefined` | The number of ticks to generate. If specified, this overrides the automatic generation.
| `format` | `object` | Yes | | The [`Intl.NumberFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat) options used by the default label formatter
| `maxTicksLimit` | `number` | Yes | `11` | Maximum number of ticks and gridlines to show.
| `precision` | `number` | Yes | | if defined and `stepSize` is not specified, the step size will be rounded to this many decimal places.
Expand Down
78 changes: 52 additions & 26 deletions src/scales/scale.linearbase.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,20 @@ import {formatNumber} from '../core/core.intl';
import {_addGrace} from '../helpers/helpers.options';

/**
* Generate a set of linear ticks
* Generate a set of linear ticks for an axis
* 1. If generationOptions.min, generationOptions.max, and generationOptions.step are defined:
* if (max - min) / step is an integer, ticks are generated as [min, min + step, ..., max]
* Note that the generationOptions.maxCount setting is respected in this scenario
*
* 2. If generationOptions.min, generationOptions.max, and generationOptions.count is defined
* spacing = (max - min) / count
* Ticks are generated as [min, min + spacing, ..., max]
*
* 3. If generationOptions.count is defined
* spacing = (niceMax - niceMin) / count
*
* 4. Compute optimal spacing of ticks using niceNum algorithm
*
* @param generationOptions the options used to generate the ticks
* @param dataRange the range of the data
* @returns {object[]} array of tick objects
Expand All @@ -17,13 +30,14 @@ function generateTicks(generationOptions, dataRange) {
// for details.

const MIN_SPACING = 1e-14;
const {stepSize, min, max, precision} = generationOptions;
const unit = stepSize || 1;
const maxNumSpaces = generationOptions.maxTicks - 1;
const {step, min, max, precision, count, maxTicks} = generationOptions;
const unit = step || 1;
const maxSpaces = maxTicks - 1;
const {min: rmin, max: rmax} = dataRange;
const minDefined = !isNullOrUndef(min);
const maxDefined = !isNullOrUndef(max);
let spacing = niceNum((rmax - rmin) / maxNumSpaces / unit) * unit;
const countDefined = !isNullOrUndef(count);
let spacing = niceNum((rmax - rmin) / maxSpaces / unit) * unit;
let factor, niceMin, niceMax, numSpaces;

// Beyond MIN_SPACING floating point numbers being to lose precision
Expand All @@ -33,15 +47,12 @@ function generateTicks(generationOptions, dataRange) {
}

numSpaces = Math.ceil(rmax / spacing) - Math.floor(rmin / spacing);
if (numSpaces > maxNumSpaces) {
if (numSpaces > maxSpaces) {
// If the calculated num of spaces exceeds maxNumSpaces, recalculate it
spacing = niceNum(numSpaces * spacing / maxNumSpaces / unit) * unit;
spacing = niceNum(numSpaces * spacing / maxSpaces / unit) * unit;
}

if (stepSize || isNullOrUndef(precision)) {
// If a precision is not specified, calculate factor based on spacing
factor = Math.pow(10, _decimalPlaces(spacing));
} else {
if (!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;
Expand All @@ -50,23 +61,37 @@ function generateTicks(generationOptions, dataRange) {
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 && minDefined && maxDefined) {
// If very close to our whole number, use it.
if (almostWhole((max - min) / stepSize, spacing / 1000)) {
niceMin = min;
niceMax = max;
}
}

numSpaces = (niceMax - niceMin) / spacing;
// If very close to our rounded value, use it.
if (almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) {
numSpaces = Math.round(numSpaces);
if (minDefined && maxDefined && step && almostWhole((max - min) / step, spacing / 1000)) {
// Case 1: If min, max and stepSize are set and they make an evenly spaced scale use it.
// spacing = step;
// numSpaces = (max - min) / spacing;
numSpaces = Math.min((max - min) / spacing, maxTicks);
spacing = (max - min) / numSpaces;
niceMin = min;
niceMax = max;
} else if (countDefined) {
// Cases 2 & 3, we have a count specified. Handle optional user defined edges to the range.
// Sometimes these are no-ops, but it makes the code a lot clearer
// and when a user defined range is specified, we want the correct ticks
niceMin = minDefined ? min : niceMin;
niceMax = maxDefined ? max : niceMax;
numSpaces = count - 1;
spacing = (niceMax - niceMin) / numSpaces;
} else {
numSpaces = Math.ceil(numSpaces);
// Case 4
numSpaces = (niceMax - niceMin) / spacing;

// If very close to our rounded value, use it.
if (almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) {
numSpaces = Math.round(numSpaces);
} else {
numSpaces = Math.ceil(numSpaces);
}
}

// The spacing will have changed in cases 1, 2, and 3 so the factor cannot be computed
// until this point
factor = Math.pow(10, isNullOrUndef(precision) ? _decimalPlaces(spacing) : precision);
niceMin = Math.round(niceMin * factor) / factor;
niceMax = Math.round(niceMax * factor) / factor;

Expand Down Expand Up @@ -204,7 +229,8 @@ export default class LinearScaleBase extends Scale {
min: opts.min,
max: opts.max,
precision: tickOpts.precision,
stepSize: tickOpts.stepSize
step: tickOpts.stepSize,
count: tickOpts.count,
};
const ticks = generateTicks(numericGeneratorOptions, _addGrace(me, opts.grace));

Expand Down
28 changes: 28 additions & 0 deletions test/fixtures/scale.linear/tick-count-data-limits.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
module.exports = {
description: 'https://github.com/chartjs/Chart.js/issues/4234',
config: {
type: 'line',
data: {
datasets: [{
data: [0, 2, 45, 30]
}],
labels: ['A', 'B', 'C', 'D']
},
options: {
scales: {
y: {
ticks: {
count: 21,
callback: (v) => v.toString(),
}
},
x: {
display: false
}
}
}
},
options: {
spriteText: true
}
};
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 23 additions & 0 deletions test/fixtures/scale.linear/tick-count-min-max-not-aligned.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module.exports = {
description: 'https://github.com/chartjs/Chart.js/issues/4234',
config: {
type: 'line',
options: {
scales: {
y: {
max: 27,
min: -3,
ticks: {
count: 11,
}
},
x: {
display: false
}
}
}
},
options: {
spriteText: true
}
};
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 23 additions & 0 deletions test/fixtures/scale.linear/tick-count-min-max.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module.exports = {
description: 'https://github.com/chartjs/Chart.js/issues/4234',
config: {
type: 'line',
options: {
scales: {
y: {
max: 50,
min: 0,
ticks: {
count: 21,
}
},
x: {
display: false
}
}
}
},
options: {
spriteText: true
}
};
Binary file added test/fixtures/scale.linear/tick-count-min-max.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 23 additions & 0 deletions test/fixtures/scale.linear/tick-step-min-max.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module.exports = {
description: 'https://github.com/chartjs/Chart.js/issues/4234',
config: {
type: 'line',
options: {
scales: {
y: {
max: 27,
min: -3,
ticks: {
stepSize: 3,
}
},
x: {
display: false
}
}
}
},
options: {
spriteText: true
}
};
Binary file added test/fixtures/scale.linear/tick-step-min-max.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions types/index.esm.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2825,6 +2825,11 @@ export type LinearScaleOptions = CartesianScaleOptions & {
* @see https://www.chartjs.org/docs/next/axes/cartesian/linear#step-size
*/
stepSize: number;

/**
* User defined count of ticks
*/
count: number;
};
};

Expand Down Expand Up @@ -3080,6 +3085,11 @@ export type RadialLinearScaleOptions = CoreScaleOptions & {
*/
stepSize: number;

/**
* User defined number of ticks
*/
count: number;

/**
* If true, draw a background behind the tick labels.
* @default true
Expand Down