Create link between same level nodes in LayeredDigraphLayout

Hello All,

I am new to gojs. I find it a very good tool to create diagram. I am studying the LayeredDigraphLayout. The diagram connect nodes in layer, the link 's “from” is node at higher layer, and the link 's “to” is node at lower layer, and the diagram show a link from top to bottom layer. However, there are nodes at the same level and I want to connect (link) them. Please show me how to create link between same level nodes in LayeredDigraphLayout by example.

Eg: node A1 and A2 is at same layer 1 and I want to connect A1 to A2 with horizontal line.
So I have link: { “from”: “A1”, “to”: “A2”, “labelKeys”: 1, “category”:“productA”, “text”:""}
It means I want to set this link as label link but it does not work.

I read the API label node but I do not understand how to set node as label node, set label link.

Please help
Thank you very much.

You seem to be asking at least two, if not more, questions.

You are right that each Link normally instructs the LayeredDigraphLayout to make sure they are in different layers. The simplest way to to prevent that from happening is to set Part.isLayoutPositioned to false on such Links. Perhaps you can just set that in your “productA” link template.

That way the layout will ignore that Link and thus allow (but does not mandate) that the two Nodes may be in the same layer. Furthermore just because the two Nodes happen to be in the same layer will not (and cannot) guarantee that the Nodes will be near each other.

The other part of the question seems to be saying that such Links will each have a Node on them. Did you really want there to be a Node rather than just a label containing some text or other GraphObjects? Did you want there to be Links connecting with such label Nodes? For example, did you want behavior like that shown in https://gojs.net/latest/samples/linksToLinks.html ?

If so, then you need to make sure that the “labelKeys” property is an Array of key values, and that there be node data objects with those keys in the Model.nodeDataArray.

If not, it would be a lot easier to get the results that you want without using label Nodes. https://gojs.net/latest/intro/linkLabels.html

Providing a sketch or screenshot would have helped explain what you want.

Dear Walter,

Thank you very much for your help. I am now understand about labeled node and labeled link with your link to link example. I hope there are many more examples like that in intro session because it is easier to understand than API. I will try Part.isLayoutPositioned = false, In that case, I have to set position of that node manually?

I finally find that labeled node is a normal node and it can auto get a key. It helps make a link from/to another link and labeled link can not help the layout determine the correct layer of nodes in LayeredDigraphLayout.

I have read the genogram and find it exciting but very complicated. I am trying another way to do it without coding too much. I hope that you will help me more.

Please let me know how much to pay for licence gojs for non-commercial purpose?

Thank you very much.

No, I was just suggesting that you set Link.isLayoutPositioned to false, so that the link is not routed by the layout and so that the link has no effect on the layout.

Setting Node.isLayoutPositioned to false on a Node would cause the layout to ignore the node (and all links connecting with it), so the node would not be positioned nor would it have an effect on the results of the layout on the other nodes and links.

Ah, so you want to customize GenogramLayout. That is difficult to do. You can search this forum for additional information. We plan to completely re-write GenogramLayout so that it doesn’t use LayeredDigraphLayout and so that it produces better results that are more easily customized. But there is no schedule for that, so for now, you should use what you have in that Genogram sample.

Dear Walter,

Thank you for info about the link.isLayoutPositioned = false. However, I set it and it still affects the node 's position that connect with another node through this link. Please check code I send.

After several days, I finally achieve the GenogramLayout by my way, without using the labeled node and labeled link and edges, and my code is very short and extremely simple (I am new). Please check if there are errors. I have not checked multi spouses yet. And the graph is not optimized for not link crossing.



Because the diagram is very big with so many nodes, so I have to zoom to see detail. Please help to expand/collapse parts of graph so I do not have to zoom. I also want to add a web link for each person. Please help.

Thank you very much.

P/s: the pictures I send are pictures in code.

My code:

Genogram Show Graph

GoJS Genogram

A genogram or pedigree chart is an extended family tree diagram that displays information about each person or each relationship.

===========
My data:

