documentationfor yFiles for HTML 3.0.0.1

Grouping Nodes

The classic graph model is extended so that nodes can have other nodes as their parent. The parent of a node is referred to as a group node, and 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 a 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 a child of the given parent node, whereas setting it to null will create the new node top-level, i.e., as a 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. For example, it is 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: Color.DARK_ORANGE })
graph.nodeDefaults.labels.layoutParameter = InteriorNodeLabelModel.CENTER
graph.groupNodeDefaults.style = new GroupNodeStyle({
  tabFill: 'lightseagreen',
  tabPadding: 20
})
graph.groupNodeDefaults.labels.layoutParameter =
  new GroupNodeLabelModel().createTabParameter()

This allows you to visually distinguish group nodes from regular nodes, making the graph easier to understand.

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 the new parent of the child node. Setting null as the 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 the parent for all nodes in the given Enumerable.
groupNodes(parent: INode, children: IEnumerable<INode>): void
groupNodes(children: IEnumerable<INode>, style: INodeStyle, tag: Object): INode
Creates a new group node and sets it as the 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 you can obtain for an IGraph with groupingSupport.

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 a 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; that is, 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; that is, all of a node’s ancestors will be enumerated after the respective node.getDescendantsBottomUp(outerGroup)1, 2, Inner Group, 3.
isDescendantDetermines whether 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
getAncestorsA list that includes the given node and all its parents up to the root, but not the root itself.getAncestors(n2)n2, innerGroup, outerGroup
getNearestCommonAncestor(INode...)getNearestCommonAncestor(IEnumerable<INode>)Gets 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 (for example, if the user drags a node into a group node), the size of the group node is automatically adjusted.

Using the IGraph APIs to add or remove child nodes or to create a group node for a given set of children does not automatically adjust the group node’s layout. You must manually adjust the group node size after changing its children.

adjustGroupNodeLayout(groupNode: INode): void
Adjusts the size of the specified group node to enclose all child nodes with minimal space. This method does not affect the group node’s ancestors.

Note that adjustGroupNodeLayout does not adjust the ancestors of the specified group node. You can easily accomplish this by iterating over the nodes returned by getAncestors:

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

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