Skip to content

Commit 8de2b64

Browse files
committed
feature&fix(axis):
(1) feature: Enable uniform bandWidth calculation in numeric axis (e.g., for tooltip shadow); it previously only applicable to category axis, but buggy in numeric axis with bar series. And enable the clip of tooltip shadow. (2) refactor: Introduce a dedicated workflow phase for series aggregation and data statistics computation on a single axis, allowting the results to be reused across multiple features. (3) fix: Fix duplicate ticks in TimeScale and customValues, which cause jitter of splitArea. (4) fix: Fix category showMin/MaxLabel handling when step > 1 and showMin/MaxLabel: false (5) chore: Tweak bad effects introduced by the previous implementation of SCALE_EXTENT_KIND_MAPPING. (6) chore: Clean some code.
1 parent b16d96c commit 8de2b64

41 files changed

Lines changed: 1073 additions & 522 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/chart/bar/install.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
import { EChartsExtensionInstallRegisters } from '../../extension';
2121
import * as zrUtil from 'zrender/src/core/util';
22-
import {layout, createProgressiveLayout, registerBarGridAxisContainShapeHandler} from '../../layout/barGrid';
22+
import {layout, createProgressiveLayout, registerBarGridAxisHandlers} from '../../layout/barGrid';
2323
import dataSample from '../../processor/dataSample';
2424

2525
import BarSeries from './BarSeries';
@@ -67,5 +67,5 @@ export function install(registers: EChartsExtensionInstallRegisters) {
6767
);
6868
});
6969

70-
registerBarGridAxisContainShapeHandler(registers);
70+
registerBarGridAxisHandlers(registers);
7171
}

src/chart/bar/installPictorialBar.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
import { EChartsExtensionInstallRegisters } from '../../extension';
2121
import PictorialBarView from './PictorialBarView';
2222
import PictorialBarSeriesModel from './PictorialBarSeries';
23-
import { createProgressiveLayout, layout, registerBarGridAxisContainShapeHandler } from '../../layout/barGrid';
23+
import { createProgressiveLayout, layout, registerBarGridAxisHandlers } from '../../layout/barGrid';
2424
import { curry } from 'zrender/src/core/util';
2525

