From 095581a3b30b456c38e41d3e5a05f227622b8152 Mon Sep 17 00:00:00 2001 From: Yuxin <55794321+yvonneyx@users.noreply.github.com> Date: Tue, 20 Feb 2024 12:51:49 +0800 Subject: [PATCH] feat: edge supports `portLinkToCenter` attributes (#5447) * feat(port): add linkToCenter attribute * fix: ci * refactor: rename getCurveControlPoint function * refactor: update snapshots related to port --------- Co-authored-by: banxuan.zyx --- .../snapshots/static/edge-port.svg | 56 +++++++++---------- .../snapshots/static/node-circle.svg | 6 +- .../snapshots/static/node-ellipse.svg | 6 +- .../snapshots/static/node-rect.svg | 6 +- .../snapshots/static/node-star.svg | 6 +- .../snapshots/static/node-triangle.svg | 6 +- packages/g6/src/elements/edges/base-edge.ts | 13 ++--- packages/g6/src/elements/edges/cubic.ts | 6 +- packages/g6/src/elements/edges/line.ts | 2 +- packages/g6/src/elements/nodes/base-node.ts | 28 ++++++---- packages/g6/src/elements/nodes/star.ts | 2 +- packages/g6/src/elements/nodes/triangle.ts | 2 +- packages/g6/src/types/element.ts | 2 +- packages/g6/src/types/node.ts | 11 ++++ packages/g6/src/utils/edge.ts | 53 +++++------------- packages/g6/src/utils/element.ts | 40 +++++++++++-- packages/g6/src/utils/is.ts | 17 ++++++ 17 files changed, 151 insertions(+), 111 deletions(-) diff --git a/packages/g6/__tests__/integration/snapshots/static/edge-port.svg b/packages/g6/__tests__/integration/snapshots/static/edge-port.svg index 2f8488b518f..6cad3f7b35c 100644 --- a/packages/g6/__tests__/integration/snapshots/static/edge-port.svg +++ b/packages/g6/__tests__/integration/snapshots/static/edge-port.svg @@ -238,15 +238,15 @@ marker-end="true" transform="matrix(1,0,0,1,0,0)" > - + - + - + @@ -384,15 +384,15 @@ marker-end="true" transform="matrix(1,0,0,1,0,0)" > - + - + - + @@ -529,15 +529,15 @@ marker-end="true" transform="matrix(1,0,0,1,0,0)" > - + - + - + @@ -674,15 +674,15 @@ marker-end="true" transform="matrix(1,0,0,1,0,0)" > - + - + - + diff --git a/packages/g6/__tests__/integration/snapshots/static/node-circle.svg b/packages/g6/__tests__/integration/snapshots/static/node-circle.svg index ab87c3a1d21..9740b59b12c 100644 --- a/packages/g6/__tests__/integration/snapshots/static/node-circle.svg +++ b/packages/g6/__tests__/integration/snapshots/static/node-circle.svg @@ -429,14 +429,14 @@ marker-end="true" transform="matrix(1,0,0,1,0,0)" > - + - + - + - + - + - + - + - + - + - + > extends BaseSha const targetPort = findPort(targetNode, targetPortKey, sourceNode); const sourcePoint = sourcePort - ? getEllipseIntersectPoint(targetNode.getCenter(), sourcePort.getBounds()) - : sourceNode.getIntersectPoint(targetPort?.getPosition() || targetNode.getCenter()); + ? getPortConnectionPoint(sourcePort, targetNode, targetPort) + : getNodeConnectionPoint(sourceNode, targetNode, targetPort); const targetPoint = targetPort - ? getEllipseIntersectPoint(sourceNode.getCenter(), targetPort.getBounds()) - : targetNode.getIntersectPoint(sourcePort?.getPosition() || sourceNode.getCenter()); + ? getPortConnectionPoint(targetPort, sourceNode, sourcePort) + : getNodeConnectionPoint(targetNode, sourceNode, sourcePort); - return [sourcePoint || sourceNode.getCenter(), targetPoint || targetNode.getCenter()]; + return [sourcePoint, targetPoint]; } protected getHaloStyle(attributes: ParsedBaseEdgeStyleProps): false | PathStyleProps { diff --git a/packages/g6/src/elements/edges/cubic.ts b/packages/g6/src/elements/edges/cubic.ts index 815e1fc1c3a..a2ac3695ba6 100644 --- a/packages/g6/src/elements/edges/cubic.ts +++ b/packages/g6/src/elements/edges/cubic.ts @@ -2,7 +2,7 @@ import type { DisplayObjectConfig } from '@antv/g'; import type { PathArray } from '@antv/util'; import { deepMix } from '@antv/util'; import type { BaseEdgeProps, Point } from '../../types'; -import { calculateControlPoint, getCubicPath, parseCurveOffset, parseCurvePosition } from '../../utils/edge'; +import { getCubicPath, getCurveControlPoint, parseCurveOffset, parseCurvePosition } from '../../utils/edge'; import type { BaseEdgeStyleProps } from './base-edge'; import { BaseEdge } from './base-edge'; @@ -61,8 +61,8 @@ export class Cubic extends BaseEdge { return controlPoints?.length === 2 ? controlPoints : [ - calculateControlPoint(sourcePoint, targetPoint, curvePosition[0], curveOffset[0]), - calculateControlPoint(sourcePoint, targetPoint, curvePosition[1], curveOffset[1]), + getCurveControlPoint(sourcePoint, targetPoint, curvePosition[0], curveOffset[0]), + getCurveControlPoint(sourcePoint, targetPoint, curvePosition[1], curveOffset[1]), ]; } } diff --git a/packages/g6/src/elements/edges/line.ts b/packages/g6/src/elements/edges/line.ts index 1d390aa8323..051cdbf5520 100644 --- a/packages/g6/src/elements/edges/line.ts +++ b/packages/g6/src/elements/edges/line.ts @@ -5,7 +5,7 @@ import type { BaseEdgeProps } from '../../types'; import type { BaseEdgeStyleProps, ParsedBaseEdgeStyleProps } from './base-edge'; import { BaseEdge } from './base-edge'; -type LineKeyStyleProps = BaseEdgeProps<{}>; +type LineKeyStyleProps = BaseEdgeProps; export type LineStyleProps = BaseEdgeStyleProps; type LineOptions = DisplayObjectConfig; diff --git a/packages/g6/src/elements/nodes/base-node.ts b/packages/g6/src/elements/nodes/base-node.ts index 1c5aa75c2c7..c38a6f31384 100644 --- a/packages/g6/src/elements/nodes/base-node.ts +++ b/packages/g6/src/elements/nodes/base-node.ts @@ -1,4 +1,4 @@ -import type { DisplayObject, DisplayObjectConfig, CircleStyleProps as GCircleStyleProps, Group } from '@antv/g'; +import type { DisplayObject, DisplayObjectConfig, Group } from '@antv/g'; import { Circle as GCircle } from '@antv/g'; import { deepMix, isEmpty } from '@antv/util'; import type { @@ -8,7 +8,9 @@ import type { Keyframe, LabelPosition, Point, + Port, PortPosition, + PortStyleProps, PrefixObject, } from '../../types'; import { getPortPosition, getTextStyleByPosition, getXYByPosition } from '../../utils/element'; @@ -24,7 +26,7 @@ type NodeBadgesStyleProps = { badges?: NodeBadgeStyleProps[]; badgeZIndex?: number; }; -export type NodePortStyleProps = Partial & { +export type NodePortStyleProps = PortStyleProps & { key?: string; position: string | [number, number]; width?: number; @@ -33,6 +35,7 @@ export type NodePortStyleProps = Partial & { type NodePortsStyleProps = { ports?: NodePortStyleProps[]; portZIndex?: number; + portLinkToCenter?: boolean; }; type NodeIconStyleProps = IconStyleProps; @@ -80,6 +83,7 @@ export abstract class BaseNode< port: true, ports: [], portZIndex: 2, + portLinkToCenter: false, badge: true, badges: [], badgeZIndex: 3, @@ -179,9 +183,9 @@ export abstract class BaseNode< return { ...textStyle, ...restStyle }; } - protected getPortsStyle(attributes: ParsedBaseNodeStyleProps

): Record { + protected getPortsStyle(attributes: ParsedBaseNodeStyleProps

): Record { const ports = this.getPorts(); - const portsStyle: Record = {}; + const portsStyle: Record = {}; Object.keys(ports).forEach((key) => { portsStyle[key] = false; @@ -189,14 +193,18 @@ export abstract class BaseNode< if (attributes.port === false || isEmpty(attributes.ports)) return portsStyle; - const { ports: portOptions = [], portZIndex } = attributes; + const { ports: portOptions = [], portZIndex, portLinkToCenter } = attributes; portOptions.forEach((option, i) => { - portsStyle[option.key || i] = { zIndex: portZIndex, ...this.getPortStyle(attributes, option) }; + portsStyle[option.key || i] = { + zIndex: portZIndex, + linkToCenter: portLinkToCenter, + ...this.getPortStyle(attributes, option), + }; }); return portsStyle; } - protected getPortStyle(attributes: ParsedBaseNodeStyleProps

, style: NodePortStyleProps): GCircleStyleProps { + protected getPortStyle(attributes: ParsedBaseNodeStyleProps

, style: NodePortStyleProps): PortStyleProps { const { position = 'left', width = 8, height = 8, ...restStyle } = style; const bounds = this.getKey().getLocalBounds(); const r = Math.min(width, height) / 2; @@ -216,7 +224,7 @@ export abstract class BaseNode< * Get the ports for the node. * @returns Ports shape map. */ - public getPorts(): Record { + public getPorts(): Record { return subObject(this.shapeMap, 'port-'); } @@ -229,9 +237,9 @@ export abstract class BaseNode< } /** - * Whether the point is intersected with the node. + * Get the point on the outer contour of the node that is the intersection with a line starting in the center the ending in the point `p`. * @param point - The point to intersect with the node. - * @returns boolean + * @returns The intersection point. */ public getIntersectPoint(point: Point): Point { const keyShapeBounds = this.getKey().getBounds(); diff --git a/packages/g6/src/elements/nodes/star.ts b/packages/g6/src/elements/nodes/star.ts index a6fca97c284..75160a164f9 100644 --- a/packages/g6/src/elements/nodes/star.ts +++ b/packages/g6/src/elements/nodes/star.ts @@ -39,6 +39,6 @@ export class Star extends Polygon { const bbox = this.getKey().getLocalBounds(); const ports = getStarPorts(this.getOuterR(attributes), this.getInnerR(attributes)); const [cx, cy] = getPortPosition(bbox, position as StarPortPosition, ports, false); - return { cx, cy, r, ...restStyle }; + return Object.assign({ cx, cy, r }, restStyle); } } diff --git a/packages/g6/src/elements/nodes/triangle.ts b/packages/g6/src/elements/nodes/triangle.ts index 81a5f697ebe..9cc811fb239 100644 --- a/packages/g6/src/elements/nodes/triangle.ts +++ b/packages/g6/src/elements/nodes/triangle.ts @@ -43,7 +43,7 @@ export class Triangle extends Polygon { const ports = getTrianglePorts(width, height, direction); const [cx, cy] = getPortPosition(bbox, position as TrianglePortPosition, ports, false); const r = Math.min(anchorWidth, anchorHeight) / 2; - return { cx, cy, r, ...restStyle }; + return Object.assign({ cx, cy, r }, restStyle); } // icon 处于三角形的重心 diff --git a/packages/g6/src/types/element.ts b/packages/g6/src/types/element.ts index f7d0bb40a47..5719abd94f0 100644 --- a/packages/g6/src/types/element.ts +++ b/packages/g6/src/types/element.ts @@ -53,7 +53,7 @@ export type BaseNodeProps = BaseElementProps & { } & BaseStyleProps & ShapeProps; -export type BaseEdgeProps = BaseElementProps & { +export type BaseEdgeProps = BaseElementProps & { /** * 边的起点 shape * The source shape. Represents the start of the edge diff --git a/packages/g6/src/types/node.ts b/packages/g6/src/types/node.ts index 6c52df2de2e..2e0e620322d 100644 --- a/packages/g6/src/types/node.ts +++ b/packages/g6/src/types/node.ts @@ -1,3 +1,5 @@ +import type { DisplayObject, CircleStyleProps as GCircleStyleProps } from '@antv/g'; + export type RelativePosition = | 'top' | 'top-left' @@ -19,3 +21,12 @@ export type TrianglePortPosition = [number, number] | 'top' | 'left' | 'right' | export type BadgePosition = RelativePosition; export type LabelPosition = RelativePosition; + +export type PortStyleProps = GCircleStyleProps & { + /** + * 是否连接到中心 + * Whether to connect to the center + */ + linkToCenter?: boolean; +}; +export type Port = DisplayObject; diff --git a/packages/g6/src/utils/edge.ts b/packages/g6/src/utils/edge.ts index c1612a46f56..515b8e38c72 100644 --- a/packages/g6/src/utils/edge.ts +++ b/packages/g6/src/utils/edge.ts @@ -3,7 +3,8 @@ import type { PathArray } from '@antv/util'; import { isEqual, isNumber } from '@antv/util'; import type { EdgeKey, EdgeLabelPosition, EdgeLabelStyleProps, LoopEdgePosition, Node, Point, Vector2 } from '../types'; import { getBBoxHeight, getBBoxWidth } from './bbox'; -import { getEllipseIntersectPoint, isCollinear, isHorizontal, parsePoint } from './point'; +import { getPortConnectionPoint } from './element'; +import { isCollinear, isHorizontal, parsePoint } from './point'; import { add, distance, @@ -116,8 +117,7 @@ export function getQuadraticPath( curveOffset: number, controlPoint?: Point, ): PathArray { - const actualControlPoint = - controlPoint || calculateControlPoint(sourcePoint, targetPoint, curvePosition, curveOffset); + const actualControlPoint = controlPoint || getCurveControlPoint(sourcePoint, targetPoint, curvePosition, curveOffset); return [ ['M', sourcePoint[0], sourcePoint[1]], @@ -128,14 +128,14 @@ export function getQuadraticPath( /** * 计算曲线的控制点 * - * Calculate the control point of Quadratic Bessel curve + * Calculate the control point of the curve * @param sourcePoint - 起点 | Source point * @param targetPoint - 终点 | Target point * @param curvePosition - 控制点在连线上的相对位置(取值范围为 0-1) | The relative position of the control point on the line (value range from 0 to 1) * @param curveOffset - 控制点距离两端点连线的距离 | The distance between the control point and the line * @returns 控制点 | Control points */ -export function calculateControlPoint( +export function getCurveControlPoint( sourcePoint: Point, targetPoint: Point, curvePosition: number, @@ -366,34 +366,6 @@ export function getLoopControlPoints( return [extendVector(center, sourcePoint, offset), extendVector(center, targetPoint, offset)]; } -/** - * 若存在起点或终点的锚点,则调整起点或终点位置 - * - * Adjust the start or end point position if there is an port point - * @param sourcePoint - 起点 | Source point - * @param targetPoint - 终点 | Target point - * @param controlPoints - 控制点 | Control point - * @param sourcePort - 起点锚点 | Source port - * @param targetPort - 终点锚点 | Target port - * @returns 调整后的起点和终点 | Adjusted start and end points - */ -export function adjustLoopEndpointsIfNeed( - sourcePoint: Point, - targetPoint: Point, - controlPoints: [Point, Point], - sourcePort?: GCircle, - targetPort?: GCircle, -) { - if (sourcePort) { - sourcePoint = getEllipseIntersectPoint(controlPoints[0], sourcePort.getBounds()); - } - - if (targetPort) { - targetPoint = getEllipseIntersectPoint(controlPoints[1], targetPort.getBounds()); - } - return [sourcePoint, targetPoint]; -} - /** * 获取自环边的起点、终点和控制点 * @@ -437,13 +409,14 @@ export function getLoopPoints( const controlPoints = getLoopControlPoints(node, sourcePoint, targetPoint, dist); - [sourcePoint, targetPoint] = adjustLoopEndpointsIfNeed( - sourcePoint, - targetPoint, - controlPoints, - sourcePort, - targetPort, - ); + // 如果定义了端点锚点,调整端点以与锚点边界相交 + // If the endpoint anchor point is defined, adjust the endpoint to intersect with the anchor point boundary + if (sourcePort) { + sourcePoint = getPortConnectionPoint(sourcePort, controlPoints[0]); + } + if (targetPort) { + targetPoint = getPortConnectionPoint(targetPort, controlPoints[1]); + } return { sourcePoint, targetPoint, controlPoints }; } diff --git a/packages/g6/src/utils/element.ts b/packages/g6/src/utils/element.ts index 6b223c3328c..9ef0d5d507c 100644 --- a/packages/g6/src/utils/element.ts +++ b/packages/g6/src/utils/element.ts @@ -1,10 +1,11 @@ -import type { AABB, DisplayObject, Circle as GCircle, TextStyleProps } from '@antv/g'; +import type { AABB, DisplayObject, TextStyleProps } from '@antv/g'; import { get, isEmpty, isString } from '@antv/util'; import type { TriangleDirection } from '../elements/nodes/triangle'; import type { Node, Point } from '../types'; -import type { LabelPosition, RelativePosition } from '../types/node'; +import type { LabelPosition, Port, RelativePosition } from '../types/node'; import { getBBoxHeight, getBBoxWidth } from './bbox'; -import { findNearestPoints } from './point'; +import { isPoint } from './is'; +import { findNearestPoints, getEllipseIntersectPoint } from './point'; /** * 判断两个节点是否相同 @@ -72,7 +73,7 @@ export function getPortPosition( * @param oppositeNode - 对端节点 | Opposite Node * @returns 锚点 | Port */ -export function findPort(node: Node, portKey?: string, oppositeNode?: Node): GCircle | undefined { +export function findPort(node: Node, portKey?: string, oppositeNode?: Node): Port | undefined { if (portKey && node.getPorts()[portKey]) { return node.getPorts()[portKey]; } else { @@ -87,6 +88,37 @@ export function findPort(node: Node, portKey?: string, oppositeNode?: Node): GCi } } +/** + * 获取锚点的连接点 + * + * Get the Port Connection Point + * @param port - 锚点 | Port + * @param opposite - 对端的具体点或节点 | Opposite Point or Node + * @param oppositePort - 对端锚点 | Opposite Port + * @returns 锚点的连接点 | Port Point + */ +export function getPortConnectionPoint(port: Port, opposite: Point | Node, oppositePort?: Port): Point { + return port.attributes.linkToCenter + ? port.getPosition() + : getEllipseIntersectPoint( + oppositePort?.getPosition() || (isPoint(opposite) ? opposite : opposite.getCenter()), + port.getBounds(), + ); +} + +/** + * 获取节点的连接点 + * + * Get the Node Connection Point + * @param node - 节点 | Node + * @param oppositeNode - 对端节点 | Opposite Node + * @param oppositePort - 对端锚点 | Opposite Port + * @returns 节点的连接点 | Node Point + */ +export function getNodeConnectionPoint(node: Node, oppositeNode: Node, oppositePort?: Port): Point { + return node.getIntersectPoint(oppositePort?.getPosition() || oppositeNode.getCenter()) || node.getCenter(); +} + /** * Get the Text style by `position`. * @param bbox - BBox of element. diff --git a/packages/g6/src/utils/is.ts b/packages/g6/src/utils/is.ts index 0730233f4a1..da79a8ecc3d 100644 --- a/packages/g6/src/utils/is.ts +++ b/packages/g6/src/utils/is.ts @@ -34,3 +34,20 @@ export function isVector2(vector: Point): vector is Vector2 { export function isVector3(vector: Point): vector is Vector3 { return vector.length === 3; } + +/** + * 判断是否为点 + * + * Judge whether the point is valid + * @param p - 点 | point + * @returns 是否为点 | whether the point is valid + */ +export function isPoint(p: any): p is Point { + if (p instanceof Float32Array) return true; + + if (Array.isArray(p) && (p.length === 2 || p.length === 3)) { + return p.every((elem) => typeof elem === 'number'); + } + + return false; +}