diff --git a/packages/g6/__tests__/demo/static/controller-layout-dendrogram.ts b/packages/g6/__tests__/demo/static/controller-layout-dendrogram.ts index 301266fe03e..f652905385a 100644 --- a/packages/g6/__tests__/demo/static/controller-layout-dendrogram.ts +++ b/packages/g6/__tests__/demo/static/controller-layout-dendrogram.ts @@ -23,6 +23,14 @@ export const controllerLayoutDendrogram: STDTestCase = async (context) => { labelText: (data: any) => data.id, labelPlacement: 'right', labelMaxWidth: 200, + ports: [ + { + placement: 'right', + }, + { + placement: 'left', + }, + ], }, }, edge: { diff --git a/packages/g6/__tests__/demo/static/node-circle.ts b/packages/g6/__tests__/demo/static/node-circle.ts index 34a020d1ab5..d7ea9328b79 100644 --- a/packages/g6/__tests__/demo/static/node-circle.ts +++ b/packages/g6/__tests__/demo/static/node-circle.ts @@ -32,6 +32,7 @@ export const nodeCircle: StaticTestCase = async (context) => { iconWidth: 20, iconSrc: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg', halo: (d: any) => d.id.includes('halo'), + portR: 3, ports: (d: any) => d.id.includes('ports') ? [{ placement: 'left' }, { placement: 'right' }, { placement: 'top' }, { placement: 'bottom' }] diff --git a/packages/g6/__tests__/demo/static/node-diamond.ts b/packages/g6/__tests__/demo/static/node-diamond.ts index 4291a2c25ab..76ca48fcef9 100644 --- a/packages/g6/__tests__/demo/static/node-diamond.ts +++ b/packages/g6/__tests__/demo/static/node-diamond.ts @@ -31,6 +31,7 @@ export const nodeDiamond: StaticTestCase = async (context) => { iconHeight: 20, iconSrc: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg', halo: (d: any) => d.id.includes('halo'), + portR: 3, ports: (d: any) => d.id.includes('ports') ? [{ placement: 'left' }, { placement: 'right' }, { placement: 'top' }, { placement: 'bottom' }] diff --git a/packages/g6/__tests__/demo/static/node-ellipse.ts b/packages/g6/__tests__/demo/static/node-ellipse.ts index 8e69756541c..4d33f085ec0 100644 --- a/packages/g6/__tests__/demo/static/node-ellipse.ts +++ b/packages/g6/__tests__/demo/static/node-ellipse.ts @@ -32,6 +32,7 @@ export const nodeEllipse: StaticTestCase = async (context) => { iconWidth: 20, iconSrc: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg', halo: (d: any) => d.id.includes('halo'), + portR: 3, ports: (d: any) => d.id.includes('ports') ? [{ placement: 'left' }, { placement: 'right' }, { placement: 'top' }, { placement: 'bottom' }] diff --git a/packages/g6/__tests__/demo/static/node-image.ts b/packages/g6/__tests__/demo/static/node-image.ts index 3ab5f10a26a..569e3565b9e 100644 --- a/packages/g6/__tests__/demo/static/node-image.ts +++ b/packages/g6/__tests__/demo/static/node-image.ts @@ -31,6 +31,7 @@ export const nodeImage: StaticTestCase = async (context) => { src: 'https://gw.alipayobjects.com/mdn/rms_6ae20b/afts/img/A*N4ZMS7gHsUIAAAAAAAAAAABkARQnAQ', halo: (d: any) => d.id.includes('halo'), haloStroke: '#227eff', + portR: 3, ports: (d: any) => d.id.includes('ports') ? [{ placement: 'left' }, { placement: 'right' }, { placement: 'top' }, { placement: 'bottom' }] diff --git a/packages/g6/__tests__/demo/static/node-rect.ts b/packages/g6/__tests__/demo/static/node-rect.ts index 4daa6a53403..f5678a757ae 100644 --- a/packages/g6/__tests__/demo/static/node-rect.ts +++ b/packages/g6/__tests__/demo/static/node-rect.ts @@ -33,6 +33,7 @@ export const nodeRect: StaticTestCase = async (context) => { iconHeight: 20, iconSrc: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg', halo: (d: any) => d.id.includes('halo'), + portR: 3, ports: (d: any) => d.id.includes('ports') ? [{ placement: 'left' }, { placement: 'right' }, { placement: 'top' }, { placement: 'bottom' }] diff --git a/packages/g6/__tests__/demo/static/node-star.ts b/packages/g6/__tests__/demo/static/node-star.ts index 90d79a3c341..79000fe8763 100644 --- a/packages/g6/__tests__/demo/static/node-star.ts +++ b/packages/g6/__tests__/demo/static/node-star.ts @@ -30,6 +30,7 @@ export const nodeStar: StaticTestCase = async (context) => { labelText: (d: any) => d.id, iconSrc: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg', halo: (d: any) => d.id.includes('halo'), + portR: 3, ports: (d: any) => d.id.includes('ports') ? [{ placement: 'left' }, { placement: 'right' }, { placement: 'top' }, { placement: 'bottom' }] diff --git a/packages/g6/__tests__/demo/static/node-triangle.ts b/packages/g6/__tests__/demo/static/node-triangle.ts index 7ae662f861c..fb8f10a7763 100644 --- a/packages/g6/__tests__/demo/static/node-triangle.ts +++ b/packages/g6/__tests__/demo/static/node-triangle.ts @@ -30,6 +30,7 @@ export const nodeTriangle: StaticTestCase = async (context) => { labelText: (d: any) => d.id, iconSrc: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg', halo: (d: any) => d.id.includes('halo'), + portR: 3, ports: (d: any) => d.id.includes('ports') ? [{ placement: 'left' }, { placement: 'top' }, { placement: 'bottom' }] : [], badges: (d: any) => diff --git a/packages/g6/__tests__/integration/snapshots/static/controller-layout-dendrogram.svg b/packages/g6/__tests__/integration/snapshots/static/controller-layout-dendrogram.svg index c27ddf5abf6..a4645f4eee5 100644 --- a/packages/g6/__tests__/integration/snapshots/static/controller-layout-dendrogram.svg +++ b/packages/g6/__tests__/integration/snapshots/static/controller-layout-dendrogram.svg @@ -26,11 +26,11 @@ stroke="transparent" stroke-width="3" /> - + @@ -58,11 +58,11 @@ stroke="transparent" stroke-width="3" /> - + @@ -90,11 +90,11 @@ stroke="transparent" stroke-width="3" /> - + @@ -122,11 +122,11 @@ stroke="transparent" stroke-width="3" /> - + @@ -154,11 +154,11 @@ stroke="transparent" stroke-width="3" /> - + @@ -186,11 +186,11 @@ stroke="transparent" stroke-width="3" /> - + @@ -218,11 +218,11 @@ stroke="transparent" stroke-width="3" /> - + @@ -250,11 +250,11 @@ stroke="transparent" stroke-width="3" /> - + @@ -282,11 +282,11 @@ stroke="transparent" stroke-width="3" /> - + @@ -314,11 +314,11 @@ stroke="transparent" stroke-width="3" /> - + @@ -346,11 +346,11 @@ stroke="transparent" stroke-width="3" /> - + @@ -378,11 +378,11 @@ stroke="transparent" stroke-width="3" /> - + @@ -410,11 +410,11 @@ stroke="transparent" stroke-width="3" /> - + @@ -442,11 +442,11 @@ stroke="transparent" stroke-width="3" /> - + @@ -474,11 +474,11 @@ stroke="transparent" stroke-width="3" /> - + @@ -506,11 +506,11 @@ stroke="transparent" stroke-width="3" /> - + @@ -538,11 +538,11 @@ stroke="transparent" stroke-width="3" /> - + @@ -570,11 +570,11 @@ stroke="transparent" stroke-width="3" /> - + @@ -602,11 +602,11 @@ stroke="transparent" stroke-width="3" /> - + @@ -634,11 +634,11 @@ stroke="transparent" stroke-width="3" /> - + @@ -666,11 +666,11 @@ stroke="transparent" stroke-width="3" /> - + @@ -698,11 +698,11 @@ stroke="transparent" stroke-width="3" /> - + @@ -730,11 +730,11 @@ stroke="transparent" stroke-width="3" /> - + @@ -794,11 +794,11 @@ stroke="transparent" stroke-width="3" /> - + @@ -826,11 +826,11 @@ stroke="transparent" stroke-width="3" /> - + @@ -858,11 +858,11 @@ stroke="transparent" stroke-width="3" /> - + @@ -922,11 +922,11 @@ stroke="transparent" stroke-width="3" /> - + @@ -954,11 +954,11 @@ stroke="transparent" stroke-width="3" /> - + diff --git a/packages/g6/__tests__/integration/snapshots/static/node-circle.svg b/packages/g6/__tests__/integration/snapshots/static/node-circle.svg index d8f9fe36acf..190569e157f 100644 --- a/packages/g6/__tests__/integration/snapshots/static/node-circle.svg +++ b/packages/g6/__tests__/integration/snapshots/static/node-circle.svg @@ -385,9 +385,9 @@ cx="3" cy="3" stroke-width="1" - r="3" stroke="rgba(0,0,0,1)" stroke-opacity="0.65" + r="3" /> @@ -398,9 +398,9 @@ cx="3" cy="3" stroke-width="1" - r="3" stroke="rgba(0,0,0,1)" stroke-opacity="0.65" + r="3" /> @@ -411,9 +411,9 @@ cx="3" cy="3" stroke-width="1" - r="3" stroke="rgba(0,0,0,1)" stroke-opacity="0.65" + r="3" /> @@ -424,9 +424,9 @@ cx="3" cy="3" stroke-width="1" - r="3" stroke="rgba(0,0,0,1)" stroke-opacity="0.65" + r="3" /> diff --git a/packages/g6/__tests__/integration/snapshots/static/node-diamond.svg b/packages/g6/__tests__/integration/snapshots/static/node-diamond.svg index d85d5d46dbc..75adf726e1e 100644 --- a/packages/g6/__tests__/integration/snapshots/static/node-diamond.svg +++ b/packages/g6/__tests__/integration/snapshots/static/node-diamond.svg @@ -370,9 +370,9 @@ cx="3" cy="3" stroke-width="1" - r="3" stroke="rgba(0,0,0,1)" stroke-opacity="0.65" + r="3" /> @@ -383,9 +383,9 @@ cx="3" cy="3" stroke-width="1" - r="3" stroke="rgba(0,0,0,1)" stroke-opacity="0.65" + r="3" /> @@ -396,9 +396,9 @@ cx="3" cy="3" stroke-width="1" - r="3" stroke="rgba(0,0,0,1)" stroke-opacity="0.65" + r="3" /> @@ -409,9 +409,9 @@ cx="3" cy="3" stroke-width="1" - r="3" stroke="rgba(0,0,0,1)" stroke-opacity="0.65" + r="3" /> diff --git a/packages/g6/__tests__/integration/snapshots/static/node-ellipse.svg b/packages/g6/__tests__/integration/snapshots/static/node-ellipse.svg index 1989ba423ce..4e6572842f0 100644 --- a/packages/g6/__tests__/integration/snapshots/static/node-ellipse.svg +++ b/packages/g6/__tests__/integration/snapshots/static/node-ellipse.svg @@ -398,9 +398,9 @@ cx="3" cy="3" stroke-width="1" - r="3" stroke="rgba(0,0,0,1)" stroke-opacity="0.65" + r="3" /> @@ -411,9 +411,9 @@ cx="3" cy="3" stroke-width="1" - r="3" stroke="rgba(0,0,0,1)" stroke-opacity="0.65" + r="3" /> @@ -424,9 +424,9 @@ cx="3" cy="3" stroke-width="1" - r="3" stroke="rgba(0,0,0,1)" stroke-opacity="0.65" + r="3" /> @@ -437,9 +437,9 @@ cx="3" cy="3" stroke-width="1" - r="3" stroke="rgba(0,0,0,1)" stroke-opacity="0.65" + r="3" /> diff --git a/packages/g6/__tests__/integration/snapshots/static/node-image.svg b/packages/g6/__tests__/integration/snapshots/static/node-image.svg index 2926ea392c6..942d0f8abf1 100644 --- a/packages/g6/__tests__/integration/snapshots/static/node-image.svg +++ b/packages/g6/__tests__/integration/snapshots/static/node-image.svg @@ -309,9 +309,9 @@ cx="3" cy="3" stroke-width="1" - r="3" stroke="rgba(0,0,0,1)" stroke-opacity="0.65" + r="3" /> @@ -322,9 +322,9 @@ cx="3" cy="3" stroke-width="1" - r="3" stroke="rgba(0,0,0,1)" stroke-opacity="0.65" + r="3" /> @@ -335,9 +335,9 @@ cx="3" cy="3" stroke-width="1" - r="3" stroke="rgba(0,0,0,1)" stroke-opacity="0.65" + r="3" /> @@ -348,9 +348,9 @@ cx="3" cy="3" stroke-width="1" - r="3" stroke="rgba(0,0,0,1)" stroke-opacity="0.65" + r="3" /> diff --git a/packages/g6/__tests__/integration/snapshots/static/node-rect.svg b/packages/g6/__tests__/integration/snapshots/static/node-rect.svg index 0558c37281c..cf9f745c623 100644 --- a/packages/g6/__tests__/integration/snapshots/static/node-rect.svg +++ b/packages/g6/__tests__/integration/snapshots/static/node-rect.svg @@ -381,9 +381,9 @@ cx="3" cy="3" stroke-width="1" - r="3" stroke="rgba(0,0,0,1)" stroke-opacity="0.65" + r="3" /> @@ -394,9 +394,9 @@ cx="3" cy="3" stroke-width="1" - r="3" stroke="rgba(0,0,0,1)" stroke-opacity="0.65" + r="3" /> @@ -407,9 +407,9 @@ cx="3" cy="3" stroke-width="1" - r="3" stroke="rgba(0,0,0,1)" stroke-opacity="0.65" + r="3" /> @@ -420,9 +420,9 @@ cx="3" cy="3" stroke-width="1" - r="3" stroke="rgba(0,0,0,1)" stroke-opacity="0.65" + r="3" /> diff --git a/packages/g6/__tests__/integration/snapshots/static/node-star.svg b/packages/g6/__tests__/integration/snapshots/static/node-star.svg index 9e6b8a45852..8b6fe8bcbec 100644 --- a/packages/g6/__tests__/integration/snapshots/static/node-star.svg +++ b/packages/g6/__tests__/integration/snapshots/static/node-star.svg @@ -390,9 +390,9 @@ cx="3" cy="3" stroke-width="1" - r="3" stroke="rgba(0,0,0,1)" stroke-opacity="0.65" + r="3" /> @@ -403,9 +403,9 @@ cx="3" cy="3" stroke-width="1" - r="3" stroke="rgba(0,0,0,1)" stroke-opacity="0.65" + r="3" /> @@ -416,9 +416,9 @@ cx="3" cy="3" stroke-width="1" - r="3" stroke="rgba(0,0,0,1)" stroke-opacity="0.65" + r="3" /> @@ -429,9 +429,9 @@ cx="3" cy="3" stroke-width="1" - r="3" stroke="rgba(0,0,0,1)" stroke-opacity="0.65" + r="3" /> diff --git a/packages/g6/__tests__/integration/snapshots/static/node-triangle.svg b/packages/g6/__tests__/integration/snapshots/static/node-triangle.svg index 6c34cf6b4c7..9e4b6f12dfd 100644 --- a/packages/g6/__tests__/integration/snapshots/static/node-triangle.svg +++ b/packages/g6/__tests__/integration/snapshots/static/node-triangle.svg @@ -370,9 +370,9 @@ cx="3" cy="3" stroke-width="1" - r="3" stroke="rgba(0,0,0,1)" stroke-opacity="0.65" + r="3" /> @@ -383,9 +383,9 @@ cx="3" cy="3" stroke-width="1" - r="3" stroke="rgba(0,0,0,1)" stroke-opacity="0.65" + r="3" /> @@ -396,9 +396,9 @@ cx="3" cy="3" stroke-width="1" - r="3" stroke="rgba(0,0,0,1)" stroke-opacity="0.65" + r="3" /> diff --git a/packages/g6/__tests__/unit/utils/element.spec.ts b/packages/g6/__tests__/unit/utils/element.spec.ts index 88b3fd0871d..f362378bf50 100644 --- a/packages/g6/__tests__/unit/utils/element.spec.ts +++ b/packages/g6/__tests__/unit/utils/element.spec.ts @@ -2,8 +2,9 @@ import { Polyline } from '@/src/elements/edges'; import { Circle } from '@/src/elements/nodes'; import { findPorts, + getAllPorts, getPortConnectionPoint, - getPortPosition, + getPortXYByPlacement, getRectPoints, getStarPoints, getStarPorts, @@ -13,11 +14,13 @@ import { isEdge, isNode, isSameNode, + isSimplePort, isVisible, updateStyle, } from '@/src/utils/element'; import { getXYByPlacement } from '@/src/utils/position'; -import { AABB, Line, Rect } from '@antv/g'; +import { AABB, DisplayObject, Line, Rect } from '@antv/g'; +import { PortStyleProps } from '../../../src/types'; describe('element', () => { const bbox = new AABB(); @@ -64,16 +67,53 @@ describe('element', () => { expect(getXYByPlacement(bbox)).toEqual([150, 150]); }); - it('getPortPosition', () => { - expect(getPortPosition(bbox, 'left')).toEqual([100, 150]); - expect(getPortPosition(bbox, 'right')).toEqual([200, 150]); - expect(getPortPosition(bbox, 'top')).toEqual([150, 100]); - expect(getPortPosition(bbox, 'bottom')).toEqual([150, 200]); + it('getPortXYByPlacement', () => { + expect(getPortXYByPlacement(bbox, 'left')).toEqual([100, 150]); + expect(getPortXYByPlacement(bbox, 'right')).toEqual([200, 150]); + expect(getPortXYByPlacement(bbox, 'top')).toEqual([150, 100]); + expect(getPortXYByPlacement(bbox, 'bottom')).toEqual([150, 200]); - expect(getPortPosition(bbox)).toEqual([150, 150]); + expect(getPortXYByPlacement(bbox)).toEqual([150, 150]); - expect(getPortPosition(bbox, [0.5, 1])).toEqual([150, 200]); - expect(getPortPosition(bbox, [0, 0.5])).toEqual([100, 150]); + expect(getPortXYByPlacement(bbox, [0.5, 1])).toEqual([150, 200]); + expect(getPortXYByPlacement(bbox, [0, 0.5])).toEqual([100, 150]); + }); + + it('getAllPorts', () => { + const node = new Circle({ + style: { + x: 0, + y: 0, + size: 100, + port: true, + ports: [ + { key: 'left', placement: [0, 0.5], r: 4 }, + { key: 'right', placement: [1, 0.5] }, + ], + }, + }); + expect(Object.values(getAllPorts(node)).length).toBe(2); + expect(getAllPorts(node)['right']).toEqual([50, 0]); + }); + + it('isSimplePort', () => { + expect( + isSimplePort({ + placement: 'left', + }), + ).toBeTruthy(); + expect( + isSimplePort({ + placement: 'left', + r: 0, + }), + ).toBeTruthy(); + expect( + isSimplePort({ + placement: 'left', + r: 4, + }), + ).toBeFalsy(); }); it('findPorts', () => { @@ -93,8 +133,9 @@ describe('element', () => { }); const sourcePortKey = 'left'; const targetPortKey = 'top'; - expect(findPorts(sourceNode, targetNode, sourcePortKey, targetPortKey)[0]?.id).toEqual('port-left'); - expect(findPorts(sourceNode, targetNode, sourcePortKey, targetPortKey)[1]?.id).toEqual('port-top'); + const [sourcePort, targetPort] = findPorts(sourceNode, targetNode, sourcePortKey, targetPortKey); + expect((sourcePort as DisplayObject)?.id).toEqual('port-left'); + expect((targetPort as DisplayObject)?.id).toEqual('port-top'); }); it('getPortConnectionPoint', () => { diff --git a/packages/g6/src/elements/edges/polyline.ts b/packages/g6/src/elements/edges/polyline.ts index 4882593e243..8847a93fba3 100644 --- a/packages/g6/src/elements/edges/polyline.ts +++ b/packages/g6/src/elements/edges/polyline.ts @@ -4,7 +4,7 @@ import { deepMix } from '@antv/util'; import type { Padding, Point, Port } from '../../types'; import { getBBoxHeight, getBBoxWidth, getNodeBBox } from '../../utils/bbox'; import { getPolylineLoopPath, getPolylinePath } from '../../utils/edge'; -import { findPorts, getConnectionPoint } from '../../utils/element'; +import { findPorts, getConnectionPoint, getPortPosition } from '../../utils/element'; import { subStyleProps } from '../../utils/prefix'; import { orth } from '../../utils/router/orth'; import type { BaseEdgeStyleProps, LoopStyleProps } from './base-edge'; @@ -82,8 +82,8 @@ export class Polyline extends BaseEdge { const [sourcePort, targetPort] = findPorts(sourceNode, targetNode, sourcePortKey, targetPortKey); return { - sourcePoint: sourcePort?.getPosition() || sourceNode.getCenter(), - targetPoint: targetPort?.getPosition() || targetNode.getCenter(), + sourcePoint: sourcePort ? getPortPosition(sourcePort) : sourceNode.getCenter(), + targetPoint: targetPort ? getPortPosition(targetPort) : targetNode.getCenter(), sourcePort, targetPort, }; diff --git a/packages/g6/src/elements/nodes/base-node.ts b/packages/g6/src/elements/nodes/base-node.ts index e1e299a0c22..00762ebf11f 100644 --- a/packages/g6/src/elements/nodes/base-node.ts +++ b/packages/g6/src/elements/nodes/base-node.ts @@ -14,7 +14,7 @@ import type { PortStyleProps, PrefixObject, } from '../../types'; -import { getPortPosition, getTextStyleByPlacement } from '../../utils/element'; +import { getPortXYByPlacement, getTextStyleByPlacement, isSimplePort } from '../../utils/element'; import { getPaletteColors } from '../../utils/palette'; import { getRectIntersectPoint } from '../../utils/point'; import { getXYByPlacement } from '../../utils/position'; @@ -182,18 +182,19 @@ export abstract class BaseNode extends BaseS const portStyle = subStyleProps(this.getGraphicStyle(attributes), 'port'); const { ports: portOptions = [] } = attributes; - - portOptions.forEach((option, i) => { - const [cx, cy] = this.getPortXY(attributes, option); - portsShapeStyle[option.key || i] = Object.assign({}, portStyle, { cx, cy }, option); - }); + portOptions + .filter((option) => !isSimplePort({ ...portStyle, ...option })) + .forEach((option, i) => { + const [cx, cy] = this.getPortXY(attributes, option); + portsShapeStyle[option.key || i] = Object.assign({}, portStyle, { cx, cy }, option); + }); return portsShapeStyle; } protected getPortXY(attributes: Required, style: NodePortStyleProps): Point { const { placement = 'left' } = style; const bounds = this.getKey().getLocalBounds(); - return getPortPosition(bounds, placement as PortPlacement); + return getPortXYByPlacement(bounds, placement as PortPlacement); } /** diff --git a/packages/g6/src/elements/nodes/star.ts b/packages/g6/src/elements/nodes/star.ts index 3685f2be4f3..5580539ce90 100644 --- a/packages/g6/src/elements/nodes/star.ts +++ b/packages/g6/src/elements/nodes/star.ts @@ -1,7 +1,7 @@ import type { DisplayObjectConfig } from '@antv/g'; import { ICON_SIZE_RATIO } from '../../constants/element'; import type { Point, StarPortPlacement } from '../../types'; -import { getPortPosition, getStarPoints, getStarPorts } from '../../utils/element'; +import { getPortXYByPlacement, getStarPoints, getStarPorts } from '../../utils/element'; import type { IconStyleProps } from '../shapes'; import { NodePortStyleProps } from './base-node'; import type { ParsedPolygonStyleProps, PolygonStyleProps } from './polygon'; @@ -44,6 +44,6 @@ export class Star extends Polygon { const { placement = 'top' } = style; const bbox = this.getKey().getLocalBounds(); const ports = getStarPorts(this.getOuterR(attributes), this.getInnerR(attributes)); - return getPortPosition(bbox, placement as StarPortPlacement, ports, false); + return getPortXYByPlacement(bbox, placement as StarPortPlacement, ports, false); } } diff --git a/packages/g6/src/elements/nodes/triangle.ts b/packages/g6/src/elements/nodes/triangle.ts index b28656390b0..90e43caffd8 100644 --- a/packages/g6/src/elements/nodes/triangle.ts +++ b/packages/g6/src/elements/nodes/triangle.ts @@ -3,7 +3,7 @@ import { deepMix, isEmpty } from '@antv/util'; import { ICON_SIZE_RATIO } from '../../constants/element'; import type { Point, TrianglePortPlacement } from '../../types'; import { getIncircleRadius, getTriangleCenter } from '../../utils/bbox'; -import { getPortPosition, getTrianglePoints, getTrianglePorts } from '../../utils/element'; +import { getPortXYByPlacement, getTrianglePoints, getTrianglePorts } from '../../utils/element'; import { subStyleProps } from '../../utils/prefix'; import { IconStyleProps } from '../shapes'; import type { NodePortStyleProps } from './base-node'; @@ -43,7 +43,7 @@ export class Triangle extends Polygon { const bbox = this.getKey().getLocalBounds(); const [width, height] = this.getSize(attributes); const ports = getTrianglePorts(width, height, direction); - return getPortPosition(bbox, placement as TrianglePortPlacement, ports, false); + return getPortXYByPlacement(bbox, placement as TrianglePortPlacement, ports, false); } // icon 处于内切三角形的重心 diff --git a/packages/g6/src/themes/dark.ts b/packages/g6/src/themes/dark.ts index 4390dc77079..c7887a839d0 100644 --- a/packages/g6/src/themes/dark.ts +++ b/packages/g6/src/themes/dark.ts @@ -44,7 +44,6 @@ export const dark: Theme = { lineWidth: 0, portFill: NODE_COLOR, portLineWidth: 1, - portR: 3, portStroke: NODE_STROKE, portStrokeOpacity: 0.65, size: 32, diff --git a/packages/g6/src/themes/light.ts b/packages/g6/src/themes/light.ts index 476e97dc07a..c6af4e365ca 100644 --- a/packages/g6/src/themes/light.ts +++ b/packages/g6/src/themes/light.ts @@ -44,7 +44,6 @@ export const light: Theme = { lineWidth: 0, portFill: NODE_COLOR, portLineWidth: 1, - portR: 3, portStroke: NODE_STROKE, portStrokeOpacity: 0.65, size: 32, diff --git a/packages/g6/src/types/node.ts b/packages/g6/src/types/node.ts index 9da63d6d779..61a14b6a802 100644 --- a/packages/g6/src/types/node.ts +++ b/packages/g6/src/types/node.ts @@ -1,5 +1,6 @@ import type { DisplayObject, CircleStyleProps as GCircleStyleProps } from '@antv/g'; import type { CardinalPlacement, CornerPlacement, DirectionalPlacement, RelativePlacement } from './placement'; +import { Point } from './point'; export type PortPlacement = RelativePlacement | CardinalPlacement; export type StarPortPlacement = RelativePlacement | 'top' | 'left' | 'right' | 'left-bottom' | 'right-bottom'; @@ -15,4 +16,4 @@ export type PortStyleProps = GCircleStyleProps & { */ linkToCenter?: boolean; }; -export type Port = DisplayObject; +export type Port = DisplayObject | Point; diff --git a/packages/g6/src/utils/edge.ts b/packages/g6/src/utils/edge.ts index 74c33428b9a..d13a3148d84 100644 --- a/packages/g6/src/utils/edge.ts +++ b/packages/g6/src/utils/edge.ts @@ -1,9 +1,18 @@ -import type { AABB, Circle as GCircle } from '@antv/g'; +import type { AABB } from '@antv/g'; import type { PathArray } from '@antv/util'; import { isEqual, isNumber } from '@antv/util'; -import type { EdgeKey, EdgeLabelPlacement, EdgeLabelStyleProps, LoopPlacement, Node, Point, Vector2 } from '../types'; -import { getBBoxHeight, getBBoxWidth, getNearestSideToPoint, getNodeBBox } from './bbox'; -import { getNodeConnectionPoint, getPortConnectionPoint } from './element'; +import type { + EdgeKey, + EdgeLabelPlacement, + EdgeLabelStyleProps, + LoopPlacement, + Node, + Point, + Port, + Vector2, +} from '../types'; +import { getBBoxHeight, getBBoxSize, getBBoxWidth, getNearestSideToPoint, getNodeBBox } from './bbox'; +import { getAllPorts, getNodeConnectionPoint, getPortConnectionPoint, getPortPosition } from './element'; import { isCollinear, isHorizontal, moveTo, parsePoint } from './point'; import { freeJoin } from './router/orth'; import { add, distance, manhattanDistance, multiply, normalize, perpendicular, subtract } from './vector'; @@ -280,22 +289,21 @@ export function getLoopEndpoints( node: Node, placement: LoopPlacement, clockwise: boolean, - sourcePort?: GCircle, - targetPort?: GCircle, - rawSourcePoint?: Point, - rawTargetPoint?: Point, + sourcePort?: Port, + targetPort?: Port, ): [Point, Point] { const bbox = getNodeBBox(node); const center = node.getCenter(); - let sourcePoint = rawSourcePoint || sourcePort?.getPosition(); - let targetPoint = rawTargetPoint || targetPort?.getPosition(); + let sourcePoint = sourcePort && getPortPosition(sourcePort); + let targetPoint = targetPort && getPortPosition(targetPort); if (!sourcePoint || !targetPoint) { const radians = getRadians(bbox); const angle1 = radians[placement][0]; const angle2 = radians[placement][1]; - const r = Math.max(getBBoxWidth(bbox), getBBoxHeight(bbox)); + const [width, height] = getBBoxSize(bbox); + const r = Math.max(width, height); const point1: Point = add(center, [r * Math.cos(angle1), r * Math.sin(angle1), 0]); const point2: Point = add(center, [r * Math.cos(angle2), r * Math.sin(angle2), 0]); @@ -331,22 +339,12 @@ export function getCubicLoopPath( dist: number, sourcePortKey?: string, targetPortKey?: string, - rawSourcePoint?: Point, - rawTargetPoint?: Point, ) { const sourcePort = node.getPorts()[(sourcePortKey || targetPortKey)!]; const targetPort = node.getPorts()[(targetPortKey || sourcePortKey)!]; // 1. 获取起点和终点 | Get the start and end points - let [sourcePoint, targetPoint] = getLoopEndpoints( - node, - placement, - clockwise, - sourcePort, - targetPort, - rawSourcePoint, - rawTargetPoint, - ); + let [sourcePoint, targetPoint] = getLoopEndpoints(node, placement, clockwise, sourcePort, targetPort); // 2. 获取控制点 | Get the control points const controlPoints = getCubicLoopControlPoints(node, sourcePoint, targetPoint, dist); @@ -415,22 +413,13 @@ export function getPolylineLoopPath( dist: number, sourcePortKey?: string, targetPortKey?: string, - rawSourcePoint?: Point, - rawTargetPoint?: Point, ) { - const sourcePort = node.getPorts()[(sourcePortKey || targetPortKey)!]; - const targetPort = node.getPorts()[(targetPortKey || sourcePortKey)!]; + const allPortsMap = getAllPorts(node); + const sourcePort = allPortsMap[(sourcePortKey || targetPortKey)!]; + const targetPort = allPortsMap[(targetPortKey || sourcePortKey)!]; // 1. 获取起点和终点 | Get the start and end points - let [sourcePoint, targetPoint] = getLoopEndpoints( - node, - placement, - clockwise, - sourcePort, - targetPort, - rawSourcePoint, - rawTargetPoint, - ); + let [sourcePoint, targetPoint] = getLoopEndpoints(node, placement, clockwise, sourcePort, targetPort); // 2. 获取控制点 | Get the control points const controlPoints = getPolylineLoopControlPoints(node, sourcePoint, targetPoint, dist); diff --git a/packages/g6/src/utils/element.ts b/packages/g6/src/utils/element.ts index b42b2caf93e..091c3269d98 100644 --- a/packages/g6/src/utils/element.ts +++ b/packages/g6/src/utils/element.ts @@ -2,6 +2,7 @@ import type { AABB, DisplayObject, TextStyleProps } from '@antv/g'; import { get, isString } from '@antv/util'; import { BaseEdge } from '../elements/edges/base-edge'; import { BaseNode } from '../elements/nodes'; +import { NodePortStyleProps } from '../elements/nodes/base-node'; import type { TriangleDirection } from '../elements/nodes/triangle'; import type { Edge, Node, Placement, Point, Position } from '../types'; import type { LabelPlacement, Port } from '../types/node'; @@ -17,7 +18,7 @@ import { getXYByPlacement } from './position'; * @param shape - 实例 | instance * @returns 是否是 BaseNode 的实例 | whether the instance is BaseNode */ -export function isNode(shape: DisplayObject): shape is Node { +export function isNode(shape: any): shape is Node { return shape instanceof BaseNode && shape.type === 'node'; } @@ -28,7 +29,7 @@ export function isNode(shape: DisplayObject): shape is Node { * @param shape - 实例 | instance * @returns 是否是 BaseEdge 的实例 | whether the instance is BaseEdge */ -export function isEdge(shape: DisplayObject): shape is Edge { +export function isEdge(shape: any): shape is Edge { return shape instanceof BaseEdge; } @@ -61,7 +62,7 @@ const PORT_MAP: Record = { * @param isRelative - Whether the position in MAP is relative. * @returns [x, y] */ -export function getPortPosition( +export function getPortXYByPlacement( bbox: AABB, placement?: Placement, ports: Record = PORT_MAP, @@ -76,6 +77,51 @@ export function getPortPosition( return [bbox.min[0] + getBBoxWidth(bbox) * x, bbox.min[1] + getBBoxHeight(bbox) * y]; } +/** + * 获取节点上的所有连接桩 + * + * Get all ports + * @param node - 节点 | Node + * @returns 所有连接桩 | All Ports + */ +export function getAllPorts(node: Node): Record { + // 1. 需要绘制的连接桩 | Get the ports that need to be drawn + const ports = node.getPorts() as Record; + + // 2. 不需要额外绘制的连接桩 | Get the ports that do not need to be drawn + const portsStyle = node.attributes.ports; + portsStyle.forEach((portStyle: NodePortStyleProps, i: number) => { + const { key, placement } = portStyle; + if (isSimplePort(portStyle)) { + ports[key || i] ||= getXYByPlacement(node.getKey().getBounds(), placement); + } + }); + return ports; +} + +/** + * 是否为简单连接桩,如果是则不会额外绘制图形 + * + * Whether it is a simple port, which will not draw additional graphics + * @param portStyle - 连接桩样式 | Port Style + * @returns 是否是简单连接桩 | Whether it is a simple port + */ +export function isSimplePort(portStyle: NodePortStyleProps): boolean { + const { r } = portStyle; + return !r || Number(r) === 0; +} + +/** + * 获取连接桩的位置 + * + * Get the position of the port + * @param port - 连接桩 | Port + * @returns 连接桩的位置 | Port Position + */ +export function getPortPosition(port: Port): Position { + return isPoint(port) ? port : port.getPosition(); +} + /** * 查找起始连接桩和目标连接桩 * @@ -111,16 +157,22 @@ export function findPorts( * @param oppositePortKey - 对端连接桩的 key | Opposite Port Key * @returns 连接桩 | Port */ -export function findPort(node: Node, oppositeNode: Node, portKey?: string, oppositePortKey?: string): Port | undefined { - if (portKey) return node.getPorts()[portKey]; - - const ports = Object.values(node.getPorts()); +export function findPort( + node: Node, + oppositeNode: Node, + portKey?: string, + oppositePortKey?: string, +): Point | Port | undefined { + const portsMap = getAllPorts(node); + if (portKey) return portsMap[portKey]; + + const ports = Object.values(portsMap); if (ports.length === 0) return undefined; - const positions = ports.map((port) => port.getPosition()); + const positions = ports.map((port) => getPortPosition(port)); const oppositePositions = findConnectionPoints(oppositeNode, oppositePortKey); const [nearestPosition] = findNearestPoints(positions, oppositePositions); - return ports.find((port) => port.getPosition() === nearestPosition); + return ports.find((port) => getPortPosition(port) === nearestPosition); } /** @@ -136,9 +188,10 @@ export function findPort(node: Node, oppositeNode: Node, portKey?: string, oppos * @returns 连接点 | Connection Point */ function findConnectionPoints(node: Node, portKey?: string): Position[] { - if (portKey) return [node.getPorts()[portKey].getPosition()]; - const oppositePorts = Object.values(node.getPorts()); - return oppositePorts.length > 0 ? oppositePorts.map((port) => port.getPosition()) : [node.getCenter()]; + const allPortsMap = getAllPorts(node); + if (portKey) return [getPortPosition(allPortsMap[portKey])]; + const oppositePorts = Object.values(allPortsMap); + return oppositePorts.length > 0 ? oppositePorts.map((port) => getPortPosition(port)) : [node.getCenter()]; } /** @@ -149,7 +202,7 @@ function findConnectionPoints(node: Node, portKey?: string): Position[] { * @param opposite - 对端的具体点或节点 | Opposite Point or Node * @returns 连接点 | Connection Point */ -export function getConnectionPoint(node: Port | Node, opposite: Point | Node | Port): Point { +export function getConnectionPoint(node: Point | Port | Node, opposite: Point | Node | Port): Point { return isNode(node) ? getNodeConnectionPoint(node, opposite) : getPortConnectionPoint(node, opposite); } @@ -162,7 +215,9 @@ export function getConnectionPoint(node: Port | Node, opposite: Point | Node | P * @param oppositePort - 对端连接桩 | Opposite Port * @returns 连接桩的连接点 | Port Point */ -export function getPortConnectionPoint(port: Port, opposite: Point | Node | Port): Point { +export function getPortConnectionPoint(port: Point | Port, opposite: Point | Node | Port): Point { + if (isPoint(port)) return port; + // 1. linkToCenter 为 true,则返回连接桩的中心 | If linkToCenter is true, return the center of the port if (port.attributes.linkToCenter) return port.getPosition(); diff --git a/packages/site/examples/item/defaultNodes/demo/circle.ts b/packages/site/examples/item/defaultNodes/demo/circle.ts index a8a1261d21e..758a6cc8651 100644 --- a/packages/site/examples/item/defaultNodes/demo/circle.ts +++ b/packages/site/examples/item/defaultNodes/demo/circle.ts @@ -27,6 +27,7 @@ const graph = new Graph({ iconWidth: 20, iconSrc: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg', halo: (d) => d.id.includes('halo'), + portR: 3, ports: (d) => d.id.includes('ports') ? [{ position: 'left' }, { position: 'right' }, { position: 'top' }, { position: 'bottom' }] diff --git a/packages/site/examples/item/defaultNodes/demo/diamond.ts b/packages/site/examples/item/defaultNodes/demo/diamond.ts index aadade2df79..26ad4a9e62d 100644 --- a/packages/site/examples/item/defaultNodes/demo/diamond.ts +++ b/packages/site/examples/item/defaultNodes/demo/diamond.ts @@ -27,6 +27,7 @@ const graph = new Graph({ iconHeight: 20, iconSrc: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg', halo: (d) => d.id.includes('halo'), + portR: 3, ports: (d) => d.id.includes('ports') ? [{ position: 'left' }, { position: 'right' }, { position: 'top' }, { position: 'bottom' }] diff --git a/packages/site/examples/item/defaultNodes/demo/ellipse.ts b/packages/site/examples/item/defaultNodes/demo/ellipse.ts index 21326fccd49..03b5420bc73 100644 --- a/packages/site/examples/item/defaultNodes/demo/ellipse.ts +++ b/packages/site/examples/item/defaultNodes/demo/ellipse.ts @@ -27,6 +27,7 @@ const graph = new Graph({ iconWidth: 20, iconSrc: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg', halo: (d) => d.id.includes('halo'), + portR: 3, ports: (d) => d.id.includes('ports') ? [{ placement: 'left' }, { placement: 'right' }, { placement: 'top' }, { placement: 'bottom' }] diff --git a/packages/site/examples/item/defaultNodes/demo/image.ts b/packages/site/examples/item/defaultNodes/demo/image.ts index 115f0d908f4..1dfd574d2f4 100644 --- a/packages/site/examples/item/defaultNodes/demo/image.ts +++ b/packages/site/examples/item/defaultNodes/demo/image.ts @@ -25,6 +25,7 @@ const graph = new Graph({ labelMaxWidth: 120, labelText: (d) => d.id, halo: (d) => d.id.includes('halo'), + portR: 3, ports: (d) => d.id.includes('ports') ? [{ placement: 'left' }, { placement: 'right' }, { placement: 'top' }, { placement: 'bottom' }] diff --git a/packages/site/examples/item/defaultNodes/demo/radiusRect.ts b/packages/site/examples/item/defaultNodes/demo/radiusRect.ts index 833aa3bb718..18ca987e491 100644 --- a/packages/site/examples/item/defaultNodes/demo/radiusRect.ts +++ b/packages/site/examples/item/defaultNodes/demo/radiusRect.ts @@ -27,6 +27,7 @@ const graph = new Graph({ iconWidth: 20, iconHeight: 20, halo: (d) => d.id.includes('halo'), + portR: 3, ports: (d) => d.id.includes('ports') ? [{ placement: 'left' }, { placement: 'right' }, { placement: 'top' }, { placement: 'bottom' }] diff --git a/packages/site/examples/item/defaultNodes/demo/rect.ts b/packages/site/examples/item/defaultNodes/demo/rect.ts index e7fd2074098..568237730d9 100644 --- a/packages/site/examples/item/defaultNodes/demo/rect.ts +++ b/packages/site/examples/item/defaultNodes/demo/rect.ts @@ -26,6 +26,7 @@ const graph = new Graph({ iconWidth: 20, iconHeight: 20, halo: (d) => d.id.includes('halo'), + portR: 3, ports: (d) => d.id.includes('ports') ? [{ position: 'left' }, { position: 'right' }, { position: 'top' }, { position: 'bottom' }] diff --git a/packages/site/examples/item/defaultNodes/demo/star.ts b/packages/site/examples/item/defaultNodes/demo/star.ts index 291c200c428..1bccbb1e8e9 100644 --- a/packages/site/examples/item/defaultNodes/demo/star.ts +++ b/packages/site/examples/item/defaultNodes/demo/star.ts @@ -24,6 +24,7 @@ const graph = new Graph({ labelText: (d) => d.id, iconSrc: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg', halo: (d) => d.id.includes('halo'), + portR: 3, ports: (d) => d.id.includes('ports') ? [{ placement: 'left' }, { placement: 'right' }, { placement: 'top' }, { placement: 'bottom' }] diff --git a/packages/site/examples/item/defaultNodes/demo/triangle.ts b/packages/site/examples/item/defaultNodes/demo/triangle.ts index 5fea84a1b6d..01080451e32 100644 --- a/packages/site/examples/item/defaultNodes/demo/triangle.ts +++ b/packages/site/examples/item/defaultNodes/demo/triangle.ts @@ -25,6 +25,7 @@ const graph = new Graph({ labelText: (d) => d.id, iconSrc: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg', halo: (d) => d.id.includes('halo'), + portR: 3, ports: (d) => (d.id.includes('ports') ? [{ position: 'left' }, { position: 'top' }, { position: 'bottom' }] : []), badges: (d) => d.id.includes('badges')