Expanding nodes only inserts edges where isTreeLink is true

Hello everyone,

I am evaluating GoJS 2.2 on Chrome for our ontology modelling platform and I am trying to build a hybrid of a ER and UML diagram. I’d like to be able to browse (expand/collapse) sub class relationships in a UML tree layout but also display the relationships of classes amongst each other.

My problem is that every time I click on the expand button to insert new sub classes, the links that I have generated which have isTreeLink = false are not displayed. The code calls the methods diagram.commandHandler.expandTree(node) like shown in the TreeLayout example. The non-tree links are only rendered as soon as I expand the newly inserted child node by clicking on the tree toggle button. It seems, that the links are only rendered when is isTreeExpanded = true on the node. Is that correct?

How can I tell GoJS to not use these links for layout but still insert and render them? I am currently using a TreeLayout and set isTreeLink on the links accordingly. I’d really appreciate any hints and comments. Thank you!

PS. I am really impressed by the library so far! :)

Yes, all tree-oriented operations ignore all Links that have Link.isTreeLink false. It should be the case that if it has “Tree” in the name of the property or method, that’s the policy. That applies not only to expanding and collapsing subtrees, but also methods such as Node.findTreeChildrenNodes and Node.findTreeParentLink.

I’m wondering how your situation is different from the Org Chart Extras sample: Org Chart Extras

In that sample I think collapsing and expanding works the way that I believe you want it to work.

Thank you for your quick response, Walter. I’ll give it a try! Looks good.

I had a look at the example code but I there is no event handler for the TreeExpanderButton. How does it work when I need to dynamically generate the data upon clicking the expand button? Just adding new nodes and edges to the model does not have any effect for me…

There’s the Incremental Tree sample for that situation, where the first time the user clicks a button on a node it loads the children for that node. Incremental Tree

If you need to get the data from a server, this sample pretends to do that: Incremental Tree (We wanted our web site to be a static site, so there isn’t any real server with a web api to respond to such samples.)

I’ve seen this example and actually build parts of my solution from it. However, this example uses the following code which seems to be my problem:

if (node.isTreeExpanded) {
  diagram.commandHandler.collapseTree(node);
} else {
  diagram.commandHandler.expandTree(node);
}

These methods hide my non-tree links and I have not yet found out how to work around it.

The normal behavior for links is that if either the Link.fromNode or the Link.toNode are not visible, the link will not be visible either. This avoids leaving links “dangling”.

As you can see in the Org Chart Extras sample, when both connected nodes are visible, those non-tree links do appear. You haven’t explicitly set or bound the visibility of your extra links, have you? I’m talking about the “isCompatibleTo” link that you marked in yellow in your screenshot.

Dear Walter,

I do insert those links after I call the collapseTree and expandTree handlers to ensure that all nodes are inserted and visible:

...
    if (node.isTreeExpanded) {
      diagram.commandHandler.collapseTree(node);
    } else {
      diagram.commandHandler.expandTree(node);
    }

    diagram.commitTransaction("CollapseExpandTree");

    diagram.zoomToFit();

    if (data.children) {
      for (const child of data.children) {
        for (const relation of child.relations) {
          const edge = this.getEdge(relation);

          const from = diagram.findNodeForKey(edge.from);
          const to = diagram.findNodeForKey(edge.to);

          console.info(from, to);

          diagram.model.addLinkData(edge);

          child.isTreeExpanded = true;
          child.visible = true;
        }
      }
    }

And there is no binding on the visible property in the link template as far as I can tell. That’s what’s bugging me:

this.diagram.linkTemplate =
      $(go.Link,
        {
          selectionAdorned: true,
          layerName: "Background",
          reshapable: true,
          routing: go.Link.AvoidsNodes,
          corner: 5,
          selectionChanged: (edge) => {
            console.info(edge);

            if (!edge.data.isTreeLink) {
              this._store.dispatch(SelectResourceAction({ uri: edge.data.uri }));
            }
          }
        },
        new go.Binding("fromSpot", "isTreeLink", (v) => v ? go.Spot.TopCenter : go.Spot.LeftRightSides),
        new go.Binding("toSpot", "isTreeLink", (v) => v ? go.Spot.BottomCenter : go.Spot.LeftRightSides),
        $(go.Shape,
          {
            stroke: "#333",
            strokeWidth: 2.5,
            minSize: new go.Size(1000, NaN)
          }
        ),
        $(go.Shape,
          {},
          new go.Binding("scale", "isTreeLink", (v) => v ? 1.5 : 1.2),
          new go.Binding("toArrow", "isTreeLink", (v) => v ? "Triangle" : "Standard"),
          new go.Binding("fill", "isTreeLink", (v) => v ? "white" : "#333")),
        $(go.TextBlock,
          {
            textAlign: "center",
            segmentIndex: -1,
            segmentOffset: new go.Point(-30, NaN),
            segmentOrientation: go.Link.OrientUpright
          },
          new go.Binding("text", "cardinality")),
        $(go.TextBlock,
          {
            textAlign: "left",
            // Place the label  at the second last line segment.
            segmentIndex: -2,
            // ..near the junction. When using negative segment indices 
            // then the fraction logic is inversed as well. So 80%
            // is equivalent to 20% from the last junction point.
            segmentFraction: 0.8,
            segmentOffset: new go.Point(10, NaN),
            segmentOrientation: go.Link.OrientUpright
          },
          new go.Binding("text", "label"))
      );

