Here’s the complete sample. It supports five ports on each node, and much like the Draggable Link sample, allows users to draw new links from or to either individual ports or to the whole default port (which in this case is not the whole node but just the bigger circular Shape).
<!DOCTYPE html>
<html>
<head>
<title>Minimal GoJS Sample</title>
<!-- Copyright 1998-2024 by Northwoods Software Corporation. -->
</head>
<body>
<div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:600px"></div>
<script src="https://unpkg.com/gojs"></script>
<script id="code">
const $ = go.GraphObject.make;
const myDiagram =
new go.Diagram("myDiagramDiv",
{
"undoManager.isEnabled": true
});
myDiagram.nodeTemplate =
$(go.Node, "Vertical",
{
locationSpot: go.Spot.Center,
locationObjectName: "ICON",
selectionObjectName: "ICON",
mouseEnter: (e, node) => showSmallPorts(node, true),
mouseLeave: (e, node) => showSmallPorts(node, false),
},
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
$(go.Panel, "Spot",
$(go.Shape, "Circle",
{
name: "ICON",
width: 48, height: 48, fill: "transparent", stroke: "crimson", strokeWidth: 3,
portId: "", fromLinkable: true, toLinkable: true, cursor: "pointer"
}),
$(go.Shape, "Circle", { width: 36, height: 36, fill: "pink", stroke: "pink" }),
makePort('T', go.Spot.Top),
makePort('L', go.Spot.Left),
makePort('R', go.Spot.Right),
makePort('B', go.Spot.Bottom)
),
$(go.TextBlock,
new go.Binding("text"))
);
function makePort(name, spot) {
const $ = go.GraphObject.make;
// the port is basically just a small transparent square
const port = $(go.Shape, "Circle", {
fill: null, // not seen, by default; set by showSmallPorts, defined below
strokeWidth: 0,
desiredSize: new go.Size(10, 10),
alignment: spot, // align the port on the main Shape
//alignmentFocus: spot.opposite(), // just inside the Shape
portId: name, // declare this object to be a "port"
fromSpot: spot,
toSpot: spot, // declare where links may connect at this port
fromLinkable: name !== "T", // declare which directions a link may be drawn
toLinkable: name !== "B",
cursor: "pointer" // show a different cursor to indicate potential link drawing
});
// If you don't use the CustomLink class, you'll need this for the bottom port
// if (spot.equals(go.Spot.Bottom)) {
// port.fromEndSegmentLength = 30;
// port.fromShortLength = 15;
// port.toEndSegmentLength = 30;
// port.toShortLength = 15;
// }
return port;
}
function showSmallPorts(node, show) {
node.ports.each((port) => {
// don't change the default port, which is the big shape
if (port.portId === "") return;
port.fill = show ? "dodgerblue" : null;
});
}
class CustomLink extends go.Link {
getLinkPoint(node, port, spot, from, ortho, othernode, otherport) {
const pt = super.getLinkPoint(node, port, spot, from, ortho, othernode, otherport);
if (spot.equals(go.Spot.Bottom)) {
return new go.Point(pt.x, pt.y + 15);
} else if (spot.isNone()) {
const dir = this.getLinkDirection(node, port, pt, spot, from, ortho, othernode, otherport);
if (dir === 90) return new go.Point(pt.x, node.actualBounds.bottom);
}
return pt;
}
}
myDiagram.linkTemplate =
$(CustomLink,
{ routing: go.Link.Orthogonal },
$(go.Shape, { stroke: "#555", strokeWidth: 2 }),
$(go.Shape, { toArrow: "Triangle", fill: "#555", strokeWidth: 0 })
);
myDiagram.model = new go.GraphLinksModel({
linkFromPortIdProperty: "fromPort",
linkToPortIdProperty: "toPort",
nodeDataArray:
[
{ key: 1, text: "Alpha", loc: "0 0" },
{ key: 2, text: "Beta", loc: "150 150" },
{ key: 3, text: "Gamma", loc: "200 -100" },
{ key: 4, text: "Delta", loc: "-150 50" },
],
linkDataArray:
[
{ from: 1, fromPort: "B", to: 2, toPort: "T" },
{ from: 3, to: 1 },
{ from: 1, fromPort: "L", to: 4, toPort: "R" },
]
});
</script>
</body>
</html>