Skip to content

Commit

Permalink
feat(label): support labels auto-rotation
Browse files Browse the repository at this point in the history
  • Loading branch information
agurtovoy committed Jan 16, 2024
1 parent 53bb9ff commit fcd1590
Show file tree
Hide file tree
Showing 8 changed files with 755 additions and 166 deletions.
16 changes: 4 additions & 12 deletions src/component/axis/AxisBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {isRadianAroundZero, remRadian} from '../../util/number';
import {createSymbol, normalizeSymbolOffset} from '../../util/symbol';
import * as matrixUtil from 'zrender/src/core/matrix';
import {applyTransform as v2ApplyTransform} from 'zrender/src/core/vector';
import {shouldShowAllLabels} from '../../coord/axisHelper';
import {shouldShowAllLabels, isNameLocationCenter} from '../../coord/axisHelper';
import { AxisBaseModel } from '../../coord/AxisBaseModel';
import { ZRTextVerticalAlign, ZRTextAlign, ECElement, ColorString } from '../../util/types';
import { AxisBaseOption } from '../../coord/axisCommonTypes';
Expand Down Expand Up @@ -376,8 +376,8 @@ const builders: Record<'axisLine' | 'axisTickLabel' | 'axisName', AxisElementsBu
const nameLocation = axisModel.get('nameLocation');
const nameDirection = opt.nameDirection;
const textStyleModel = axisModel.getModel('nameTextStyle');
const gap = axisModel.get('nameGap') || 0;

