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

优化 label 类以及修复相关 BUG #2143

Merged
merged 16 commits into from
Mar 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
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'),
visiky marked this conversation as resolved.
Show resolved Hide resolved
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