Twin adding to a node that is in between

I have adopted some code from the twin supported Genogram to be able to represent twins in my pedigree editor app. The representation looks fantastic.

However, I also realised it will only work fine when I add twins to the last node on the right. If I tried to pick a node in between, it will always draw its twin at the last on the right. e.g:

Is there a way to prevent this?

As far as I know, if I add a spouse to a particular node, it will automatically move the new node to the selected node. Is it possible to do the same for adding twins? Does it do anything with the vertexes?

I wonder if the order of the children is causing that. That is, in your scenario, the user selected “Male Child 2” and added a “Twin” which got put at the end of the list of parent links.

I suppose the thing to do would be to customize the order in which LayoutVertexes and LayoutEdges are added to the LayoutNetwork in GenogramLayout.add.

    GenogramLayout.prototype.add = function(net, parts, nonmemberonly) {
      var coll = new go.Set().addAll(parts);
      // consider all Nodes in the given collection
      var it = coll.iterator;
      while (it.next()) {
        var node = it.value;
        if (!(node instanceof go.Node)) continue;
        if (!node.isLayoutPositioned || !node.isVisible()) continue;
        if (nonmemberonly && node.containingGroup !== null) continue;
        // if it's an unmarried Node, or if it's a Link Label Node, create a LayoutVertex for it
        if (node.isLinkLabel) {
          // get marriage Link
          var link = node.labeledLink;
          var spouseA = link.fromNode;
          var spouseB = link.toNode;
          // create vertex representing both husband and wife
          var vertex = net.addNode(node);
          // now define the vertex size to be big enough to hold both spouses
          vertex.width = spouseA.actualBounds.width + 30 + spouseB.actualBounds.width;
          vertex.height = Math.max(spouseA.actualBounds.height, spouseB.actualBounds.height);
          vertex.focus = new go.Point(spouseA.actualBounds.width + 30 / 2, vertex.height / 2);
        } else {
          // don't add a vertex for any married person!
          // instead, code above adds label node for marriage link
          // assume a marriage Link has a label Node
          if (!node.linksConnected.any(function(l) { return l.isLabeledLink; })) {
            var vertex = net.addNode(node);
          }
        }
      }

      // now do all Links
      it.reset();
      while (it.next()) {
        var link = it.value;
        if (!(link instanceof go.Link)) continue;
        if (!link.isLayoutPositioned || !link.isVisible()) continue;
        if (nonmemberonly && link.containingGroup !== null) continue;
        // if it's a parent-child link, add a LayoutEdge for it
        if (!link.isLabeledLink) {
          var parent = net.findVertex(link.fromNode);  // should be a label node
          var child = net.findVertex(link.toNode);
          if (child !== null) {  // an unmarried child
            net.linkVertexes(parent, child, link);
          } else {  // a married child
            link.toNode.linksConnected.each(function(l) {
              if (!l.isLabeledLink) return;  // if it has no label node, it's a parent-child link
              // found the Marriage Link, now get its label Node
              var mlab = l.labelNodes.first();
              // parent-child link should connect with the label node,
              // so the LayoutEdge should connect with the LayoutVertex representing the label node
              var mlabvert = net.findVertex(mlab);
              if (mlabvert !== null) {
                net.linkVertexes(parent, mlabvert, link);
              }
            });
          }
        }
      }

      // sort the children for each marriage vertex
      var allv = new go.Set().addAll(net.vertexes);
      allv.each(function(mv) {
        if (mv.node === null || !mv.node.isLinkLabel) return;

        var edgelist = new go.List();
        mv.destinationEdges.each(function(e) {
          // remember each edge, the child vertex, and all of the edges coming out from the child vertex
          edgelist.add([e.toVertex, e, new go.List().addAll(e.toVertex.edges)]);
        });

        edgelist.each(function(kvp) {
          var child = kvp[0];
          net.deleteVertex(child);  // also deletes edge to child and all edges coming out from child
        });

        edgelist.sort(function(ea, eb) {
          // compare ea[0].node with eb[0].node
          return 1 or 0 or -1  //???
        });

        //// for debugging:
        //edgelist.each(function(kvp) {
        //  var parentstr = mv.node.labeledLink.fromNode.data.n + "&" + mv.node.labeledLink.toNode.data.n;
        //  var child = kvp[0].node;
        //  var childstr = child.isLinkLabel ?
        //                 child.labeledLink.fromNode.data.n + "&" + child.labeledLink.toNode.data.n :
        //                 child.data.n;
        //  console.log(parentstr + ": " + childstr);
        //});

        edgelist.each(function(kvp) {
          var child = kvp[0];
          net.addVertex(child);
          var parentedge = kvp[1];
          net.addEdge(parentedge);
          var childedges = kvp[2];
          childedges.each(function(e) { net.addEdge(e); });
        });
      });
    };