{ “class”: “go.GraphLinksModel”,
“nodeDataArray”: [
{ “key”: -8,“name”: “Trần Văn Trượng”, “s”:“M”, “color”:“red”, “source”: “36.jpg”, “birthDate”:"", “dieDate”:"", “web”: “”},
{ “key”: -7,“name”: “Trần Văn Đạ”, “s”:“M”, “color”:“red”, “source”: “35.jpg”},
{ “key”: -6,“name”: “Trương Thị Tý”, “s”:“F”, “color”:“red”, “source”: “34.jpg”},
{ “key”: -5,“name”: “Trần Thị Lý”, “s”:“F”, “color”:“red”, “source”: “33.jpg”},
{ “key”: -4,“name”: “Trần Thị Lựu”, “s”:“F”, “color”:“red”, “source”: “32.jpg”},
{ “key”: -3,“name”: “Trần Trọng Đạt”, “s”:“M”, “color”:“red”, “source”: “31.jpg”},
{ “key”: -2,“name”: “Trần Quốc Nguyên”, “s”:“M”, “color”:“red”, “source”: “30.jpg”},
{ “key”: -1,“name”: “Trần Văn Mẫn”, “s”:“M”, “color”:“red”, “source”: “29.jpg”},
{ “key”: 0, “name”: “Trần Văn Cầu”, “s”:“M”, “color”:“red”, “source”: “28.jpg”},
{ “key”: -9,“name”: “Nguyễn Văn Chiểu”, “s”:“M”, “color”:“red”, “source”: “44.jpg”},
{ “key”: -10,“name”: “Nguyễn Thị Cấn”, “s”:“F”, “color”:“red”, “source”: “45.jpg”},
{ “key”: -11,“name”: “Nguyễn Văn Hào”, “s”:“M”, “color”:“red”, “source”: “46.jpg”},
{ “key”: -12,“name”: “Nguyễn Thị Hoa”, “s”:“F”, “color”:“red”, “source”: “47.jpg”},
{ “key”: 1, “name”: “Nguyễn Thị Ngân”, “s”:“F”, “color”:“red”, “source”: “1.jpg”},
{ “key”: 2, “name”: “Trần Bích Ngọc”, “s”:“F”, “color”:“red”, “source”: “2.jpg”},
{ “key”: 3, “name”: “Trần Thị Ánh Tuyết”, “s”:“F”, “color”:“red”, “source”: “3.jpg”},
{ “key”: 4, “name”: “Trần Ngọc Dung”, “s”:“F”, “color”:“red”, “source”: “4.jpg”},
{ “key”: 5, “name”: “Trần Đại Long”, “s”:“M”, “color”:“red”, “source”: “5.jpg”},
{ “key”: 6, “name”: “Trần Bích Hạnh”, “s”:“F”, “color”:“red”, “source”: “6.png”},
{ “key”: 7, “name”: “Hoàng Minh Đức”, “s”:“M”, “color”:“red”, “source”: “7.jpg”} ,
{ “key”: 8, “name”: “Hoàng Minh Hiếu”, “s”:“M”, “color”:“red”, “source”: “8.jpg”} ,
{ “key”: 9, “name”: “Mai Thị Ánh Hồng”, “s”:“F”, “color”:“red”, “source”: “9.jpg”},
{ “key”: 10, “name”: “Mai Bình An”, “s”:“F”, “color”:“red”, “source”: “10.jpg”},
{ “key”: 11, “name”: “Mai Thị Cát Tường”, “s”:“F”, “color”:“red”, “source”: “11.jpg”},
{ “key”: 12, “name”: “Trần Thị Thanh Tú”, “s”:“F”, “color”:“red”, “source”: “12.jpg”},
{ “key”: 13, “name”: “Đoàn Xuân Tâm”, “s”:“M”, “color”:“red”, “source”: “13.jpg”},
{ “key”: 14, “name”: “Đoàn Xuân Hòa”, “s”:“M”, “color”:“red”, “source”: “14.jpg”},
{ “key”: 15, “name”: “Hoàng Ngọc Lan Tiên”, “s”:“F”, “color”:“red”, “source”: “15.jpg”},
{ “key”: 17,“name”: “Hoàng Hồng Phúc”, “s”:“M”, “color”:“red”, “source”: “17.jpg”},
{ “key”: 18,“name”: “Mai Sỹ Hùng”, “s”:“M”, “color”:“red”, “source”: “18.jpg”},
{ “key”: 19,“name”: “Nguyễn Minh Nhật”, “s”:“M”, “color”:“red”, “source”: “19.jpg”},
{ “key”: 20,“name”: “Bùi Thị Thanh Minh”, “s”:“F”, “color”:“red”, “source”: “20.jpg”},
{ “key”: 21,“name”: “Đoàn Văn Xuân”, “s”:“M”, “color”:“red”, “source”: “21.jpg”},
{ “key”: 22,“name”: “Phạm Thị Hảo”, “s”:“F”, “color”:“red”, “source”: “22.jpg”},
{ “key”: 23,“name”: “Trần Tuyết Vân”, “s”:“F”, “color”:“red”, “source”: “23.jpg”},
{ “key”: 24,“name”: “Trần Thúy Loan”, “s”:“F”, “color”:“red”, “source”: “24.jpg”},
{ “key”: 25,“name”: “Trần Quốc Tuấn”, “s”:“M”, “color”:“red”, “source”: “25.jpg”},
{ “key”: 26,“name”: “Trần Thúy Ngọc”, “s”:“F”, “color”:“red”, “source”: “26.jpg”},
{ “key”: 27,“name”: “Trần Quốc Khánh”, “s”:“M”, “color”:“red”, “source”: “27.jpg”},
{ “key”: 28,“name”: “Trần Văn Tân”, “s”:“M”, “color”:“red”, “source”: “37.jpg”},
{ “key”: 29,“name”: “Trần Thị Tuyết”, “s”:“F”, “color”:“red”, “source”: “38.jpg”},
{ “key”: 30,“name”: “Trần Hồng Sương”, “s”:“F”, “color”:“red”, “source”: “39.jpg”},
{ “key”: 31,“name”: “Lê Văn Hào”, “s”:“M”, “color”:“red”, “source”: “40.jpg”},
{ “key”: 32,“name”: “Nguyễn Văn Trầm”, “s”:“M”, “color”:“red”, “source”: “41.jpg”},
{ “key”: 33,“name”: “Nguyễn Hoàng Đức”, “s”:“M”, “color”:“red”, “source”: “42.jpg”},
{ “key”: 34,“name”: “Nguyễn Hoàng Trung”, “s”:“M”, “color”:“red”, “source”: “43.jpg”}
],
“linkDataArray”: [
{ “from”: -8, “to”: -7},
{ “from”: -7, “to”: -5},
{ “from”: -7, “to”: -4},
{ “from”: -7, “to”: -3},
{ “from”: -7, “to”: -2},
{ “from”: -7, “to”: -1},
{ “from”: -7, “to”: 0},
{ “from”: -3, “to”: 28},
{ “from”: -3, “to”: 29},
{ “from”: -3, “to”: 30},
{ “from”: -3, “to”: 2},
{ “from”: -3, “to”: 3},
{ “from”: -3, “to”: 4},
{ “from”: -3, “to”: 5},
{ “from”: -3, “to”: 6},
{ “from”: 2, “to”: 7},
{ “from”: 2, “to”: 8},
{ “from”: 3, “to”: 9},
{ “from”: 3, “to”: 10},
{ “from”: 3, “to”: 11},
{ “from”: 5, “to”: 12},
{ “from”: 6, “to”: 13},
{ “from”: 6, “to”: 14},
{ “from”: 7, “to”: 15},
{ “from”: -2, “to”: 24},
{ “from”: -2, “to”: 25},
{ “from”: -2, “to”: 26},
{ “from”: 30, “to”: 33},
{ “from”: 30, “to”: 34},
{ “from”: -2, “to”: 27},
{ “from”: -9, “to”: 1},
{ “from”: -11, “to”: 19},
{ “from”: -9, “to”: -10, “category”:“Marriage”, “text”:""},
{ “from”: 1, “to”: 16, “category”:“Marriage”, “text”:""},
{ “from”: 2, “to”: 17, “category”:“Marriage”, “text”:""},
{ “from”: 3, “to”: 18, “category”:“Marriage”, “text”:""},
{ “from”: 4, “to”: 19, “category”:“Marriage”, “text”:""},
{ “from”: 5, “to”: 20, “category”:“Marriage”, “text”:""},
{ “from”: 6, “to”: 21, “category”:“Marriage”, “text”:""},
{ “from”: 7, “to”: 22, “category”:“Marriage”, “text”:""},
{ “from”: -3, “to”: 1, “category”:“Marriage”, “text”:""},
{ “from”: -7, “to”: -6, “category”:“Marriage”, “text”:""},
{ “from”: -2, “to”: 23, “category”:“Marriage”, “text”:""},
{ “from”: 29, “to”: 31, “category”:“Marriage”, “text”:""},
{ “from”: 30, “to”: 32, “category”:“Marriage”, “text”:""},
{ “from”: -11, “to”: -12, “category”:“Marriage”, “text”:""},
{ “from”: -9, “to”: -3, “category”: “childInLaw”},
{ “from”: -11, “to”: 4, “category”: “childInLaw”}

]

}

