Skip to content

Commit 8ddaa5c

Browse files
committed
Fix category axis min/max ticks/labels show hide when interval > 0. Remove unnecessary code. Fix relevant TS.
1 parent 7a9eda4 commit 8ddaa5c

22 files changed

Lines changed: 325 additions & 165 deletions

src/chart/bar/BaseBarSeries.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ import SeriesData from '../../data/SeriesData';
3333
import {dimPermutations} from '../../component/marker/MarkAreaView';
3434
import { each } from 'zrender/src/core/util';
3535
import type Axis2D from '../../coord/cartesian/Axis2D';
36+
import type Model from '../../model/Model';
37+
import { CategoryAxisBaseOption } from '../../coord/axisCommonTypes';
3638

3739

3840
export interface BaseBarSeriesOption<StateOption, ExtraStateOption extends StatesMixinBase = DefaultStatesMixin>
@@ -106,7 +108,8 @@ class BaseBarSeriesModel<Opts extends BaseBarSeriesOption<unknown> = BaseBarSeri
106108
// If axis type is category, use tick coords instead
107109
if (axis.type === 'category' && dims != null) {
108110
const tickCoords = axis.getTicksCoords();
109-
const alignTicksWithLabel = axis.getTickModel().get('alignWithLabel');
111+
const alignTicksWithLabel = (axis.getTickModel() as Model<CategoryAxisBaseOption['axisTick']>)
112+
.get('alignWithLabel');
110113

111114
let targetTickId = clampData[idx];
112115
// The index of rightmost tick of markArea is 1 larger than x1/y1 index

src/chart/helper/SymbolDraw.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,6 @@ class SymbolDraw {
195195
return data.getItemLayout(idx);
196196
};
197197

198-
199198
// There is no oldLineData only when first rendering or switching from
200199
// stream mode to normal mode, where previous elements should be removed.
201200
if (!oldData) {

src/chart/line/LineView.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,9 @@ function getIsIgnoreFunc(
400400
zrUtil.each(categoryAxis.getViewLabels(), function (labelItem) {
401401
const ordinalNumber = (categoryAxis.scale as OrdinalScale)
402402
.getRawOrdinalNumber(labelItem.tick.value);
403-
labelMap[ordinalNumber] = 1;
403+
if (!labelItem.tick.offInterval) {
404+
labelMap[ordinalNumber] = 1;
405+
}
404406
});
405407

406408
return function (dataIndex: number) {

src/component/axis/AngleAxisView.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ function fixAngleOverlap(list: TickCoord[]) {
7272
}
7373
}
7474

75-
type TickCoord = ReturnType<AngleAxis['getTicksCoords']>[number];
75+
type TickCoord = Pick<ReturnType<AngleAxis['getTicksCoords']>[number], 'coord'>;
7676
type TickLabel = ReturnType<AngleAxis['getViewLabels']>[number] & {
7777
coord: number
7878
};

src/component/axis/AxisBuilder.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1148,7 +1148,9 @@ function fixMinMaxLabelShow(
11481148
deal(showMaxLabelOption, labelsLen - 1, labelsLen - 2);
11491149
}
11501150

1151-
// PENDING: Is it necessary to display a tick while the corresponding label is ignored?
1151+
// Under default settings, it is visually odd to display a tick without its label,
1152+
// since ticks extend from axis line towards labels and serve as visual guide.
1153+
// PENDING: Is there opposite cases?
11521154
function syncLabelIgnoreToMajorTicks(
11531155
cfg: AxisBuilderCfgDetermined,
11541156
labelLayoutList: LabelLayoutData[],

src/component/axis/axisSplitHelper.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ export function rectCoordAxisBuildSplitArea(
5353

5454
const ticksCoords = axis.getTicksCoords({
5555
tickModel: splitAreaModel,
56-
clamp: true,
5756
breakTicks: 'none',
5857
pruneByBreak: 'preserve_extent_bound',
5958
});

src/coord/Axis.ts

Lines changed: 84 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,24 @@
1818
*/
1919

2020
import {each, map} from 'zrender/src/core/util';
21-
import {linearMap, round} from '../util/number';
21+
import {linearMap} from '../util/number';
2222
import {
2323
createAxisTicks,
2424
createAxisLabels,
2525
calculateCategoryInterval,
2626
AxisLabelsComputingContext,
2727
AxisTickLabelComputingKind,
2828
createAxisLabelsComputingContext,
29+
AxisLabelInfoDetermined,
2930
} from './axisTickLabelBuilder';
3031
import Scale, { ScaleGetTicksOpt } from '../scale/Scale';
3132
import { DimensionName, NullUndefined, ScaleDataValue, ScaleTick } from '../util/types';
3233
import OrdinalScale from '../scale/Ordinal';
3334
import Model from '../model/Model';
34-
import { AxisBaseOption, CategoryAxisBaseOption, OptionAxisType } from './axisCommonTypes';
35+
import {
36+
AxisBaseOption, AxisTickOptionUnion, CategoryAxisBaseOption,
37+
CategoryTickLabelSplitBuildingOption, OptionAxisType
38+
} from './axisCommonTypes';
3539
import { AxisBaseModel } from './AxisBaseModel';
3640
import { isOrdinalScale } from '../scale/helper';
3741
import { calcBandWidth } from './axisBand';
@@ -40,8 +44,7 @@ const NORMALIZED_EXTENT = [0, 1] as [number, number];
4044

4145
export interface AxisTickCoord {
4246
coord: number;
43-
// That is `scaleTick.value`.
44-
tickValue?: ScaleTick['value'];
47+
tickValue: ScaleTick['value'];
4548
// `true` if onBand fixed.
4649
onBand?: boolean;
4750
}
@@ -159,42 +162,46 @@ class Axis {
159162
* `axis.getTicksCoords` considers `onBand`, which is used by
160163
* `boundaryGap:true` of category axis and splitLine and splitArea.
161164
* @param opt.tickModel default: axis.model.getModel('axisTick')
162-
* @param opt.clamp If `true`, the first and the last
163-
* tick must be at the axis end points. Otherwise, clip ticks
164-
* that outside the axis extent.
165165
*/
166166
getTicksCoords(opt?: {
167-
tickModel?: Model,
168-
clamp?: boolean,
167+
tickModel?: Model<CategoryTickLabelSplitBuildingOption>,
169168
breakTicks?: ScaleGetTicksOpt['breakTicks'],
170169
pruneByBreak?: ScaleGetTicksOpt['pruneByBreak']
171170
}): AxisTickCoord[] {
172171
opt = opt || {};
173-
174172
const tickModel = opt.tickModel || this.getTickModel();
173+
175174
const result = createAxisTicks(this, tickModel as AxisBaseModel, {
176175
breakTicks: opt.breakTicks,
177176
pruneByBreak: opt.pruneByBreak,
178177
});
179-
const ticksCoords = map(result.ticks, function (tick) {
178+
const preTicksCoords = map(result.ticks, function (tick) {
180179
const tickValue = tick.value;
181180
return {
182181
coord: this.dataToCoord(
183182
isOrdinalScale(this.scale)
184183
? (this.scale as OrdinalScale).getRawOrdinalNumber(tickValue)
185184
: tickValue
186185
),
187-
tickValue,
186+
tick,
188187
};
189188
}, this);
190189

191-
const alignWithLabel = tickModel.get('alignWithLabel');
190+
const alignWithLabel = (tickModel as Model<CategoryAxisBaseOption['axisTick']>).get('alignWithLabel');
192191

193-
fixOnBandTicksCoords(
194-
this, ticksCoords, alignWithLabel, opt.clamp
192+
const onBandModified = fixOnBandTicksCoords(
193+
this,
194+
preTicksCoords,
195+
alignWithLabel,
195196
);
196197

197-
return ticksCoords;
198+
return map(preTicksCoords, function (item) {
199+
return {
200+
coord: item.coord,
201+
tickValue: item.tick.value,
202+
onBand: onBandModified,
203+
};
204+
});
198205
}
199206

200207
getMinorTicksCoords(): AxisTickCoord[][] {
@@ -214,7 +221,7 @@ class Axis {
214221
return map(minorTicksGroup, function (minorTick) {
215222
return {
216223
coord: this.dataToCoord(minorTick),
217-
tickValue: minorTick
224+
tickValue: minorTick,
218225
};
219226
}, this);
220227
}, this);
@@ -223,7 +230,7 @@ class Axis {
223230

224231
getViewLabels(
225232
ctx?: AxisLabelsComputingContext
226-
): ReturnType<typeof createAxisLabels>['labels'] {
233+
): AxisLabelInfoDetermined[] {
227234
ctx = ctx || createAxisLabelsComputingContext(AxisTickLabelComputingKind.determine);
228235
return createAxisLabels(this, ctx).labels;
229236
}
@@ -239,7 +246,7 @@ class Axis {
239246
* In GL, this method may be overridden to:
240247
* `axisModel.getModel('axisTick', grid3DModel.getModel('axisTick'));`
241248
*/
242-
getTickModel(): Model {
249+
getTickModel(): Model<AxisTickOptionUnion> {
243250
return this.model.getModel('axisTick');
244251
}
245252

@@ -275,79 +282,80 @@ function makeExtentWithBands(axis: Axis): number[] {
275282
const margin = size / (axis.scale as OrdinalScale).count() / 2;
276283
extent[0] += margin;
277284
extent[1] -= margin;
285+
278286
}
279287
return extent;
280288
}
281289

282-
// If axis has labels [1, 2, 3, 4]. Bands on the axis are
283-
// |---1---|---2---|---3---|---4---|.
284-
// So the displayed ticks and splitLine/splitArea should between
285-
// each data item, otherwise cause misleading (e.g., split tow bars
286-
// of a single data item when there are two bar series).
287-
// Also consider if tickCategoryInterval > 0 and onBand, ticks and
288-
// splitLine/spliteArea should layout appropriately corresponding
289-
// to displayed labels. (So we should not use `getBandWidth` in this
290-
// case).
290+
/**
291+
* `axis.onBand: true` (i.e., `boundaryGap: true` in ec option) and `CategoryTickLabelSplitIntervalOption`
292+
* affects `axisTick`/`axisLabel`/`splitLine`/`splitArea`.
293+
*
294+
* Currently, the visual result is best only when `axisTick/splitLine/splitArea.interval === 0`.
295+
* The typical case is:
296+
* |---|---|---| <= This is the input `preTicksCoords`
297+
* 0 1 2 3 (having been added half band width by `makeExtentWithBands`).
298+
* |---|---|---|---| <= This is the result.
299+
* 0 1 2 3
300+
*
301+
* When `interval > 0`, the visual result may be odd for `axisLabel` and `customValues`, but acceptable
302+
* for `axisTick` `splitLine` and `splitArea`:
303+
* |---~---|---~---~---|---| <= This is the input `preTicksCoords`; `interval: 2; min: 1; max: 7`.
304+
* ₁ ₂ 3 ₄ ₅ 6 ₇ Subscript numbers (`₀`, `₁`, `₃`) indicate axis labels are hidden
305+
* (by default settings) due to off-interval.
306+
* A tilde (`~`) indicates a tick ignored due to off-interval.
307+
* |---~---|---~---~---|---~---| <= This is the result.
308+
* ₁ ₂ 3 ₄ ₅ 6 ₇
309+
*
310+
* NOTE:
311+
* - A inappropriate result may cause misleading (e.g., split 2 bars of a single data item when there
312+
* are two bar series).
313+
* - See also #11176 #11186 .
314+
* PENDING:
315+
* - The show/hide of `axisLabel` may be optimized when `interval > 1 and be an even number`,
316+
* but that may introduce complex and still not perfect in odd number, and may not necessary if
317+
* `axisTick: {show: false}` and `axisLabel` can auto hidden when overlapping.
318+
*/
291319
function fixOnBandTicksCoords(
292320
axis: Axis,
293-
ticksCoords: AxisTickCoord[],
321+
preTicksCoords: {
322+
coord: AxisTickCoord['coord'],
323+
tick: ScaleTick
324+
}[],
294325
alignWithLabel: boolean,
295-
clamp: boolean
296-
) {
297-
const ticksLen = ticksCoords.length;
326+
// return: whether coords are modified according to `onBand`.
327+
): boolean {
328+
const ticksLen = preTicksCoords.length;
298329

299330
if (!axis.onBand || alignWithLabel || !ticksLen) {
300-
return;
331+
return false;
301332
}
302333

303-
const axisExtent = axis.getExtent();
304-
let last;
305-
let diffSize;
306-
if (ticksLen === 1) {
307-
ticksCoords[0].coord = axisExtent[0];
308-
ticksCoords[0].onBand = true;
309-
last = ticksCoords[1] = {coord: axisExtent[1], tickValue: ticksCoords[0].tickValue, onBand: true};
334+
// Assume:
335+
// - If `onBand: true`, `bandWidth` has been calculated by `ticksLen + 1` rather than `ticksLen`.
336+
// - If `interval > 0`, some ticks may be ignored, but `ticksCoords` has always included boundary
337+
// ticks of axis extent, and be `offInterval: true` if off-interval.
338+
// - No need to consider breaks, since axis break is not supported in category axis.
339+
const bandWidth = calcBandWidth(axis).w;
340+
if (!bandWidth) {
341+
return false;
310342
}
311-
else {
312-
const crossLen = ticksCoords[ticksLen - 1].tickValue - ticksCoords[0].tickValue;
313-
const shift = (ticksCoords[ticksLen - 1].coord - ticksCoords[0].coord) / crossLen;
314-
315-
each(ticksCoords, function (ticksItem) {
316-
ticksItem.coord -= shift / 2;
317-
ticksItem.onBand = true;
318-
});
319343

320-
const dataExtent = axis.scale.getExtent();
321-
diffSize = 1 + dataExtent[1] - ticksCoords[ticksLen - 1].tickValue;
344+
each(preTicksCoords, function (ticksItem) {
345+
ticksItem.coord -= bandWidth / 2;
346+
});
322347

323-
last = {coord: ticksCoords[ticksLen - 1].coord + shift * diffSize, tickValue: dataExtent[1] + 1, onBand: true};
324-
325-
ticksCoords.push(last);
348+
const dataExtent = axis.scale.getExtent();
349+
const oldLast = preTicksCoords[ticksLen - 1];
350+
if (oldLast.tick.offInterval) {
351+
preTicksCoords.pop();
326352
}
353+
preTicksCoords.push({
354+
coord: oldLast.coord + bandWidth,
355+
tick: {value: dataExtent[1] + 1},
356+
});
327357

328-
const inverse = axisExtent[0] > axisExtent[1];
329-
330-
// Handling clamp.
331-
if (littleThan(ticksCoords[0].coord, axisExtent[0])) {
332-
clamp ? (ticksCoords[0].coord = axisExtent[0]) : ticksCoords.shift();
333-
}
334-
if (clamp && littleThan(axisExtent[0], ticksCoords[0].coord)) {
335-
ticksCoords.unshift({coord: axisExtent[0], onBand: true});
336-
}
337-
if (littleThan(axisExtent[1], last.coord)) {
338-
clamp ? (last.coord = axisExtent[1]) : ticksCoords.pop();
339-
}
340-
if (clamp && littleThan(last.coord, axisExtent[1])) {
341-
ticksCoords.push({coord: axisExtent[1], onBand: true});
342-
}
343-
344-
function littleThan(a: number, b: number): boolean {
345-
// Avoid rounding error cause calculated tick coord different with extent.
346-
// It may cause an extra unnecessary tick added.
347-
a = round(a, 10);
348-
b = round(b, 10);
349-
return inverse ? a > b : a < b;
350-
}
358+
return true;
351359
}
352360

353361
export default Axis;

src/coord/axisCommonTypes.ts

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ export interface CategoryAxisBaseOption extends AxisBaseOptionCommon {
210210
axisTick?: AxisBaseOptionCommon['axisTick'] & {
211211
// If tick is align with label when boundaryGap is true
212212
alignWithLabel?: boolean,
213-
interval?: 'auto' | number | ((index: number, value: string) => boolean)
213+
interval?: CategoryTickLabelSplitIntervalOption,
214214
}
215215
}
216216
export interface ValueAxisBaseOption extends NumericAxisBaseOptionCommon {
@@ -233,6 +233,9 @@ export interface TimeAxisBaseOption extends NumericAxisBaseOptionCommon {
233233
type?: 'time';
234234
axisLabel?: AxisLabelOption<'time'>;
235235
}
236+
237+
export type AxisTickOptionUnion = AxisBaseOptionCommon['axisTick'] | CategoryAxisBaseOption['axisTick'];
238+
236239
interface AxisNameTextStyleOption extends LabelCommonOption {
237240
rich?: RichTextOption
238241
}
@@ -350,9 +353,8 @@ export type AxisTickLabelCustomValuesOption = (number | string | Date)[];
350353

351354
interface AxisLabelOption<TType extends OptionAxisType> extends AxisLabelBaseOption {
352355
formatter?: LabelFormatters[TType]
353-
interval?: TType extends 'category'
354-
? ('auto' | number | ((index: number, value: string) => boolean))
355-
: unknown // Reserved but not used.
356+
// For non category axis, this option does not work.
357+
interval?: CategoryTickLabelSplitIntervalOption
356358
}
357359

358360
interface MinorTickOption {
@@ -364,7 +366,7 @@ interface MinorTickOption {
364366

365367
interface SplitLineOption {
366368
show?: boolean,
367-
interval?: 'auto' | number | ((index:number, value: string) => boolean),
369+
interval?: CategoryTickLabelSplitIntervalOption,
368370
// true | false
369371
showMinLine?: boolean,
370372
// true | false
@@ -380,10 +382,23 @@ interface MinorSplitLineOption {
380382

381383
interface SplitAreaOption {
382384
show?: boolean,
383-
interval?: 'auto' | number | ((index:number, value: string) => boolean)
385+
interval?: CategoryTickLabelSplitIntervalOption,
384386
// colors will display in turn
385-
areaStyle?: AreaStyleOption<ZRColor[]>
387+
areaStyle?: AreaStyleOption<ZRColor[]>,
386388
}
387389

390+
/**
391+
* `0` means show all; `1` means show one every other one; `2` means show one out of
392+
* every three; and so one.
393+
*/
394+
type CategoryTickLabelSplitIntervalOption =
395+
'auto' | number | ((index: number, value: string) => boolean) | NullUndefined;
396+
397+
export type CategoryTickLabelSplitBuildingOption = {
398+
show?: boolean,
399+
interval?: CategoryTickLabelSplitIntervalOption,
400+
};
401+
402+
388403
export type AxisBaseOption = ValueAxisBaseOption | LogAxisBaseOption
389404
| CategoryAxisBaseOption | TimeAxisBaseOption;

0 commit comments

Comments
 (0)