documentationfor yFiles for HTML 2.6

Grouping Nodes

The classic graph model is extended so that nodes can have other nodes as parent. The parent of a node is referred to as a group node, nodes with a common parent are considered as being in the same group or being grouped.

All nodes have a parent. This can either be another node or null if the node is top-level and not contained in any group node.

IGraph provides methods to create new nodes as group nodes. It also supports creating new nodes as child nodes of a given group node.

createNode(parent: INode, layout: Rect, style: INodeStyle, tag: Object): INode
Creates a new node as child of an existing group node.
createGroupNode(parent: INode, layout: Rect, style: INodeStyle, tag: Object): INode
Creates a new group node. Setting the parent parameter to an existing node will create the new node as child of the given parent node whereas setting it to null will create the new node top-level, i.e. as direct child of the graph. The new node’s properties will get the default values as defined in the groupNodeDefaults if they are not passed as parameters.

As shown in section Setting Defaults for new Items a group node has different defaults than normal (leaf) nodes. It is, for example, possible to use different node styles and label placements for group and normal nodes:

Set different defaults for newly created group and leaf nodes
// set the defaults for the nodes
// note that there are different defaults for group nodes and "normal" nodes
graph.nodeDefaults.style = new ShapeNodeStyle({ fill: Fill.DARK_ORANGE })
graph.nodeDefaults.labels.layoutParameter = InteriorLabelModel.CENTER
graph.groupNodeDefaults.style = new GroupNodeStyle({
  tabFill: 'lightblue'
})
graph.groupNodeDefaults.labels.layoutParameter = new GroupNodeLabelModel().createDefaultParameter()

Keeping this in mind we can try to create the sample graph which is shown in Grouping Hierarchy. The following example shows how the aforementioned createNode and createGroupNode can be used to build a group node with two children:

// Create a group node at the root
const independentGroup = graph.createGroupNode(null, new Rect(180, -20, 120, 60))
graph.addLabel(independentGroup, 'Independent Group')

// Create two nodes as children of independentGroup
const n4 = graph.createNode(independentGroup, new Rect(200, 0, 30, 30))
graph.addLabel(n4, '4')
const n5 = graph.createNode(independentGroup, new Rect(250, 0, 30, 30))
graph.addLabel(n5, '5')

A group node is defined as a node which is capable of having children. It does not necessarily have to have children, though. You can query and also set whether a node is a group node using the following methods.

isGroupNode(node: INode): boolean
Determines whether a node is a normal node or a group node.
setIsGroupNode(node: INode, isGroupNode: boolean): void
Converts a normal node into a group node and vice versa.

Note that turning a group node into a normal node will fail if the node has child nodes. The other way around, if you set a normal node as a parent of another node, it will automatically become a group node.

You can still change the parent of a node after creation. Setting a node’s parent to null will place the node top-level into the graph. IGraph supports setting the parent for one or more nodes at the same time:

setParent(node: INode, parent: INode): void
Sets the given node as new parent of the child node. Setting null as parent will re-parent the node to the root, i.e. as not in a group.
groupNodes(parent: INode, children: IEnumerable<INode>): void
Sets the given node as parent for all nodes in the given Enumerable.
groupNodes(children: IEnumerable<INode>): INode
Creates a new group node and sets it as parent for the given nodes.

Grouping nodes doesn’t affect the group or child node’s layout. You have to adjust the layout explicitly as shown below. Automatic Layout Algorithms also automatically adjust the size of all group nodes in the graph to encompass their children.

With this knowledge we can build the other half of the sample graph shown in Grouping Hierarchy:

Grouping nodes
// Create two nodes at top level
const n1 = graph.createNode(new Rect(0, 0, 30, 30))
graph.addLabel(n1, '1')
const n2 = graph.createNode(new Rect(50, 0, 30, 30))
graph.addLabel(n2, '2')

// Create a group node
const innerGroup = graph.createGroupNode(null, new Rect(-10, -20, 100, 60))
graph.addLabel(innerGroup, 'Inner Group')