I’m sorry, but I do not see any code. To answer your implicit questions:

To expand/collapse parts of a graph, you probably want to use either a “TreeExpanderButton” in your Nodes or a custom “Button” whose GraphObject.click event handler toggles Node.visible in whatever manner you choose. Read more at http://gojs.net/latest/intro/buttons.html and http://gojs.net/latest/intro/subtrees.html.

It’s also possible that you could use Groups and the “SubGraphExpanderButton”. http://gojs.net/latest/intro/subgraphs.html

You can use the “HyperlinkText” that (temporarily) is defined at https://gojs.net/latest/extensions/HyperlinkText.js and is demonstrated at https://gojs.net/latest/extensions/Hyperlink.html. That code should work in version 1.6.

The implementation of “HyperlinkText” also demonstrates how you can define your own GraphObject.click event handler to open a web page, in case you don’t like “HyperlinkText”.

Dear Walter,

Thank you very much for your advise. To expand/collapse parts of a graph, can I still use “TreeExpanderButton” if the model and layout is not a tree but a graph?

For genogram, Please see my code that use a very lightweight custom layout:

var $$ = go.GraphObject.make;  // for conciseness in defining templates, avoid $ due to jQuery  

function init() {  
  myDiagram =
    $$(go.Diagram, "myDiagramDiv",
      {
        initialAutoScale: go.Diagram.Uniform,
        initialContentAlignment: go.Spot.Center,
		// allow double-click in background to create a new node
		"clickCreatingTool.archetypeNodeData": { n: "New Person", "a": ["B", "H", "S"] },
		
        "undoManager.isEnabled": true,
        // when a node is selected, draw a big yellow circle behind it
        nodeSelectionAdornmentTemplate:
          $$(go.Adornment, "Auto",
            { layerName: "Grid" },  // the predefined layer that is behind everything else
            $$(go.Shape, "Circle", { fill: "yellow", stroke: null }),
            $$(go.Placeholder)
          ),
        layout:  // use a custom layout, defined below
          $$(GenogramLayout, { direction: 90, layerSpacing: 30, columnSpacing: 40, setsPortSpots: false }) //setsPortSpots de cac link marriage di dung huong
      });

  // two different node templates, one for each sex,
  // named by the category value in the node data object

  myDiagram.nodeTemplateMap.add("M",  // male
    $$(go.Node, "Vertical",
		{desiredSize: new go.Size(50, 120)},
		$$(go.Panel,  "Spot",
		  $$(go.Picture,
			  { margin: 10, width: 49, height: 49, background: "red" },
			  new go.Binding("source")
			),
			  
		  $$(go.Shape, {
				geometryString: "F M0 0 L100 0 L100 100 L0 100 z M5,50 a45,45 0 1,0 90,0 a45,45 0 1,0 -90,0 z",
				desiredSize: new go.Size(50, 50),
				strokeWidth: 0,
				fill: 'white'
			},
			new go.Binding("fill", "color")
		  )
		),
		$$(go.TextBlock,
			{ textAlign: "center", maxSize: new go.Size(50, NaN) },
			new go.Binding("text", "name"))			
    ));
	
  myDiagram.nodeTemplateMap.add("F",  // female
    $$(go.Node, "Vertical",
		{desiredSize: new go.Size(50, 120)},
		$$(go.Panel, "Spot",
		  $$(go.Picture,
			  { margin: 10, width: 49, height: 49, background: "white" },
			  new go.Binding("source")
		  ),				  
		  $$(go.Shape, {
				geometryString: "F M0 0 L100 0 L100 100 L0 100 z M5,50 a45,45 0 1,0 90,0 a45,45 0 1,0 -90,0 z",
				desiredSize: new go.Size(50, 50),
				strokeWidth: 0,
				fill: 'white'
			}
		  )           
		),
		$$(go.TextBlock,
			{ textAlign: "center", maxSize: new go.Size(50, NaN) },
			new go.Binding("text", "name"))
    ));


  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 })
    );

  myDiagram.linkTemplateMap.add("Marriage",  // for marriage relationships
    $$(go.Link,
      { layerName: "marry", selectable: false,
	  //isLayoutPositioned: false, //why not effect to position of node connected with this link?
	    fromSpot: new go.Spot(1, 0.3), toSpot: new go.Spot(0, 0.3) //go.Spot.LeftSide
		
	  },
      $$(go.Shape, { strokeWidth: 2, stroke: "blue" })
  ));

  myDiagram.linkTemplateMap.add("childInLaw",  // for sonInLaw, daughterInLaw relationships
    $$(go.Link,
      { selectable: false},
      $$(go.Shape, { strokeWidth: 0})
	  )
  );	    
  
  jQuery.getJSON("people2.txt", function(jsondata){
	setupDiagram(myDiagram, jsondata, 5);
  });
    
}

