Skip to content

Commit a6ab245

Browse files
committed
feat(alignTicks): (1) Fix LogScale precision. (2) Tweak align ticks layout. (3) Remove unreasonable clamp in Interval calcNiceExtent, and clarify the definition of _niceExtent.
1 parent ffcc636 commit a6ab245

12 files changed

Lines changed: 479 additions & 298 deletions

src/coord/axisAlignTicks.ts

Lines changed: 126 additions & 74 deletions
Large diffs are not rendered by default.

src/coord/axisHelper.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,7 @@ export function niceScaleExtent(
174174
scale.setExtent(extentInfo.min, extentInfo.max);
175175
scale.calcNiceExtent({
176176
splitNumber: model.get('splitNumber'),
177-
fixMin: extentInfo.minFixed,
178-
fixMax: extentInfo.maxFixed,
177+
fixMinMax: [extentInfo.minFixed, extentInfo.maxFixed],
179178
minInterval: isIntervalOrTime ? model.get('minInterval') : null,
180179
maxInterval: isIntervalOrTime ? model.get('maxInterval') : null
181180
});
@@ -337,6 +336,9 @@ export function getDataDimensionsOnAxis(data: SeriesData, axisDim: string): Dime
337336
return zrUtil.keys(dataDimMap);
338337
}
339338