You’ll need to customize the function passed to sort. Note that the presence of marriages to either or both of the twins will affect the order of children, in any case.

This is rather tricky, which is why I took the trouble to implement the framework for you. Most of the code is the same as for the regular GenogramLayout.add. But you’ll need to spend a good bit of time learning about and adapting the code.

Hi Walter,

Thank you very much for your effort taking the time to put up a code.

Actually, I discovered a way which bring one step forward towards the outcome I’m hoping for, by adding this into prototype.commitNodes (isTwin is a custom property):

                // Handling twin
                this.network.vertexes.each(function(vertex) {
                    var node = vertex.node;
                    
                    if (node === null) { return; } 

                    if (node.data.isTwin) {
                        var orgNode = node.diagram.findNodeForKey(node.data.birth);
                        if(node.data.birth !== node.data.pedigreeMemberId) {
                            //node.move(new go.Point(orgNode.actualBounds.x + orgNode.actualBounds.width + layout.spouseSpacing, vertex.y));
                            vertex.x = orgNode.actualBounds.x;
                            node.move(new go.Point(orgNode.actualBounds.x, vertex.y));
                            orgNode.move(new go.Point(node.actualBounds.x + orgNode.actualBounds.width, vertex.y));
                        }
                    }
                });

This will actually move the node next to the selected node’s position.

However, one problem left is that, although the node is moved correctly, but it doesn’t push away the other nodes, which makes the two nodes overlap with each other. And the other new siblings created after the twin formulated will leave a big gap - e.g.,:

So, talking about marriages, when creating a new spouse will be able to move the node likewise my example, but at the same time, layout all the remaining nodes properly, so no nodes overlapped. How was this done? Is there a grouping done to the married couples? And how can I applied the same to the twins that I’m trying to achieve?

Sorry, but commitNodes is called at the end of the layout procedures, when it is time to assign the locations of the Nodes from the calculated locations stored in the LayoutVertexes. That time is much too late to affect the behavior of the layout.

Hi Walter,

Ok, I’ve took your code and modified the way I want it to work. I haven’t touch the sort, instead, I have manipulate the adding of kvp in “mv.destinationEdges”, which finally takes even closer to the result I want.

I met an issue, I cannot add a spouse to the twin nodes. It will either freeze the app or move the couple to the end - e.g.:

I have identify that the causes of freezing is because when a marriage is detected, the node is not added into the edgelist, which fail to identify its position for grouping.

I couldn’t identify the reason why the married twin would be moved to the end of the list.

Any ideas?

I’m not sure I have a good answer for you. The GenogramLayout is treating married couples specially – as if they were single nodes in a graph. So they aren’t really “children” of the parents’ marriage, because there might be two of them. Anyway, they are treated at a different time, so changing the order in which the “destinationEdges” are presented will have no effect.

We plan to create a completely new GenogramLayout that addresses these desired features and others, but we’ve been so busy we haven’t had time to finish the work.

Hi Walter,

So can you please advise which place I should change the ordering of the nodes to support twins? Currently I just need to know the part of the code that handles the married couple insertion. Once the married couples are process, then I can reshuffle the twin-related nodes.

I thought that was the code I gave you earlier in this topic.

Was just asking in the code that you sent, which part of it actually places the married couple into the diagram. While I know the “mv.destinationEdges” inserts and nodes in row order, and the top part of the code assign vertexs and combines the married couple. The code that actually places the married couple down into the diagram, I am still yet to identify it.

But that’s ok, I’ll figure this out eventually.

The married couples are treated as a single LayoutVertex within the LayoutNetwork that is operated upon by the LayeredDigraphLayout, and they are re-ordered to reduce logical link crossings. Actually, all vertexes are subject to re-ordering, but for unmarried individuals, that would not be necessary.