// create and initialize the Diagram.model given an array of node data representing people
function setupDiagram(diagram, array, focusId) {
  diagram.model =
    go.GraphObject.make(go.GraphLinksModel,
      { nodeCategoryProperty: "s",
        // create all of the nodes for people
        nodeDataArray: array.nodeDataArray,
		linkDataArray: array.linkDataArray
      });

  var node = diagram.findNodeForKey(focusId);
  if (node !== null) {
    diagram.select(node);
  }  
}

// A custom layout that shows the two families related to a person's parents
function GenogramLayout() {
  go.LayeredDigraphLayout.call(this);
  this.initializeOption = go.LayeredDigraphLayout.InitDepthFirstIn;	  
  //this.spouseSpacing = 5;  // minimum space between spouses
}
go.Diagram.inherit(GenogramLayout, go.LayeredDigraphLayout);

/** @override */
GenogramLayout.prototype.makeNetwork = function(coll) {
  // generate LayoutEdges for each parent-child Link
  var net = this.createNetwork();
  if (coll instanceof go.Diagram) {
    this.add(net, coll.nodes, true);
    this.add(net, coll.links, true);
  } else if (coll instanceof go.Group) {
    this.add(net, coll.memberParts, false);
  } else if (coll.iterator) {
    this.add(net, coll.iterator, false);
  }
  return net;
};

// internal method for creating LayeredDigraphNetwork where husband/wife pairs are represented
// by a single LayeredDigraphVertex corresponding to the label Node on the marriage Link
GenogramLayout.prototype.add = function(net, coll, nonmemberonly) {
  
  // 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;
    
	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 (link.category != "Marriage") {
      var parent = net.findVertex(link.fromNode); 
      var child = net.findVertex(link.toNode);
	  
      net.linkVertexes(parent, child, link);
	}
  }

};

/** @override */

GenogramLayout.prototype.commitNodes = function() {
  go.LayeredDigraphLayout.prototype.commitNodes.call(this);

  // position node connect with link of category "Marriage"
  this.network.vertexes.each(function(v) {
	var nodesOut = v.node.findNodesOutOf();
	var nodeIt = nodesOut.iterator;
	while (nodeIt.next()) {			
		var links = v.node.findLinksTo(nodeIt.value);
		
		//traverse links:
		var linkIt = links.iterator;
		while (linkIt.next()) {			
			var link = linkIt.value;
			if(link.category == "Marriage"){
				//nodeIt.value.position = new go.Point(v.x + v.node.actualBounds.width + this.spouseSpacing, v.y);
				nodeIt.value.position = new go.Point(v.x + v.node.actualBounds.width + 5, v.y);
			}
		}
	}					
  });
  	  	
};	

// end GenogramLayout class

======================
My data:

