TreeLayout StyleRootOnly root nodes overlapping

Hello! Cant find a solution.
We have a diagram with TreeLayout, and it must looks like this:

Setup is:

    this.contentAlignment = go.Spot.TopLeft; // Diagram align center
    this.toolManager.draggingTool.isEnabled = false; // Disable node moving
    this.toolManager.panningTool.isEnabled = true; // Diagram moving
    this.allowMove = false; // Disable node moving
    this.model = new go.TreeModel([]); // Define diagram model
    this.defaultCursor = 'pointer'; // Define diagram cursor
    this.nodeTemplateMap = templateMap;
    this.padding = new go.Margin(0);

    this.layout = $(
        go.TreeLayout,
        {
            treeStyle: go.TreeLayout.StyleRootOnly,
            angle: 90,
            layerSpacing: 20,
            compaction: go.TreeLayout.CompactionNone,
            alternateAlignment: go.TreeLayout.AlignmentStart,
            alternateNodeIndent: 50,
            alternateNodeIndentPastParent: 0.4,
            alternateLayerSpacingParentOverlap: 1,
            alternatePortSpot: new go.Spot(0.01, 1, 16, 0),
            alternateCompaction: go.TreeLayout.CompactionNone
        }
    );

 // Connection Arrows of Nodes
    this.linkTemplate = $(
        go.Link,
        go.Link.Orthogonal,
        {
            corner: 0,
            selectable: false
        },
        $(
            go.Shape,
            {
                strokeWidth: 1
            },
            new go.Binding('stroke', 'fromNode', n => n.data.parent ? Diagram.Color.Grey : null).ofObject()
        )
    );

And there is one issue with Top folders (root nodes) overlapping only if it is expanded, and all direct child (collapsed, or without children) template width is less than Top folder parent width.


