Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support minGap cfg for autoHide overlap #206

Merged
merged 1 commit into from
Nov 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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