Skip to content

Commit

Permalink
feat: support minGap cfg for autoHide overlap
Browse files Browse the repository at this point in the history
  • Loading branch information
lessmost committed Nov 29, 2020
1 parent 10f572d commit 3d6c8cf
Show file tree
Hide file tree
Showing 4 changed files with 448 additions and 59 deletions.
36 changes: 29 additions & 7 deletions src/axis/line.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { IGroup } from '@antv/g-base';
import { vec2 } from '@antv/matrix-util';
import { each, isFunction, isNil, isNumberEqual } from '@antv/util';
import { each, isFunction, isNil, isNumberEqual, isObject } from '@antv/util';
import { ILocation } from '../interfaces';
import { BBox, LineAxisCfg, Point, RegionLocationCfg } from '../types';
import { AxisLabelAutoHideCfg, BBox, LineAxisCfg, Point, RegionLocationCfg } from '../types';
import Theme from '../util/theme';
import AxisBase from './base';
import * as OverlapUtil from './overlap';
Expand Down Expand Up @@ -121,7 +121,7 @@ class Line extends AxisBase<LineAxisCfg> implements ILocation<RegionLocationCfg>
}
const overlapOrder = this.get('overlapOrder');
each(overlapOrder, (name) => {
if (labelCfg[name]) {
if (labelCfg[name] && this.canProcessOverlap(name)) {
this.autoProcessOverlap(name, labelCfg[name], labelGroup, limitLength);
}
});
Expand Down Expand Up @@ -163,19 +163,41 @@ class Line extends AxisBase<LineAxisCfg> implements ILocation<RegionLocationCfg>
return maxLength;
}

/**
* 是否可以执行某一 overlap
* @param name
*/
private canProcessOverlap(name: string) {
const labelCfg = this.get('label');

// 对 autoRotate,如果配置了旋转角度,直接进行固定角度旋转
if (name === 'autoRotate') {
return !isNil(labelCfg.rotate);
}

// 默认所有 overlap 都可执行
return true;
}