It seems like overall branch with calculating depending on child width.
Trying to fix this 2nd day in a row .(
Thank you!

How are you collapsing/expanding those root nodes? What is your node template?

Here is the node templates:
One on the top (root node)

/** Folder Node */
export const nodeTemplate = $(
    go.Node,
    'Horizontal',
    {
        selectionAdorned: false,
        layerName: 'Foreground',
        height: 36,
    },
    new go.Binding('selectable', '', n => !n.isDisabled),
    $(
        go.Panel,
        'Horizontal',
        {
            height: 32,
            padding: new go.Margin(0, 24, 0, 0),
            alignment: go.Spot.BottomLeft
        },
        new go.Binding('background', '', (n: NodeForTree) => {
            if (n.isDisabled) { return Diagram.Color.GreyLight; }
            return n.isSelected
                ? Diagram.Color.Blue
                : n.category === GoType.SubRootNode
                    ? Diagram.Color.GreyLight
                    : Diagram.Color.White;
        }),
        $(Element.VerticalLine),
        $(Element.ArrowButton),
        $(
            go.Panel,
            'Table',
            {
                maxSize: new go.Size(220, NaN),
                alignment: go.Spot.Left,
            },
            new go.Binding('minSize', '', (n: NodeForTree) => new go.Size(n.isFeatured || n.isPendingApproval ? NaN : 40, NaN)),
            $(
                go.TextBlock,
                {
                    row: 0,
                    column: 0,
                    font: '600 14px Open Sans, sans-serif',
                    wrap: go.TextBlock.None,
                    alignment: go.Spot.Left,
                    overflow: go.TextBlock.OverflowEllipsis
                },
                new go.Binding('maxSize', '', n => (n.isFeatured ? new go.Size(192, NaN) : null)),
                new go.Binding('stroke', '', (n: NodeForTree) => n.isSelected ? Diagram.Color.White : Diagram.Color.Black),
                new go.Binding('text', '', (n: NodeForTree) => n.shortName),
                new go.Binding('opacity', 'status', status => status === NodeStatus.MarkedForRemoval ? 0.4 : 1),
            ),
        ),
        $(Element.ClockIcon),
        $(Element.FeaturedIcon),
    ),
    $(Element.StatusCircle),
    {
        toolTip: nodeTooltip
    },
    {
        click(e, obj): void {
            // Do nothing
        }
    },
);

And for child nodes:

/** Non-Folder Node */
export const innerNodeTemplate = $(
    go.Node,
    'Horizontal',
    {
        selectionAdorned: false,
        layerName: 'Foreground',
        height: 54,
    },
    new go.Binding('selectable', '', n => !n.isDisabled),
    $(
        go.Panel,
        'Horizontal',
        {
            height: 50,
            padding: new go.Margin(0, 24, 0, 0),
            alignment: go.Spot.BottomLeft
        },
        new go.Binding('background', '', (n: NodeForTree) => {

            if (n.isDisabled) { return Diagram.Color.GreyLight; }

            if (n.isSelected) {
                switch (n.category) {
                    case NodeType.FRA:
                        return NodeTypesColor.FRA;

                    case NodeType.CustomBot:
                        return NodeTypesColor.CustomBot;

                    case NodeType.Router:
                        return NodeTypesColor.Router;

                    case NodeType.Skill:
                        return NodeTypesColor.Skill;
                }
            }
            return Diagram.Color.White;
        }),
        $(Element.VerticalLine),
        $(Element.ArrowButton),
        $(
            go.Panel,
            'Table',
            {
                maxSize: new go.Size(220, NaN),
                alignment: go.Spot.Left,
            },
            new go.Binding('minSize', '', (n: NodeForTree) => new go.Size(n.isFeatured || n.isPendingApproval ? NaN : 40, NaN)),

            $(
                go.TextBlock,
                {
                    row: 0,
                    column: 0,
                    font: '400 14px Open Sans, sans-serif',
                    wrap: go.TextBlock.None,
                    alignment: go.Spot.Left,
                    overflow: go.TextBlock.OverflowEllipsis
                },
                new go.Binding('maxSize', '', n => (n.isFeatured ? new go.Size(192, NaN) : null)),
                new go.Binding('stroke', '', (n: NodeForTree) => n.isSelected ? Diagram.Color.White : Diagram.Color.Black),
                new go.Binding('text', '', (n: NodeForTree) => n.shortName),
                new go.Binding('opacity', 'status', status => status === NodeStatus.MarkedForRemoval ? 0.4 : 1),
            ),
            $(
                go.TextBlock,
                {
                    row: 1,
                    column: 0
                },
                {
                    font: '400 13px Open Sans, sans-serif',
                    alignment: go.Spot.Left,
                    margin: new go.Margin(3, 0, 0, 0),
                },
                new go.Binding('stroke', '', (n: NodeForTree) => n.isSelected ? Diagram.Color.White : Diagram.Color.Black),
                new go.Binding('text', '', (n: NodeForTree) => n.normalizedName.normalized),
                new go.Binding('opacity', '', n => n.isDisabled ? 0.3 : 0.5),
                new go.Binding('opacity', 'status', status => status === NodeStatus.MarkedForRemoval ? 0.3 : 0.5),
            )
        ),
        $(Element.ClockIcon),
        $(Element.FeaturedIcon),
    ),
    $(Element.StatusCircle),
    {
        toolTip: nodeTooltip
    },
    {
        click(e, obj): void {
            // Do nothing
        }
    },
);

And here is the Arrow button (to expand/collapse)

go.GraphObject.defineBuilder(Element.ArrowButton, (): go.Panel => {

    const size: number = 17;

    const arrowButton = $(
        'Button',
        {
            visible: false,
            name: Diagram.Target.ARROW,
            margin: new go.Margin(0, 8, 0, 0),
        },
        new go.Binding('visible', 'isTreeLeaf', isLeaf => !isLeaf).ofObject(),
        $(
            go.Shape,
            'Ellipse',
            {
                desiredSize: new go.Size(size, size),
                stroke: null,
                isPanelMain: true
            },
            new go.Binding('fill', '', n => n.isCollapsed ? Diagram.Color.BlueLight : n.category === GoType.SubRootNode ? Diagram.Color.White : Diagram.Color.Grey)
        ),
        $(
            go.Shape,
            {
                geometry: new Arrow().geometry,
                strokeWidth: 1,
                stroke: Diagram.Color.White
            },
            new go.Binding('geometry', 'isCollapsed', c => c ? new Arrow().geometry.rotate(-90) : new Arrow().geometry),
            new go.Binding('stroke', '', n => n.isCollapsed ? Diagram.Color.White : n.category === GoType.SubRootNode ? Diagram.Color.GreyDark : Diagram.Color.White),
        ),
        {
            click(e, btn): void {
                let node = btn.part;

                if (node instanceof go.Adornment) {
                    node = node.adornedPart;
                }
                if (!(node instanceof go.Node)) { return; }

                const diagram = node.diagram;
                if (diagram === null) { return; }

                const cmd = diagram.commandHandler;
                if (node.isTreeExpanded) {
                    if (!cmd.canCollapseTree(node)) { return; }
                } else {
                    if (!cmd.canExpandTree(node)) { return; }
                }

                e.handled = true;

                const expandedNodes = JSON.parse(sessionStorage.getItem(Common.SessionStorageKey.ExpandedNodes)) || [];

                if (node.isTreeExpanded) {
                    cmd.collapseTree(node);
                    const index = expandedNodes.indexOf(node.data.id);
                    expandedNodes.splice(index, 1);
                    node.diagram.model.setDataProperty(node.data, 'isCollapsed', true);
                } else {
                    cmd.expandTree(node);
                    node.diagram.model.setDataProperty(node.data, 'isCollapsed', false);
                    expandedNodes.push(node.data.id);
                }

                sessionStorage.setItem(Common.SessionStorageKey.ExpandedNodes, JSON.stringify(expandedNodes));
            }
        }
    );

    return arrowButton;
});

I had a hard time adapting your code, so I gave up and started from scratch:

<!DOCTYPE html>
<html>
<head>
<title>Minimal GoJS Sample</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<meta charset="UTF-8">
<script src="go.js"></script>
<script id="code">
  function init() {
    var $ = go.GraphObject.make;

    myDiagram =
      $(go.Diagram, "myDiagramDiv",
          {
            layout: $(go.TreeLayout,
                {
                  arrangement: go.TreeLayout.ArrangementHorizontal,
                  arrangementSpacing: new go.Size(25, 25),
                  alignment: go.TreeLayout.AlignmentBottomRightBus,
                  angle: 90,
                  layerSpacing: 50,
                  rowSpacing: 10,
                  setsPortSpot: false
                }),
            "LayoutCompleted": function(e) {
              const b = e.diagram.computePartsBounds(e.diagram.nodes.filter(n => n.findTreeLevel() > 0));
              b.inflate(50, 25);
              back.position = b.position;
              back.desiredSize = b.size;
            },
            "undoManager.isEnabled": true
          });

    const back =
      $(go.Part,
        { layerName: "Grid", background: "whitesmoke" }
      );
    myDiagram.add(back);

    myDiagram.nodeTemplate =
      $(go.Node, 'Auto',
        { fromSpot: new go.Spot(0.0001, 1, 18, 0) },
        $(go.Shape, { fill: "lightgray" }),
        $(go.Panel, 'Horizontal',
          { margin: 10 },
          $("TreeExpanderButton"),
          $(go.TextBlock,
            new go.Binding('text'))
        )
      );

    myDiagram.linkTemplate =
      $(go.Link, go.Link.Orthogonal,
        $(go.Shape)
      );

    myDiagram.model = new go.TreeModel(
    [
      { key: 1, text: "Alpha" },
      { key: 2, text: "Beta", parent: 1 },
      { key: 3, text: "Gamma", parent: 1 },
      { key: 4, text: "Delta", parent: 1 },
      { key: 11, text: "Alpha1" },
      { key: 12, text: "Beta1", parent: 11 },
      { key: 13, text: "Gamma1", parent: 11 },
      { key: 21, text: "Alpha2" },
      { key: 22, text: "Beta2", parent: 21 },
      { key: 23, text: "Gamma2", parent: 21 },
      { key: 24, text: "Delta2", parent: 21 },
      { key: 31, text: "Alpha3" },
      { key: 32, text: "Beta3", parent: 31 },
      { key: 33, text: "Gamma3", parent: 31 }
    ]);
  }
</script>
</head>
<body onload="init()">
  <div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:600px"></div>
</body>
</html>

Wow, great, thank you! This is exactly what we need, and simplier)
I have implemented your solution, and it works well. Only one question left, how to set and keep a distance from left past parent for every node. I have noticed that now it is proportionally to a node name length.


