Skip to content

Commit 15af0db

Browse files
committed
fix: Fix min/max label show in category axis. Previously, axisLabel.showMinLabel/showMaxLabel did not control label and ticks on axis edges, but controls the filtered labels by axisLabel.interval settings. This impl did not align with the intent of that option.
1 parent 91a60fc commit 15af0db

12 files changed

Lines changed: 365 additions & 160 deletions

File tree

src/component/axis/AxisBuilder.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ import {
7575
AxisLabelsComputingContext, AxisTickLabelComputingKind, createAxisLabelsComputingContext
7676
} from '../../coord/axisTickLabelBuilder';
7777
import { AxisTickCoord } from '../../coord/Axis';
78-
import { isTimeScale } from '../../scale/helper';
78+
import { isOrdinalScale, isTimeScale } from '../../scale/helper';
7979

8080

8181
const PI = Math.PI;
@@ -1081,6 +1081,7 @@ function fixMinMaxLabelShow(
10811081
) {
10821082
let outmostLabelLayout = ensureLabelLayoutWithGeometry(labelLayoutList[outmostLabelIdx]);
10831083
let innerLabelLayout = ensureLabelLayoutWithGeometry(labelLayoutList[innerLabelIdx]);
1084+
const scale = axis.scale;
10841085
if (!outmostLabelLayout || !innerLabelLayout) {
10851086
return;
10861087
}
@@ -1089,9 +1090,15 @@ function fixMinMaxLabelShow(
10891090
// In this case, users are unlikely to expect labels to be hidden.
10901091
return;
10911092
}
1092-
if (isTimeScale(axis.scale) && getLabelInner(outmostLabelLayout.label).labelInfo.tick.notNice) {
1093-
// TimeScale does not expand extent to "nice", so eliminate labels that are not nice.
1093+
const tick = getLabelInner(outmostLabelLayout.label).labelInfo.tick;
1094+
if (// TimeScale does not expand extent to "nice", so eliminate labels that are not nice.
1095+
(isTimeScale(scale) && tick.notNice)
1096+
// Category axis does not expect tick that out of axisLabel.internal to be displayed
1097+
// unless required.
1098+
|| (isOrdinalScale(scale) && tick.offInterval)
1099+
) {
10941100
ignoreEl(outmostLabelLayout.label);
1101+
return;
10951102
}
10961103
}
10971104

src/coord/Axis.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -176,16 +176,15 @@ class Axis {
176176
breakTicks: opt.breakTicks,
177177
pruneByBreak: opt.pruneByBreak,
178178
});
179-
const ticks = result.ticks;
180-
181-
const ticksCoords = map(ticks, function (tickVal) {
179+
const ticksCoords = map(result.ticks, function (tick) {
180+
const tickValue = tick.value;
182181
return {
183182
coord: this.dataToCoord(
184183
isOrdinalScale(this.scale)
185-
? (this.scale as OrdinalScale).getRawOrdinalNumber(tickVal)
186-
: tickVal
184+
? (this.scale as OrdinalScale).getRawOrdinalNumber(tickValue)
185+
: tickValue
187186
),
188-
tickValue: tickVal
187+
tickValue,
189188
};
190189
}, this);
191190

src/coord/axisTickLabelBuilder.ts

Lines changed: 37 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ interface AxisCategoryLabelCreated {
5353
labelCategoryInterval: number
5454
}
5555
interface AxisCategoryTickCreated {
56-
ticks: number[]
56+
ticks: ScaleTick[]
5757
tickCategoryInterval?: number
5858
}
5959

@@ -105,15 +105,17 @@ export function createAxisLabelsComputingContext(kind: AxisTickLabelComputingKin
105105
};
106106
}
107107

108+
/**
109+
* CAUTION: Do not modify the result.
110+
*/
108111
export function createAxisLabels(axis: Axis, ctx: AxisLabelsComputingContext): {
109112
labels: AxisLabelInfoDetermined[]
110113
} {
111114
const custom = axis.getLabelModel().get('customValues');
112115
if (custom) {
113116
const scale = axis.scale;
114117
return {
115-
labels: zrUtil.map(parseTickLabelCustomValues(custom, scale), (numval, index) => {
116-
const tick = {value: numval};
118+
labels: zrUtil.map(parseTickLabelCustomValues(custom, scale), (tick, index) => {
117119
return {
118120
formattedLabel: makeLabelFormatter(axis)(tick, index),
119121
rawLabel: scale.getLabel(tick),
@@ -129,16 +131,15 @@ export function createAxisLabels(axis: Axis, ctx: AxisLabelsComputingContext): {
129131
}
130132

131133
/**
134+
* CAUTION: Do not modify the result.
135+
*
132136
* @param tickModel For example, can be axisTick, splitLine, splitArea.
133137
*/
134138
export function createAxisTicks(
135139
axis: Axis,
136140
tickModel: AxisBaseModel,
137141
opt?: Pick<ScaleGetTicksOpt, 'breakTicks' | 'pruneByBreak'>
138-
): {
139-
ticks: number[],
140-
tickCategoryInterval?: number
141-
} {
142+
): AxisCategoryTickCreated {
142143
const scale = axis.scale;
143144
const custom = axis.getTickModel().get('customValues');
144145
if (custom) {
@@ -149,13 +150,13 @@ export function createAxisTicks(
149150
// Only ordinal scale support tick interval
150151
return axis.type === 'category'
151152
? makeCategoryTicks(axis, tickModel)
152-
: {ticks: zrUtil.map(scale.getTicks(opt), tick => tick.value)};
153+
: {ticks: scale.getTicks(opt)};
153154
}
154155

155156
function parseTickLabelCustomValues(
156157
customValues: AxisTickLabelCustomValuesOption,
157158
scale: Scale,
158-
): number[] {
159+
): ScaleTick[] {
159160
const extent = scale.getExtent();
160161
const tickNumbers: number[] = [];
161162
zrUtil.each(customValues, function (val) {
@@ -166,7 +167,9 @@ function parseTickLabelCustomValues(
166167
});
167168
removeDuplicates(tickNumbers, removeDuplicatesGetKeyFromItemItself, null);
168169
asc(tickNumbers);
169-
return tickNumbers;
170+
return zrUtil.map(tickNumbers, function (tickVal) {
171+
return {value: tickVal};
172+
});
170173
}
171174

172175
function makeCategoryLabels(axis: Axis, ctx: AxisLabelsComputingContext): ReturnType<typeof createAxisLabels> {
@@ -182,10 +185,7 @@ function makeCategoryLabelsActually(
182185
axis: Axis,
183186
labelModel: Model<AxisBaseOption['axisLabel']>,
184187
ctx: AxisLabelsComputingContext
185-
): {
186-
labels: AxisLabelInfoDetermined[]
187-
labelCategoryInterval: number
188-
} {
188+
): AxisCategoryLabelCreated {
189189
const labelsCache = ensureCategoryLabelCache(axis);
190190
const optionLabelInterval = getOptionCategoryInterval(labelModel);
191191
const isEstimate = ctx.kind === AxisTickLabelComputingKind.estimate;
@@ -226,7 +226,7 @@ function makeCategoryLabelsActually(
226226
return result;
227227
}
228228

229-
function makeCategoryTicks(axis: Axis, tickModel: AxisBaseModel) {
229+
function makeCategoryTicks(axis: Axis, tickModel: AxisBaseModel): AxisCategoryTickCreated {
230230
const ticksCache = ensureCategoryTickCache(axis);
231231
const optionTickInterval = getOptionCategoryInterval(tickModel);
232232
const result = axisCacheGet(ticksCache, optionTickInterval);
@@ -235,7 +235,7 @@ function makeCategoryTicks(axis: Axis, tickModel: AxisBaseModel) {
235235
return result;
236236
}
237237

238-
let ticks: number[];
238+
let ticks: ScaleTick[];
239239
let tickCategoryInterval;
240240

241241
// Optimize for the case that large category data and no label displayed,
@@ -256,7 +256,7 @@ function makeCategoryTicks(axis: Axis, tickModel: AxisBaseModel) {
256256
);
257257
tickCategoryInterval = labelsResult.labelCategoryInterval;
258258
ticks = zrUtil.map(labelsResult.labels, function (labelItem) {
259-
return labelItem.tick.value;
259+
return labelItem.tick;
260260
});
261261
}
262262
else {
@@ -338,7 +338,7 @@ function makeAutoCategoryInterval(axis: Axis, ctx: AxisLabelsComputingContext):
338338
* To get precise result, at least one of `getRotate` and `isHorizontal`
339339
* should be implemented in axis.
340340
*/
341-
export function calculateCategoryInterval(axis: Axis, ctx: AxisLabelsComputingContext) {
341+
export function calculateCategoryInterval(axis: Axis, ctx: AxisLabelsComputingContext): number {
342342
const kind = ctx.kind;
343343

344344
const params = fetchAutoCategoryIntervalCalculationParams(axis);
@@ -474,14 +474,14 @@ function makeLabelsByNumericCategoryInterval(
474474
): AxisLabelInfoDetermined[];
475475
function makeLabelsByNumericCategoryInterval(
476476
axis: Axis, categoryInterval: number, onlyTick: true
477-
): number[];
477+
): ScaleTick[];
478478
function makeLabelsByNumericCategoryInterval(
479479
axis: Axis, categoryInterval: number, onlyTick?: boolean
480480
) {
481481
const labelFormatter = makeLabelFormatter(axis);
482482
const ordinalScale = axis.scale as OrdinalScale;
483483
const ordinalExtent = ordinalScale.getExtent();
484-
const result: (AxisLabelInfoDetermined | number)[] = [];
484+
const result: (AxisLabelInfoDetermined | ScaleTick)[] = [];
485485

486486
// TODO: axisType: ordinalTime, pick the tick from each month/day/year/...
487487

@@ -500,23 +500,23 @@ function makeLabelsByNumericCategoryInterval(
500500
// But they should be always included and the display strategy is adopted uniformly
501501
// later in `AxisBuilder`.
502502
if (startTick !== ordinalExtent[0]) {
503-
addItem(ordinalExtent[0]);
503+
addItem(ordinalExtent[0], true);
504504
}
505505

506506
// Optimize: avoid generating large array by `ordinalScale.getTicks()`.
507507
let tickValue = startTick;
508508
for (; tickValue <= ordinalExtent[1]; tickValue += step) {
509-
addItem(tickValue);
509+
addItem(tickValue, false);
510510
}
511511

512512
if (tickValue - step !== ordinalExtent[1]) {
513-
addItem(ordinalExtent[1]);
513+
addItem(ordinalExtent[1], true);
514514
}
515515

516-
function addItem(tickValue: number) {
517-
const tickObj = {value: tickValue};
516+
function addItem(tickValue: number, offInterval: boolean) {
517+
const tickObj: ScaleTick = {value: tickValue, offInterval};
518518
result.push(onlyTick
519-
? tickValue
519+
? tickObj
520520
: {
521521
formattedLabel: labelFormatter(tickObj),
522522
rawLabel: ordinalScale.getLabel(tickObj),
@@ -540,21 +540,24 @@ function makeLabelsByCustomizedCategoryInterval(
540540
): AxisLabelInfoDetermined[];
541541
function makeLabelsByCustomizedCategoryInterval(
542542
axis: Axis, categoryInterval: CategoryIntervalCb, onlyTick: true
543-
): number[];
543+
): ScaleTick[];
544544
function makeLabelsByCustomizedCategoryInterval(
545545
axis: Axis, categoryInterval: CategoryIntervalCb, onlyTick?: boolean
546546
) {
547547
const ordinalScale = axis.scale;
548548
const labelFormatter = makeLabelFormatter(axis);
549-
const result: (AxisLabelInfoDetermined | number)[] = [];
549+
const result: (AxisLabelInfoDetermined | ScaleTick)[] = [];
550550

551-
zrUtil.each(ordinalScale.getTicks(), function (tick) {
551+
const ticks = ordinalScale.getTicks();
552+
zrUtil.each(ticks, function (tick, idx) {
552553
const rawLabel = ordinalScale.getLabel(tick);
553-
const tickValue = tick.value;
554-
if (categoryInterval(tickValue, rawLabel)) {
555-
result.push(
556-
onlyTick
557-
? tickValue
554+
const isOnInterval = categoryInterval(tick.value, rawLabel);
555+
tick.offInterval = !isOnInterval;
556+
// axis extent min max labels should be always included and the display strategy
557+
// is adopted uniformly later in `AxisBuilder`.
558+
if (isOnInterval || idx === 0 || idx === ticks.length - 1) {
559+
result.push(onlyTick
560+
? tick
558561
: {
559562
formattedLabel: labelFormatter(tick),
560563
rawLabel: rawLabel,

src/scale/Scale.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,14 @@ abstract class Scale<
104104
*/
105105
abstract getLabel(tick: ScaleTick): string;
106106

107+
/**
108+
* Create ticks. The result can be modified by the caller.
109+
*/
107110
abstract getTicks(opt?: ScaleGetTicksOpt): ScaleTick[];
108111

112+
/**
113+
* Create minor ticks. The result can be modified by the caller.
114+
*/
109115
abstract getMinorTicks(splitNumber: number): number[][];
110116

111117
static registerClass: clazzUtil.ClassManager['registerClass'];

src/util/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,9 @@ export interface ScaleTick {
590590
time?: TimeScaleTick['time'];
591591
// NOTICE: null/undefined mean it is unknown whether this tick is "nice".
592592
notNice?: boolean | NullUndefined;
593+
// Only works on category axis.
594+
// Be `true` if this tick is out of category interval.
595+
offInterval?: boolean | NullUndefined,
593596
};
594597
export interface TimeScaleTick extends ScaleTick {
595598
time: {

test/-cases.js

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)