private autoProcessOverlap(name: string, value: any, labelGroup: IGroup, limitLength: number) {
const isVertical = this.isVertical();
let hasAdjusted = false;
const util = OverlapUtil[name];
if (value === true) {
const labelCfg = this.get('label');
// 默认使用固定角度的旋转方案
hasAdjusted = util.getDefault()(isVertical, labelGroup, limitLength, labelCfg.rotate);
// true 形式的配置:使用 overlap 默认的的处理方法进行处理
hasAdjusted = util.getDefault()(isVertical, labelGroup, limitLength);
} else if (isFunction(value)) {
// 用户可以传入回调函数
// 回调函数形式的配置: 用户可以传入回调函数
hasAdjusted = value(isVertical, labelGroup, limitLength);
} else if (isObject(value)) {
// object 形式的配置方式:包括 处理方法 type, 和可选参数配置 cfg
const overlapCfg = value as { type: string; cfg?: AxisLabelAutoHideCfg };
if (util[overlapCfg.type]) {
hasAdjusted = util[overlapCfg.type](isVertical, labelGroup, limitLength, overlapCfg.cfg);
}
} else if (util[value]) {
// 按照名称执行旋转函数
// 字符串类型的配置:按照名称执行 overlap 处理方法
hasAdjusted = util[value](isVertical, labelGroup, limitLength);
}
if (name === 'autoRotate') {
Expand Down
96 changes: 69 additions & 27 deletions src/axis/overlap/auto-hide.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { IElement, IGroup } from '@antv/g-base';
import { getWrapBehavior } from '@antv/util';
import { AxisLabelAutoHideCfg } from '../../types';
import { getMaxLabelWidth } from '../../util/label';
import { getAngleByMatrix } from '../../util/matrix';
import { near } from '../../util/util';
Expand Down Expand Up @@ -32,35 +32,40 @@ function getRotateAngle(label: IElement) {
// }

// 是否重叠
function isOverlap(isVertical: boolean, first: IElement, second: IElement) {
function isOverlap(isVertical: boolean, first: IElement, second: IElement, minGap: number) {
let overlap = false;
const angle = getRotateAngle(first);
const distance = isVertical
? Math.abs(second.attr('y') - first.attr('y'))
: Math.abs(second.attr('x') - first.attr('x'));
const prevBBox = first.getBBox();
const prevBBox = (isVertical
? second.attr('y') > first.attr('y')
: second.attr('x') > first.attr('x'))
? first.getBBox()
: second.getBBox();

if (isVertical) {
const ratio = Math.abs(Math.cos(angle));
if (near(ratio, 0, Math.PI / 180)) {
overlap = prevBBox.width > distance;
overlap = prevBBox.width + minGap > distance;
} else {
overlap = prevBBox.height > distance * ratio;
overlap = prevBBox.height / ratio + minGap > distance;
}
} else {
const ratio = Math.abs(Math.sin(angle));
if (near(ratio, 0, Math.PI / 180)) {
overlap = prevBBox.width > distance;
overlap = prevBBox.width + minGap > distance;
} else {
overlap = prevBBox.height > distance * ratio;
overlap = prevBBox.height / ratio + minGap > distance;
}
}

return overlap;
}

// 保留第一个或者最后一个
function reserveOne(isVertical: boolean, labelsGroup: IGroup, reversed: boolean) {
function reserveOne(isVertical: boolean, labelsGroup: IGroup, reversed: boolean, autoHideCfg?: AxisLabelAutoHideCfg) {
const minGap = autoHideCfg?.minGap || 0;
const labels = labelsGroup
.getChildren()
.slice() // 复制数组
Expand All @@ -80,7 +85,7 @@ function reserveOne(isVertical: boolean, labelsGroup: IGroup, reversed: boolean)
const label = labels[i];
const curBBox = label.getBBox();
// 不再考虑超出限制,而仅仅根据是否重叠进行隐藏 isOutLimit(isVertical, label, limitLength) ||
const isHide = isOverlap(isVertical, prev, label);
const isHide = isOverlap(isVertical, prev, label, minGap);
if (isHide) {
label.hide();
hasHide = true;
Expand All @@ -92,7 +97,8 @@ function reserveOne(isVertical: boolean, labelsGroup: IGroup, reversed: boolean)
}

// 均匀抽样隐藏标签,注意这里假设 label/tick 是均匀的
function parityHide(isVertical: boolean, labelsGroup: IGroup) {
function parityHide(isVertical: boolean, labelsGroup: IGroup, autoHideCfg?: AxisLabelAutoHideCfg) {
const minGap = autoHideCfg?.minGap || 0;
const labels = labelsGroup.getChildren().slice(); // 复制数组
if (labels.length < 2) {
// 如果数量小于 2 则直接返回,等于 2 时可能也会重合
Expand All @@ -113,18 +119,18 @@ function parityHide(isVertical: boolean, labelsGroup: IGroup) {
const ratio = Math.abs(Math.cos(angle));
if (near(ratio, 0, Math.PI / 180)) {
const maxWidth = getMaxLabelWidth(labels);
interval = maxWidth / distance;
interval = (maxWidth + minGap) / distance;
} else {
interval = firstBBox.height / (distance * ratio);
interval = (firstBBox.height / ratio + minGap) / distance;
}
} else {
// 水平坐标轴
const ratio = Math.abs(Math.sin(angle));
if (near(ratio, 0, Math.PI / 180)) {
const maxWidth = getMaxLabelWidth(labels);
interval = maxWidth / distance;
interval = (maxWidth + minGap) / distance;
} else {
interval = firstBBox.height / (distance * ratio);
interval = (firstBBox.height / ratio + minGap) / distance;
}
}
// interval > 1 时需要对 label 进行隐藏
Expand All @@ -149,26 +155,48 @@ export function getDefault() {
* 保证首个 label 可见,即使超过 limitLength 也不隐藏
* @param {boolean} isVertical 是否垂直
* @param {IGroup} labelsGroup label 的分组
* @param {number} limitLength 另一个方向的长度限制,autoHide 不关心
* @param {AxisLabelAutoHideCfg} autoHideCfg autoHide overlap 的可选配置参数
*/
export function reserveFirst(isVertical: boolean, labelsGroup: IGroup): boolean {
return reserveOne(isVertical, labelsGroup, false);
export function reserveFirst(
isVertical: boolean,
labelsGroup: IGroup,
limitLength?: number,
autoHideCfg?: AxisLabelAutoHideCfg
): boolean {
return reserveOne(isVertical, labelsGroup, false, autoHideCfg);
}

/**
* 保证最后一个 label 可见,即使超过 limitLength 也不隐藏
* @param {boolean} isVertical 是否垂直
* @param {IGroup} labelsGroup label 的分组
* @param {number} limitLength 另一个方向的长度限制,autoHide 不关心
* @param {AxisLabelAutoHideCfg} autoHideCfg autoHide overlap 的可选配置参数
*/
export function reserveLast(isVertical: boolean, labelsGroup: IGroup): boolean {
return reserveOne(isVertical, labelsGroup, true);
export function reserveLast(
isVertical: boolean,
labelsGroup: IGroup,
limitLength?: number,
autoHideCfg?: AxisLabelAutoHideCfg
): boolean {
return reserveOne(isVertical, labelsGroup, true, autoHideCfg);
}

/**
* 保证第一个最后一个 label 可见,即使超过 limitLength 也不隐藏
* @param {boolean} isVertical 是否垂直
* @param {IGroup} labelsGroup label 的分组
* @param {number} limitLength 另一个方向的长度限制,autoHide 不关心
* @param {AxisLabelAutoHideCfg} autoHideCfg autoHide overlap 的可选配置参数
*/
export function reserveBoth(isVertical: boolean, labelsGroup: IGroup): boolean {
export function reserveBoth(
isVertical: boolean,
labelsGroup: IGroup,
limitLength?: number,
autoHideCfg?: AxisLabelAutoHideCfg
): boolean {
const minGap = autoHideCfg?.minGap || 0;
const labels = labelsGroup.getChildren().slice(); // 复制数组
if (labels.length <= 2) {
// 如果数量小于或等于 2 则直接返回
Expand All @@ -184,7 +212,7 @@ export function reserveBoth(isVertical: boolean, labelsGroup: IGroup): boolean {
const label = labels[i];
const curBBox = label.getBBox();
// 废弃 isOutLimit(isVertical, label, limitLength) ||
const isHide = isOverlap(isVertical, preLabel, label);
const isHide = isOverlap(isVertical, preLabel, label, minGap);
if (isHide) {
label.hide();
hasHide = true;
Expand All @@ -193,7 +221,7 @@ export function reserveBoth(isVertical: boolean, labelsGroup: IGroup): boolean {
}
}

const overlap = isOverlap(isVertical, preLabel, last);
const overlap = isOverlap(isVertical, preLabel, last, minGap);
if (overlap) {
// 发生冲突,则隐藏前一个保留后一个
preLabel.hide();
Expand All @@ -206,9 +234,16 @@ export function reserveBoth(isVertical: boolean, labelsGroup: IGroup): boolean {
* 保证 label 均匀显示 和 不出现重叠,主要解决文本层叠的问题,对于 limitLength 不处理
* @param {boolean} isVertical 是否垂直
* @param {IGroup} labelsGroup label 的分组
* @param {number} limitLength 另一个方向的长度限制,autoHide 不关心
* @param {AxisLabelAutoHideCfg} autoHideCfg autoHide overlap 的可选配置参数
*/
export function equidistance(isVertical: boolean, labelsGroup: IGroup): boolean {
let hasHide = parityHide(isVertical, labelsGroup);
export function equidistance(
isVertical: boolean,
labelsGroup: IGroup,
limitLength?: number,
autoHideCfg?: AxisLabelAutoHideCfg
): boolean {
let hasHide = parityHide(isVertical, labelsGroup, autoHideCfg);

// 处理 timeCat 类型的 tick,在均匀的基础上,再次检查出现重叠的进行隐藏
if (reserveOne(isVertical, labelsGroup, false)) {
Expand All @@ -222,10 +257,17 @@ export function equidistance(isVertical: boolean, labelsGroup: IGroup): boolean
* 同 equidistance, 首先会保证 labels 均匀显示,然后会保留首尾
* @param isVertical
* @param labelsGroup
* @param {number} limitLength 另一个方向的长度限制,autoHide 不关心
* @param {AxisLabelAutoHideCfg} autoHideCfg autoHide overlap 的可选配置参数
*/
export function equidistanceWithReverseBoth(isVertical: boolean, labelsGroup: IGroup): boolean {
export function equidistanceWithReverseBoth(
isVertical: boolean,
labelsGroup: IGroup,
limitLength?: number,
autoHideCfg?: AxisLabelAutoHideCfg
): boolean {
const labels = labelsGroup.getChildren().slice(); // 复制数组
let hasHide = parityHide(isVertical, labelsGroup);
let hasHide = parityHide(isVertical, labelsGroup, autoHideCfg);

if (labels.length > 2) {
const first = labels[0];
Expand All @@ -234,15 +276,15 @@ export function equidistanceWithReverseBoth(isVertical: boolean, labelsGroup: IG
// 如果第一个被隐藏了
if (!first.get('visible')) {
first.show();
if (reserveOne(isVertical, labelsGroup, false)) {
if (reserveOne(isVertical, labelsGroup, false, autoHideCfg)) {
hasHide = true;
}
}

// 如果最后一个被隐藏了
if (!last.get('visible')) {
last.show();
if (reserveOne(isVertical, labelsGroup, true)) {
if (reserveOne(isVertical, labelsGroup, true, autoHideCfg)) {
hasHide = true;
}
}
Expand Down
10 changes: 8 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,12 @@ export interface AxisTickLineCfg {

type avoidCallback = (isVertical: boolean, labelGroup: IGroup, limitLength?: number) => boolean;

/** 坐标轴自动隐藏的配置 */
export interface AxisLabelAutoHideCfg {
/** 最小间距配置 */
minGap?: number;
}

/**
* @interface
* 坐标轴文本定义
Expand Down Expand Up @@ -188,9 +194,9 @@ export interface AxisLabelCfg {
autoRotate?: boolean | avoidCallback | string;
/**
* 是否自动隐藏,默认 false
* @type {boolean|avoidCallback|string}
* @type {boolean|avoidCallback|string|{type:string,cfg?:AxisLabelAutoHideCfg}}
*/
autoHide?: boolean | avoidCallback | string;
autoHide?: boolean | avoidCallback | string | { type: string; cfg?: AxisLabelAutoHideCfg };
/**
* 是否自动省略,默认 false
* @type {boolean|avoidCallback|string}
Expand Down
Loading

0 comments on commit 3d6c8cf

Please sign in to comment.