{ “class”: “go.GraphLinksModel”,
“nodeDataArray”: [
{ “key”: -8,“name”: “Trần Văn Trượng”, “s”:“M”, “color”:“red”, “source”: “36.jpg”, “birthDate”:"", “dieDate”:"", “web”: “”},
{ “key”: -7,“name”: “Trần Văn Đạ”, “s”:“M”, “color”:“red”, “source”: “35.jpg”},
{ “key”: -6,“name”: “Trương Thị Tý”, “s”:“F”, “color”:“red”, “source”: “34.jpg”},
{ “key”: -5,“name”: “Trần Thị Lý”, “s”:“F”, “color”:“red”, “source”: “33.jpg”},
{ “key”: -4,“name”: “Trần Thị Lựu”, “s”:“F”, “color”:“red”, “source”: “32.jpg”},
{ “key”: -3,“name”: “Trần Trọng Đạt”, “s”:“M”, “color”:“red”, “source”: “31.jpg”},
{ “key”: -2,“name”: “Trần Quốc Nguyên”, “s”:“M”, “color”:“red”, “source”: “30.jpg”},
{ “key”: -1,“name”: “Trần Văn Mẫn”, “s”:“M”, “color”:“red”, “source”: “29.jpg”},
{ “key”: 0, “name”: “Trần Văn Cầu”, “s”:“M”, “color”:“red”, “source”: “28.jpg”},
{ “key”: -9,“name”: “Nguyễn Văn Chiểu”, “s”:“M”, “color”:“red”, “source”: “48.jpg”},
{ “key”: -10,“name”: “Nguyễn Thị Cấn”, “s”:“F”, “color”:“red”, “source”: “49.jpg”},
{ “key”: -11,“name”: “Nguyễn Văn Hào”, “s”:“M”, “color”:“red”, “source”: “46.jpg”},
{ “key”: -12,“name”: “Nguyễn Thị Hoa”, “s”:“F”, “color”:“red”, “source”: “47.jpg”},
{ “key”: 1, “name”: “Nguyễn Thị Ngân”, “s”:“F”, “color”:“red”, “source”: “1.jpg”},
{ “key”: 2, “name”: “Trần Bích Ngọc”, “s”:“F”, “color”:“red”, “source”: “2.jpg”},
{ “key”: 3, “name”: “Trần Thị Ánh Tuyết”, “s”:“F”, “color”:“red”, “source”: “3.jpg”},
{ “key”: 4, “name”: “Trần Ngọc Dung”, “s”:“F”, “color”:“red”, “source”: “4.jpg”},
{ “key”: 5, “name”: “Trần Đại Long”, “s”:“M”, “color”:“red”, “source”: “5.jpg”},
{ “key”: 6, “name”: “Trần Bích Hạnh”, “s”:“F”, “color”:“red”, “source”: “6.png”},
{ “key”: 7, “name”: “Hoàng Minh Đức”, “s”:“M”, “color”:“red”, “source”: “7.jpg”} ,
{ “key”: 8, “name”: “Hoàng Minh Hiếu”, “s”:“M”, “color”:“red”, “source”: “8.jpg”} ,
{ “key”: 9, “name”: “Mai Thị Ánh Hồng”, “s”:“F”, “color”:“red”, “source”: “9.jpg”},
{ “key”: 10, “name”: “Mai Bình An”, “s”:“F”, “color”:“red”, “source”: “10.jpg”},
{ “key”: 11, “name”: “Mai Thị Cát Tường”, “s”:“F”, “color”:“red”, “source”: “11.jpg”},
{ “key”: 12, “name”: “Trần T Thanh Tú”, “s”:“F”, “color”:“red”, “source”: “12.jpg”},
{ “key”: 13, “name”: “Đoàn Xuân Tâm”, “s”:“M”, “color”:“red”, “source”: “13.jpg”},
{ “key”: 14, “name”: “Đoàn Xuân Hòa”, “s”:“M”, “color”:“red”, “source”: “14.jpg”},
{ “key”: 15, “name”: “Hoàng Ngọc Lan Tiên”, “s”:“F”, “color”:“red”, “source”: “15.jpg”},
{ “key”: 17,“name”: “Hoàng Hồng Phúc”, “s”:“M”, “color”:“red”, “source”: “17.jpg”},
{ “key”: 18,“name”: “Mai Sỹ Hùng”, “s”:“M”, “color”:“red”, “source”: “18.jpg”},
{ “key”: 19,“name”: “Nguyễn Minh Nhật”, “s”:“M”, “color”:“red”, “source”: “19.jpg”},
{ “key”: 20,“name”: “Bùi Thị Thanh Minh”, “s”:“F”, “color”:“red”, “source”: “20.jpg”},
{ “key”: 21,“name”: “Đoàn Văn Xuân”, “s”:“M”, “color”:“red”, “source”: “21.jpg”},
{ “key”: 22,“name”: “Phạm Thị Hảo”, “s”:“F”, “color”:“red”, “source”: “22.jpg”},
{ “key”: 23,“name”: “Trần Tuyết Vân”, “s”:“F”, “color”:“red”, “source”: “23.jpg”},
{ “key”: 24,“name”: “Trần Thúy Loan”, “s”:“F”, “color”:“red”, “source”: “24.jpg”},
{ “key”: 25,“name”: “Trần Quốc Tuấn”, “s”:“M”, “color”:“red”, “source”: “25.jpg”},
{ “key”: 26,“name”: “Trần Thúy Ngọc”, “s”:“F”, “color”:“red”, “source”: “26.jpg”},
{ “key”: 27,“name”: “Trần Quốc Khánh”, “s”:“M”, “color”:“red”, “source”: “27.jpg”},
{ “key”: 28,“name”: “Trần Văn Tân”, “s”:“M”, “color”:“red”, “source”: “37.jpg”},
{ “key”: 29,“name”: “Trần Thị Tuyết”, “s”:“F”, “color”:“red”, “source”: “38.jpg”},
{ “key”: 30,“name”: “Trần Hồng Sương”, “s”:“F”, “color”:“red”, “source”: “39.jpg”},
{ “key”: 31,“name”: “Lê Văn Hào”, “s”:“M”, “color”:“red”, “source”: “40.jpg”},
{ “key”: 32,“name”: “Nguyễn Văn Trầm”, “s”:“M”, “color”:“red”, “source”: “41.jpg”},
{ “key”: 33,“name”: “Nguyễn Hoàng Đức”, “s”:“M”, “color”:“red”, “source”: “50.jpg”},
{ “key”: 34,“name”: “Nguyễn Hoàng Trung”, “s”:“M”, “color”:“red”, “source”: “51.jpg”}
],
“linkDataArray”: [
{ “from”: -8, “to”: -7},
{ “from”: -7, “to”: -5},
{ “from”: -7, “to”: -4},
{ “from”: -7, “to”: -3},
{ “from”: -7, “to”: -2},
{ “from”: -7, “to”: -1},
{ “from”: -7, “to”: 0},
{ “from”: -3, “to”: 28},
{ “from”: -3, “to”: 29},
{ “from”: -3, “to”: 30},
{ “from”: -3, “to”: 2},
{ “from”: -3, “to”: 3},
{ “from”: -3, “to”: 4},
{ “from”: -3, “to”: 5},
{ “from”: -3, “to”: 6},
{ “from”: 2, “to”: 7},
{ “from”: 2, “to”: 8},
{ “from”: 3, “to”: 9},
{ “from”: 3, “to”: 10},
{ “from”: 3, “to”: 11},
{ “from”: 5, “to”: 12},
{ “from”: 6, “to”: 13},
{ “from”: 6, “to”: 14},
{ “from”: 7, “to”: 15},
{ “from”: -2, “to”: 24},
{ “from”: -2, “to”: 25},
{ “from”: -2, “to”: 26},
{ “from”: 30, “to”: 33},
{ “from”: 30, “to”: 34},
{ “from”: -2, “to”: 27},
{ “from”: -9, “to”: 1},
{ “from”: -11, “to”: 19},
{ “from”: -9, “to”: -10, “category”:“Marriage”},
{ “from”: 2, “to”: 17, “category”:“Marriage”},
{ “from”: 3, “to”: 18, “category”:“Marriage”},
{ “from”: 4, “to”: 19, “category”:“Marriage”},
{ “from”: 5, “to”: 20, “category”:“Marriage”},
{ “from”: 6, “to”: 21, “category”:“Marriage”},
{ “from”: 7, “to”: 22, “category”:“Marriage”},
{ “from”: -3, “to”: 1, “category”:“Marriage”},
{ “from”: -7, “to”: -6, “category”:“Marriage”},
{ “from”: -2, “to”: 23, “category”:“Marriage”},
{ “from”: 29, “to”: 31, “category”:“Marriage”},
{ “from”: 30, “to”: 32, “category”:“Marriage”},
{ “from”: -11, “to”: -12, “category”:“Marriage”},
{ “from”: -9, “to”: -3, “category”: “childInLaw”},
{ “from”: -11, “to”: 4, “category”: “childInLaw”}

]

}

