Skip to content

Commit 40b77b4

Browse files
committed
fix: (1) Fix time axis reach safeLimit when value scale is milisecond. (2) Fix regression - containShape should be word properly in single value case. And simply the code.
1 parent 2064963 commit 40b77b4

19 files changed

Lines changed: 384 additions & 293 deletions

src/chart/helper/axisSnippets.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,16 @@ export function createBandWidthBasedAxisContainShapeHandler(axisStatKey: AxisSta
4343
// |----|----------------------|--|
4444
// minValNew minValOld maxValOld maxValNew
4545
// (Note: `|---|` above represents "pixels" rather than "data".)
46+
// Regarding single data item case:
47+
// (See test/axis-extreme2.html)
48+
// - TimeScale and OrdinalScale do not need to handle it, since they have
49+
// ensured single data item is laid out at the middle of the axis.
50+
// - IntervalScale and LogScale ...................
4651

47-
return function (axis, scale, ecModel) {
52+
return function (axis, ecModel) {
4853
const bandWidthResult = calcBandWidth(axis, {fromStat: {key: axisStatKey}});
49-
const invRatio = bandWidthResult.invRatio;
50-
if (isNullableNumberFinite(invRatio) && isNullableNumberFinite(bandWidthResult.w)) {
51-
return [-bandWidthResult.w / 2 * invRatio, bandWidthResult.w / 2 * invRatio];
54+
if (isNullableNumberFinite(bandWidthResult.w2)) {
55+
return [-bandWidthResult.w2 / 2, bandWidthResult.w2 / 2];
5256
}
5357
};
5458
}

src/component/dataZoom/AxisProxy.ts

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,7 @@ interface MinMaxSpan {
4848

4949
export interface AxisProxyWindow {
5050
// NOTE: May include non-effective portion.
51-
value: number[];
52-
// Although `dataZoom` effectively calculates based on "noZoomMapMM" (where
53-
// containShape is considered), `dataZoom` labels always show `noZoomEffMM`.
54-
noZoomEffMM: ScaleRawExtentResultForZoom['noZoomEffMM'];
51+
value: ScaleRawExtentResultForZoom;
5552
percent: number[];
5653
// Percent invert from "value window", which may be slightly different from "percent window" due to some
5754
// handling such as rounding. The difference may be magnified in cases like "alignTicks", so we use
@@ -151,8 +148,6 @@ class AxisProxy {
151148

152149
/**
153150
* [CAVEAT] Keep this method pure, so that it can be called multiple times.
154-
*
155-
* Only calculate by given range and cumulative series data extent, do not change anything.
156151
*/
157152
calculateDataWindow(
158153
opt: {
@@ -162,7 +157,7 @@ class AxisProxy {
162157
endValue?: number | string | Date
163158
}
164159
): AxisProxyWindow {
165-
const {noZoomMapMM: dataExtent, noZoomEffMM} = this._extent;
160+
const dataExtent = this._extent;
166161
const axis = this.getAxisModel().axis;
167162
const scale = axis.scale;
168163
const dataZoomModel = this._dataZoomModel;
@@ -330,7 +325,6 @@ class AxisProxy {
330325

331326
return {
332327
value: valueWindow,
333-
noZoomEffMM: noZoomEffMM.slice(),
334328
percent: percentWindow,
335329
percentInverted: percentInvertedWindow,
336330
valuePrecision: precision,
@@ -376,10 +370,6 @@ class AxisProxy {
376370
}
377371
const {percent, value} = this._window = this.calculateDataWindow(opt);
378372

379-
// For value axis, if min/max/scale are not set, we just use the extent obtained
380-
// by series data, which may be a little different from the extent calculated by
381-
// `axisHelper.getScaleExtent`. But the different just affects the experience a
382-
// little when zooming. So it will not be fixed until some users require it strongly.
383373
if (percent[0] !== 0) {
384374
rawExtentInfo.setZoomMM(0, value[0]);
385375
}
@@ -487,7 +477,7 @@ class AxisProxy {
487477
private _updateMinMaxSpan() {
488478
const minMaxSpan = this._minMaxSpan = {} as MinMaxSpan;
489479
const dataZoomModel = this._dataZoomModel;
490-
const dataExtent = this._extent.noZoomMapMM;
480+
const dataExtent = this._extent;
491481

492482
each(['min', 'max'], function (minMax) {
493483
let percentSpan = dataZoomModel.get(minMax + 'Span' as 'minSpan' | 'maxSpan');

src/component/dataZoom/SliderZoomView.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import * as eventTool from 'zrender/src/core/event';
2222
import * as graphic from '../../util/graphic';
2323
import * as throttle from '../../util/throttle';
2424
import DataZoomView from './DataZoomView';
25-
import { linearMap, asc, parsePercent, round, mathMax, mathMin } from '../../util/number';
25+
import { linearMap, asc, parsePercent, round } from '../../util/number';
2626
import * as layout from '../../util/layout';
2727
import sliderMove from '../helper/sliderMove';
2828
import GlobalModel from '../../model/Global';
@@ -1143,10 +1143,7 @@ function formatLabel(
11431143
// Do not display values out of `SCALE_EXTENT_KIND_EFFECTIVE` - generally they are meaningless.
11441144
// For example, `scaleExtent[0]` is often `0`, and negative values are unlikely to be meaningful.
11451145
// That is, "nice" expansion and `SCALE_EXTENT_KIND_MAPPING` expansion are always not display in labels.
1146-
const value = (extentIdx ? mathMin : mathMax)(
1147-
window.value[extentIdx],
1148-
window.noZoomEffMM[extentIdx],
1149-
);
1146+
const value = window.value[extentIdx];
11501147

11511148
const valueStr = (value == null || isNaN(value))
11521149
? ''

src/coord/axisAlignTicks.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ export function scaleCalcAlign(
192192

193193
function loopIncreaseInterval(cb: () => boolean) {
194194
// Typically this loop runs less than 5 times. But we still
195-
// use a safeguard for future changes.
195+
// use a fail-safe for future changes.
196196
const LOOP_MAX = 50;
197197
let loopGuard = 0;
198198
for (; loopGuard < LOOP_MAX; loopGuard++) {
@@ -304,7 +304,7 @@ export function scaleCalcAlign(
304304
// - In LogScale, series data are usually either all > 1 or all < 1, rather than both,
305305
// that is, logarithm result is typically either all positive or all negative.
306306
let moreCountPair: number[];
307-
const mayEnhanceZero = targetExtentInfo.needCrossZero || isTargetLogScale;
307+
const mayEnhanceZero = targetExtentInfo.incl0 || isTargetLogScale;
308308
// `bounds < 0` or `bounds > 0` may require more complex handling, so we only auto handle
309309
// `bounds === 0`.
310310
if (mayEnhanceZero && targetExtent[0] === 0) {

src/coord/axisBand.ts

Lines changed: 44 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,9 @@
1717
* under the License.
1818
*/
1919

20-
import { assert, each } from 'zrender/src/core/util';
20+
import { assert, each, retrieve2 } from 'zrender/src/core/util';
2121
import { NullUndefined } from '../util/types';
2222
import type Axis from './Axis';
23-
import type Scale from '../scale/Scale';
2423
import { isOrdinalScale } from '../scale/helper';
2524
import { isNullableNumberFinite, mathAbs, mathMax } from '../util/number';
2625
import {
@@ -34,15 +33,15 @@ import type SeriesModel from '../model/Series';
3433
const FALLBACK_BAND_WIDTH_RATIO = 0.8;
3534

3635
export type AxisBandWidthResult = {
37-
// The result `bandWidth`. In pixel.
36+
// bandWidth in pixel.
3837
// Never be null/undefined.
39-
// May be NaN if no meaningfull `bandWidth`. But it's unlikely to be NaN, since edge cases
38+
// May be NaN if no meaningfull value. But it's unlikely to be NaN, since edge cases
4039
// are handled internally whenever possible.
4140
w: number;
42-
// This is a ratio from pixel span to data span.
43-
// Note that the conversion can not be performed if it is not valid, typically when only
44-
// one or no series data item exists on the axis.
45-
invRatio?: number | NullUndefined;
41+
// bandWidth in data space.
42+
// Never be null/undefined.
43+
// May be NaN if no meaningfull value, typically when no valid series data item.
44+
w2: number;
4645
};
4746

4847
/**
@@ -86,16 +85,36 @@ export function calcBandWidth(
8685
opt?: CalculateBandWidthOpt | NullUndefined
8786
): AxisBandWidthResult {
8887
opt = opt || {};
89-
const out: AxisBandWidthResult = {w: NaN};
88+
const out: AxisBandWidthResult = {w: NaN, w2: NaN};
9089
const scale = axis.scale;
9190
const fromStat = opt.fromStat;
9291
const min = opt.min;
9392

93+
// [BAND_WIDTH_USED_SCALE_LINEAR_SPAN]
94+
// - Band width should always respect to the currently specified extent, and `SCALE_EXTENT_KIND_MAPPING`
95+
// should be used if specified.
96+
// Otherwise, the result may incorrect, especially when data count is small.
97+
// For example, when "containShape" is calculating, no `SCALE_EXTENT_KIND_MAPPING` is set, so here only
98+
// `SCALE_EXTENT_KIND_EFFECTIVE` is returned, say, `[3, 5]`, based on which a `SCALE_EXTENT_KIND_MAPPING`
99+
// is calculated, say `[2.5, 5.5]` (expanded by `0.5`). Then when rendering, that `SCALE_EXTENT_KIND_MAPPING`
100+
// is returned here.
101+
// See AXIS_CONTAIN_SHAPE_COMMON_STRATEGY for more details.
102+
// - The span should be in the linear space (typically, the innermost space).
103+
// - We use the scale extent after being zoommed and `intervalScaleEnsureValidExtent`-ish applied and
104+
// "nice"/"align" applied, because:
105+
// - For OrdinalScale, fine;
106+
// - For numeric scale, `scaleLinearSpan` is normally not used for a consistent result when `dataZoom`
107+
// is applied, but used when none or single data item case.
108+
const scaleLinearSpan = getScaleLinearSpanForMapping(scale);
109+
const axisExtent = axis.getExtent();
110+
// Always use a new pxSpan because it may be changed in `grid` contain label calculation.
111+
const pxSpan = mathAbs(axisExtent[1] - axisExtent[0]);
112+
94113
if (isOrdinalScale(scale)) {
95-
calcBandWidthForCategoryAxis(out, axis, scale);
114+
calcBandWidthForCategoryAxis(out, axis, scaleLinearSpan, pxSpan);
96115
}
97116
else if (fromStat) {
98-
calcBandWidthForNumericAxis(out, axis, scale, fromStat);
117+
calcBandWidthForNumericAxis(out, axis, scaleLinearSpan, pxSpan, fromStat);
99118
}
100119
else if (min == null) {
101120
if (__DEV__) {
@@ -111,52 +130,36 @@ export function calcBandWidth(
111130
return out;
112131
}
113132

114-
/**
115-
* Only reasonable on 'category'.
116-
*
117-
* It can be used as a fallback, as it does not produce a significant negative impact
118-
* on non-category axes.
119-
*
120-
* @see CalculateBandWidthOpt
121-
*/
122133
function calcBandWidthForCategoryAxis(
123134
out: AxisBandWidthResult,
124135
axis: Axis,
125-
scale: Scale
136+
scaleLinearSpan: number,
137+
pxSpan: number,
126138
): void {
127-
const axisExtent = axis.getExtent();
128-
const pxSpan = mathAbs(axisExtent[1] - axisExtent[0]);
129-
// See the reason on BAND_WIDTH_USED_LINEAR_SCALE_SPAN.
130-
const linearScaleSpan = getScaleLinearSpanForMapping(scale);
131139
const onBand = axis.onBand;
132140

133-
let len = linearScaleSpan + (onBand ? 1 : 0);
141+
let len = scaleLinearSpan + (onBand ? 1 : 0);
134142
// Fix #2728, avoid NaN when only one data.
135143
len === 0 && (len = 1);
136144

137145
out.w = pxSpan / len;
138146
// NOTE:
139-
// - When `linearScaleSpan === 0`, no need to expand extent.
147+
// - When `scaleLinearSpan === 0`, no need to expand extent.
140148
// - `onBand: true` (`boundaryGap: true`) does not need to support `containShape`,
141149
// thereby no `invRatio`.
142-
if (!onBand && linearScaleSpan && pxSpan) {
143-
out.invRatio = linearScaleSpan / pxSpan;
150+
if (!onBand && scaleLinearSpan && pxSpan) {
151+
out.w2 = out.w * scaleLinearSpan / pxSpan;
144152
}
145153
}
146154

147-
/**
148-
* @see CalculateBandWidthOpt
149-
*/
150155
function calcBandWidthForNumericAxis(
151156
out: AxisBandWidthResult,
152157
axis: Axis,
153-
scale: Scale,
158+
scaleLinearSpan: number,
159+
pxSpan: number,
154160
fromStat: CalculateBandWidthOpt['fromStat'],
155161
): void {
156162

157-
let bandWidth = NaN;
158-
let invRatio: number | NullUndefined;
159-
160163
if (__DEV__) {
161164
assert(fromStat);
162165
}
@@ -184,36 +187,16 @@ function calcBandWidthForNumericAxis(
184187
}
185188
);
186189

187-
const axisExtent = axis.getExtent();
188-
// Always use a new pxSpan because it may be changed in `grid` contain label calculation.
189-
const pxSpan = mathAbs(axisExtent[1] - axisExtent[0]);
190-
191-
// [BAND_WIDTH_USED_LINEAR_SCALE_SPAN]
192-
// Here we deliberately use `getScaleLinearSpanForMapping` rather than `scale.getExtent()`,
193-
// because band width should always respect to the currently specified extent (e.g., specified by
194-
// `calcContainShape`). Otherwise, the result may incorrect, especially when data count is small.
195-
// For example, when "containShape" is calculating, no `SCALE_EXTENT_KIND_MAPPING` is set, so here only
196-
// `SCALE_EXTENT_KIND_EFFECTIVE` is returned, say, `[3, 5]`, based on which a `SCALE_EXTENT_KIND_MAPPING`
197-
// is calculated, say `[2.5, 5.5]` (expanded by `0.5`). Then when rendering, that `SCALE_EXTENT_KIND_MAPPING`
198-
// is returned here.
199-
// See AXIS_CONTAIN_SHAPE_COMMON_STRATEGY for more details.
200-
const linearScaleSpan = getScaleLinearSpanForMapping(scale);
201-
202-
// `linearScaleSpan` may be `0` or `Infinity` or `NaN`, since normalizers like
190+
// `scaleLinearSpan` may be `0` or `Infinity` or `NaN`, since normalizers like
203191
// `intervalScaleEnsureValidExtent` may not have been called yet.
204-
if (isNullableNumberFinite(linearScaleSpan) && linearScaleSpan > 0
192+
if (isNullableNumberFinite(scaleLinearSpan) && scaleLinearSpan > 0
205193
&& isNullableNumberFinite(bandWidthInData)
206194
) {
207-
// NOTE: even when the `bandWidth` is far smaller than `1`, we should still preserve the
208-
// precision, because it is required to convert back to data space by `invRatio` for
209-
// displaying of zoomed ticks and band.
210-
bandWidth = pxSpan / linearScaleSpan * bandWidthInData;
211-
invRatio = linearScaleSpan / pxSpan;
195+
out.w = pxSpan / scaleLinearSpan * bandWidthInData;
196+
out.w2 = bandWidthInData;
212197
}
213198
else if (allSingularOrNone) {
214-
bandWidth = pxSpan * FALLBACK_BAND_WIDTH_RATIO;
199+
out.w = pxSpan * FALLBACK_BAND_WIDTH_RATIO;
200+
out.w2 = out.w * scaleLinearSpan / pxSpan;
215201
}
216-
217-
out.w = bandWidth;
218-
out.invRatio = invRatio;
219202
}

src/coord/axisModelCommonMixin.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ interface AxisModelCommonMixin<Opt extends AxisBaseOption> extends Pick<Model<Op
3030
// eslint-disable-next-line @typescript-eslint/no-unused-vars
3131
class AxisModelCommonMixin<Opt extends AxisBaseOption> {
3232

33-
getNeedCrossZero(): boolean {
33+
needIncludeZero(): boolean {
3434
return !(this.option as ValueAxisBaseOption).scale;
3535
}
3636

src/coord/axisNiceTicks.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ export function scaleCalcNice2(
266266
});
267267

268268
if (axis && ecModel) {
269-
adoptScaleExtentKindMapping(axis, scale, rawExtentResult);
269+
adoptScaleExtentKindMapping(axis, scale, rawExtentResult, ecModel);
270270
}
271271

272272
if (__DEV__) {

src/coord/cartesian/Grid.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ import {
5959
createCartesianAxisViewCommonPartBuilder,
6060
updateCartesianAxisViewCommonPartBuilder,
6161
} from './cartesianAxisHelper';
62-
import { AxisBaseOptionCommon, CategoryAxisBaseOption, NumericAxisBaseOptionCommon } from '../axisCommonTypes';
62+
import { AxisBaseOptionCommon, NumericAxisBaseOptionCommon } from '../axisCommonTypes';
6363
import { AxisBaseModel } from '../AxisBaseModel';
6464
import { isIntervalOrLogScale, isOrdinalScale } from '../../scale/helper';
6565
import { scaleCalcAlign } from '../axisAlignTicks';

0 commit comments

Comments
 (0)