Configuring tree layout

Hi!

I’m using TreeLayout to display GraphLinks model.

The minimal configuration I’m trying to use looks like this:
$(go.TreeLayout, { treeStyle: go.TreeLayout.StyleLastParents, layerSpacing: 80, nodeSpacing: 80, alternateLayerSpacing: 160, alternateNodeSpacing: 160, }),

But further configuration of layout causes me a lot of problems:

  • enabling alignment: go.TreeLayout.AlignmentBus, alternateAlignment: go.TreeLayout.AlignmentTopLeftBus,
    Results in strange geometry of links

  • enabling arrangement: go.TreeLayout.ArrangementHorizontal,
    Results in whole tree being collapsed into single line

What am I doing wrong or is it something wrong with my model?

Another question I have is how to prevent multiple links from collapsing into a single line.

Is the graph tree-structured?

The direction of each link matters. Which way are they going? Try this Diagram initialization first:

    isTreePathToChildren: false,
    layout: $(go.TreeLayout),
    . . .

With the options you provided it looks like this now:

The graph is tree-structured however could be multiple trees.

I have ports on the nodes black is input (toLinkable:true) blue is output (fromLinkable: true).
Judging by links the direction is correct I have relinkableTo: true, on my linkTemplate.

That’s good. Now I suggest simplifying your node template by not setting GraphObject.portId.

All right, no portIds. Also added toArrow, so the direction is clear.

OK. If you want to allow users to reconfigure the tree by relinking, and if you want to keep it tree-structured, set Diagram.validCycle:
validCycle: go.Diagram.CycleSourceTree

Read more at GoJS Validation -- Northwoods Software.

Yes, thank you! It helped!
So now the arrangement:go.TreeLayout.ArrangementHorizontal and alignment:go.TreeLayout.AlignmentBus as I need it.

I still have question about the links though. As I understood from http://gojs.net/latest/intro/connectionPoints.html I should be setting properties fromSpot and toSpot on my nodeTemplate. Have also tried to set it not a node but on a port. But it has no effect on how the diagram looks.

By default the “directional” Layouts also set the Link.fromSpot and Link.toSpot, since that usually produces the best results. And the Link spot properties take precedence over the port spot properties.

But you can turn off that behavior. For TreeLayout, set TreeLayout.setsPortSpot and/or TreeLayout.setsChildPortSpot to false.

Setting fromSpot: go.Spot.RightSide, toSpot: go.Spot.LeftSide on linkTemplate, and setsPortSpot: false, setsChildPortSpot: false on TreeLayout solved the problem.

However links now cross the nodes. Setting Link.routing to go.Link.AvoidsNodes doesn’t change the behavior.

If you are using AvoidsNodes routing, maybe the two rows are too close to each other horizontally. Try increasing the rowSpacing.

It seems I have different problem than rowSpacing.

The routing works correctly after some changes are introduced to the model:

In this case when the new node is created.

It’s as if the initial layout wasn’t operating with the initialized nodes for some reason.

What’s your Diagram initialization, including .layout?

let diagram = $(go.Diagram, element[0], { initialContentAlignment: go.Spot.RightSide, initialAutoScale: go.Diagram.Uniform, initialDocumentSpot: go.Spot.TopCenter, initialViewportSpot: go.Spot.TopCenter, validCycle: go.Diagram.CycleSourceTree, isReadOnly: false, maxSelectionCount: 100, isTreePathToChildren: false, layout: $(go.TreeLayout, { treeStyle: go.TreeLayout.StyleLayered, arrangement: go.TreeLayout.ArrangementHorizontal, alignment: go.TreeLayout.AlignmentBus, angle: 180, rowSpacing: 80, nodeSpacing: 120, layerSpacing: 120, sorting: go.TreeLayout.SortingAscending, comparer: (v1, v2) => { if (v1.children.length > v2.children.length) { return 1; } else if (v1.children.length === v2.children.length) { return 0; } else { return -1; } }, setsPortSpot: false, setsChildPortSpot: false } ), "ModelChanged": updateAngular, "ChangedSelection": updateSelection, "undoManager.isEnabled": true, "animationManager.duration": 800, "animationManager.isEnabled": true, "clickCreatingTool.archetypeNodeData": { Name: "new node" }, "linkingTool.archetypeLinkData": { Amount: 1, UnitName:'p', fromPort: 'out', toPort: 'in' } });

Another issue which is probably related is that animation isn’t happening.

When diagram is initialized model consists of only one node. Model is updated as soon as data is available.

Ah, when you update the model with newly available data, do you do so within a transaction?

And whether you do or not, those Diagram.initial… properties will not apply, because you have already done the initial loading of the model with just a single node in it.

But what you could do is set up that single node and then call Diagram.delayInitialization with no arguments. Then when eventually you do perform a transaction loading the newly acquired data, that should cause the standard “initial…” properties and events to be used and to be raised.

I’m following the basic angular sample.
Does it mean that update here should be wrapped in a transaction:

// notice when the value of "model" changes: update the Diagram.model scope.$watch("model", function(newmodel) { var oldmodel = diagram.model; if (oldmodel !== newmodel) { diagram.removeDiagramListener("ChangedSelection", updateSelection); diagram.model = newmodel; diagram.addDiagramListener("ChangedSelection", updateSelection); } });

Oh, no, replacing the whole model, Diagram.model, causes initialization to happen (again).

Helps with animation and layout on initialization.

But animation is not running when I’m adding nodes or links.

How do you add nodes or links? If you are not replacing the model, you have to conduct a single transaction around all of the changes that you want to happen as one operation.

I’m using these functions to add nodes:

function newDetachedNode() {
	diagram.startTransaction("Create new detached node");
	let newNode = { Id: temporaryNodeKey(), Name: "new node"};
	diagram.model.addNodeData(newNode);
	diagram.commitTransaction("Create new detached node");
	editTextOnNewNode(newNode);
}

function newChildNode() {
	if (diagram.model.selectedNodeData) {
		const selectedNode: GoCanvasNode = diagram.selection.first();
		if (selectedNode instanceof go.Node) {
			diagram.startTransaction("Create new child node");
			let newNode = { Id: temporaryNodeKey(), Name: "new node"};
			diagram.model.addNodeData(newNode);
			let newLink = { toNode: selectedNode.data.Id, fromNode: newNode.Id};
			diagram.model.addLinkData(newLink);
			diagram.commitTransaction("Create new child node");
			editTextOnNewNode(newNode);
		}
	}
}

function editTextOnNewNode(node) {
	let newNode = diagram.findNodeForKey(node.Id);
	diagram.select(newNode);
	diagram.commandHandler.editTextBlock();
}

I do not know what GoCanvasNode and selectedNodeData are, but that code looks sensible.

But because you are executing two separate transactions, one to add parts and one to edit, any layout animation that the first transaction would have done will be short-circuited by the second transaction. The layout animation is purely a visual effect that has no bearing on the actual state changes that occurred.

I suppose you could start the text editing only after the animation is finished, in an “AnimationFinished” DiagramEvent listener. I would guess you would want to remove that listener or disable it during the execution of that listener, so that it doesn’t happen again after unrelated changes.