Custom PortAlignTreeLayout layout

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?

I don’t think you can easily do such a shift of the nodes after the TreeLayout has already computed where the nodes should be. As you can see in your results, the shift does not take into account “cousins” – really, any section of the tree beyond the immediate siblings.

What are the properties of the Nodes? And is that tiny orange dot the actual port? Is its portId set?

I updated the code section.

The project needs to render such a structure diagram

TreeModel doesn’t support multiple ports per Node. There’s no point in putting that data into your model and trying to use data bindings in the Link template.

Instead of assigning portId to “centerPort”, set it to the empty string, “”.