Linking nodes in a LayeredDigraphLayout

Hi,

I am making attempt to modify the Genogram example (based on LayeredDigraph) to support drawing lines between nodes to represent consanguineous relationship.

I had enabled the link drawing tool which works, but whenever a new line is drawn, the nodes start to reposition themselves, which I don’t want it to happen since it mess up the diagram.

Is there a setting that allows me to link the lines between nodes without repositioning the nodes?

David

Either set Link.isLayoutPositioned to false, or remove some of the flags from Link.layoutConditions, or set Layout.isOngoing to false. More discussion is at GoJS Layouts -- Northwoods Software in the section about layout invalidation.

Hi Walter,

Thank you.

The Link.isLayoutPositioned works until I add a node (Layout.isOngoing = false is not an option), that line is set to be:

fromSpot: go.Spot.LeftRightSides,
toSpot: go.Spot.LeftRightSides,

but after a node is added, the diagram reconnect that line to up and down. Could this be a bug of GoJs or is there a setting I need to set?

David

There is the LayeredDigraphLayout.setsPortSpots property. Normally the layout will set the Link.fromSpot to come out of each node in the direction appropriate for the LayeredDigraphLayout.direction, and similarly the Link.toSpot to go into each node.

Setting LayeredDigraphLayout.setsPortSpots to false will avoid that from happening automatically, but I wonder if the rest of the layout needs to do that.

What doesn’t make sense to me is that if you have set Link.isLayoutPositioned to false on some Links, then the layout will not even look at those links and therefore would not try to set their fromSpot or toSpot.

Hi Walter,

I would still need LayeredDigraphLayout.setsPortSpots for adding the nodes correctly.

The Link.isLayoutPositioned issue is kind of strange, because I have also assigned “Spot.LeftRightSides” to both fromSport and toSpot for the link. But when a node is added, for unknown reason, it ignores this setting and make the fromSpot and toSpot using top and bottom. So even the fromSpot and toSpot have been override, I think Link.isLayoutPositioned will also be affected.

What’s even stranger is that this doesn’t happens always. By linking the nodes from certain direction, it will draw fine. The bug happens when the nodes are linked in the other certain direction.

Are you using multiple ports per node? That’s a difference from the original samples/genogram.html.

Should still be the with the original genogram as we haven’t been adding new ports. It is still the typical top, bottom, left and right.

The Genogram sample, Genogram, does not have multiple ports per node.

Having separate ports on the top, bottom, left, and right sides is not typical.

Now I’m a bit confused. If there are no ports, how are those lines connected between the nodes.

We haven’t purposely add any new ports. All we’ve did is to assign values to the property “fromSpot” and “toSpot” to limit the line connection to only left and right for some of the nodes (e.g., add a spouse to a node via clicking a button).

e.g., we created some functionality based on the code below from the Genogram sample:

  myDiagram.linkTemplate =  // for parent-child relationships
    $(go.Link,
      {
        routing: go.Link.Orthogonal, curviness: 15,
        layerName: "Background", selectable: false,
        fromSpot: go.Spot.Bottom, toSpot: go.Spot.Top
      },
      $(go.Shape, { strokeWidth: 2 })
    );

OK. Are you creating a third kind of link? If so, did you define a third Link template, and are you specifying that link template names as the category in your link data objects?

You might want to read:
http://gojs.net/latest/intro/templateMaps.html

And look at this sample:
http://gojs.net/temp/genogramTwins.html

I think so, we’ve did it by adding the following:

                diagram.linkTemplateMap.add("consanguinityLink", // for consanguineous relationships
                    $(go.Link, {
                            routing: go.Link.AvoidsNodes,
                            curviness: 50,
                            corner: 20,
                            selectable: false,
                            curve: go.Link.JumpOver,
                            fromSpot: go.Spot.LeftRightSides,
                            toSpot: go.Spot.LeftRightSides,
                            fromEndSegmentLength: 30,
                            toEndSegmentLength: 30,
                            isLayoutPositioned: false
                        },
                        $(go.Shape, {
                            stroke: "transparent",
                            pathPattern: $(go.Shape, {
                                geometryString: "M0 0 L1 0 M0 5 L1 5",
                                stroke: "#ff8080"
                            })
                        })
                    ));

OK, so did you set

    category: "consanguinityLink"

on each such link data in your model?

Or did you create such links only programmatically?

We use the linkingTool to draw the consanguineous relationship.

The set category is done via:

diagram.model.setCategoryForLinkData(tool.archetypeLinkData, ‘consanguinityLink’);

So it has to be programmatically.

Are you also adding a Node? Have you set Part.layoutConditions on that, in order to avoid invalidating the layout?

I don’t think I have.

Speaking of which, when you mention about adding a node, I am thinking that I may need to add one node between the nodes I’m linking. If I’m not mistaken, a marriage line between two nodes has a node in the middle that links to the child. And, I don’t have that. Could that be the problem?

OK, I started from the already-modified sample, genogramTwins.html.

First, I set GraphObject.fromLinkable and GraphObject.toLinkable on the ports of the “M” and “F” node templates:

              { width: 40, height: 40, strokeWidth: 2, fill: "white", portId: "", fromLinkable: true, toLinkable: true }),