After this solution implementation:

            layout: $(go.TreeLayout,
                {
                  arrangement: go.TreeLayout.ArrangementHorizontal,
                  arrangementSpacing: new go.Size(25, 25),
                  treeStyle: go.TreeLayout.StyleRootOnly,
                  angle: 0,
                  alignment: go.TreeLayout.AlignmentStart,
                  nodeIndent: 50,
                  nodeIndentPastParent: 1.0,
                  nodeSpacing: 15,
                  layerSpacing: 40,
                  layerSpacingParentOverlap: 1.0,
                  setsPortSpot: false
                }),

With this configuration space is set as expected.
But all child nodes showing as a row:


I tried to remove treeStyle: go.TreeLayout.StyleRootOnly property, and got this:

But we need to avoid intersection, as all sibling nodes must be at it own row. To achieve that a tried to add compaction: go.TreeLayout.CompactionNone property, and got result we wanted:

except the the thing that we faced before - an intersection of a root nodes.
Can you help us to fix this?
Thank you!

My guess at this time that it is a bug. We’ll look into it.

Yeah, it seems so, because, as you can see, we achieve the same result with 2 different approaches.
We will be waiting for your answer and suggestions.
Thank you!

OK, the bug fix will be in version 2.1.9, which we should release next week. Thanks for reporting the problem!

Thank you, Walter! We`ll be waiting for this update

Hi Walter! We have updated our gojs package from 2.0.14 to a latest 2.1.9. Bug is fixed, great, thank you!