Skip to content

Commit

Permalink
fix(axis): improve auto hide overlap
Browse files Browse the repository at this point in the history
  • Loading branch information
lessmost committed Nov 2, 2020
1 parent dbe504e commit a8b476c
Show file tree
Hide file tree
Showing 2 changed files with 168 additions and 36 deletions.
116 changes: 82 additions & 34 deletions src/axis/overlap/auto-hide.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ function isOverlap(isVertical: boolean, rotated: boolean, preBox, curBox, revers

// 保留第一个或者最后一个
function reserveOne(isVertical: boolean, labelsGroup: IGroup, reversed: boolean) {
const labels = labelsGroup.getChildren().slice(); // 复制数组
const labels = labelsGroup
.getChildren()
.slice() // 复制数组
.filter((item) => item.get('visible'));
if (!labels.length) {
return false;
}
Expand Down Expand Up @@ -74,6 +77,49 @@ function reserveOne(isVertical: boolean, labelsGroup: IGroup, reversed: boolean)
return hasHide;
}

// 均匀抽样隐藏标签,注意这里假设 label/tick 是均匀的
function parityHide(isVertical: boolean, labelsGroup: IGroup) {
const labels = labelsGroup.getChildren().slice(); // 复制数组
if (labels.length < 2) {
// 如果数量小于 2 则直接返回,等于 2 时可能也会重合
return false;
}
let hasHide = false;
const first = labels[0];
const firstBBox = first.getBBox();
const second = labels[1];
const rotated = isRotate(first);
const count = labels.length;
let interval = 0; // 不重叠的坐标文本间距个数
if (isVertical) {
// 垂直的坐标轴计算垂直方向的间距
const distance = Math.abs(second.attr('y') - first.attr('y'));
interval = firstBBox.height / distance;
} else {
// 水平坐标轴
if (rotated) {
const distance = Math.abs(second.attr('x') - first.attr('x'));
interval = firstBBox.width / distance;
} else {
const maxWidth = getMaxLabelWidth(labels);
const distance = Math.abs(second.attr('x') - first.attr('x'));
interval = maxWidth / distance;
}
}
// interval > 1 时需要对 label 进行隐藏
if (interval > 1) {
interval = Math.ceil(interval);
for (let i = 0; i < count; i++) {
if (i % interval !== 0) {
// 仅保留被整除的 label
labels[i].hide();
hasHide = true;
}
}
}
return hasHide;
}

export function getDefault() {
return equidistance;
}
Expand Down Expand Up @@ -140,48 +186,50 @@ export function reserveBoth(isVertical: boolean, labelsGroup: IGroup): boolean {
}

/**
* 保证 label 均匀显示,主要解决文本层叠的问题,对于 limitLength 不处理
* 保证 label 均匀显示 和 不出现重叠,主要解决文本层叠的问题,对于 limitLength 不处理
* @param {boolean} isVertical 是否垂直
* @param {IGroup} labelsGroup label 的分组
*/
export function equidistance(isVertical: boolean, labelsGroup: IGroup): boolean {
const labels = labelsGroup.getChildren().slice(); // 复制数组
if (labels.length < 2) {
// 如果数量小于 2 则直接返回,等于 2 时可能也会重合
return false;
let hasHide = parityHide(isVertical, labelsGroup);

// 处理 timeCat 类型的 tick,在均匀的基础上,再次检查出现重叠的进行隐藏
if (reserveOne(isVertical, labelsGroup, false)) {
hasHide = true;
}
let hasHide = false;
const first = labels[0];
const firstBBox = first.getBBox();
const second = labels[1];
const rotated = isRotate(first);
const count = labels.length;
let interval = 0; // 不重叠的坐标文本间距个数
if (isVertical) {
// 垂直的坐标轴计算垂直方向的间距
const distance = Math.abs(second.attr('y') - first.attr('y'));
interval = firstBBox.height / distance;
} else {
// 水平坐标轴
if (rotated) {
const distance = Math.abs(second.attr('x') - first.attr('x'));
interval = firstBBox.width / distance;
} else {
const maxWidth = getMaxLabelWidth(labels);
const distance = Math.abs(second.attr('x') - first.attr('x'));
interval = maxWidth / distance;

return hasHide;
}

/**
* 同 equidistance, 首先会保证 labels 均匀显示,然后会保留首尾
* @param isVertical
* @param labelsGroup
*/
export function equidistanceWithReverseBoth(isVertical: boolean, labelsGroup: IGroup): boolean {
const labels = labelsGroup.getChildren().slice(); // 复制数组
let hasHide = parityHide(isVertical, labelsGroup);

if (labels.length > 2) {
const first = labels[0];
const last = labels[labels.length - 1];

// 如果第一个被隐藏了
if (!first.get('visible')) {
first.show();
if (reserveOne(isVertical, labelsGroup, false)) {
hasHide = true;
}
}
}
// interval > 1 时需要对 label 进行隐藏
if (interval > 1) {
interval = Math.ceil(interval);
for (let i = 0; i < count; i++) {
if (i % interval !== 0) {
// 仅保留被整除的 label
labels[i].hide();

// 如果最后一个被隐藏了
if (!last.get('visible')) {
last.show();
if (reserveOne(isVertical, labelsGroup, true)) {
hasHide = true;
}
}
}

return hasHide;
}
88 changes: 86 additions & 2 deletions tests/unit/axis/auto-hide-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ describe('test auto hide', () => {
dom.id = 'cah';
const canvas = new Canvas({
container: 'cah',
width: 500,
height: 500,
width: 1000,
height: 1000,
});
const group = canvas.addGroup();
const labels = ['123', '12', '2344', '13455222', '2345', '2333', '222', '2222', '11', '33'];
Expand All @@ -33,6 +33,24 @@ describe('test auto hide', () => {
}
});
}
function addLabel(label: string, dx: number, dy: number, angle?: number) {
const last = group.getLast();
const x = (last ? last.attr('x') : 100) + dx;
const y = (last ? last.attr('y') : 100) + dy;
const shape = group.addShape({
type: 'text',
attrs: {
x,
y,
text: label,
fill: 'red',
},
});
if (angle) {
const matrix = getMatrixByAngle({ x, y }, angle);
shape.attr('matrix', matrix);
}
}

function getChildren(container) {
const children = container.getChildren();
Expand Down Expand Up @@ -198,4 +216,70 @@ describe('test auto hide', () => {
HideUtil.reserveLast(true, group);
expect(getCount(group)).toBe(labels.length);
});

it('equal distance, with last extra tick', () => {
addLabels(60, 0);
addLabel('last_last_x', 60, 0);
addLabel('last_x', 50, 0);
HideUtil.equidistance(false, group);

// no overlap
const children = getChildren(group);
children.forEach((cur, idx) => {
if (idx > 0) {
const prev = children[idx - 1];
expect(prev.getBBox().maxX < cur.getBBox().minX).toBe(true);
}
});
});

it('equidistanceWithReverseBoth, horizontal', () => {
// 宽度足够,不会隐藏
addLabels(100, 0);
HideUtil.equidistanceWithReverseBoth(false, group);
expect(getCount(group)).toBe(labels.length);

// 出现隐藏,首尾被保留
addLabels(50, 0);
HideUtil.equidistanceWithReverseBoth(false, group);
expect(getCount(group)).toBe(6);
expect(getFirst(group).attr('text')).toBe(labels[0]);
expect(getLast(group).attr('text')).toBe(labels[labels.length - 1]);
});

it('equidistanceWithReverseBoth, horizontal, with rotate', () => {
addLabels(50, 0, Math.PI / 4);
HideUtil.equidistanceWithReverseBoth(false, group);
expect(getCount(group)).toBe(labels.length);

addLabels(15, 0, Math.PI / 4);
HideUtil.equidistanceWithReverseBoth(false, group);
expect(getCount(group)).toBe(6);
expect(getFirst(group).attr('text')).toBe(labels[0]);
expect(getLast(group).attr('text')).toBe(labels[labels.length - 1]);
});

it('equidistanceWithReverseBoth, vertical', () => {
addLabels(0, 20);
HideUtil.equidistanceWithReverseBoth(true, group);
expect(getCount(group)).toBe(labels.length);

addLabels(0, 10);
HideUtil.equidistanceWithReverseBoth(true, group);
expect(getCount(group)).toBe(5);
expect(getFirst(group).attr('text')).toBe(labels[0]);
expect(getLast(group).attr('text')).toBe(labels[labels.length - 1]);
});

it('equidistanceWithReverseBoth, vertical, with rotate', () => {
addLabels(0, 20, (3 * Math.PI) / 4);
HideUtil.equidistanceWithReverseBoth(true, group);
expect(getCount(group)).toBe(labels.length);

addLabels(0, 10, (3 * Math.PI) / 4);
HideUtil.equidistanceWithReverseBoth(true, group);
expect(getCount(group)).toBe(5);
expect(getFirst(group).attr('text')).toBe(labels[0]);
expect(getLast(group).attr('text')).toBe(labels[labels.length - 1]);
});
});

0 comments on commit a8b476c

Please sign in to comment.