Skip to content

Commit eb35c0b

Browse files
committed
feat(label): support labels auto-rotation
1 parent 7731729 commit eb35c0b

File tree

8 files changed

+753
-166
lines changed

8 files changed

+753
-166
lines changed

src/component/axis/AxisBuilder.ts

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import {isRadianAroundZero, remRadian} from '../../util/number';
2626
import {createSymbol, normalizeSymbolOffset} from '../../util/symbol';
2727
import * as matrixUtil from 'zrender/src/core/matrix';
2828
import {applyTransform as v2ApplyTransform} from 'zrender/src/core/vector';
29-
import {shouldShowAllLabels} from '../../coord/axisHelper';
29+
import {shouldShowAllLabels, isNameLocationCenter} from '../../coord/axisHelper';
3030
import { AxisBaseModel } from '../../coord/AxisBaseModel';
3131
import { ZRTextVerticalAlign, ZRTextAlign, ECElement, ColorString } from '../../util/types';
3232
import { AxisBaseOption } from '../../coord/axisCommonTypes';
@@ -374,8 +374,8 @@ const builders: Record<'axisLine' | 'axisTickLabel' | 'axisName', AxisElementsBu
374374
const nameLocation = axisModel.get('nameLocation');
375375
const nameDirection = opt.nameDirection;
376376
const textStyleModel = axisModel.getModel('nameTextStyle');
377-
const gap = axisModel.get('nameGap') || 0;
378377

