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 tonull
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:
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:
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:
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.
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:
Method | Description | Example usage | Example output |
---|---|---|---|
IGraph | |||
null if the node is not in a group. | getParent(n2) | Inner Group | |
getParent(outerGroup) | null | ||
getChildren(outerGroup) | Inner Group , 3 | ||
getChildren(null) | Outer Group , Independent Group | ||
GroupingSupport | |||
getDescendants(outerGroup) | 3 , Inner Group , 2 , 1 . | ||
getDescendantsBottomUp(outerGroup) | 1 , 2 , Inner Group , 3 . | ||
isDescendant(n1, outerGroup) | true | ||
isDescendant(n4, outerGroup) | false | ||
isDescendant(n3) | n3 , innerGroup , outerGroup | ||
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.