Well, it’s easy to filter the ports for each side:
<!DOCTYPE html>
<html>
<head>
<title>Grouped Ports</title>
<!-- Copyright 1998-2024 by Northwoods Software Corporation. -->
<meta name="description" content="A Node whose lists of ports are grouped together.">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:600px"></div>
<script src="../latest/release/go.js"></script>
<script id="code">
const $ = go.GraphObject.make;
const myDiagram =
new go.Diagram("myDiagramDiv");
myDiagram.nodeTemplate =
$(go.Node,
$(go.Panel, "Table",
$(go.Shape,
{ column: 1, columnSpan: 2, fill: "white", stretch: go.Stretch.Fill },
new go.Binding("fill", "color")),
$(go.Panel, "Table",
{ column: 0 },
new go.Binding("itemArray", "ports", a => getPorts(a, true)),
{
itemTemplate:
$(go.Panel, "Spot",
new go.Binding("portId", "id"),
new go.Binding("row", "index"),
{ width: 30, height: 15 },
$(go.Shape,
{ fill: "white" },
new go.Binding("fill", "color")),
$(go.TextBlock,
{ margin: new go.Margin(1, 0, 0, 0) },
new go.Binding("text", "id"))
)
}
),
$(go.Panel, "Table",
{ column: 1, alignment: go.Spot.Right, margin: 2 },
new go.Binding("itemArray", "ports", a => convertPortsToAttributes(a, true)),
{
itemTemplate:
$(go.Panel, "Auto",
new go.Binding("row", "index"),
{ width: 30, height: 15 },
new go.Binding("height", "rows", r => r * 15),
$(go.Shape,
{ fill: "white", stretch: go.Stretch.Fill },
new go.Binding("fill", "color")),
$(go.TextBlock,
{ margin: new go.Margin(1, 0, 0, 0) },
new go.Binding("text"))
)
}
),
$(go.Panel, "Table",
{ column: 2, alignment: go.Spot.Right, margin: 2 },
new go.Binding("itemArray", "ports", a => convertPortsToAttributes(a, false)),
{
itemTemplate:
$(go.Panel, "Auto",
new go.Binding("row", "index"),
{ width: 30, height: 15 },
new go.Binding("height", "rows", r => r * 15),
$(go.Shape,
{ fill: "white", stretch: go.Stretch.Fill },
new go.Binding("fill", "color")),
$(go.TextBlock,
{ margin: new go.Margin(1, 0, 0, 0) },
new go.Binding("text"))
)
}
),
$(go.Panel, "Table",
{ column: 3 },
new go.Binding("itemArray", "ports", a => getPorts(a, false)),
{
itemTemplate:
$(go.Panel, "Spot",
new go.Binding("portId", "id"),
new go.Binding("row", "index"),
{ width: 30, height: 15 },
$(go.Shape,
{ fill: "white" },
new go.Binding("fill", "color")),
$(go.TextBlock,
{ margin: new go.Margin(1, 0, 0, 0) },
new go.Binding("text", "id"))
)
}
),
)
);
function getPorts(arr, left) {
const a2 = [];
arr.forEach(port => {
if (left && port.side !== "left") return;
if (!left && port.side === "left") return;
a2.push(port);
});
return a2;
}
function convertPortsToAttributes(arr, left) {
const a2 = [];
let prevText = "";
arr.forEach(port => {
if (left && port.side !== "left") return;
if (!left && port.side === "left") return;
if (port.text !== prevText) {
a2.push({ index: port.index, rows: 1, text: port.text, color: port.color });
prevText = port.text;
} else {
a2[a2.length - 1].rows++;
}
});
return a2;
}
myDiagram.model = new go.GraphLinksModel({
linkFromPortIdProperty: "fpid",
linkToPortIdProperty: "tpid",
nodeDataArray: [
{
key: 1, text: "Alpha", color: "lightblue",
ports: [
{ id: "a1", index: 0, text: "A", color: "yellow", side: "right" },
{ id: "a2", index: 1, text: "A", color: "yellow", side: "right" },
{ id: "a3", index: 2, text: "A", color: "yellow", side: "right" },
{ id: "a4", index: 3, text: "A", color: "yellow", side: "right" },
{ id: "b1", index: 4, text: "B", color: "lightgreen", side: "right" },
{ id: "b2", index: 5, text: "B", color: "lightgreen", side: "right" },
{ id: "c1", index: 6, text: "C", color: "orange", side: "right" },
{ id: "a11", index: 0, text: "A", color: "yellow", side: "left" },
{ id: "a12", index: 1, text: "A", color: "yellow", side: "left" },
{ id: "a13", index: 2, text: "A", color: "yellow", side: "left" },
{ id: "b11", index: 3, text: "B", color: "lightgreen", side: "left" },
]
}
]
}
);
</script>
</body>
</html>