======================
Another way not need a custom layout, but generations are layered correctly from top to bottom:
My code:

var $$ = go.GraphObject.make;  // for conciseness in defining templates, avoid $ due to jQuery  

function init() {  
  myDiagram =
    $$(go.Diagram, "myDiagramDiv",
      {
        initialAutoScale: go.Diagram.Uniform,
        initialContentAlignment: go.Spot.Center,
		// allow double-click in background to create a new node
		"clickCreatingTool.archetypeNodeData": { n: "New Person", "a": ["B", "H", "S"] },
		
        "undoManager.isEnabled": true,
        // when a node is selected, draw a big yellow circle behind it
        nodeSelectionAdornmentTemplate:
          $$(go.Adornment, "Auto",
            { layerName: "Grid" },  // the predefined layer that is behind everything else
            $$(go.Shape, "Circle", { fill: "yellow", stroke: null }),
            $$(go.Placeholder)
          ),
        layout:  // use a custom layout, defined below
          $$(go.LayeredDigraphLayout, { direction: 90, layerSpacing: 30, columnSpacing: 10, setsPortSpots: false }) //setsPortSpots de cac link marriage di dung huong
      });

  // two different node templates, one for each sex,
  // named by the category value in the node data object

  myDiagram.nodeTemplateMap.add("M",  // male
    $$(go.Node, "Vertical",
		{desiredSize: new go.Size(50, 120)},
		$$(go.Panel,  "Spot",
		  $$(go.Picture,
			  { margin: 10, width: 49, height: 49, background: "red" 
			  },
			  new go.Binding("source")
			),
			  
		  $$(go.Shape, {
				geometryString: "F M0 0 L100 0 L100 100 L0 100 z M5,50 a45,45 0 1,0 90,0 a45,45 0 1,0 -90,0 z",
				desiredSize: new go.Size(50, 50),
				strokeWidth: 0,
				fill: 'white'
			},
			new go.Binding("fill", "color")
		  )
		),
		$$(go.TextBlock,
			{ textAlign: "center", maxSize: new go.Size(50, NaN) },
			new go.Binding("text", "name"))			
    ));
	
  myDiagram.nodeTemplateMap.add("F",  // female
    $$(go.Node, "Vertical",
		{desiredSize: new go.Size(50, 120)},
		$$(go.Panel, "Spot",
		  $$(go.Picture,
			  { margin: 10, width: 49, height: 49, background: "white" 
			  },
			  new go.Binding("source")
		  ),				  
		  $$(go.Shape, {
				geometryString: "F M0 0 L100 0 L100 100 L0 100 z M5,50 a45,45 0 1,0 90,0 a45,45 0 1,0 -90,0 z",
				desiredSize: new go.Size(50, 50),
				strokeWidth: 0,
				fill: 'white'
			}
		  )           
		),
		$$(go.TextBlock,
			{ textAlign: "center", maxSize: new go.Size(50, NaN) },
			new go.Binding("text", "name"))
    ));

  myDiagram.nodeTemplateMap.add("TT",  // to tien: not visible
    $$(go.Node, "Vertical",
		{desiredSize: new go.Size(0, 0)}
    ));

  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 })
    );

  myDiagram.linkTemplateMap.add("Marriage",  // for marriage relationships
    $$(go.Link,
      { layerName: "marry", selectable: false,
	    isLayoutPositioned: false //why not effect to position of node connected with this link?		    
	  },
      $$(go.Shape, { strokeWidth: 2, stroke: "blue" })
  ));

  myDiagram.linkTemplateMap.add("childInLaw",  // for sonInLaw, daughterInLaw relationships
    $$(go.Link,
      { selectable: false},
      $$(go.Shape, { strokeWidth: 0})
	  )
  );	    
  
  jQuery.getJSON("people3.txt", function(jsondata){
	setupDiagram(myDiagram, jsondata, 5);
  });
    
}

// create and initialize the Diagram.model given an array of node data representing people
function setupDiagram(diagram, array, focusId) {
  diagram.model =
    go.GraphObject.make(go.GraphLinksModel,
      { nodeCategoryProperty: "s",
        // create all of the nodes for people
        nodeDataArray: array.nodeDataArray,
		linkDataArray: array.linkDataArray
      });

  var node = diagram.findNodeForKey(focusId);
  if (node !== null) {
    diagram.select(node);
  }  
}

