Here’s the snippet:
import * as go from 'gojs';
var diagram = go.GraphObject.make(go.Diagram, 'box');
const linkTemplate = new go.Link({
routing: go.Link.AvoidsNodes,
corner: 6,
})
.add(new go.Shape({ strokeWidth: 2, stroke: 'black' }))
.add(new go.Shape({ toArrow: 'Standard', alignment: go.Spot.Center }))
.bind(new go.Binding('points').makeTwoWay());
const portToAlignment = {
TopPort: go.Spot.MiddleTop,
RightPort: go.Spot.MiddleRight,
BottomPort: go.Spot.MiddleBottom,
LeftPort: go.Spot.MiddleLeft,
};
function createPort(name) {
return new go.Shape('Circle', {
name: name,
alignment: portToAlignment[name],
alignmentFocus: go.Spot.Center,
fill: 'gray',
strokeWidth: 0,
opacity: 0,
desiredSize: new go.Size(16, 16),
portId: name,
fromSpot: go.Spot.Center,
toSpot: go.Spot.Center,
fromEndSegmentLength: 0,
toEndSegmentLength: 0,
cursor: 'pointer',
fromLinkable: true,
shadowVisible: true,
});
}
const availablePortNames = new Set([
'TopPort',
'RightPort',
'BottomPort',
'LeftPort',
]);
export const nodeTemplate = new go.Node(go.Panel.Spot, {
alignment: go.Spot.TopLeft,
stretch: go.GraphObject.Fill,
background: 'transparent',
resizeObjectName: 'box',
selectionObjectName: 'box',
fromLinkableDuplicates: false,
toLinkableDuplicates: false,
selectionAdorned: false,
mouseEnter: (_e, obj) => {
if (obj instanceof go.Panel) {
availablePortNames.forEach((n) => {
const panel = obj?.findObject(n);
if (panel != null) panel.opacity = 1;
});
}
},
mouseLeave: (_e, obj) => {
if (obj instanceof go.Panel) {
availablePortNames.forEach((n) => {
const panel = obj?.findObject(n);
if (panel != null) panel.opacity = 0;
});
}
},
})
.bind(
new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(
go.Point.stringify
)
)
.add(
new go.Panel('Auto', {
alignment: go.Spot.TopLeft,
portId: 'RectPort',
toLinkable: true,
alignmentFocus: go.Spot.Center,
fromLinkableDuplicates: false,
toLinkableDuplicates: false,
toSpot: go.Spot.Center,
}).add(
new go.Shape('RoundedRectangle', {
isPanelMain: true,
fill: 'whitesmoke',
stroke: 'gray',
strokeWidth: 2,
strokeDashArray: [],
stretch: go.GraphObject.Fill,
toLinkable: false,
name: 'box',
desiredSize: new go.Size(230, NaN),
minSize: new go.Size(170, 100),
maxSize: new go.Size(300, NaN),
})
)
)
.add(createPort('LeftPort'))
.add(createPort('TopPort'))
.add(createPort('RightPort'))
.add(createPort('BottomPort'))
.add(
new go.Panel(go.Panel.Horizontal, {
toLinkable: false,
alignment: new go.Spot(0, 0, -2, 0),
alignmentFocus: go.Spot.Center,
}).add(
new go.Shape('Circle', {
fill: 'gold',
width: 24,
height: 24,
})
)
);
diagram.nodeTemplate = nodeTemplate;
diagram.linkTemplate = linkTemplate;
diagram.model = new go.GraphLinksModel(
[
{ key: 'A', name: 'A', loc: '0 0' },
{ key: 'B', name: 'B', loc: '200 200' },
{ key: 'C', name: 'C', loc: '600 250' },
],
[
{ from: 'A', to: 'B' },
{ from: 'B', to: 'C' },
]
);
Just note that link being connected to the closest location is necessary and desired:
![gojs_link](https://forum.nwoods.com/uploads/db3963/original/2X/1/1c93252d353ba540a0107ae6744928264eb18c98.gif)
Also, the link creation starts from the circle ports. top/right/bottom/left ones, but connects to the rounded rectangle.