Tree Layout - Expand Only selected nodes and Collapse Some Nodes

Hi Walter,

In tree layout, suppose we have a parent node and 5 child nodes, by default 3 nodes should be expanded from parent node and 2 nodes should be collapsed based on node data condition. I have to expand that node if I toggle a button in node not the TreeExpanderButton. TreeExpanderButton should expand those 3 nodes only.

I’m a bit confused. Two small screenshots showing before and after would help a lot.

I’m guessing that you cannot use a “TreeExpanderButton” because it hides all of the children of a node, and you only want to hide some of the children. Is that correct?

When hiding a child node did you also want to hide all of its children, or only some of them? I’ll assume all of them.

Your “Button” when clicked will need to perform a transaction that iterates over the button.part.findTreeChildrenNodes(). If you want to hide a visible child node, call Node.collapseTree on it and then set its visible property to false. If you want to show a not-visible child node, make it visible and call Node.expandTree on it.

I want some of the children to hide.
Sorry for my Ugly diagram.

Here is what I want.
image
I have a parent node and 6 children, but initially only 4 children should visible based on node data condition.And on collapsing or expanding TreeExpanderButton only those 4 children should be expanded or collapsed.
Another 2 children should visible , when I click a circle button above the parent node.
Basically that should act as a TreeExpanderButton for that 2 children alone.

OK, then what I described in my previous reply’s last paragraph is exactly what you want to do.

Can We Override TreeExpanderButton to Collapse or Expand only some of the Children nodes??

You could override the click function of the “TreeExpanderButton” button, but it’s more common to define your own “Button”.

An example of overriding the “TreeExpanderButton” is in the Incremental Tree sample. You can see all predefined buttons in Buttons.js.

I have tried this, but didn’t achieved. Could you please help me by giving me minimal diagram??

???

You could do something like this:

function init() {
  var $ = go.GraphObject.make;

  myDiagram =
    $(go.Diagram, "myDiagramDiv",
      {
        "undoManager.isEnabled": true,
        layout: $(go.TreeLayout)
      }
    );

  // define a simple Node template
  myDiagram.nodeTemplate =
    $(go.Node, "Spot",  // the Shape will go around the TextBlock
      new go.Binding("visible", "isOther", function (o) { return !o; }),
      $(go.Panel, "Auto",
        $(go.Shape, "RoundedRectangle", { strokeWidth: 0 },
          // Shape.fill is bound to Node.data.color
          new go.Binding("fill", "color")),
        $(go.TextBlock,
          { margin: 8 },  // some room around the text
          // TextBlock.text is bound to Node.data.key
          new go.Binding("text", "key"))
      ),
      makeCustomExpander(go.Spot.Right, false),
      makeCustomExpander(go.Spot.TopRight, true)
    );

  function makeCustomExpander(align, others) {
    return $("Button",
      {
        alignment: align,
        click: function(e, obj) {
          var node = obj.part;  // get the Node containing this Button
          if (node === null) return;
          e.handled = true;
          expandNode(node, others);
        }
      },
      $(go.Shape,  // the icon
        {
          name: 'ButtonIcon',
          figure: 'MinusLine',
          stroke: '#424242',
          strokeWidth: 2,
          desiredSize: new go.Size(8, 8)
        },
        // bind the Shape.figure to the node.data._othersExpanded value using this converter:
        new go.Binding('figure', others ? '_othersExpanded' : '_primariesExpanded',
          function (exp, shape) {
            var but = shape.panel;
            return exp ? 'MinusLine' : 'PlusLine';
          }
        )
      ),
      // assume initially not visible because there are no links coming out
      { visible: false },
      // bind the button visibility to whether it's not a leaf node
      new go.Binding('visible', 'isTreeLeaf',
        function (leaf) { return !leaf; }
      ).ofObject()
    );
  }

  function expandNode(node, others) {
    var diagram = node.diagram;
    var nd = node.data;
    diagram.startTransaction("CollapseExpandTree");
    var children = node.findTreeChildrenNodes();
    children.each(function(c) {
      var cd = c.data;
      if (others && !cd.isOther) return;
      if (!others && cd.isOther) return;
      if (c.visible) {
        c.collapseTree();
        c.visible = false;
      } else {
        c.visible = true;
        c.expandTree();
      }
    });
    if (others) {
      myDiagram.model.set(node.data, '_othersExpanded', !node.data._othersExpanded);
    } else {
      myDiagram.model.set(node.data, '_primariesExpanded', !node.data._primariesExpanded);
    }
    diagram.commitTransaction("CollapseExpandTree");
  }

  // create the model data that will be represented by Nodes and Links
  myDiagram.model = new go.TreeModel(
  [
    { key: "Alpha", _othersExpanded: false, _primariesExpanded: true, color: "lightblue" },
    { key: "Beta",  _othersExpanded: false, _primariesExpanded: true, color: "orange", parent: "Alpha" },
    { key: "Gamma", _othersExpanded: false, _primariesExpanded: true, color: "lightgreen", parent: "Alpha" },
    { key: "Delta", _othersExpanded: false, _primariesExpanded: true, color: "pink", isOther: true, parent: "Alpha" }
  ]);
}