378+
const gap = axisModel.axis.getNameGap();
379379
const extent = axisModel.axis.getExtent();
380380
const gapSignal = extent[0] > extent[1] ? -1 : 1;
381381
const pos = [
@@ -599,11 +599,6 @@ function isTwoLabelOverlapped(
599599
return firstRect.intersect(nextRect);
600600
}
601601

602-
function isNameLocationCenter(nameLocation: string) {
603-
return nameLocation === 'middle' || nameLocation === 'center';
604-
}
605-
606-
607602
function createTicks(
608603
ticksCoords: TickCoord[],
609604
tickTransform: matrixUtil.MatrixArray,
@@ -740,13 +735,10 @@ function buildAxisLabel(
740735

741736
const labelModel = axisModel.getModel('axisLabel');
742737
const labelMargin = labelModel.get('margin');
743-
const labels = axis.getViewLabels();
738+
const { labels, rotation } = axis.getViewLabelsAndRotation();
744739

745740
// Special label rotate.
746-
const labelRotation = (
747-
retrieve(opt.labelRotate, labelModel.get('rotate')) || 0
748-
) * PI / 180;
749-
741+
const labelRotation = (opt.labelRotate || labelModel.get('rotate') || rotation || 0) * PI / 180;
750742
const labelLayout = AxisBuilder.innerTextLayout(opt.rotation, labelRotation, opt.labelDirection);
751743
const rawCategoryData = axisModel.getCategories && axisModel.getCategories(true);
752744

src/coord/Axis.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ import {linearMap, getPixelPrecision, round} from '../util/number';
2222
import {
2323
createAxisTicks,
2424
createAxisLabels,
25-
calculateCategoryInterval
25+
calculateCategoryInterval,
26+
calculateCategoryAutoLayout,
27+
getAxisNameGap
2628
} from './axisTickLabelBuilder';
2729
import Scale from '../scale/Scale';
2830
import { DimensionName, ScaleDataValue, ScaleTick } from '../util/types';
@@ -224,6 +226,14 @@ class Axis {
224226
return createAxisLabels(this).labels;
225227
}
226228

229+
getViewLabelsAndRotation(): ReturnType<typeof createAxisLabels> {
230+
return createAxisLabels(this);
231+
}
232+
233+
getNameGap(): ReturnType<typeof getAxisNameGap> {
234+
return getAxisNameGap(this);
235+
}
236+
227237
getLabelModel(): Model<AxisBaseOption['axisLabel']> {
228238
return this.model.getModel('axisLabel');
229239
}
@@ -269,6 +279,14 @@ class Axis {
269279
return calculateCategoryInterval(this);
270280
}
271281

282+
/**
283+
* Only be called in category axis.
284+
* Can be overridden, consider other axes like in 3D.
285+
* @return Auto layout properties (interval, rotation) for cateogry axis tick and label
286+
*/
287+
calculateCategoryAutoLayout(interval?: number): ReturnType<typeof calculateCategoryAutoLayout> {
288+
return calculateCategoryAutoLayout(this, interval);
289+
}
272290
}
273291

274292
function fixExtentWithBands(extent: [number, number], nTick: number): void {

src/coord/axisCommonTypes.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,9 @@ export interface AxisBaseOptionCommon extends ComponentOption,
4545
placeholder?: string;
4646
};
4747
nameTextStyle?: AxisNameTextStyleOption;
48-
// The gap between axisName and axisLine.
48+
// The gap between axisName and axisLine/labels
4949
nameGap?: number;
50+
nameLayout?: 'auto';
5051

5152
silent?: boolean;
5253
triggerEvent?: boolean;
@@ -219,6 +220,8 @@ interface AxisLabelBaseOption extends Omit<TextCommonOption, 'color'> {
219220
// Whether axisLabel is inside the grid or outside the grid.
220221
inside?: boolean,
221222
rotate?: number,
223+
autoRotate?: boolean | [number, ...number[]],
224+
minDistance?: number,
222225
// true | false | null/undefined (auto)
223226
showMinLabel?: boolean,
224227
// true | false | null/undefined (auto)
@@ -232,7 +235,11 @@ interface AxisLabelBaseOption extends Omit<TextCommonOption, 'color'> {
232235
// Color can be callback
233236
color?: ColorString | ((value?: string | number, index?: number) => ColorString)
234237
overflow?: TextStyleProps['overflow']
238+
// approximate auto-layout computations (autoRotate, hideOverlap) if the total number of axis labels is over
239+
// the trheshold; defaults to 40
240+
layoutApproximationThreshold?: number
235241
}
242+
236243
interface AxisLabelOption<TType extends OptionAxisType> extends AxisLabelBaseOption {
237244
formatter?: LabelFormatters[TType]
238245
}

src/coord/axisDefault.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ const defaultOption: AxisBaseOption = {
8383
// Whether axisLabel is inside the grid or outside the grid.
8484
inside: false,
8585
rotate: 0,
86+
minDistance: 10,
8687
// true | false | null/undefined (auto)
8788
showMinLabel: null,
8889
// true | false | null/undefined (auto)

src/coord/axisHelper.ts

Lines changed: 4 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import {
2626
makeColumnLayout,
2727
retrieveColumnLayout
2828
} from '../layout/barGrid';
29-
import BoundingRect, { RectLike } from 'zrender/src/core/BoundingRect';
3029

3130
import TimeScale from '../scale/Time';
3231
import Model from '../model/Model';
@@ -290,69 +289,6 @@ export function getAxisRawValue(axis: Axis, tick: ScaleTick): number | string {
290289
return axis.type === 'category' ? axis.scale.getLabel(tick) : tick.value;
291290
}
292291

293-
/**
294-
* @param axis
295-
* @return Be null/undefined if no labels.
296-
*/
297-
export function estimateLabelUnionRect(axis: Axis) {
298-
const axisModel = axis.model;
299-
const scale = axis.scale;
300-
301-
if (!axisModel.get(['axisLabel', 'show']) || scale.isBlank()) {
302-
return;
303-
}
304-
305-
let realNumberScaleTicks: ScaleTick[];
306-
let tickCount;
307-
const categoryScaleExtent = scale.getExtent();
308-
309-
// Optimize for large category data, avoid call `getTicks()`.
310-
if (scale instanceof OrdinalScale) {
311-
tickCount = scale.count();
312-
}
313-
else {
314-
realNumberScaleTicks = scale.getTicks();
315-
tickCount = realNumberScaleTicks.length;
316-
}
317-
318-
const axisLabelModel = axis.getLabelModel();
319-
const labelFormatter = makeLabelFormatter(axis);
320-
321-
let rect;
322-
let step = 1;
323-
// Simple optimization for large amount of labels
324-
if (tickCount > 40) {
325-
step = Math.ceil(tickCount / 40);
326-
}
327-
for (let i = 0; i < tickCount; i += step) {
328-
const tick = realNumberScaleTicks
329-
? realNumberScaleTicks[i]
330-
: {
331-
value: categoryScaleExtent[0] + i
332-
};
333-
const label = labelFormatter(tick, i);
334-
const unrotatedSingleRect = axisLabelModel.getTextRect(label);
335-
const singleRect = rotateTextRect(unrotatedSingleRect, axisLabelModel.get('rotate') || 0);
336-
337-
rect ? rect.union(singleRect) : (rect = singleRect);
338-
}
339-
340-
return rect;
341-
}
342-
343-
function rotateTextRect(textRect: RectLike, rotate: number) {
344-
const rotateRadians = rotate * Math.PI / 180;
345-
const beforeWidth = textRect.width;
346-
const beforeHeight = textRect.height;
347-
const afterWidth = beforeWidth * Math.abs(Math.cos(rotateRadians))
348-
+ Math.abs(beforeHeight * Math.sin(rotateRadians));
349-
const afterHeight = beforeWidth * Math.abs(Math.sin(rotateRadians))
350-
+ Math.abs(beforeHeight * Math.cos(rotateRadians));
351-
const rotatedRect = new BoundingRect(textRect.x, textRect.y, afterWidth, afterHeight);
352-
353-
return rotatedRect;
354-
}
355-
356292
/**
357293
* @param model axisLabelModel or axisTickModel
358294
* @return {number|String} Can be null|'auto'|number|function
@@ -399,3 +335,7 @@ export function unionAxisExtentFromData(dataExtent: number[], data: SeriesData,
399335
});
400336
}
401337
}
338+
339+
export function isNameLocationCenter(nameLocation: string) {
340+
return nameLocation === 'middle' || nameLocation === 'center';
341+
}

0 commit comments

Comments
 (0)