How does the refresh of the diagram work when I change the model? Is there any method that I can call to trigger rendering?

Always use Model methods to modify state and make all changes within a single transaction.

Could you please give examples of data, child , and relation?

This is the data structure associated with all nodes. It contains the children and relations you asked for.

{
    "id": "http://semiodesk.com/furniture-ontology/Shelf",
    "color": "#005a9c",
    "label": "Shelf",
    "attributes": [],
    "relations": [
        {
            "id": "http://semiodesk.com/furniture-ontology/isCompatibleTo",
            "label": "isCompatibleTo",
            "domain": {
                "id": "http://semiodesk.com/furniture-ontology/Shelf"
            },
            "range": {
                "id": "http://semiodesk.com/furniture-ontology/Wardrobe"
            }
        }
    ],
    "children": [
        {
            "id": "http://semiodesk.com/furniture-ontology/Billy",
            "color": "#005a9c",
            "label": "Billy",
            "attributes": [],
            "relations": [],
            "children": null,
            "hasSubClasses": false,
            "images": [
                {
                    "url": "https://commons.wikimedia.org/w/thumb.php?f=Bookcase.jpg&w=256",
                    "__gohashid": 5485
                }
            ],
            "__gohashid": 5453,
            "location": {
                "x": 0,
                "y": 761.0913899932318,
                "v": false
            }
        },
        {
            "id": "http://semiodesk.com/furniture-ontology/Gersby",
            "color": "#005a9c",
            "label": "Gersby",
            "attributes": [],
            "relations": [],
            "children": null,
            "hasSubClasses": false,
            "images": [],
            "__gohashid": 5498,
            "location": {
                "x": 282.5228474983079,
                "y": 761.0913899932318,
                "v": false
            }
        },
        {
            "id": "http://semiodesk.com/furniture-ontology/Kallax",
            "color": "#005a9c",
            "label": "Kallax",
            "attributes": [],
            "relations": [],
            "children": null,
            "hasSubClasses": false,
            "images": [],
            "__gohashid": 5536,
            "location": {
                "x": 385.9465739028659,
                "y": 761.0913899932318,
                "v": false
            }
        }
    ],
    "hasSubClasses": true,
    "images": [],
    "__gohashid": 1210,
    "location": {
        "x": 195.86147054518295,
        "y": 589.5685424949238,
        "v": false
    },
    "isTreeExpanded": true,
    "visible": true
}

This is the node data that is created for edges/links produced by this.getEdge which is passed to the model using diagram.model.addLinkData:

{
    "id": "http://semiodesk.com/furniture-ontology/Shelf_http://semiodesk.com/furniture-ontology/isCompatibleTo_http://semiodesk.com/furniture-ontology/Wardrobe",
    "uri": "http://semiodesk.com/furniture-ontology/isCompatibleTo",
    "isTreeLink": false,
    "label": "isCompatibleTo",
    "from": "http://semiodesk.com/furniture-ontology/Shelf",
    "to": "http://semiodesk.com/furniture-ontology/Wardrobe",
    "cardinality": "0..n"
}

I wonder because the edges become visible when both nodes, the from and to node of the relation have isTreeExpanded set to true. So it seems that GoJS is treating the link as a tree link, even if it’s explicitly marked otherwise.

I would expect the link to be visible when both connected nodes are in the model and visible…

You don’t seem to have a Binding of the Link.isTreeLink property on your link template.

I added this to the Link template:

        new go.Binding("isTreeLink", "isTreeLink"),

and made sure that the link data object has a isTreeLink attribute initialized with a boolean value. Now it’s working.

Thank you, Walter! :-)