===============
My data: people3.txt

{ “class”: “go.GraphLinksModel”,
“nodeDataArray”: [
{ “key”: -14,“name”: “Ông nội Nguyễn Minh Nhật”, “s”:“TT”, “color”:“red”, “source”: “”},
{ “key”: -13,“name”: “Ông nội Nguyễn Thị Ngân”, “s”:“TT”, “color”:“red”, “source”: “”},
{ “key”: -12,“name”: “Nguyễn Thị Hoa”, “s”:“F”, “color”:“red”, “source”: “47.jpg”},
{ “key”: -11,“name”: “Nguyễn Văn Hào”, “s”:“M”, “color”:“red”, “source”: “46.jpg”},
{ “key”: -10,“name”: “Nguyễn Thị Cấn”, “s”:“F”, “color”:“red”, “source”: “49.jpg”},
{ “key”: -9,“name”: “Nguyễn Văn Chiểu”, “s”:“M”, “color”:“red”, “source”: “48.jpg”},
{ “key”: -8,“name”: “Trần Văn Trượng”, “s”:“M”, “color”:“red”, “source”: “36.jpg”, “birthDate”:"", “dieDate”:"", “web”: “”},
{ “key”: -7,“name”: “Trần Văn Đạ”, “s”:“M”, “color”:“red”, “source”: “35.jpg”},
{ “key”: -6,“name”: “Trương Thị Tý”, “s”:“F”, “color”:“red”, “source”: “34.jpg”},
{ “key”: -5,“name”: “Trần Thị Lý”, “s”:“F”, “color”:“red”, “source”: “33.jpg”},
{ “key”: -4,“name”: “Trần Thị Lựu”, “s”:“F”, “color”:“red”, “source”: “32.jpg”},
{ “key”: -3,“name”: “Trần Trọng Đạt”, “s”:“M”, “color”:“red”, “source”: “31.jpg”},
{ “key”: -2,“name”: “Trần Quốc Nguyên”, “s”:“M”, “color”:“red”, “source”: “30.jpg”},
{ “key”: -1,“name”: “Trần Văn Mẫn”, “s”:“M”, “color”:“red”, “source”: “29.jpg”},
{ “key”: 0, “name”: “Trần Văn Cầu”, “s”:“M”, “color”:“red”, “source”: “28.jpg”},
{ “key”: 1, “name”: “Nguyễn Thị Ngân”, “s”:“F”, “color”:“red”, “source”: “1.jpg”},
{ “key”: 2, “name”: “Trần Bích Ngọc”, “s”:“F”, “color”:“red”, “source”: “2.jpg”},
{ “key”: 3, “name”: “Trần Thị Ánh Tuyết”, “s”:“F”, “color”:“red”, “source”: “3.jpg”},
{ “key”: 4, “name”: “Trần Ngọc Dung”, “s”:“F”, “color”:“red”, “source”: “4.jpg”},
{ “key”: 5, “name”: “Trần Đại Long”, “s”:“M”, “color”:“red”, “source”: “5.jpg”},
{ “key”: 6, “name”: “Trần Bích Hạnh”, “s”:“F”, “color”:“red”, “source”: “6.png”},
{ “key”: 7, “name”: “Hoàng Minh Đức”, “s”:“M”, “color”:“red”, “source”: “7.jpg”} ,
{ “key”: 8, “name”: “Hoàng Minh Hiếu”, “s”:“M”, “color”:“red”, “source”: “8.jpg”} ,
{ “key”: 9, “name”: “Mai Thị Ánh Hồng”, “s”:“F”, “color”:“red”, “source”: “9.jpg”},
{ “key”: 10, “name”: “Mai Bình An”, “s”:“F”, “color”:“red”, “source”: “10.jpg”},
{ “key”: 11, “name”: “Mai Thị Cát Tường”, “s”:“F”, “color”:“red”, “source”: “11.jpg”},
{ “key”: 12, “name”: “Trần T Thanh Tú”, “s”:“F”, “color”:“red”, “source”: “12.jpg”},
{ “key”: 13, “name”: “Đoàn Xuân Tâm”, “s”:“M”, “color”:“red”, “source”: “13.jpg”},
{ “key”: 14, “name”: “Đoàn Xuân Hòa”, “s”:“M”, “color”:“red”, “source”: “14.jpg”},
{ “key”: 15, “name”: “Hoàng Ngọc Lan Tiên”, “s”:“F”, “color”:“red”, “source”: “15.jpg”},
{ “key”: 17,“name”: “Hoàng Hồng Phúc”, “s”:“M”, “color”:“red”, “source”: “17.jpg”},
{ “key”: 18,“name”: “Mai Sỹ Hùng”, “s”:“M”, “color”:“red”, “source”: “18.jpg”},
{ “key”: 19,“name”: “Nguyễn Minh Nhật”, “s”:“M”, “color”:“red”, “source”: “19.jpg”},
{ “key”: 20,“name”: “Bùi Thị Thanh Minh”, “s”:“F”, “color”:“red”, “source”: “20.jpg”},
{ “key”: 21,“name”: “Đoàn Văn Xuân”, “s”:“M”, “color”:“red”, “source”: “21.jpg”},
{ “key”: 22,“name”: “Phạm Thị Hảo”, “s”:“F”, “color”:“red”, “source”: “22.jpg”},
{ “key”: 23,“name”: “Trần Tuyết Vân”, “s”:“F”, “color”:“red”, “source”: “23.jpg”},
{ “key”: 24,“name”: “Trần Thúy Loan”, “s”:“F”, “color”:“red”, “source”: “24.jpg”},
{ “key”: 25,“name”: “Trần Quốc Tuấn”, “s”:“M”, “color”:“red”, “source”: “25.jpg”},
{ “key”: 26,“name”: “Trần Thúy Ngọc”, “s”:“F”, “color”:“red”, “source”: “26.jpg”},
{ “key”: 27,“name”: “Trần Quốc Khánh”, “s”:“M”, “color”:“red”, “source”: “27.jpg”},
{ “key”: 28,“name”: “Trần Văn Tân”, “s”:“M”, “color”:“red”, “source”: “37.jpg”},
{ “key”: 29,“name”: “Trần Thị Tuyết”, “s”:“F”, “color”:“red”, “source”: “38.jpg”},
{ “key”: 30,“name”: “Trần Hồng Sương”, “s”:“F”, “color”:“red”, “source”: “39.jpg”},
{ “key”: 31,“name”: “Lê Văn Hào”, “s”:“M”, “color”:“red”, “source”: “40.jpg”},
{ “key”: 32,“name”: “Nguyễn Văn Trầm”, “s”:“M”, “color”:“red”, “source”: “41.jpg”},
{ “key”: 33,“name”: “Nguyễn Hoàng Đức”, “s”:“M”, “color”:“red”, “source”: “50.jpg”},
{ “key”: 34,“name”: “Nguyễn Hoàng Trung”, “s”:“M”, “color”:“red”, “source”: “51.jpg”}
],
“linkDataArray”: [
{ “from”: -8, “to”: -7},
{ “from”: -7, “to”: -5},
{ “from”: -7, “to”: -4},
{ “from”: -7, “to”: -3},
{ “from”: -7, “to”: -2},
{ “from”: -7, “to”: -1},
{ “from”: -7, “to”: 0},
{ “from”: -3, “to”: 28},
{ “from”: -3, “to”: 29},
{ “from”: -3, “to”: 30},
{ “from”: -3, “to”: 2},
{ “from”: -3, “to”: 3},
{ “from”: -3, “to”: 4},
{ “from”: -3, “to”: 5},
{ “from”: -3, “to”: 6},
{ “from”: 2, “to”: 7},
{ “from”: 2, “to”: 8},
{ “from”: 3, “to”: 9},
{ “from”: 3, “to”: 10},
{ “from”: 3, “to”: 11},
{ “from”: 5, “to”: 12},
{ “from”: 6, “to”: 13},
{ “from”: 6, “to”: 14},
{ “from”: 7, “to”: 15},
{ “from”: -2, “to”: 24},
{ “from”: -2, “to”: 25},
{ “from”: -2, “to”: 26},
{ “from”: 30, “to”: 33},
{ “from”: 30, “to”: 34},
{ “from”: -2, “to”: 27},
{ “from”: -9, “to”: 1},
{ “from”: -11, “to”: 19},
{ “from”: -9, “to”: -10, “category”:“Marriage”},
{ “from”: 2, “to”: 17, “category”:“Marriage”},
{ “from”: 3, “to”: 18, “category”:“Marriage”},
{ “from”: 4, “to”: 19, “category”:“Marriage”},
{ “from”: 5, “to”: 20, “category”:“Marriage”},
{ “from”: 6, “to”: 21, “category”:“Marriage”},
{ “from”: 7, “to”: 22, “category”:“Marriage”},
{ “from”: -3, “to”: 1, “category”:“Marriage”},
{ “from”: -7, “to”: -6, “category”:“Marriage”},
{ “from”: -2, “to”: 23, “category”:“Marriage”},
{ “from”: 29, “to”: 31, “category”:“Marriage”},
{ “from”: 30, “to”: 32, “category”:“Marriage”},
{ “from”: -11, “to”: -12, “category”:“Marriage”},
{ “from”: -7, “to”: 23, “category”: “childInLaw”},
{ “from”: -8, “to”: -6, “category”: “childInLaw”},
{ “from”: -9, “to”: -3, “category”: “childInLaw”},
{ “from”: -11, “to”: 4, “category”: “childInLaw”},
{ “from”: -3, “to”: 17, “category”: “childInLaw”},
{ “from”: -3, “to”: 18, “category”: “childInLaw”},
{ “from”: -3, “to”: 19, “category”: “childInLaw”},
{ “from”: -3, “to”: 20, “category”: “childInLaw”},
{ “from”: -3, “to”: 21, “category”: “childInLaw”},
{ “from”: -3, “to”: 31, “category”: “childInLaw”},
{ “from”: -3, “to”: 32, “category”: “childInLaw”},
{ “from”: 2, “to”: 22, “category”: “childInLaw”},
{ “from”: -13, “to”: -9, “category”: “childInLaw”},
{ “from”: -13, “to”: -10, “category”: “childInLaw”},
{ “from”: -14, “to”: -11, “category”: “childInLaw”},
{ “from”: -14, “to”: -12, “category”: “childInLaw”}
]
}

I use “childInLaw” link to make spouses the same level, due to same parent. For couple at top level, I create an invisible parent node (TT). So not need custom layout.

This way is even simpler than previous. However, I am not satisfied because marriage couple does not stand together, so there are many crossing links between couple. Please advise me how to arrange nodes in order from left to right?

Thank you very much.

If the graph is not tree-structured, than you might not be able to use a “TreeExpanderButton”. That’s why I also suggested the alternative to do whatever you want. One such alternative is implemented at https://gojs.net/latest/samples/customExpandCollapse.html.

This might help: Twin adding to a node that is in between

Hello Walter,

After your suggest about group, I use it to combine marriage couple to one group, and use LayerDigraphLayout. So I do not need custom layout and code very simple. The result looks very nice. Please see it:

Thank you very much for your help.
Best Regards,

Ok, I’m glad you figured out a good solution.