Skip to content

Commit

Permalink
Merge pull request #2143 from antvis/label-refactor
Browse files Browse the repository at this point in the history
优化 label 类以及修复相关 BUG
  • Loading branch information
visiky authored Mar 20, 2020
2 parents 394d8e8 + ac4f3f4 commit cfe9e3f
Show file tree
Hide file tree
Showing 31 changed files with 1,221 additions and 618 deletions.
1 change: 1 addition & 0 deletions examples/pie/basic/demo/pie-texture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const data = [
const chart = new Chart({
container: 'container',
autoFit: true,
height: 500,
});

chart.data(data);
Expand Down
6 changes: 3 additions & 3 deletions src/chart/controller/annotation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { COMPONENT_TYPE, DIRECTION, LAYER, VIEW_LIFE_CIRCLE } from '../../consta

import Geometry from '../../geometry/base';
import Element from '../../geometry/element';
import { getDistanceToCenter, getPointAngle } from '../../util/coordinate';
import { getAngleByPoint, getDistanceToCenter } from '../../util/coordinate';
import { omit } from '../../util/helper';
import View from '../view';
import { Controller } from './base';
Expand Down Expand Up @@ -528,8 +528,8 @@ export default class Annotation extends Controller<BaseOption[]> {
const { start, end } = option as ArcOption;
const sp = this.parsePosition(start);
const ep = this.parsePosition(end);
const startAngle = getPointAngle(coordinate, sp);
let endAngle = getPointAngle(coordinate, ep);
const startAngle = getAngleByPoint(coordinate, sp);
let endAngle = getAngleByPoint(coordinate, ep);
if (startAngle > endAngle) {
endAngle = Math.PI * 2 + endAngle;
}
Expand Down
4 changes: 2 additions & 2 deletions src/chart/controller/tooltip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { deepMix, each, find, flatten, get, isArray, isEqual, isFunction, mix }
import { Crosshair, HtmlTooltip, IGroup } from '../../dependents';
import Geometry from '../../geometry/base';
import { MappingDatum, Point, TooltipOption } from '../../interface';
import { getDistanceToCenter, getPointAngle, isPointInCoordinate } from '../../util/coordinate';
import { getAngleByPoint, getDistanceToCenter, isPointInCoordinate } from '../../util/coordinate';
import { polarToCartesian } from '../../util/graphics';
import { findDataByPoint, getTooltipItems } from '../../util/tooltip';
import { Controller } from './base';
Expand Down Expand Up @@ -397,7 +397,7 @@ export default class Tooltip extends Controller<TooltipOption> {
}
} else {
// 极坐标下 x 轴上的 crosshairs 表现为半径
const angle = getPointAngle(coordinate, point);
const angle = getAngleByPoint(coordinate, point);
const center = coordinate.getCenter();
// @ts-ignore
const radius = coordinate.getRadius();
Expand Down
103 changes: 70 additions & 33 deletions src/component/labels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,20 +50,28 @@ export default class Labels {
this.shapesMap = {};
const container = this.container;
const offscreenGroup = this.createOffscreenGroup(); // 创建虚拟分组
// 在虚拟 group 中创建 shapes
// step 1: 在虚拟 group 中创建 shapes
for (const item of items) {
if (item) {
this.renderLabel(item, offscreenGroup);
}
}
this.adjustLabels(shapes); // 调整 labels

// step 2: 根据布局,调整 labels
this.doLayout(items, shapes);

// step 3: 绘制 labelLine
this.renderLabelLine(items);

// step 4: 根据用户设置的偏移量调整 label
this.adjustLabel(items);

// 进行添加、更新、销毁操作
const lastShapesMap = this.lastShapesMap;
const shapesMap = this.shapesMap;
each(shapesMap, (shape, id) => {
if (shape.destroyed) {
// label 在布局调整环节被删除了(adjustLabels
// label 在布局调整环节被删除了(doLayout
delete shapesMap[id];
} else {
if (lastShapesMap[id]) {
Expand Down Expand Up @@ -167,7 +175,7 @@ export default class Labels {
if ((content.isGroup && content.isGroup()) || (content.isShape && content.isShape())) {
// 如果 content 是 Group 或者 Shape,根据 textAlign 调整位置后,直接将其加入 labelGroup
const { width, height } = content.getCanvasBBox();
const textAlign = cfg.textAlign || 'left';
const textAlign = get(cfg, 'textAlign', 'left');

let x = cfg.x;
const y = cfg.y - (height / 2);
Expand All @@ -187,6 +195,7 @@ export default class Labels {
x: cfg.x,
y: cfg.y,
textAlign: cfg.textAlign,
textBaseline: get(cfg, 'textBaseline', 'middle'),
text: cfg.content,
...cfg.style,
},
Expand All @@ -197,12 +206,11 @@ export default class Labels {
if (cfg.rotate) {
rotate(labelShape, cfg.rotate);
}
this.drawLabelLine(cfg, labelGroup);
this.shapesMap[id] = labelGroup;
}

// 根据type对label布局
private adjustLabels(shapes) {
private doLayout(items: LabelItem[], shapes: Record<string, IShape | IGroup>) {
if (this.layout) {
const layouts = isArray(this.layout) ? this.layout : [this.layout];
each(layouts, (layout: GeometryLabelLayoutCfg) => {
Expand All @@ -215,38 +223,47 @@ export default class Labels {
geometryShapes.push(shapes[id]);
});

layoutFn(labelShapes, geometryShapes, this.region, layout.cfg);
layoutFn(items, labelShapes, geometryShapes, this.region, layout.cfg);
}
});
}
}

private drawLabelLine(labelCfg: LabelItem, container: IGroup) {
if (!labelCfg.labelLine) {
// labelLine: null | false,关闭 label 对应的 labelLine
return;
}
const labelLineCfg = get(labelCfg, 'labelLine', {});
let path = labelLineCfg.path;
if (!path) {
const start = labelCfg.start;
path = [
['M', start.x, start.y],
['L', labelCfg.x, labelCfg.y],
];
}
container.addShape('path', {
capture: false, // labelLine 默认不参与事件捕获
attrs: {
path,
stroke: labelCfg.color ? labelCfg.color : get(labelCfg, ['style', 'fill'], '#000'),
fill: null,
...labelLineCfg.style,
},
id: labelCfg.id,
origin: labelCfg.mappingData,
data: labelCfg.data,
coordinate: labelCfg.coordinate,
private renderLabelLine(labelItems: LabelItem[]) {
each(labelItems, (labelItem) => {
if (!labelItem) {
return;
}
if (!labelItem.labelLine) {
// labelLine: null | false,关闭 label 对应的 labelLine
return;
}
const labelLineCfg = get(labelItem, 'labelLine', {});
const id = labelItem.id;
let path = labelLineCfg.path;
if (!path) {
const start = labelItem.start;
path = [
['M', start.x, start.y],
['L', labelItem.x, labelItem.y],
];
}
const labelGroup = this.shapesMap[id];
if (!labelGroup.destroyed) {
labelGroup.addShape('path', {
capture: false, // labelLine 默认不参与事件捕获
attrs: {
path,
stroke: labelItem.color ? labelItem.color : get(labelItem, ['style', 'fill'], '#000'),
fill: null,
...labelLineCfg.style,
},
id,
origin: labelItem.mappingData,
data: labelItem.data,
coordinate: labelItem.coordinate,
});
}
});
}

Expand All @@ -256,4 +273,24 @@ export default class Labels {
const newGroup = new GroupClass({});
return newGroup;
}

private adjustLabel(items: LabelItem[]) {
each(items, (item) => {
if (item) {
const id = item.id;
const labelGroup = this.shapesMap[id];
if (!labelGroup.destroyed) {
const labelShape = labelGroup.find(ele => ele.get('type') === 'text');
if (labelShape) {
if (item.offsetX) {
labelShape.attr('x', labelShape.attr('x') + item.offsetX);
}
if (item.offsetY) {
labelShape.attr('y', labelShape.attr('y') + item.offsetY);
}
}
}
}
});
}
}
97 changes: 40 additions & 57 deletions src/geometry/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import { getShapeFactory } from './shape/base';
import { group } from './util/group-data';
import { isModelChange } from './util/is-model-change';
import { parseFields } from './util/parse-fields';
import GeometryLabel from './label/base';

/** @ignore */
interface AttributeInstanceCfg {
Expand Down Expand Up @@ -192,17 +193,17 @@ export default class Geometry extends Base {
protected lastElementsMap: Record<string, Element> = {};
/** 是否生成多个点来绘制图形。 */
protected generatePoints: boolean = false;
/** 虚拟 Group,用于图形更新 */
protected offscreenGroup: IGroup;
/** 存储发生图形属性映射前的数据 */
protected beforeMappingData: Data[] = null;
/** 存储每个 shape 的默认 size,用于 Interval、Schema 几何标记 */
protected defaultSize: number;

private adjusts: Record<string, Adjust> = {};
private lastAttributeOption;
private labelsRenderer: Labels;
private idFields: string[] = [];
private geometryLabel: GeometryLabel;
/** 虚拟 Group,用于图形更新 */
private offscreenGroup: IGroup;

/**
* 创建 Geometry 实例。
Expand Down Expand Up @@ -875,13 +876,13 @@ export default class Geometry extends Base {
* @override
*/
public clear() {
const { container, labelsRenderer } = this;
const { container, geometryLabel } = this;
if (container) {
container.clear();
}

if (labelsRenderer) {
labelsRenderer.clear();
if (geometryLabel) {
geometryLabel.clear();
}

// 属性恢复至出厂状态
Expand Down Expand Up @@ -910,9 +911,9 @@ export default class Geometry extends Base {
this.offscreenGroup = null;
}

if (this.labelsRenderer) {
this.labelsRenderer.destroy();
this.labelsRenderer = null;
if (this.geometryLabel) {
this.geometryLabel.destroy();
this.geometryLabel = null;
}
super.destroy();
}
Expand Down Expand Up @@ -1176,6 +1177,18 @@ export default class Geometry extends Base {
return this.elements.map((element: Element) => element.shape);
}

/**
* 获取虚拟 Group。
* @returns
*/
public getOffscreenGroup() {
if (!this.offscreenGroup) {
const GroupCtor = this.container.getGroupBase(); // 获取分组的构造函数
this.offscreenGroup = new GroupCtor({});
}
return this.offscreenGroup;
}

/**
* 调整度量范围。主要针对发生层叠以及一些特殊需求的 Geometry,比如 Interval 下的柱状图 Y 轴默认从 0 开始。
*/
Expand Down Expand Up @@ -1318,34 +1331,25 @@ export default class Geometry extends Base {
return elements;
}

/**
* 获取虚拟 Group。
* @returns
*/
protected getOffscreenGroup() {
if (!this.offscreenGroup) {
const GroupCtor = this.container.getGroupBase(); // 获取分组的构造函数
this.offscreenGroup = new GroupCtor({});
}
return this.offscreenGroup;
}

/**
* 获取渲染的 label 类型。
*/
protected getLabelType(): string {
const { labelOption, coordinate, type } = this;
const coordinateType = coordinate.type;
let labelType = get(labelOption, ['cfg', 'type']) || 'base';
if (labelType === 'base') {
let labelType = get(labelOption, ['cfg', 'type']);
if (!labelType) {
// 用户未定义,则进行默认的逻辑
if (coordinateType === 'polar') {
// 极坐标文本
// 极坐标下使用通用的极坐标文本
labelType = 'polar';
} else if (coordinateType === 'theta') {
// 饼图文本
// theta 坐标系下使用饼图文本
labelType = 'pie';
} else if (type === 'interval' || type === 'polygon') {
labelType = 'interval';
} else {
labelType = 'base';
}
}

Expand Down Expand Up @@ -1803,40 +1807,19 @@ export default class Geometry extends Base {
}

private renderLabels(mappingArray: MappingDatum[], isUpdate: boolean = false) {
const { labelOption, animateOption, coordinate } = this;
const labelType = this.getLabelType();

const GeometryLabelsCtor = getGeometryLabel(labelType);
const geometryLabels = new GeometryLabelsCtor(this);
const labelItems = geometryLabels.getLabelItems(mappingArray);

let labelsRenderer = this.labelsRenderer;
if (!labelsRenderer) {
labelsRenderer = new Labels({
container: this.labelsContainer,
layout: get(labelOption, ['cfg', 'layout']),
});
this.labelsRenderer = labelsRenderer;
let geometryLabel = this.geometryLabel;

if (!geometryLabel) {
// 初次创建
const labelType = this.getLabelType();
const GeometryLabelsCtor = getGeometryLabel(labelType);
geometryLabel = new GeometryLabelsCtor(this);
this.geometryLabel = geometryLabel;
}
labelsRenderer.region = this.canvasRegion;

const shapes = {};
each(this.elementsMap, (element: Element, id: string) => {
shapes[id] = element.shape;
});
// 因为有可能 shape 还在进行动画,导致 shape.getBBox() 获取到的值不是最终态,所以需要从 offscreenGroup 获取
each(this.offscreenGroup.getChildren(), (child) => {
const id = this.getElementId(child.get('origin').mappingData);
shapes[id] = child;
});

// 设置动画配置,如果 geometry 的动画关闭了,那么 label 的动画也会关闭
labelsRenderer.animate = animateOption ? getDefaultAnimateCfg('label', coordinate) : false;

// 渲染文本
labelsRenderer.render(labelItems, shapes, isUpdate);
geometryLabel.render(mappingArray, isUpdate);

const labelsMap = this.labelsRenderer.shapesMap;
// 将 label 同 element 进行关联
const labelsMap = geometryLabel.labelsRenderer.shapesMap;
each(this.elementsMap, (element: Element, id) => {
const labels = filterLabelsById(id, labelsMap); // element 实例同 label 进行绑定
element.labelShape = labels;
Expand Down
Loading

0 comments on commit cfe9e3f

Please sign in to comment.