2626
export function install(registers: EChartsExtensionInstallRegisters) {
@@ -31,5 +31,5 @@ export function install(registers: EChartsExtensionInstallRegisters) {
3131
// Do layout after other overall layout, which can prepare some information.
3232
registers.registerLayout(registers.PRIORITY.VISUAL.PROGRESSIVE_LAYOUT, createProgressiveLayout('pictorialBar'));
3333

34-
registerBarGridAxisContainShapeHandler(registers);
34+
registerBarGridAxisHandlers(registers);
3535
}

src/chart/line/LineView.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,7 @@ function getIsIgnoreFunc(
399399

400400
zrUtil.each(categoryAxis.getViewLabels(), function (labelItem) {
401401
const ordinalNumber = (categoryAxis.scale as OrdinalScale)
402-
.getRawOrdinalNumber(labelItem.tickValue);
402+
.getRawOrdinalNumber(labelItem.tick.value);
403403
labelMap[ordinalNumber] = 1;
404404
});
405405

src/chart/sankey/sankeyLayout.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { GraphNode, GraphEdge } from '../../data/Graph';
2525
import { LayoutOrient } from '../../util/types';
2626
import GlobalModel from '../../model/Global';
2727
import { createBoxLayoutReference, getLayoutRect } from '../../util/layout';
28+
import { asc } from '../../util/number';
2829

2930
export default function sankeyLayout(ecModel: GlobalModel, api: ExtensionAPI) {
3031

@@ -290,9 +291,7 @@ function prepareNodesByBreadth(nodes: GraphNode[], orient: LayoutOrient) {
290291
const groupResult = groupData(nodes, function (node) {
291292
return node.getLayout()[keyAttr] as number;
292293
});
293-
groupResult.keys.sort(function (a, b) {
294-
return a - b;
295-
});
294+
asc(groupResult.keys);
296295
zrUtil.each(groupResult.keys, function (key) {
297296
nodesByBreadth.push(groupResult.buckets.get(key));
298297
});

src/component/axis/AngleAxisView.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,8 @@ class AngleAxisView extends AxisView {
101101
labelItem = zrUtil.clone(labelItem);
102102
const scale = angleAxis.scale;
103103
const tickValue = scale.type === 'ordinal'
104-
? (scale as OrdinalScale).getRawOrdinalNumber(labelItem.tickValue)
105-
: labelItem.tickValue;
104+
? (scale as OrdinalScale).getRawOrdinalNumber(labelItem.tick.value)
105+
: labelItem.tick.value;
106106
labelItem.coord = angleAxis.dataToCoord(tickValue);
107107
return labelItem;
108108
});
@@ -250,7 +250,7 @@ const angelAxisElementsBuilders: Record<typeof elementList[number], AngleAxisEle
250250
// Use length of ticksAngles because it may remove the last tick to avoid overlapping
251251
zrUtil.each(labels, function (labelItem, idx) {
252252
let labelModel = commonLabelModel;
253-
const tickValue = labelItem.tickValue;
253+
const tickValue = labelItem.tick.value;
254254

255255
const r = radiusExtent[getRadiusIdx(polar)];
256256
const p = polar.coordToPoint([r + labelMargin, labelItem.coord]);

src/component/axis/AxisBuilder.ts

Lines changed: 45 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ import {
4444
DimensionName,
4545
} from '../../util/types';
4646
import {
47-
AxisBaseOption, AxisBaseOptionCommon, AxisLabelBaseOptionNuance
47+
AxisBaseOption, AxisBaseOptionCommon, AxisLabelBaseOptionNuance,
48+
AxisShowMinMaxLabelOption,
4849
} from '../../coord/axisCommonTypes';
4950
import type Element from 'zrender/src/Element';
5051
import { PathProps, PathStyleProps } from 'zrender/src/graphic/Path';
@@ -70,9 +71,11 @@ import BoundingRect from 'zrender/src/core/BoundingRect';
7071
import Point from 'zrender/src/core/Point';
7172
import { copyTransform } from 'zrender/src/core/Transformable';
7273
import {
74+
AxisLabelInfoDetermined,
7375
AxisLabelsComputingContext, AxisTickLabelComputingKind, createAxisLabelsComputingContext
7476
} from '../../coord/axisTickLabelBuilder';
7577
import { AxisTickCoord } from '../../coord/Axis';
78+
import { isTimeScale } from '../../scale/helper';
7679

7780

7881
const PI = Math.PI;
@@ -112,14 +115,13 @@ type AxisLabelText = graphic.Text & {
112115
} & ECElement;
113116

114117
export const getLabelInner = makeInner<{
115-
break: VisualAxisBreak;
116-
tickValue: number;
118+
labelInfo: AxisLabelInfoDetermined; // Never be null/undefined.
117119
layoutRotation: number;
118120
}, graphic.Text>();
119121

120122
const getTickInner = makeInner<{
121-
onBand: AxisTickCoord['onBand']
122-
tickValue: AxisTickCoord['tickValue']
123+
onBand: AxisTickCoord['onBand'];
124+
tickValue: AxisTickCoord['tickValue'];
123125
}, graphic.Line>();
124126

125127

@@ -1061,7 +1063,10 @@ function fixMinMaxLabelShow(
10611063
labelLayoutList: LabelLayoutData[],
10621064
optionHideOverlap: AxisBaseOption['axisLabel']['hideOverlap']
10631065
) {
1064-
if (shouldShowAllLabels(axisModel.axis)) {
1066+
const axis = axisModel.axis;
1067+
const customValuesOption = axisModel.get(['axisLabel', 'customValues']);
1068+
1069+
if (shouldShowAllLabels(axis)) {
10651070
return;
10661071
}
10671072

@@ -1070,7 +1075,7 @@ function fixMinMaxLabelShow(
10701075
// Assert no ignore in labels.
10711076

10721077
function deal(
1073-
showMinMaxLabel: boolean,
1078+
showMinMaxLabelOption: AxisShowMinMaxLabelOption,
10741079
outmostLabelIdx: number,
10751080
innerLabelIdx: number,
10761081
) {
@@ -1079,8 +1084,18 @@ function fixMinMaxLabelShow(
10791084
if (!outmostLabelLayout || !innerLabelLayout) {
10801085
return;
10811086
}
1087+
if (showMinMaxLabelOption == null) {
1088+
if (!optionHideOverlap && customValuesOption) {
1089+
// In this case, users are unlikely to expect labels to be hidden.
1090+
return;
1091+
}
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.
1094+
ignoreEl(outmostLabelLayout.label);
1095+
}
1096+
}
10821097

1083-
if (showMinMaxLabel === false || outmostLabelLayout.suggestIgnore) {
1098+
if (showMinMaxLabelOption === false || outmostLabelLayout.suggestIgnore) {
10841099
ignoreEl(outmostLabelLayout.label);
10851100
return;
10861101
}
@@ -1107,7 +1122,7 @@ function fixMinMaxLabelShow(
11071122
innerLabelLayout = newLabelLayoutWithGeometry({marginForce}, innerLabelLayout);
11081123
}
11091124
if (labelIntersect(outmostLabelLayout, innerLabelLayout, null, {touchThreshold})) {
1110-
if (showMinMaxLabel) {
1125+
if (showMinMaxLabelOption) {
11111126
ignoreEl(innerLabelLayout.label);
11121127
}
11131128
else {
@@ -1119,11 +1134,11 @@ function fixMinMaxLabelShow(
11191134
// If min or max are user set, we need to check
11201135
// If the tick on min(max) are overlap on their neighbour tick
11211136
// If they are overlapped, we need to hide the min(max) tick label
1122-
const showMinLabel = axisModel.get(['axisLabel', 'showMinLabel']);
1123-
const showMaxLabel = axisModel.get(['axisLabel', 'showMaxLabel']);
1137+
const showMinLabelOption = axisModel.get(['axisLabel', 'showMinLabel']);
1138+
const showMaxLabelOption = axisModel.get(['axisLabel', 'showMaxLabel']);
11241139
const labelsLen = labelLayoutList.length;
1125-
deal(showMinLabel, 0, 1);
1126-
deal(showMaxLabel, labelsLen - 1, labelsLen - 2);
1140+
deal(showMinLabelOption, 0, 1);
1141+
deal(showMaxLabelOption, labelsLen - 1, labelsLen - 2);
11271142
}
11281143

11291144
// PENDING: Is it necessary to display a tick while the corresponding label is ignored?
@@ -1146,7 +1161,7 @@ function syncLabelIgnoreToMajorTicks(
11461161
const labelInner = getLabelInner(labelLayout.label);
11471162
if (tickInner.tickValue != null
11481163
&& !tickInner.onBand
1149-
&& tickInner.tickValue === labelInner.tickValue
1164+
&& tickInner.tickValue === labelInner.labelInfo.tick.value
11501165
) {
11511166
ignoreEl(tickEl);
11521167
return;
@@ -1355,9 +1370,11 @@ function buildAxisLabel(
13551370
let z2Max = -Infinity;
13561371

13571372
each(labels, function (labelItem, index) {
1373+
const labelItemTick = labelItem.tick;
1374+
const labelItemTickValue = labelItemTick.value;
13581375
const tickValue = axis.scale.type === 'ordinal'
1359-
? (axis.scale as OrdinalScale).getRawOrdinalNumber(labelItem.tickValue)
1360-
: labelItem.tickValue;
1376+
? (axis.scale as OrdinalScale).getRawOrdinalNumber(labelItemTickValue)
1377+
: labelItemTickValue;
13611378
const formattedLabel = labelItem.formattedLabel;
13621379
const rawLabel = labelItem.rawLabel;
13631380

@@ -1396,7 +1413,7 @@ function buildAxisLabel(
13961413
itemLabelModel.getShallow('verticalAlignMaxLabel', true),
13971414
verticalAlign
13981415
);
1399-
const z2 = 10 + (labelItem.time?.level || 0);
1416+
const z2 = 10 + (labelItemTick.time?.level || 0);
14001417
z2Min = Math.min(z2Min, z2);
14011418
z2Max = Math.max(z2Max, z2);
14021419

@@ -1443,8 +1460,7 @@ function buildAxisLabel(
14431460
textEl.anid = 'label_' + tickValue;
14441461

14451462
const inner = getLabelInner(textEl);
1446-
inner.break = labelItem.break;
1447-
inner.tickValue = tickValue;
1463+
inner.labelInfo = labelItem;
14481464
inner.layoutRotation = labelLayout.rotation;
14491465

14501466
graphic.setTooltipConfig({
@@ -1464,11 +1480,13 @@ function buildAxisLabel(
14641480
eventData.targetType = 'axisLabel';
14651481
eventData.value = rawLabel;
14661482
eventData.tickIndex = index;
1467-
if (labelItem.break) {
1483+
const labelItemTickBreak = labelItem.tick.break;
1484+
const labelItemTickBreakParsedBreak = labelItemTickBreak.parsedBreak;
1485+
if (labelItemTickBreak) {
14681486
eventData.break = {
14691487
// type: labelItem.break.type,
1470-
start: labelItem.break.parsedBreak.vmin,
1471-
end: labelItem.break.parsedBreak.vmax,
1488+
start: labelItemTickBreakParsedBreak.vmin,
1489+
end: labelItemTickBreakParsedBreak.vmax,
14721490
};
14731491
}
14741492
if (axis.type === 'category') {
@@ -1477,8 +1495,8 @@ function buildAxisLabel(
14771495

14781496
getECData(textEl).eventData = eventData;
14791497

1480-
if (labelItem.break) {
1481-
addBreakEventHandler(axisModel, api, textEl, labelItem.break);
1498+
if (labelItemTickBreak) {
1499+
addBreakEventHandler(axisModel, api, textEl, labelItemTickBreak);
14821500
}
14831501
}
14841502

@@ -1488,7 +1506,7 @@ function buildAxisLabel(
14881506

14891507
const labelLayoutList = map(labelEls, label => ({
14901508
label,
1491-
priority: getLabelInner(label).break
1509+
priority: getLabelInner(label).labelInfo.tick.break
14921510
? label.z2 + (z2Max - z2Min + 1) // Make break labels be highest priority.
14931511
: label.z2,
14941512
defaultAttr: {
@@ -1537,7 +1555,7 @@ function updateAxisLabelChangableProps(
15371555
labelEl.ignore = false;
15381556

15391557
copyTransform(_tmpLayoutEl, _tmpLayoutElReset);
1540-
_tmpLayoutEl.x = axisModel.axis.dataToCoord(inner.tickValue);
1558+
_tmpLayoutEl.x = axisModel.axis.dataToCoord(inner.labelInfo.tick.value);
15411559
_tmpLayoutEl.y = cfg.labelOffset + cfg.labelDirection * labelMargin;
15421560
_tmpLayoutEl.rotation = inner.layoutRotation;
15431561

@@ -1590,7 +1608,7 @@ function adjustBreakLabels(
15901608
}
15911609
const breakLabelIndexPairs = scaleBreakHelper.retrieveAxisBreakPairs(
15921610
labelLayoutList,
1593-
layoutInfo => layoutInfo && getLabelInner(layoutInfo.label).break,
1611+
layoutInfo => layoutInfo && getLabelInner(layoutInfo.label).labelInfo.tick.break,
15941612
true
15951613
);
15961614
const moveOverlap = axisModel.get(['breakLabelLayout', 'moveOverlap'], true);

src/component/axis/axisSplitHelper.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import type CartesianAxisView from './CartesianAxisView';
2626
import type SingleAxisModel from '../../coord/single/AxisModel';
2727
import type CartesianAxisModel from '../../coord/cartesian/AxisModel';
2828
import AxisView from './AxisView';
29+
import type { AxisBaseModel } from '../../coord/AxisBaseModel';
2930

3031
const inner = makeInner<{
3132
// Hash map of color index
@@ -35,7 +36,7 @@ const inner = makeInner<{
3536
export function rectCoordAxisBuildSplitArea(
3637
axisView: SingleAxisView | CartesianAxisView,
3738
axisGroup: graphic.Group,
38-
axisModel: SingleAxisModel | CartesianAxisModel,
39+
axisModel: (SingleAxisModel | CartesianAxisModel) & AxisBaseModel,
3940
gridModel: GridModel | SingleAxisModel
4041
) {
4142
const axis = axisModel.axis;
@@ -44,8 +45,7 @@ export function rectCoordAxisBuildSplitArea(
4445
return;
4546
}
4647

47-
// TODO: TYPE
48-
const splitAreaModel = (axisModel as CartesianAxisModel).getModel('splitArea');
48+
const splitAreaModel = axisModel.getModel('splitArea');
4949
const areaStyleModel = splitAreaModel.getModel('areaStyle');
5050
let areaColors = areaStyleModel.get('color');
5151

@@ -107,7 +107,6 @@ export function rectCoordAxisBuildSplitArea(
107107

108108
const tickValue = ticksCoords[i - 1].tickValue;
109109
tickValue != null && newSplitAreaColors.set(tickValue, colorIndex);
110-
111110
axisGroup.add(new graphic.Rect({
112111
anid: tickValue != null ? 'area_' + tickValue : null,
113112
shape: {

src/component/axisPointer/CartesianAxisPointer.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import Grid from '../../coord/cartesian/Grid';
2727
import Axis2D from '../../coord/cartesian/Axis2D';
2828
import { PathProps } from 'zrender/src/graphic/Path';
2929
import Model from '../../model/Model';
30+
import { isNullableNumberFinite, mathMax, mathMin } from '../../util/number';
3031

3132
// Not use top level axisPointer model
3233
type AxisPointerModel = Model<CommonAxisPointerOption>;
@@ -105,8 +106,8 @@ class CartesianAxisPointer extends BaseAxisPointer {
105106

106107
const currPosition = [transform.x, transform.y];
107108
currPosition[dimIndex] += delta[dimIndex];
108-
currPosition[dimIndex] = Math.min(axisExtent[1], currPosition[dimIndex]);
109-
currPosition[dimIndex] = Math.max(axisExtent[0], currPosition[dimIndex]);
109+
currPosition[dimIndex] = mathMin(axisExtent[1], currPosition[dimIndex]);
110+
currPosition[dimIndex] = mathMax(axisExtent[0], currPosition[dimIndex]);
110111

111112
const cursorOtherValue = (otherExtent[1] + otherExtent[0]) / 2;
112113
const cursorPoint = [cursorOtherValue, cursorOtherValue];
@@ -156,13 +157,18 @@ const pointerShapeBuilder = {
156157
},
157158

158159
shadow: function (axis: Axis2D, pixelValue: number, otherExtent: number[]): PathProps & { type: 'Rect'} {
159-
const bandWidth = Math.max(1, axis.getBandWidth());
160-
const span = otherExtent[1] - otherExtent[0];
160+
let bandWidth = axis.getBandWidth();
161+
const thisExtent = axis.getGlobalExtent();
162+
bandWidth = isNullableNumberFinite(bandWidth)
163+
? mathMax(1, bandWidth) : 1;
164+
const otherSpan = otherExtent[1] - otherExtent[0];
165+
const thisX = mathMax(thisExtent[0], pixelValue - bandWidth / 2);
166+
const thisW = mathMin(thisX + bandWidth, thisExtent[1]) - thisX;
161167
return {
162168
type: 'Rect',
163169
shape: viewHelper.makeRectShape(
164-
[pixelValue - bandWidth / 2, otherExtent[0]],
165-
[bandWidth, span],
170+
[thisX, otherExtent[0]],
171+
[thisW, otherSpan],
166172
getAxisDimIndex(axis)
167173
)
168174
};

src/component/axisPointer/axisTrigger.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ function showTooltip(
368368
axisType: axisModel.type,
369369
axisId: axisModel.id,
370370
value: value as number,
371-
// Caustion: viewHelper.getValueLabel is actually on "view stage", which
371+
// Caution: viewHelper.getValueLabel is actually on "view stage", which
372372
// depends that all models have been updated. So it should not be performed
373373
// here. Considering axisPointerModel used here is volatile, which is hard
374374
// to be retrieve in TooltipView, we prepare parameters here.

src/component/brush/preprocessor.ts

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import { ECUnitOption, Dictionary } from '../../util/types';
2323
import { BrushOption, BrushToolboxIconType } from './BrushModel';
2424
import { ToolboxOption } from '../toolbox/ToolboxModel';
2525
import { ToolboxBrushFeatureOption } from '../toolbox/feature/Brush';
26-
import { normalizeToArray } from '../../util/model';
26+
import { normalizeToArray, removeDuplicates } from '../../util/model';
2727

2828
const DEFAULT_TOOLBOX_BTNS: BrushToolboxIconType[] = ['rect', 'polygon', 'keep', 'clear'];
2929

@@ -61,20 +61,9 @@ export default function brushPreprocessor(option: ECUnitOption, isNew: boolean):
6161

6262
brushTypes.push.apply(brushTypes, brushComponentSpecifiedBtns);
6363

64-
removeDuplicate(brushTypes);
64+
removeDuplicates(brushTypes, item => item + '', null);
6565

6666
if (isNew && !brushTypes.length) {
6767
brushTypes.push.apply(brushTypes, DEFAULT_TOOLBOX_BTNS);
6868
}
6969
}
70-
71-
function removeDuplicate(arr: string[]): void {
72-
const map = {} as Dictionary<number>;
73-
zrUtil.each(arr, function (val) {
74-
map[val] = 1;
75-
});
76-
arr.length = 0;
77-
zrUtil.each(map, function (flag, val) {
78-
arr.push(val);
79-
});
80-
}

0 commit comments

Comments
 (0)