Second I added templates both for the new label nodes on parent links and the new links connecting twins. Note that these new links connect not the children themselves but their respective links from their shared parent marriage label node, which itself is a label node on “Marriage” links connecting the birth mother and the birth father.

      // the representation of each label node connecting twins -- nothing shows on a parent Link
      myDiagram.nodeTemplateMap.add("TwinLinkLabel",
        $(go.Node, { segmentIndex: -2, segmentFraction: 0.5, selectable: false, width: 1, height: 1, isLayoutPositioned: false }));

      myDiagram.linkTemplateMap.add("TwinLink",  // for connecting twins/triplets
        $(go.Link, { selectable: false, isTreeLink: false, isLayoutPositioned: false },
          $(go.Shape, { strokeWidth: 2, stroke: "blue" })
      ));

Also I declared that “Marriage” links are not part of a tree relationship:

      myDiagram.linkTemplateMap.add("Marriage",  // for marriage relationships
        $(go.Link, { selectable: false, isTreeLink: false },
          $(go.Shape, { strokeWidth: 2, stroke: "darkgreen" })
      ));

That is so that we can call the “tree” methods on the Node class while ignoring all “Marriage” links.

Third, I defined a new LinkingTool that is customized to only draw new links between “twins”.

    function TwinDrawingTool() {
      go.LinkingTool.call(this);
    }
    go.Diagram.inherit(TwinDrawingTool, go.LinkingTool);

    TwinDrawingTool.prototype.findLinkablePort = function() {
      var diagram = this.diagram;
      if (diagram === null) return null;
      var obj = this.startObject;
      if (obj === null) {
        obj = diagram.findObjectAt(diagram.firstInput.documentPoint, null, null);
      }
      if (obj === null) return null;
      var node = obj.part;
      if (!(node instanceof Node)) return null;
      // require the node to have parents and siblings
      var parentMarriageNode = node.findTreeParentNode();
      if (parentMarriageNode === null) return null;
      if (parentMarriageNode.findTreeChildrenLinks().count <= 1) return null;
      return node.port;
    };

    TwinDrawingTool.prototype.isValidTo = function(tonode, toport) {
      if (tonode === null || toport === null) return false;
      if (tonode.data.birth !== undefined) return false;  // already a twin?
      if (tonode === this.originalFromNode) return false;  // not to itself!
      // require both the fromnode and the tonode to have the same marriage (label) parent node
      return this.originalFromNode.findTreeParentNode() === tonode.findTreeParentNode();
    };

    TwinDrawingTool.prototype.insertLink = function(fromnode, fromport, tonode, toport) {
      var diagram = this.diagram;
      if (diagram === null) return null;
      var fromParentLink = fromnode.findTreeParentLink();
      var toParentLink = tonode.findTreeParentLink();
      // assert
      if (fromParentLink === null || toParentLink === null || fromParentLink.fromNode !== toParentLink.fromNode) {
        throw new Error("this should never happen");
      }
      var commonParent = fromParentLink.fromNode;
      var model = diagram.model;
      // make sure the parent link of the fromnode has a label node; create it if needed
      var fromParentLabelNode = fromParentLink.labelNodes.first();
      var fromLabelData = null;
      if (fromParentLabelNode === null) {
        fromLabelData = { };
        model.setCategoryForNodeData(fromLabelData, "TwinLinkLabel");
        model.addNodeData(fromLabelData);  // assigns key
        model.addLabelKeyForLinkData(fromParentLink.data, model.getKeyForNodeData(fromLabelData));
      } else {
        fromLabelData = fromParentLabelNode.data;
      }
      // make sure the parent link of the tonode has a label node; create it if needed
      var toParentLabelNode = toParentLink.labelNodes.first();
      var toLabelData = null;
      if (toParentLabelNode === null) {
        toLabelData = { };
        model.setCategoryForNodeData(toLabelData, "TwinLinkLabel");
        model.addNodeData(toLabelData);  // assigns key
        model.addLabelKeyForLinkData(toParentLink.data, model.getKeyForNodeData(toLabelData));
      } else {
        toLabelData = toParentLabelNode.data;
      }
      // now that both label nodes exist, add a TwinLink between them
      var twinLinkData = {};
      model.setCategoryForLinkData(twinLinkData, "TwinLink");
      model.setFromKeyForLinkData(twinLinkData, model.getKeyForNodeData(fromLabelData));
      model.setToKeyForLinkData(twinLinkData, model.getKeyForNodeData(toLabelData));
      model.addLinkData(twinLinkData);
      // make sure "birth" is set on both nodes
      if (fromnode.data.birth === undefined) {
        // how many sets of twins/triplets does this marriage have already?
        var maxBirth = 0;
        commonParent.findTreeChildrenNodes().each(function(n) {
          if (n.data.birth !== undefined) {
            maxBirth = Math.max(maxBirth, n.data.birth);
          }
        })
        model.setDataProperty(fromnode.data, "birth", maxBirth + 1);
      }
      model.setDataProperty(tonode.data, "birth", fromnode.data.birth);
      return diagram.findLinkForData(twinLinkData);
    };

Install this tool by replacing the standard ToolManager.linkingTool when initializing the Diagram:

            linkingTool: new TwinDrawingTool(),