Inherit go. TreeLayout and adjust the alignment of child nodes in commitNodes().
Calculate the portId position of each parent node and align the centerports of all child nodes on the X-axis.
The X-axis position of the entire subtree is adjusted using the recursive moveTree() method
See code!
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>GoJS</title>
<script src="https://unpkg.com/gojs/release/go.js"></script>
<style>
html,
body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
#myDiagramDiv {
width: 100%;
height: 100%;
border: 1px solid black;
}
</style>
</head>
<body>
<div id="myDiagramDiv"></div>
<script>
const $ = go.GraphObject.make;
class PortAlignTreeLayout extends go.TreeLayout {
constructor(obj) {
super(obj);
}
commitNodes() {
super.commitNodes();
const diagram = this.diagram;
diagram.nodes.each(parent => {
if (parent.findTreeChildrenNodes().count > 0) {
const port = parent.findObject(this.portName);
if (!port) return;
const parentPortCenter = port.getDocumentPoint(go.Spot.Center);
const children = Array.from(parent.findTreeChildrenNodes());
if (children.length === 0) return;
// Calculates the bounding box for all child node port points
let bounds = null;
children.forEach(child => {
const childPort = child.findObject(this.portName);
if (childPort) {
const portCenter = childPort.getDocumentPoint(go.Spot.Center);
bounds = bounds ? bounds.unionRect(new go.Rect(portCenter.x, portCenter.y, 0, 0))
: new go.Rect(portCenter.x, portCenter.y, 0, 0);
}
});
if (!bounds) return;
// Calculate the X-axis center of the child node port point
const groupPortCenterX = bounds.centerX;
const offsetX = parentPortCenter.x - groupPortCenterX; // Calculate the offset (affects only the X-axis)
// Recursively move all children (keeping port points aligned)
const moveTree = (node, deltaX) => {
node.move(new go.Point(node.position.x + deltaX, node.position.y)); // Modify only the X-axis
node.findTreeChildrenNodes().each(child => moveTree(child, deltaX));
};
children.forEach(child => moveTree(child, offsetX));
}
});
}
}
const diagram = $(go.Diagram, "myDiagramDiv", {
layout: new PortAlignTreeLayout({
angle: 90,
layerSpacing: 20,
nodeSpacing: 6,
alignment: go.TreeLayout.AlignmentCenterChildren,
portName: 'centerPort'
})
});
function getRandomHexColor() {
return '#' + Math.floor(Math.random() * 16777215).toString(16);
}
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "Rectangle", { strokeWidth: 0 },
new go.Binding('fill', '', (n, node) => {
return getRandomHexColor()
}),
),
$(go.Panel, "Horizontal", {},
$(go.Shape, {
width: 0, height: 1, fill: "red",
portId: "centerPort",
name: "centerPort",
stroke: "#FF6666",
fromSpot: go.Spot.Bottom, toSpot: go.Spot.Top,
fromLinkable: true, toLinkable: true
}),
$(go.TextBlock, { margin: 0 }, new go.Binding("text", "key")),
),
);
diagram.linkTemplate =
$(go.Link,
{ routing: go.Link.Orthogonal, corner: 0 },
$(go.Shape),
$(go.Shape, { toArrow: "" }),
new go.Binding("fromPortId", "fromPort"),
new go.Binding("toPortId", "toPort")
);
diagram.model = new go.TreeModel([
{ key: "1", loc: "0 0" },
{ key: "2", parent: "1", fromPort: "centerPort", toPort: "centerPort" },
{ key: "3", parent: "1", fromPort: "centerPort", toPort: "centerPort" },
{ key: "4", parent: "1", fromPort: "centerPort", toPort: "centerPort" },
{ key: "9", parent: "4", fromPort: "centerPort", toPort: "centerPort" },
{ key: "7777777", parent: "3", fromPort: "centerPort", toPort: "centerPort" },
{ key: "888", parent: "3", fromPort: "centerPort", toPort: "centerPort" },
{ key: "55555555555", parent: "2", fromPort: "centerPort", toPort: "centerPort" },
{ key: "6666666", parent: "2", fromPort: "centerPort", toPort: "centerPort" },
{ key: "10", parent: "6666666", fromPort: "centerPort", toPort: "centerPort" },
{ key: "11 11 11 11", parent: "6666666", fromPort: "centerPort", toPort: "centerPort" },
{ key: "12", parent: "7777777", fromPort: "centerPort", toPort: "centerPort" },
]);
</script>
</body>
</html>
How do I avoid node overlap?