const gap = axisModel.axis.getNameGap();
const extent = axisModel.axis.getExtent();
const gapSignal = extent[0] > extent[1] ? -1 : 1;
const pos = [
Expand Down Expand Up @@ -601,11 +601,6 @@ function isTwoLabelOverlapped(
return firstRect.intersect(nextRect);
}

function isNameLocationCenter(nameLocation: string) {
return nameLocation === 'middle' || nameLocation === 'center';
}


function createTicks(
ticksCoords: TickCoord[],
tickTransform: matrixUtil.MatrixArray,
Expand Down Expand Up @@ -742,13 +737,10 @@ function buildAxisLabel(

const labelModel = axisModel.getModel('axisLabel');
const labelMargin = labelModel.get('margin');
const labels = axis.getViewLabels();
const { labels, rotation } = axis.getViewLabelsAndRotation();

// Special label rotate.
const labelRotation = (
retrieve(opt.labelRotate, labelModel.get('rotate')) || 0
) * PI / 180;

const labelRotation = (opt.labelRotate || labelModel.get('rotate') || rotation || 0) * PI / 180;
const labelLayout = AxisBuilder.innerTextLayout(opt.rotation, labelRotation, opt.labelDirection);
const rawCategoryData = axisModel.getCategories && axisModel.getCategories(true);

Expand Down
20 changes: 19 additions & 1 deletion src/coord/Axis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ import {linearMap, getPixelPrecision, round} from '../util/number';
import {
createAxisTicks,
createAxisLabels,
calculateCategoryInterval
calculateCategoryInterval,
calculateCategoryAutoLayout,
getAxisNameGap
} from './axisTickLabelBuilder';
import Scale from '../scale/Scale';
import { DimensionName, ScaleDataValue, ScaleTick } from '../util/types';
Expand Down Expand Up @@ -224,6 +226,14 @@ class Axis {
return createAxisLabels(this).labels;
}

getViewLabelsAndRotation(): ReturnType<typeof createAxisLabels> {
return createAxisLabels(this);
}

getNameGap(): ReturnType<typeof getAxisNameGap> {
return getAxisNameGap(this);
}

getLabelModel(): Model<AxisBaseOption['axisLabel']> {
return this.model.getModel('axisLabel');
}
Expand Down Expand Up @@ -269,6 +279,14 @@ class Axis {
return calculateCategoryInterval(this);
}

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

function fixExtentWithBands(extent: [number, number], nTick: number): void {
Expand Down
9 changes: 8 additions & 1 deletion src/coord/axisCommonTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@ export interface AxisBaseOptionCommon extends ComponentOption,
placeholder?: string;
};
nameTextStyle?: AxisNameTextStyleOption;
// The gap between axisName and axisLine.
// The gap between axisName and axisLine/labels
nameGap?: number;
nameLayout?: 'auto';

silent?: boolean;
triggerEvent?: boolean;
Expand Down Expand Up @@ -220,6 +221,8 @@ interface AxisLabelBaseOption extends Omit<TextCommonOption, 'color'> {
// Whether axisLabel is inside the grid or outside the grid.
inside?: boolean,
rotate?: number,
autoRotate?: boolean | [number, ...number[]],
minDistance?: number,
// true | false | null/undefined (auto)
showMinLabel?: boolean,
// true | false | null/undefined (auto)
Expand All @@ -241,7 +244,11 @@ interface AxisLabelBaseOption extends Omit<TextCommonOption, 'color'> {
// Color can be callback
color?: ColorString | ((value?: string | number, index?: number) => ColorString)
overflow?: TextStyleProps['overflow']
// approximate auto-layout computations (autoRotate, hideOverlap) if the total number of axis labels is over
// the trheshold; defaults to 40
layoutApproximationThreshold?: number
}

interface AxisLabelOption<TType extends OptionAxisType> extends AxisLabelBaseOption {
formatter?: LabelFormatters[TType]
}
Expand Down
1 change: 1 addition & 0 deletions src/coord/axisDefault.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ const defaultOption: AxisBaseOption = {
// Whether axisLabel is inside the grid or outside the grid.
inside: false,
rotate: 0,
minDistance: 10,
// true | false | null/undefined (auto)
showMinLabel: null,
// true | false | null/undefined (auto)
Expand Down
68 changes: 4 additions & 64 deletions src/coord/axisHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import {
makeColumnLayout,
retrieveColumnLayout
} from '../layout/barGrid';
import BoundingRect, { RectLike } from 'zrender/src/core/BoundingRect';

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

/**
* @param axis
* @return Be null/undefined if no labels.
*/
export function estimateLabelUnionRect(axis: Axis) {
const axisModel = axis.model;
const scale = axis.scale;

if (!axisModel.get(['axisLabel', 'show']) || scale.isBlank()) {
return;
}

let realNumberScaleTicks: ScaleTick[];
let tickCount;
const categoryScaleExtent = scale.getExtent();

// Optimize for large category data, avoid call `getTicks()`.
if (scale instanceof OrdinalScale) {
tickCount = scale.count();
}
else {
realNumberScaleTicks = scale.getTicks();
tickCount = realNumberScaleTicks.length;
}

const axisLabelModel = axis.getLabelModel();
const labelFormatter = makeLabelFormatter(axis);

let rect;
let step = 1;
// Simple optimization for large amount of labels
if (tickCount > 40) {
step = Math.ceil(tickCount / 40);
}
for (let i = 0; i < tickCount; i += step) {
const tick = realNumberScaleTicks
? realNumberScaleTicks[i]
: {
value: categoryScaleExtent[0] + i
};
const label = labelFormatter(tick, i);
const unrotatedSingleRect = axisLabelModel.getTextRect(label);
const singleRect = rotateTextRect(unrotatedSingleRect, axisLabelModel.get('rotate') || 0);

rect ? rect.union(singleRect) : (rect = singleRect);
}

return rect;
}

function rotateTextRect(textRect: RectLike, rotate: number) {
const rotateRadians = rotate * Math.PI / 180;
const beforeWidth = textRect.width;
const beforeHeight = textRect.height;
const afterWidth = beforeWidth * Math.abs(Math.cos(rotateRadians))
+ Math.abs(beforeHeight * Math.sin(rotateRadians));
const afterHeight = beforeWidth * Math.abs(Math.sin(rotateRadians))
+ Math.abs(beforeHeight * Math.cos(rotateRadians));
const rotatedRect = new BoundingRect(textRect.x, textRect.y, afterWidth, afterHeight);

return rotatedRect;
}

/**
* @param model axisLabelModel or axisTickModel
* @return {number|String} Can be null|'auto'|number|function
Expand Down Expand Up @@ -399,3 +335,7 @@ export function unionAxisExtentFromData(dataExtent: number[], data: SeriesData,
});
}
}

export function isNameLocationCenter(nameLocation: string) {
return nameLocation === 'middle' || nameLocation === 'center';
}

0 comments on commit fcd1590

Please sign in to comment.