From e03dcc079c78a98e932de0155a209cbf5cdf1dcc Mon Sep 17 00:00:00 2001 From: simaQ Date: Wed, 11 Mar 2020 22:47:04 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=20GeometryLabel?= =?UTF-8?q?=E3=80=82=E5=B0=86=20labels=20=E7=BB=84=E4=BB=B6=E7=9A=84?= =?UTF-8?q?=E7=94=9F=E6=88=90=E6=B8=B2=E6=9F=93=E7=A7=BB=E5=85=A5=20Geomtr?= =?UTF-8?q?yLabel=20=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/geometry/base.ts | 97 +++++++++------------- src/geometry/label/base.ts | 100 ++++++++++++++++++++--- src/geometry/label/interval.ts | 3 +- src/geometry/label/pie.ts | 16 ++-- src/geometry/label/polar.ts | 8 +- tests/unit/geometry/label/labels-spec.ts | 8 +- 6 files changed, 147 insertions(+), 85 deletions(-) diff --git a/src/geometry/base.ts b/src/geometry/base.ts index 9384f75ca1..ce9ef1e617 100644 --- a/src/geometry/base.ts +++ b/src/geometry/base.ts @@ -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 { @@ -190,8 +191,6 @@ export default class Geometry extends Base { protected lastElementsMap: Record = {}; /** 是否生成多个点来绘制图形。 */ protected generatePoints: boolean = false; - /** 虚拟 Group,用于图形更新 */ - protected offscreenGroup: IGroup; /** 存储发生图形属性映射前的数据 */ protected beforeMappingData: Data[] = null; /** 存储每个 shape 的默认 size,用于 Interval、Schema 几何标记 */ @@ -199,7 +198,9 @@ export default class Geometry extends Base { private adjusts: Record = {}; private lastAttributeOption; - private labelsRenderer: Labels; + private geometryLabel: GeometryLabel; + /** 虚拟 Group,用于图形更新 */ + private offscreenGroup: IGroup; /** * 创建 Geometry 实例。 @@ -871,13 +872,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(); } // 属性恢复至出厂状态 @@ -905,9 +906,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(); } @@ -1160,6 +1161,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 开始。 */ @@ -1300,34 +1313,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'; } } @@ -1779,40 +1783,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; diff --git a/src/geometry/label/base.ts b/src/geometry/label/base.ts index 21f14c4a42..a0e8d5e0c7 100644 --- a/src/geometry/label/base.ts +++ b/src/geometry/label/base.ts @@ -1,10 +1,16 @@ import { deepMix, each, get, isArray, isFunction, isNil, isNumber, isUndefined } from '@antv/util'; + import { FIELD_ORIGIN } from '../../constant'; import { Coordinate, Scale } from '../../dependents'; import { Datum, LabelOption, LooseObject, MappingDatum, Point } from '../../interface'; +import { LabelCfg, LabelItem, LabelPointCfg } from './interface'; + +import { getDefaultAnimateCfg } from '../../animate'; import { getPolygonCentroid } from '../../util/graphics'; + +import Labels from '../../component/labels'; import Geometry from '../base'; -import { LabelCfg, LabelItem, LabelPointCfg } from './interface'; +import Element from '../element'; export type GeometryLabelConstructor = new (cfg: any) => GeometryLabel; @@ -22,16 +28,42 @@ function avg(arr: number[]) { export default class GeometryLabel { /** geometry 实例 */ public readonly geometry: Geometry; - /** 坐标系实例 */ - protected coordinate: Coordinate; - /** 默认的 label 配置 */ - protected defaultLabelCfg: LooseObject; + public labelsRenderer: Labels; constructor(geometry: Geometry) { this.geometry = geometry; + } + + public render(mapppingArray: MappingDatum[], isUpdate: boolean) { + let labelItems = this.getItems(mapppingArray); + labelItems = this.adjustItems(labelItems); + + this.drawLines(labelItems); + + const labelsRenderer = this.getLabelsRenderer(); + const shapes = this.getGeometryShapes(); + // 渲染文本 + labelsRenderer.render(labelItems, shapes, isUpdate); + } + + public clear() { + const labelsRenderer = this.labelsRenderer; + if (labelsRenderer) { + labelsRenderer.clear(); + } + } + + public destroy() { + const labelsRenderer = this.labelsRenderer; + if (labelsRenderer) { + labelsRenderer.destroy(); + } + this.labelsRenderer = null; + } - this.coordinate = geometry.coordinate; - this.defaultLabelCfg = get(geometry.theme, 'labels', {}); // 默认样式 + // geometry 更新之后,对应的 Coordinate 也会更新,为了获取到最新鲜的 Coordinate,故使用方法获取 + public getCoordinate() { + return this.geometry.coordinate; } /** @@ -46,6 +78,13 @@ export default class GeometryLabel { return items; } + /** + * 获取 label 的默认配置 + */ + protected getDefaultLabelCfg() { + return get(this.geometry.theme, 'labels', {}); + } + /** * 设置 label 位置 * @param labelPointCfg @@ -113,7 +152,7 @@ export default class GeometryLabel { * @returns */ protected getDefaultOffset(offset: number) { - const coordinate = this.coordinate; + const coordinate = this.getCoordinate(); const vector = this.getOffsetVector(offset); return coordinate.isTransposed ? vector[0] : vector[1]; } @@ -127,7 +166,7 @@ export default class GeometryLabel { */ protected getLabelOffset(labelCfg: LabelCfg, index: number, total: number) { const offset = this.getDefaultOffset(labelCfg.offset); - const coordinate = this.coordinate; + const coordinate = this.getCoordinate(); const transposed = coordinate.isTransposed; const dim = transposed ? 'x' : 'y'; const factor = transposed ? 1 : -1; // y 方向上越大,像素的坐标越小,所以transposed时将系数变成 @@ -152,7 +191,7 @@ export default class GeometryLabel { * @returns label point */ protected getLabelPoint(labelCfg: LabelCfg, mappingData: MappingDatum, index: number): LabelPointCfg { - const coordinate = this.coordinate; + const coordinate = this.getCoordinate(); const total = labelCfg.content.length; function getDimValue(value, idx) { @@ -239,7 +278,7 @@ export default class GeometryLabel { */ protected getLabelAlign(item: LabelItem, index: number, total: number) { let align = 'center'; - const coordinate = this.coordinate; + const coordinate = this.getCoordinate(); if (coordinate.isTransposed) { const offset = this.getDefaultOffset(item.offset); if (offset < 0) { @@ -283,6 +322,26 @@ export default class GeometryLabel { return labelId; } + // 获取 labels 组件 + private getLabelsRenderer() { + const { labelsContainer, labelOption, canvasRegion, animateOption } = this.geometry; + const coordinate = this.geometry.coordinate; + + let labelsRenderer = this.labelsRenderer; + if (!labelsRenderer) { + labelsRenderer = new Labels({ + container: labelsContainer, + layout: get(labelOption, ['cfg', 'layout']), + }); + this.labelsRenderer = labelsRenderer; + } + labelsRenderer.region = canvasRegion; + // 设置动画配置,如果 geometry 的动画关闭了,那么 label 的动画也会关闭 + labelsRenderer.animate = animateOption ? getDefaultAnimateCfg('label', coordinate) : false; + + return labelsRenderer; + } + private getItems(mapppingArray: MappingDatum[]): LabelItem[] { const items = []; const labelCfgs = this.getLabelCfgs(mapppingArray); @@ -319,7 +378,7 @@ export default class GeometryLabel { private getLabelCfgs(mapppingArray: MappingDatum[]): LabelCfg[] { const geometry = this.geometry; - const defaultLabelCfg = this.defaultLabelCfg; + const defaultLabelCfg = this.getDefaultLabelCfg(); const { type, theme, labelOption, scales, coordinate } = geometry; const { fields, callback, cfg } = labelOption as LabelOption; const labelScales = fields.map((field: string) => { @@ -397,8 +456,23 @@ export default class GeometryLabel { } private getOffsetVector(offset = 0) { - const coordinate = this.coordinate; + const coordinate = this.getCoordinate(); // 如果 x,y 翻转,则偏移 x,否则偏移 y return coordinate.isTransposed ? coordinate.applyMatrix(offset, 0) : coordinate.applyMatrix(0, offset); } + + private getGeometryShapes() { + const geometry = this.geometry; + const shapes = {}; + each(geometry.elementsMap, (element: Element, id: string) => { + shapes[id] = element.shape; + }); + // 因为有可能 shape 还在进行动画,导致 shape.getBBox() 获取到的值不是最终态,所以需要从 offscreenGroup 获取 + each(geometry.getOffscreenGroup().getChildren(), (child) => { + const id = geometry.getElementId(child.get('origin').mappingData); + shapes[id] = child; + }); + + return shapes; + } } diff --git a/src/geometry/label/interval.ts b/src/geometry/label/interval.ts index 4807e37fe1..33d364c774 100644 --- a/src/geometry/label/interval.ts +++ b/src/geometry/label/interval.ts @@ -8,8 +8,9 @@ import { LabelPointCfg } from './interface'; * 柱状图 label */ export default class IntervalLabel extends GeometryLabel { + protected setLabelPosition(labelPointCfg: LabelPointCfg, mappingData: MappingDatum, index: number, position: string) { - const coordinate = this.coordinate; + const coordinate = this.getCoordinate(); const transposed = coordinate.isTransposed; const shapePoints = mappingData.points as Point[]; const point0 = coordinate.convert(shapePoints[0]); diff --git a/src/geometry/label/pie.ts b/src/geometry/label/pie.ts index bb85cd1068..8f8792fc30 100644 --- a/src/geometry/label/pie.ts +++ b/src/geometry/label/pie.ts @@ -103,8 +103,12 @@ function antiCollision(labels, lineHeight, plotRange, center, isRight) { export default class PieLabel extends PolarLabel { constructor(geometry: Geometry) { super(geometry); - this.defaultLabelCfg = get(geometry.theme, 'pieLabels', {}); } + + protected getDefaultLabelCfg() { + return get(this.geometry.theme, 'pieLabels', {}); + } + protected getDefaultOffset(offset) { return offset || 0; } @@ -119,7 +123,7 @@ export default class PieLabel extends PolarLabel { // 连接线 protected lineToLabel(label: LabelItem) { - const coordinate = this.coordinate; + const coordinate = this.getCoordinate(); // @ts-ignore const r = coordinate.getRadius(); const distance = label.offset; @@ -154,7 +158,7 @@ export default class PieLabel extends PolarLabel { } protected getLabelAlign(point: LabelItem) { - const coordinate = this.coordinate; + const coordinate = this.getCoordinate(); const center = coordinate.getCenter(); let align; @@ -179,7 +183,7 @@ export default class PieLabel extends PolarLabel { } protected getPointAngle(point) { - const coordinate = this.coordinate; + const coordinate = this.getCoordinate(); const startPoint = { x: isArray(point.x) ? point.x[0] : point.x, y: point.y[0], @@ -204,7 +208,7 @@ export default class PieLabel extends PolarLabel { } protected getCirclePoint(angle, offset, p?) { - const coordinate = this.coordinate; + const coordinate = this.getCoordinate(); const center = coordinate.getCenter(); // @ts-ignore const r = coordinate.getRadius() + offset; @@ -217,7 +221,7 @@ export default class PieLabel extends PolarLabel { // distribute labels private distribute(labels, offset) { - const coordinate = this.coordinate; + const coordinate = this.getCoordinate(); // @ts-ignore const radius = coordinate.getRadius(); const lineHeight = get(this.geometry.theme, ['pieLabels', 'labelHeight'], 14); diff --git a/src/geometry/label/polar.ts b/src/geometry/label/polar.ts index e67fe9cd80..f4cf88b675 100644 --- a/src/geometry/label/polar.ts +++ b/src/geometry/label/polar.ts @@ -17,7 +17,7 @@ export default class PolarLabel extends GeometryLabel { * @param point */ protected getLabelAlign(point: LabelItem) { - const coordinate = this.coordinate; + const coordinate = this.getCoordinate(); let align; if (point.labelEmit) { align = (point.angle <= Math.PI / 2 && point.angle > -Math.PI / 2) ? 'left' : 'right'; @@ -104,7 +104,7 @@ export default class PolarLabel extends GeometryLabel { * @param point */ protected getPointAngle(point: Point): number { - return getAngleByPoint(this.coordinate, point); + return getAngleByPoint(this.getCoordinate(), point); } /** @@ -115,7 +115,7 @@ export default class PolarLabel extends GeometryLabel { * @param isLabelEmit */ protected getCirclePoint(angle: number, offset: number, point: Point, isLabelEmit: boolean) { - const coordinate = this.coordinate; + const coordinate = this.getCoordinate(); const center = coordinate.getCenter(); let r = getDistanceToCenter(coordinate, point); if (r === 0) { @@ -163,7 +163,7 @@ export default class PolarLabel extends GeometryLabel { // 获取中心的位置 private getMiddlePoint(points: Point[]) { - const coordinate = this.coordinate; + const coordinate = this.getCoordinate(); const count = points.length; let middlePoint = { x: 0, diff --git a/tests/unit/geometry/label/labels-spec.ts b/tests/unit/geometry/label/labels-spec.ts index b5edc75333..b1aa800f1e 100644 --- a/tests/unit/geometry/label/labels-spec.ts +++ b/tests/unit/geometry/label/labels-spec.ts @@ -82,7 +82,7 @@ describe('LabelsRenderer', () => { it('render', () => { expect(interval.labelsContainer.getCount()).toBe(3); // @ts-ignore - const labelsRenderer = interval.labelsRenderer; + const labelsRenderer = interval.geometryLabel.labelsRenderer; expect(labelsRenderer.container.getCount()).toBe(3); // @ts-ignore expect(labelsRenderer.container.getFirst().getCount()).toBe(2); @@ -113,7 +113,7 @@ describe('LabelsRenderer', () => { interval.paint(); // @ts-ignore - const labelsRenderer = interval.labelsRenderer; + const labelsRenderer = interval.geometryLabel.labelsRenderer; expect(labelsRenderer.container.getCount()).toBe(2); expect(labelsRenderer.container.getFirst().get('data')).toEqual({ a: '1', percent: 0.5 }); expect(labelsRenderer.container.getFirst().get('animateCfg').update).toBe(false); @@ -126,7 +126,7 @@ describe('LabelsRenderer', () => { it('clear', () => { // @ts-ignore - const labelsRenderer = interval.labelsRenderer; + const labelsRenderer = interval.geometryLabel.labelsRenderer; labelsRenderer.clear(); expect(interval.labelsContainer.getCount()).toBe(0); @@ -137,7 +137,7 @@ describe('LabelsRenderer', () => { it('destroy', () => { // @ts-ignore - const labelsRenderer = interval.labelsRenderer; + const labelsRenderer = interval.geometryLabel.labelsRenderer; labelsRenderer.destroy(); expect(interval.labelsContainer.destroyed).toBe(true);