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';
2121import { NullUndefined } from '../util/types';
2222import type Axis from './Axis';
23- import type Scale from '../scale/Scale';
2423import { isOrdinalScale } from '../scale/helper';
2524import { isNullableNumberFinite, mathAbs, mathMax } from '../util/number';
2625import {
@@ -34,15 +33,15 @@ import type SeriesModel from '../model/Series';
3433const FALLBACK_BAND_WIDTH_RATIO = 0.8;
3534
3635export 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- */
122133function 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- */
150155function 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}
0 commit comments