339+
/**
340+
* FIXME: refactor - merge with `Scale#unionExtentFromData`
341+
*/
340342
export function unionAxisExtentFromData(dataExtent: number[], data: SeriesData, axisDim: string): void {
341343
if (data) {
342344
zrUtil.each(getDataDimensionsOnAxis(data, axisDim), function (dim) {

src/coord/scaleRawExtentInfo.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,11 @@ export interface ScaleRawExtentResult {
3838
min: number;
3939
max: number;
4040

41-
// `minFixed`/`maxFixed` marks that:
42-
// - `xxxAxis.min/max` are user specified, or
43-
// - `minDetermined/maxDetermined` are `true`
44-
// so it should be used directly in the final extent without any other "nice strategy".
41+
// `minFixed`/`maxFixed` is `true` iff:
42+
// - ec option `xxxAxis.min/max` are specified, or
43+
// - `scaleRawExtentResult.minDetermined/maxDetermined` are `true`
44+
// They typically suggest axes to use `scaleRawExtentResult.min/max` directly
45+
// as their bounds, instead of expanding the extent by some "nice strategy".
4546
readonly minFixed: boolean;
4647
readonly maxFixed: boolean;
4748

@@ -51,6 +52,7 @@ export interface ScaleRawExtentResult {
5152

5253
// Mark that the axis should be blank.
5354
readonly isBlank: boolean;
55+
readonly needCrossZero: boolean;
5456
}
5557

5658
export class ScaleRawExtentInfo {
@@ -250,7 +252,8 @@ export class ScaleRawExtentInfo {
250252
|| (isOrdinal && !axisDataLen);
251253

252254
// If data extent modified, need to recalculated to ensure cross zero.
253-
if (this._needCrossZero) {
255+
const needCrossZero = this._needCrossZero;
256+
if (needCrossZero) {
254257
// Axis is over zero and min is not set
255258
if (min > 0 && max > 0 && !minFixed) {
256259
min = 0;
@@ -295,6 +298,7 @@ export class ScaleRawExtentInfo {
295298
minDetermined: minDetermined,
296299
maxDetermined: maxDetermined,
297300
isBlank: isBlank,
301+
needCrossZero: needCrossZero,
298302
};
299303
}
300304

src/scale/Interval.ts

Lines changed: 53 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import Scale, { ScaleGetTicksOpt, ScaleSettingDefault } from './Scale';
2424
import * as helper from './helper';
2525
import {ScaleTick, ParsedAxisBreakList, ScaleDataValue, NullUndefined} from '../util/types';
2626
import { getScaleBreakHelper } from './break';
27-
import { assert } from 'zrender/src/core/util';
27+
import { assert, retrieve2 } from 'zrender/src/core/util';
2828

2929
class IntervalScale<SETTING extends ScaleSettingDefault = ScaleSettingDefault> extends Scale<SETTING> {
3030

@@ -34,13 +34,30 @@ class IntervalScale<SETTING extends ScaleSettingDefault = ScaleSettingDefault> e
3434
// Step is calculated in adjustExtent.
3535
protected _interval: number = 0;
3636
protected _intervalPrecision: number = 2;
37-
// `_intervalCount` effectively specifies the number of "nice segment". This is for special cases,
38-
// such as `alignTo: true` and min max are fixed. In this case, `_interval` may be specified with
39-
// a "not-nice" value and needs to be rounded with `_intervalPrecision` for better appearance. Then
40-
// merely accumulating `_interval` may generate incorrect number of ticks. So `_intervalCount` is
41-
// required to specify the expected tick number.
37+
protected _extentPrecision: number[] = [];
38+
/**
39+
* `_intervalCount` effectively specifies the number of "nice segments". This is for special cases,
40+
* such as `alignTicks: true` and min max are fixed. In this case, `_interval` may be specified with
41+
* a "not-nice" value and needs to be rounded with `_intervalPrecision` for better appearance. Then
42+
* merely accumulating `_interval` may generate incorrect number of ticks due to cumulative errors.
43+
* So `_intervalCount` is required to specify the expected nice ticks number.
44+
* Should ensure `_intervalCount >= -1`,
45+
* where `-1` means no nice tick (e.g., `_extent: [5.2, 5.8], _interval: 1`),
46+
* and `0` means only one nice tick (e.g., `_extent: [5, 5.8], _interval: 1`).
47+
* @see setInterval
48+
*/
4249
private _intervalCount: number | NullUndefined = undefined;
43-
// Should ensure: `_extent[0] <= _niceExtent[0] && _niceExtent[1] <= _extent[1]`
50+
/**
51+
* Should ensure:
52+
* `_extent[0] <= _niceExtent[0] && _niceExtent[1] <= _extent[1]`
53+
* But NOTICE:
54+
* `_niceExtent[0] - _niceExtent[1] <= _interval`, rather than always `< 0`,
55+
* because `_niceExtent` is typically calculated by
56+
* `[ Math.ceil(_extent[0] / _interval) * _interval, Math.floor(_extent[1] / _interval) * _interval ]`.
57+
* e.g., `_extent: [5.2, 5.8]` with interval `1` will get `_niceExtent: [6, 5]`.
58+
* e.g., `_extent: [5, 5.8]` with interval `1` will get `_niceExtent: [5, 5]`.
59+
* @see setInterval
60+
*/
4461
protected _niceExtent: [number, number];
4562

4663

@@ -90,53 +107,43 @@ class IntervalScale<SETTING extends ScaleSettingDefault = ScaleSettingDefault> e
90107
/**
91108
* @final override is DISALLOWED.
92109
*/
93-
setInterval({interval, intervalCount, intervalPrecision, niceExtent}: {
110+
setInterval({interval, intervalCount, intervalPrecision, extentPrecision, niceExtent}: {
94111
interval?: number | NullUndefined;
112+
// See comments of `_intervalCount`.
95113
intervalCount?: number | NullUndefined;
96114
intervalPrecision?: number | NullUndefined;
115+
extentPrecision?: number[] | NullUndefined;
97116
niceExtent?: number[];
98117
}): void {
99-
const intervalCountSpecified = intervalCount != null;
118+
const extent = this._extent;
119+
100120
if (__DEV__) {
101121
assert(interval != null);
102-
if (intervalCountSpecified) {
122+
if (intervalCount != null) {
103123
assert(
104-
intervalCount > 0
124+
intervalCount >= -1
105125
&& intervalPrecision != null
106126
// Do not support intervalCount on axis break currently.
107127
&& !this.hasBreaks()
108128
);
109129
}
110-
}
111-
112-
const extent = this._extent;
113-
if (__DEV__) {
114130
if (niceExtent != null) {
115-
assert(
116-
isFinite(niceExtent[0]) && isFinite(niceExtent[1])
117-
&& extent[0] <= niceExtent[0] && niceExtent[1] <= extent[1]
118-
);
131+
assert(isFinite(niceExtent[0]) && isFinite(niceExtent[1]));
132+
assert(extent[0] <= niceExtent[0] && niceExtent[1] <= extent[1]);
133+
assert(round(niceExtent[0] - niceExtent[1], getPrecision(interval)) <= interval);
119134
}
120135
}
121-
niceExtent = this._niceExtent = niceExtent != null
122-
? niceExtent.slice() as [number, number]
136+
137+
// Set or clear
138+
this._niceExtent =
139+
niceExtent != null ? niceExtent.slice() as [number, number]
123140
// Dropped the auto calculated niceExtent and use user-set extent.
124141
// We assume users want to set both interval and extent to get a better result.
125142
: extent.slice() as [number, number];
126-
127143
this._interval = interval;
128-
129-
if (!intervalCountSpecified) {
130-
// This is for cases of "nice" interval.
131-
this._intervalCount = undefined; // Clear
132-
this._intervalPrecision = helper.getIntervalPrecision(interval);
133-
}
134-
else {
135-
// This is for cases of "not-nice" interval, typically min max are fixed and
136-
// axis alignment is required.
137-
this._intervalCount = intervalCount;
138-
this._intervalPrecision = intervalPrecision;
139-
}
144+
this._intervalCount = intervalCount;
145+
this._intervalPrecision = retrieve2(intervalPrecision, helper.getIntervalPrecision(interval));
146+
this._extentPrecision = extentPrecision || [];
140147
}
141148

142149
/**
@@ -191,13 +198,17 @@ class IntervalScale<SETTING extends ScaleSettingDefault = ScaleSettingDefault> e
191198
;
192199
niceTickIdx++
193200
) {
201+
// Consider case `_extent: [5.2, 5.8], _niceExtent: [6, 5], interval: 1`,
202+
// `_intervalCount` makes sense iff `-1`.
203+
// Consider case `_extent: [5, 5.8], _niceExtent: [5, 5], interval: 1`,
204+
// `_intervalCount` makes sense iff `0`.
194205
if (intervalCount == null) {
195-
if (tick > niceTickExtent[1]) {
206+
if (tick > niceTickExtent[1] || !isFinite(tick) || !isFinite(niceTickExtent[1])) {
196207
break;
197208
}
198209
}
199210
else {
200-
if (niceTickIdx > intervalCount) { // ticks number should be `intervalCount + 1`
211+
if (niceTickIdx > intervalCount) { // nice ticks number should be `intervalCount + 1`
201212
break;
202213
}
203214
// Consider cumulative error, especially caused by rounding, the last nice
@@ -398,12 +409,13 @@ class IntervalScale<SETTING extends ScaleSettingDefault = ScaleSettingDefault> e
398409
calcNiceExtent(opt: {
399410
splitNumber: number, // By default 5.
400411
// Do not modify the original extent[0]/extent[1] except for an invalid extent.
401-
fixMin?: boolean,
402-
fixMax?: boolean,
412+
fixMinMax?: boolean[], // [fixMin, fixMax]
403413
minInterval?: number,
404414
maxInterval?: number
405415
}): void {
406-
let extent = helper.intervalScaleEnsureValidExtent(this._extent, opt);
416+
const fixMinMax = opt.fixMinMax || [];
417+
418+
let extent = helper.intervalScaleEnsureValidExtent(this._extent, fixMinMax);
407419

408420
this._innerSetExtent(extent[0], extent[1]);
409421
extent = this._extent.slice() as [number, number];
@@ -412,10 +424,10 @@ class IntervalScale<SETTING extends ScaleSettingDefault = ScaleSettingDefault> e
412424
const interval = this._interval;
413425
const intervalPrecition = this._intervalPrecision;
414426

415-
if (!opt.fixMin) {
427+
if (!fixMinMax[0]) {
416428
extent[0] = round(mathFloor(extent[0] / interval) * interval, intervalPrecition);
417429
}
418-
if (!opt.fixMax) {
430+
if (!fixMinMax[1]) {
419431
extent[1] = round(mathCeil(extent[1] / interval) * interval, intervalPrecition);
420432
}
421433
this._innerSetExtent(extent[0], extent[1]);

0 commit comments

Comments
 (0)