// set innerGroup as parent of n1 and n2
graph.setParent(n1, innerGroup)
graph.setParent(n2, innerGroup)

// create a node at root ("top level")
const n3 = graph.createNode(new Rect(120, 0, 30, 30))
graph.addLabel(n3, '3')

// Now group innerGroup and n3:
const outerGroup = graph.groupNodes([innerGroup, n3])
graph.addLabel(outerGroup, 'Outer Group')
// note that the group is created at the nearest common ancestor (!)
// therefore, inner_group and n3 are grouped, actually
graph.getChildren(outerGroup) // innerGroup, n3

// grouping nodes doesn't adjust the size automatically
// adjust outerGroup's size to enclose its children
graph.adjustGroupNodeLayout(outerGroup)

Removing a group node removes the group node but not its children. Instead, the children move one level up in the hierarchy by automatically setting their parent to the removed node’s parent.

Grouping Hierarchy

Traversing the Hierarchy

IGraph provides methods to get the children and parents of a (group) node: getParent and getChildren. A more complex analysis of the node hierarchy is supported by class GroupingSupport which can be obtained for an IGraph with the groupingSupport property.

The following table lists the hierarchy related methods of IGraph and GroupingSupport and shows their output based on the example above:

Methods which Support Traversing the Hierarchy
Method Description Example usage Example output
Methods provided by IGraph
getParentReturns the node’s parent group node or null if the node is not in a group.getParent(n2)Inner Group
getParent(outerGroup)null
getChildrenReturns an enumerable of the node’s child nodes. This method only returns the direct children. If the node has another group as child node only that group node will be returned, not its children.getChildren(outerGroup)Inner Group, 3
getChildren(null)Outer Group, Independent Group
Methods provided by GroupingSupport
getDescendantsGets all descendants of a group node, i.e. its children and recursively their children. The enumeration will be top-down, i.e. all of a node’s ancestors will be enumerated before the respective node.getDescendants(outerGroup)3, Inner Group, 2, 1.
getDescendantsBottomUpGets all descendants of a group node, i.e. its children and recursively their children. The enumeration will be bottom-up, i.e. all of a node’s ancestors will be enumerated after the respective node.getDescendantsBottomUp(outerGroup)1, 2, Inner Group, 3.
isDescendantWhether the node is a descendant of the given group, i.e. a child of the given node or a child of its children.isDescendant(n1, outerGroup)true
isDescendant(n4, outerGroup)false
getPathToRootA list which includes the given node and all its parents up to the root, but not the root itself.isDescendant(n3)n3, innerGroup, outerGroup
getNearestCommonAncestorgetNearestCommonAncestorGets the nearest common ancestor of the provided nodes. Can be null if there is none.getNearestCommonAncestor(n1, n2)Inner Group
getNearestCommonAncestor(n1, n2, n3)Outer Group
getNearestCommonAncestor(n1, n2, n3, n4)null

Adjusting the Size of a Group Node

If the grouping hierarchy is changed by user interaction, e.g. if the user drags a node into a group node, the size of the group node is already adjusted.

Using the IGraph APIs for adding or removing child nodes as well as creating a group node for a given set of children does not affect the group node’s layout. You always have to adjust the group node size separately to changing the children.

adjustGroupNodeLayout(groupNode: INode): void
Adjusts the size of the given group node group node such that the node requires the least amount of space to enclose all child nodes. This method does not affect the group node’s ancestors.

Note that adjustGroupNodeLayout doesn’t adjust the ancestors of the given group node. This can be most easily done by iterating over the nodes returned by getPathToRoot:

graph.groupingSupport.getPathToRoot(groupNode).forEach((node) => {
  graph.adjustGroupNodeLayout(node)
})

A further extension to the grouping concept is the concept of folding, i.e. expanding (opening) and collapsing (closing) group nodes. Folding is also supported by yFiles for HTML, see chapter Folding for more details.