Skip to content

Commit 7c3a412

Browse files
authored
Add includeBounds option for cartesian ticks (#9020)
* Add includeBounds option for cartesian ticks * Types, test and fix * lint * Improve linear tick min/max collision detection * Update comments
1 parent 137b51d commit 7c3a412

25 files changed

+239
-14
lines changed

docs/axes/cartesian/_common_ticks.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Namespace: `options.scales[scaleId].ticks`
99
| `sampleSize` | `number` | `ticks.length` | The number of ticks to examine when deciding how many labels will fit. Setting a smaller value will be faster, but may be less accurate when there is large variability in label length.
1010
| `autoSkip` | `boolean` | `true` | If true, automatically calculates how many labels can be shown and hides labels accordingly. Labels will be rotated up to `maxRotation` before skipping any. Turn `autoSkip` off to show all labels no matter what.
1111
| `autoSkipPadding` | `number` | `3` | Padding between the ticks on the horizontal axis when `autoSkip` is enabled.
12+
| `includeBounds` | `boolean` | `true` | Should the defined `min` and `max` values be presented as ticks even if they are not "nice".
1213
| `labelOffset` | `number` | `0` | Distance in pixels to offset the label from the centre point of the tick (in the x-direction for the x-axis, and the y-direction for the y-axis). *Note: this can cause labels at the edges to be cropped by the edge of the canvas*
1314
| `maxRotation` | `number` | `50` | Maximum rotation for tick labels when rotating to condense labels. Note: Rotation doesn't occur until necessary. *Note: Only applicable to horizontal scales.*
1415
| `minRotation` | `number` | `0` | Minimum rotation for tick labels. *Note: Only applicable to horizontal scales.*

src/scales/scale.linearbase.js

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {isNullOrUndef} from '../helpers/helpers.core';
2-
import {almostEquals, almostWhole, niceNum, _decimalPlaces, _setMinAndMaxByKey, sign} from '../helpers/helpers.math';
2+
import {almostEquals, almostWhole, niceNum, _decimalPlaces, _setMinAndMaxByKey, sign, toRadians} from '../helpers/helpers.math';
33
import Scale from '../core/core.scale';
44
import {formatNumber} from '../helpers/helpers.intl';
55

@@ -29,7 +29,7 @@ function generateTicks(generationOptions, dataRange) {
2929
// for details.
3030

3131
const MIN_SPACING = 1e-14;
32-
const {step, min, max, precision, count, maxTicks, maxDigits, horizontal} = generationOptions;
32+
const {step, min, max, precision, count, maxTicks, maxDigits, includeBounds} = generationOptions;
3333
const unit = step || 1;
3434
const maxSpaces = maxTicks - 1;
3535
const {min: rmin, max: rmax} = dataRange;
@@ -97,13 +97,17 @@ function generateTicks(generationOptions, dataRange) {
9797

9898
let j = 0;
9999
if (minDefined) {
100-
ticks.push({value: min});
101-
// If the niceMin is smaller or equal to min, skip it
102-
if (niceMin <= min) {
103-
j++;
104-
}
105-
// If the next nice tick is close to min, skip that too
106-
if (almostEquals(Math.round((niceMin + j * spacing) * factor) / factor, min, minSpacing * (horizontal ? ('' + min).length : 1))) {
100+
if (includeBounds && niceMin !== min) {
101+
ticks.push({value: min});
102+
103+
if (niceMin < min) {
104+
j++; // Skip niceMin
105+
}
106+
// If the next nice tick is close to min, skip it
107+
if (almostEquals(Math.round((niceMin + j * spacing) * factor) / factor, min, relativeLabelSize(min, minSpacing, generationOptions))) {
108+
j++;
109+
}
110+
} else if (niceMin < min) {
107111
j++;
108112
}
109113
}
@@ -112,20 +116,29 @@ function generateTicks(generationOptions, dataRange) {
112116
ticks.push({value: Math.round((niceMin + j * spacing) * factor) / factor});
113117
}
114118

115-
if (maxDefined) {
116-
// If the previous tick is close to max, replace it with max, else add max
117-
if (almostEquals(ticks[ticks.length - 1].value, max, minSpacing * (horizontal ? ('' + max).length : 1))) {
119+
if (maxDefined && includeBounds && niceMax !== max) {
120+
// If the previous tick is too close to max, replace it with max, else add max
121+
if (almostEquals(ticks[ticks.length - 1].value, max, relativeLabelSize(max, minSpacing, generationOptions))) {
118122
ticks[ticks.length - 1].value = max;
119123
} else {
120124
ticks.push({value: max});
121125
}
122-
} else {
126+
} else if (!maxDefined || niceMax === max) {
123127
ticks.push({value: niceMax});
124128
}
125129

126130
return ticks;
127131
}
128132

133+
function relativeLabelSize(value, minSpacing, {horizontal, minRotation}) {
134+
const rot = toRadians(minRotation);
135+
const useLength = (horizontal && minRotation <= 45) || (!horizontal && minRotation >= 45);
136+
const l = useLength ? minSpacing * ('' + value).length : 0;
137+
const sin = Math.sin(rot);
138+
const cos = Math.cos(rot);
139+
return horizontal ? cos * l + sin * minSpacing : sin * l + cos * minSpacing;
140+
}
141+
129142
export default class LinearScaleBase extends Scale {
130143

131144
constructor(cfg) {
@@ -232,7 +245,9 @@ export default class LinearScaleBase extends Scale {
232245
step: tickOpts.stepSize,
233246
count: tickOpts.count,
234247
maxDigits: me._maxDigits(),
235-
horizontal: me.isHorizontal()
248+
horizontal: me.isHorizontal(),
249+
minRotation: tickOpts.minRotation || 0,
250+
includeBounds: tickOpts.includeBounds !== false
236251
};
237252
const dataRange = me._range || me;
238253
const ticks = generateTicks(numericGeneratorOptions, dataRange);

0 commit comments

Comments
 (0)