One button expands/collapses any nodes that are not “isOther”, while the other does the opposite. The “_primariesExpanded” and “_othersExpanded” properties keep track of whether the subtrees are expanded or not, similar to the isTreeExpanded property for a typical tree.

Demo: https://codepen.io/jhardy/pen/eYWrVGr?editors=1010

Thank you so much…but there is one functionality not working as expected…that I can’t expand or collapse that one children when that 3 children are in collapsed state…

Any way thank you once again…I will find the bug or else if it was small bug please update me…
Thanks in advance…

I’ve updated the code and demo above. It now uses two custom properties since the standard isTreeExpanded property is not applicable to your scenario.

Thank you much jhardy

Hi JHardy,
Thank you so much for the context you have given me. It’s Working good and Fine…

By the way in that functionalities, ScrollToPart doesn’t working on Seaching for nodes…If I search for the node which is Collapsed, then it is not Expanding.
What should I do???

Hi,

I have tried below to visible searched nodes.

myDiagram.highlighteds.each(e => {e.visible = true})

But if I search for a Grand child of a parent node, it was not working.
Also If I search for direct child, then its child are not Expandable.

Could you please help me??

If the chosen node is not visible, make it visible along with expanding all parent nodes in the chain of parent nodes up to the root node.

You can either call ScrollToPart in a setTimeout function or in a “LayoutCompleted” DiagramEvent listener.

Hi Walter,
I have tried that you have told, and that working fine.

> myDiagram.startTransaction('highlight search');
> 
> myDiagram.highlighteds.each((e) => {
> 
>     let parent = e.findTreeParentNode();
> 
>     if (parent) {
> 
>         visibleAllChilds(parent);
> 
>     }
> 
>     function visibleAllChilds(parent) {
> 
>         parent["visible"] = true;
> 
>         parent.findTreeChildrenNodes().each((el) => {
> 
>             el.visible = true;
> 
>             bindButtonValues(el);
> 
>         });
> 
>         let subParent = parent.findTreeParentNode();
> 
>         if (subParent) {
> 
>             visibleAllChilds(subParent);
> 
>         }
> 
>         bindButtonValues(parent);
> 
>         e.expandTree();
> 
>     }
> 
>     function bindButtonValues(parent) {
> 
>         if (e.data["isPassedClient"]) {
> 
>             myDiagram.model.set(
> 
>                 e.data,
> 
>                 "_othersPassedExpanded",
> 
>                 !e.data._othersPassedExpanded
> 
>             );
> 
>         } else if (e.data["isFailedClient"]) {
> 
>             myDiagram.model.set(
> 
>                 e.data,
> 
>                 "_othersFailedExpanded",
> 
>                 !e.data._othersFailedExpanded
> 
>             );
> 
>         } else if (!e.data["isRg"] && !e.data["isClient"]) {
> 
>             myDiagram.model.set(
> 
>                 e.data,
> 
>                 "_primariesExpanded",
> 
>                 !e.data._primariesExpanded
> 
>             );
> 
>         }
> 
>     }
> 
> 
> });
> 
> myDiagram.updateAllTargetBindings();
> 
> myDiagram.commitTransaction('highlight search');

I have wrote this above code for search and its working fine.
But updateAllTargetBindings is not working as expected. I have binded _othersFailedExpanded to Fill property , that updates the nodedataarray, but does not update the diagram. No changes in Diagram fill property.

What is happening here and Why there is no changes?

Since you are correctly calling Model.set, there is no need to call Diagram.updateAllTargetBindings. So my guess is that there a problem with your data Binding on the Shape.fill property.

Yes…My mistake…Thank you so much Walter, jhardy