Hi Simon, sorry for the delay getting back to you. I’ve got something that might be useful, a demo of the problem happening.
I stripped back my group template to the bare bones and then slowly added things back in until the probably came back.
The issue seems to be with using a table with 2 rows, the first row being a title with a custom shape. If you take the first row out, then you can get the links to align, but with both, you can’t.
Does this help?
Thanks!
<!DOCTYPE html>
<html>
<head>
<title>Minimal GoJS Sample</title>
<!-- Copyright 1998-2022 by Northwoods Software Corporation. -->
</head>
<body>
<div id="diagram" style="border: solid 1px black; width:100%; height:600px"></div>
<script src="https://unpkg.com/gojs@2.2.22"></script>
<script id="code">
const $ = go.GraphObject.make;
const KAPPA = 4 * ((Math.sqrt(2) - 1) / 3);
go.Shape.defineFigureGenerator("HalfEllipse", function (shape, w, h) {
return new go.Geometry()
.add(new go.PathFigure(0, 0, true)
.add(new go.PathSegment(go.PathSegment.Bezier, w, .5 * h, KAPPA * w, 0, w, (.5 - KAPPA / 2) * h))
.add(new go.PathSegment(go.PathSegment.Bezier, 0, h, w, (.5 + KAPPA / 2) * h, KAPPA * w, h).close()))
.setSpots(0, 0.156, 0.844, 0.844);
});
go.Shape.defineFigureGenerator("RoundedTopRectangle", function (shape, w, h) {
// this figure takes one parameter, the size of the corner
let p1 = 5; // default corner size
if (shape !== null) {
let param1 = shape.parameter1;
if (!isNaN(param1) && param1 >= 0) p1 = param1; // can't be negative or NaN
}
p1 = Math.min(p1, w / 2);
p1 = Math.min(p1, h / 2); // limit by whole height or by half height?
let geo = new go.Geometry();
// a single figure consisting of straight lines and quarter-circle arcs
geo.add(new go.PathFigure(0, p1)
.add(new go.PathSegment(go.PathSegment.Arc, 180, 90, p1, p1, p1, p1))
.add(new go.PathSegment(go.PathSegment.Line, w - p1, 0))
.add(new go.PathSegment(go.PathSegment.Arc, 270, 90, w - p1, p1, p1, p1))
.add(new go.PathSegment(go.PathSegment.Line, w, h))
.add(new go.PathSegment(go.PathSegment.Line, 0, h).close()));
// don't intersect with two top corners when used in an "Auto" Panel
geo.spot1 = new go.Spot(0, 0, 0.3 * p1, 0.3 * p1);
geo.spot2 = new go.Spot(1, 1, -0.3 * p1, 0);
return geo;
});
const diagram = $(
go.Diagram,
"diagram",
{
layout: $(go.LayeredDigraphLayout, { isInitial: false, isOngoing: false }),
"commandHandler.archetypeGroupData": { isGroup: true },
"SelectionGrouped": e => {
// e.subject.location = e.subject.memberParts.first().location;
e.subject.move(e.subject.memberParts.first().location, true);
},
"undoManager.isEnabled": true,
"draggingTool.isGridSnapEnabled": true,
}
);
diagram.grid =
$(go.Panel, "Grid",
{ visible: true, gridCellSize: new go.Size(30, 30) },
$(go.Shape, "LineH", { stroke: "#E8E8E8" }),
$(go.Shape, "LineV", { stroke: "#E8E8E8" }),
);
const inPortTemplate = $(
go.Panel,
"TableRow",
{},
$(
go.Shape,
"HalfEllipse",
{
angle: 180,
cursor: "pointer",
fromLinkable: false,
fromMaxLinks: 1,
toSpot: go.Spot.Right,
margin: new go.Margin(5, -1, 5, 0),
toLinkable: true,
toMaxLinks: 1,
fill: "black",
desiredSize: new go.Size(9, 18),
},
new go.Binding("portId", ""),
)
);
const outPortTemplate = $(
go.Panel,
"TableRow",
{},
$(
go.Shape,
"HalfEllipse",
{
cursor: "pointer",
fromLinkable: true,
fromMaxLinks: 1,
fromSpot: go.Spot.Right,
margin: new go.Margin(5, 0, 5, -1),
toLinkable: false,
toMaxLinks: 1,
fill: "black",
desiredSize: new go.Size(9, 18),
},
new go.Binding("portId", ""),
)
);
diagram.nodeTemplate = $(
go.Node,
"Horizontal",
new go.Binding("location", "location", go.Point.parse).makeTwoWay(go.Point.stringify),
{ locationSpot: go.Spot.Center },
$(
go.Panel,
"Table",
{
itemTemplate: inPortTemplate,
},
new go.Binding("itemArray", "inPortIds")
),
$(
go.Panel,
"Auto",
{
desiredSize: new go.Size(80, 80),
},
$(
go.Shape,
{ fill: "white" },
new go.Binding("fill", "color")
),
$(go.TextBlock, new go.Binding("text")),
),
$(
go.Panel,
"Table",
{
itemTemplate: outPortTemplate,
},
new go.Binding("itemArray", "outPortIds")
),
);
diagram.groupTemplate = $(
go.Group,
"Table",
{
locationSpot: go.Spot.Center,
ungroupable: true,
isSubGraphExpanded: false,
toSpot: go.Spot.Left,
fromSpot: go.Spot.Right,
},
new go.Binding("location", "location", go.Point.parse).makeTwoWay(go.Point.stringify),
{ doubleClick: (e, grp) => {
if (grp.isSubGraphExpanded) {
e.diagram.commandHandler.collapseSubGraph();
} else {
e.diagram.commandHandler.expandSubGraph();
}
}
},
// If you remove this heading row then you can get the group to align
$(
go.Panel,
"Auto",
{
stretch: go.GraphObject.Horizontal,
row: 0,
column: 0,
},
$(
go.Shape,
"RoundedTopRectangle",
{
strokeWidth: 2,
stroke: "black",
margin: new go.Margin(0, 0, -1.5, 0),
fill: "white",
},
),
$(
go.Panel,
"Vertical",
{
padding: 12,
stretch: go.GraphObject.Fill
},
$(
go.TextBlock,
"Default Text",
{
isMultiline: false,
alignment: go.Spot.Center,
wrap: go.TextBlock.None,
overflow: go.TextBlock.OverflowEllipsis,
maxSize: new go.Size(175, Infinity),
text: "Group Title"
},
),
),
),
$(
go.Panel,
"Auto",
{
stretch: go.GraphObject.Horizontal,
row: 1,
column: 0,
},
$(
go.Shape,
"Square",
{
strokeWidth: 2,
stroke: "black",
fill: "white"
},
),
$(
go.Panel,
"Vertical",
{
stretch: go.GraphObject.Horizontal,
padding: new go.Margin(8, 0, 5, 0),
},
new go.Binding("visible", "isSubGraphExpanded", (isExpanded) => !isExpanded).ofObject(),
$(
go.Panel,
"Vertical",
{
name: "icon",
margin: new go.Margin(0, 10, 0, 10),
},
$(go.TextBlock, {text: "This is a group"}),
$(go.TextBlock, {text: "On two lines"}),
),
),
$(go.Placeholder, { padding: 20 })
)
);
diagram.linkTemplate = $(
go.Link,
{
selectionAdorned: false,
routing: go.Link.Normal,
relinkableTo: true,
fromEndSegmentLength: 10,
toEndSegmentLength: 10,
},
$(
go.Shape,
{
strokeWidth: 3,
},
)
);
diagram.model = new go.GraphLinksModel(
[
{ key: 1, text: "Alpha", color: "lightblue", location: "-300 0", inPortIds: [], outPortIds: ["out"]},
{ key: 2, text: "Beta", color: "orange", location: "-150 0", inPortIds: ["in"], outPortIds: ["out"]},
{ key: 3, text: "Gamma", color: "lightgreen", location: "0 0", inPortIds: ["in"], outPortIds: ["out"]},
{ key: 4, text: "Delta", color: "pink", location: "150 0", inPortIds: ["in"], outPortIds: [] },
// If you remove these nodes then expanding a group doesn't cause such a large movement
{ key: 5, text: "Alpha", color: "lightblue", location: "-300 500", inPortIds: [], outPortIds: ["out"]},
{ key: 6, text: "Beta", color: "orange", location: "-150 500", inPortIds: ["in"], outPortIds: ["out"]},
{ key: 7, text: "Gamma", color: "lightgreen", location: "0 500", inPortIds: ["in"], outPortIds: ["out"]},
{ key: 8, text: "Delta", color: "pink", location: "150 500", inPortIds: ["in"], outPortIds: [] }
],
[
{ from: 1, to: 2, fromPort: "out", toPort: "in" },
{ from: 2, to: 3, fromPort: "out", toPort: "in" },
{ from: 3, to: 4, fromPort: "out", toPort: "in" },
{ from: 5, to: 6, fromPort: "out", toPort: "in" },
{ from: 6, to: 7, fromPort: "out", toPort: "in" },
{ from: 7, to: 8, fromPort: "out", toPort: "in" },
]
);
diagram.model.linkFromPortIdProperty = "fromPort";
diagram.model.linkToPortIdProperty = "toPort";
</script>
</body>
</html>