documentationfor yFiles for HTML 2.6

Folding

To fully understand this chapter it is important to be familiar with the concept of grouping or group nodes as introduced in the graph model chapter.

Folding is an extension to the grouping concept. It adds the functionality of collapsing (closing) and expanding (opening) group nodes. Collapsing a group node means that its content is hidden and the group node itself becomes a normal, i.e. non-group or leaf, node. For clarification, we refer to collapsed group nodes as folder nodes or folders. Additionally, the yFiles for HTML folding support allows browsing into group or folder nodes, i.e. displaying their contents as separate graph.

Concepts of folding
(1) Group node (expanded)
(2) Folder node (collapsed group)
(3) Content of the group

The above figure illustrates that concept: Diagram (1) shows a group node (labeled “Group”) with two children, nodes 3 and 4. In diagram (2) the group is collapsed and the children are hidden. The folder can have a different color, a different size, and even a different label (“Folder”) to emphasize its different state. Since the children are hidden, the edges from the outer nodes to the nodes 3 and 4 now connect to the folder node.

Diagram (3) shows the content of the group as a separate diagram. Note that the connections from these nodes to the outer nodes are not part of this diagram.

Edge-to-edge connections are not supported in a folding-enabled graph. In particular, if such connections are present in the master graph, they will be preserved but they are not shown in any folding views.

In some use cases it is useful to combine the Folding with the Filtering feature. In these cases the order of initialization is important so make sure to read the knowledge base article about Using Filtering and Folding Together.

In the following, we first explain the basics of working with folding, introduce the predefined style for collapsible groups, and describe how to enable user interaction. In the remainder of this chapter, we explain specific aspects and underlying concepts of folding in more detail.

Working with Folding

To properly hide children when collapsing and restore the contents when expanding, folding works with two graph instances. The master graph is the complete unfolded graph: it contains all normal nodes, all groups, all edges, and all of their labels and ports. The graph in the graph component that supports collapsing and expanding is a (modified) copy of the master graph. It is called the view graph.

Note that items in the view graph are actually copies of items in the master graph. The FoldingManager synchronizes these items, but e.g. reference comparisons between master graph items and view graph items will fail.

When a group node is collapsed, all its child nodes are removed from the view graph while the master graph doesn’t change. When the group node is expanded again, new child nodes are added to the view graph so any references to the 'old' child nodes are invalid. More details are provided in Working with Master and View.

To make sure that the state of both graphs is correctly synchronized, yFiles provides the types FoldingManager and IFoldingView. A FoldingManager stores the master graph and serves as a factory for the folding views. An IFoldingView stores the view graph and manages the mapping from elements of the master graph to elements in the view graph and vice versa.

See the section Folding Revisited for more details.

Enabling Folding

To enable folding, create a new instance of FoldingManager. If you have an initial graph, set it as master graph of your manager. Then, create a folding view with the manager. Each folding view contains one view graph and that graph supports folding. Finally, set the view graph to your graph component.

The following example summarizes these steps.

Enable folding for the GraphComponent’s Graph
// first create the folding manager
const foldingManager = new FoldingManager()
// create the folding view with the manager
const view = foldingManager.createFoldingView()
// each view contains a folding-enabled graph, the view graph
const viewGraph = view.graph
// the view graph is the graph to display
graphComponent.graph = viewGraph

Once enabled, you can get the view graph of a folding view and vice versa with these properties:

IGraph.foldingView: IFoldingView
Gets the corresponding IFoldingView instance if the graph is a view graph. Returns null otherwise.
IFoldingView.graph: IGraph
Gets the view graph of this folding view.

The folding manager and master graph can be accessed via the folding view:

IFoldingView.manager: FoldingManager
Gets the corresponding FoldingManager instance that created this IFoldingView.
FoldingManager.masterGraph: IGraph
Gets the master graph of this folding manager.

Get view, manager, and master graph from the view graph
const viewGraph = graphComponent.graph

// get the folding view from the graph
const view = viewGraph.foldingView
// note: view.graph === viewGraph

// get the manager from the view graph
const manager = view.manager

// get the master graph from the manager
const masterGraph = manager.masterGraph

const viewGraph = graphComponent.graph

// get the folding view from the graph
const view = viewGraph.foldingView!
// note: view.graph === viewGraph

// get the manager from the view graph
const manager = view.manager

// get the master graph from the manager
const masterGraph = manager.masterGraph

FoldingManager.createFoldingView(root: INode, isExpanded: Predicate<INode>) takes an optional isExpanded parameter that is used as callback to determine whether a group node is initially expanded or collapsed in the view graph.

Collapsing and Expanding Groups

IFoldingView provides the methods for expanding and collapsing group nodes:

collapse(groupNode: INode): void
Collapses the given group node, i.e. removes its children from the view graph and changes the visual appearance to the folder state. The given group node may either be a master group node or a view group node.This method does nothing if the node is not a group or if the node is already collapsed.
expand(groupNode: INode): void
Opens the given folder node, i.e. re-adds its children to the view graph and changes the visual appearance to the group state. The given node may either be a master group node or a view folder node.This method does nothing if the node is not a folder or if the node is already expanded.

Expanding and collapsing a group node

Node States

In the master graph each node is either a group node or a non group node.

In the view graph only expanded group nodes are called group nodes while collapsed ones are called folder nodes.

The following methods are used to check the state of a node:

IGraph.isGroupNode(node: INode): boolean
Determines whether a node is a group node or a non-group node (i.e. a normal node in the master graph or a normal node or folder node in the view graph).
IFoldingView.isExpanded(groupNode: INode): boolean
Determines whether the provided node is currently expanded.

// get the folding view to manage expanding and collapsing
const view = graph.foldingView

// group is initially expanded
view.isExpanded(group) // true

// collapse group
view.collapse(group)
view.isExpanded(group) // false
console.log(graph.isGroupNode(group)) // false
console.log(graph.nodes.size) // 3 (node 1, node 2, group)
console.log(graph.edges.size) // 3 (node 1 -> group, node 1 -> node 2, node2 -> group)

// expand group again
view.expand(group)
view.isExpanded(group) // true
console.log(graph.isGroupNode(group)) // true
console.log(graph.nodes.size) // 5 (all nodes)
console.log(graph.edges.size) // 4 (all edges)

// get the folding view to manage expanding and collapsing
const view = graph.foldingView!

// group is initially expanded
view.isExpanded(group) // true

// collapse group
view.collapse(group)
view.isExpanded(group) // false
console.log(graph.isGroupNode(group)) // false
console.log(graph.nodes.size) // 3 (node 1, node 2, group)
console.log(graph.edges.size) // 3 (node 1 -> group, node 1 -> node 2, node2 -> group)

// expand group again
view.expand(group)
view.isExpanded(group) // true
console.log(graph.isGroupNode(group)) // true
console.log(graph.nodes.size) // 5 (all nodes)
console.log(graph.edges.size) // 4 (all edges)

A folder node can have a different layout, visual appearance, and even different labels than its (expanded) group node. Thus, changing any of these properties does not affect its expanded state. Instead, when the collapsed group is expanded again, it will have its original layout, style, and labels. The same applies to the incident edges of a folder node.

In addition, you can create folder nodes, i.e. group nodes which are initially collapsed, with the createFolderNode method of IFoldingView:

Creating a folder node
const graph = view.graph

// CreateFolderNode creates a normal node on the view graph
const folder = view.createFolderNode()
console.log(graph.isGroupNode(folder)) // false

// but its master element is a group on the master graph
const master = view.getMasterItem(folder)
console.log(view.manager.masterGraph.isGroupNode(master)) // true

// the folder can be expanded
view.expand(folder)
console.log(graph.isGroupNode(folder)) // true

Styles for Folding

yFiles for HTML offers the GroupNodeStyle for the visual representation of group and folder nodes. It supports drawing a handle that can then be used to toggle the expansion state and extensive options to configure its visual appearance.

There is also the CollapsibleNodeStyleDecorator which is a special node style for decorating any other node style with a collapse/expand handle.

The Groups and Folders section describes in detail the use of the GroupNodeStyle and the CollapsibleNodeStyleDecorator.

User Interaction

In addition to the user interaction that is supported for grouping, you can press Ctrl+- to collapse a selected group node and Ctrl++ to expand it. This can be configured on the child input mode NavigationInputMode of both GraphEditorInputMode and GraphViewerInputMode.

NavigationInputMode.allowCollapseGroup
Enables or disables the collapse group shortcut Ctrl+-.
NavigationInputMode.allowExpandGroup
Enables or disables the expand group shortcut Ctrl++.

Entering or exiting a group node as described in [folding-local_root] can also be triggered via keyboard. Press Ctrl+Enter to enter a selected group node and Ctrl+Backspace to exit the current root node.

NavigationInputMode.allowEnterGroup
Enables or disables the enter group shortcut Ctrl+Enter.
NavigationInputMode.allowExitGroup
Enables or disables the exit group shortcut Ctrl+Backspace.

By default, these actions are enabled on GraphEditorInputMode and disabled on GraphViewerInputMode since we consider them graph modifications. If your use case requires these actions, enabling them for GraphViewerInputMode is perfectly fine.

Folding Revisited

In this section, we provide more details for the types introduced above in section Working with Folding.

Recall that the FoldingManager stores the master graph and has a factory method for instances of IFoldingView. The latter stores one view graph, provides the collapse and expand methods for its graph, and manages the mapping from elements of the master graph to elements in its view graph and vice versa.

FoldingManager keeps track of the folding views it has created and can manage an arbitrary number of folding views. Typically however you’ll need just one of them for displaying in your GraphComponent. If you have several views, each one can have different folded groups and even show a different part of the master graph.

All views are synchronized. This means that changes in a view graph are immediately propagated to the master graph. Similarly, changes in the master graph are immediately propagated to all views.

A folder can have a different layout and style, and different labels and ports than its corresponding master item. These properties of a folder are stored in the FolderNodeState class, and shared between all folders of the same master node for all folded views. Similar states exist for edges that are incident to a folder, and the labels and ports of folders. These states are called folding states, and sometimes view states. All of them are managed by FoldingManager and described in more detail in the following section The State of Folded Elements.

The following figure summarizes these relations of FoldingManager, IFoldingView, their graphs, and the folding states.

Interactions between FoldingManager and its views
folding folding manager

Displaying a Group’s Contents

IFoldingView can display the contents of a group or folder as an independent graph. You can use this feature to allow your users to browse into or enter a group or folder, for example.

IFoldingView.localRoot: INode
Specifies the root of the view graph. Setting a group of the master graph limits the view graph to the contents of that group (excluding the group itself). Setting it to null results in the complete view graph.

This property is named with regards to the grouping hierarchy in which each group is the root of its content.

Setting the localRoot
localRoot is null
localRoot is the group node
// get the folding view to manage expanding and collapsing
const view = graph.foldingView

// show only the contents of groupNode
// as if they were in a separate graph
view.localRoot = group

graph.contains(group) // false
console.log(graph.nodes.size) // 2 (node 3, node 4)
console.log(graph.edges.size) // 1 (node 3 -> node 4)

// show the entire graph
// i.e. the root of the master graph is the root of this view graph
view.localRoot = null

graph.contains(group) // true
console.log(graph.nodes.size) // 5 (all nodes)
console.log(graph.edges.size) // 4 (all edges)

// get the folding view to manage expanding and collapsing
const view = graph.foldingView!

// show only the contents of groupNode
// as if they were in a separate graph
view.localRoot = group

graph.contains(group) // false
console.log(graph.nodes.size) // 2 (node 3, node 4)
console.log(graph.edges.size) // 1 (node 3 -> node 4)

// show the entire graph
// i.e. the root of the master graph is the root of this view graph
view.localRoot = null

graph.contains(group) // true
console.log(graph.nodes.size) // 5 (all nodes)
console.log(graph.edges.size) // 4 (all edges)

In case that the current root node is removed from the master graph, the folding view’s invalid property becomes true and the view can no longer be edited. By default, however, the autoSwitchToAncestor property makes sure that the local root is automatically changed such that an ancestor of the original root is used as the view’s new root.

Working with Master and View

With folding, it’s important to take care of the graph you’re working with, either master graph or view graph. For some tasks, it doesn’t matter but others can (or at least should) be done on only one of them. In the following, we first explain the conversion between the items of the two graphs in more detail and then list common tasks and the corresponding graph to use.

The elements in the master graph are referred to as master item or master element whereas the elements in the view graph are referred to as view item. Each view item has one corresponding master item but one view edge can represent more than one master edge. This depends on the options for creating view edges.

Converting Master and View Items

IFoldingView provides the following methods to convert between master and view items.

getMasterItem<T>(item: T): T
Returns the corresponding master item for a given view item. Returns null if a view item has no master item (labels of folder nodes, for example). For view edges which correspond to more than one master edge this method returns the main master edge.
getMasterEdges(foldingEdge: IEdge): IEnumerable<IEdge>
Returns all corresponding master edges for a given view edge.
getViewItem<T>(item: T): T
Returns the corresponding view item for a given master item if such a view item exists. Returns null if the element is currently not in the view graph (a child node of a collapsed group, for example).

The master graph models your complete business data and is not affected by collapse and expand actions of users. Therefore you should use it for all tasks that concern the whole model. In contrast, the view graph is the one that is visible in the GraphComponent. Automatic layouts are typically applied to this graph, and code that customizes user interaction and visualization originally gets view items.

As mentioned in Working with Folding we advice against keeping references to graph elements in a view graph. Collapsing and expanding group nodes may invalidate these references and result e.g. in "node does not belong to this graph" errors when using them later for graph operations.

Tasks for the Master Graph

The following tasks are typically done on the master graph.

  • If you store graph elements for later use, keep references to master items. Otherwise, some of the stored items might no longer be part of the (view) graph if a user collapses more groups:

/**
 * @param {!INode} viewNode
 */
storeReference(viewNode) {
  const foldingView = this.graphComponent.graph.foldingView
  const masterNode = foldingView.getMasterItem(viewNode)
  this.masterNodeReference = masterNode
}

/**
 * @returns {*}
 */
getReferencedNode() {
  const foldingView = this.graphComponent.graph.foldingView
  const viewNode = foldingView.getViewItem(this.masterNodeReference)
  return viewNode
}

storeReference(viewNode: INode): void {
  const foldingView = this.graphComponent.graph.foldingView!
  const masterNode = foldingView.getMasterItem(viewNode)
  this.masterNodeReference = masterNode
}

getReferencedNode(): any {
  const foldingView = this.graphComponent.graph.foldingView!
  const viewNode = foldingView.getViewItem(this.masterNodeReference)
  return viewNode
}

  • An IMapper which is registered at the master graph’s mapperRegistry will be available in all view graphs, too. In a view graph, the mappers are automatically wrapped such that you can use view items to query the values.This does not apply the other way around. Mappers registered for a view graph are not available in the master graph.

// create node
const viewNode = foldingView.graph.createNode()
const masterNode = foldingView.getMasterItem(viewNode)

// add and fill mapper on master graph
const masterMapper = masterGraph.mapperRegistry.createMapper(INode.$class, YString.$class, 'masterMapper')
masterMapper.set(masterNode, 'Value set on master')

const masterMapperInView = foldingView.graph.mapperRegistry.getMapper('masterMapper')
const value = masterMapperInView.get(viewNode) // value = "Value set on master"

  • Enable support for undo and redo on the master graph. In addition, mappers and other data structures that are used by undo customization should work with master items.
  • Set the style that displays the collapse/expand handle for the master nodes.

Tasks for the View Graph

The following tasks are typically done on the view graph.

  • Reacting on user action events is done on the view. However, if you store elements in your event listeners, convert the provided view items to master items and store these.
  • Especially, working with the selection is done on the view.
  • Automatic layouts are applied to the view graph. As described above, mappers registered at the master graph are automatically usable in the view graph and thus such mappers can be used to configure the layout. Mappers that are just used for the next layout calculation should be created for the view graph.

The State of Folded Elements

A folder can have a different layout, style, and tag, and different labels and ports than its corresponding master item. These properties of a folder are stored in the FolderNodeState class.

Similarly, (view) edges that connect to folders can have different source and target ports and bends, a different style and tag, and different labels and ports than any of its corresponding master edge(s). These properties are stored in the FoldingEdgeState class.

In addition, all ports and labels of folders, and all bends, ports, and labels of edges of folders have similar state classes, namely FoldingPortState, FoldingLabelState, and FoldingBendState. Note that these elements have no master item. They only exist in the folder node states and the folding edge states.

All these states are managed by FoldingManager. You can get the node and edge states from it with the following methods. The states of labels, ports, and bends are available on the respective node state or edge state.

getFolderNodeState(masterNode: INode): FolderNodeState
Returns the state of the folder(s) that corresponds to the given master node.
getFoldingEdgeState(id: FoldingEdgeStateId): FoldingEdgeState
Returns the state of the edge(s) that corresponds to the given edge ID. This ID depends on the master edge, and its source and target nodes.

The states are shared between all views of the same master. This is described in more detail in the following section Shared Folding States of Multiple Views.

Properties like the style can be directly set to the folding states. Setting a property will be immediately reflected in all managed views which show the node in collapsed state. Also, when a group node is collapsed its folder node’s properties will be set according to the folding state. The following example shows how to get the folding state for a node and set a new style to that state:

Setting the style for a collapsed node
// 'graph' is the folding enabled view graph
// 'groupNode' is the group node to set the folder style to
// 'groupNode' is contained in 'graph'

// get the folding view for the graph
const view = graph.foldingView
// the manager which manages the views
const manager = view.manager
// get the view state from the manager using the master node of 'groupNode'
const state = manager.getFolderNodeState(view.getMasterItem(groupNode))
const folderStyle = new GroupNodeStyle()
folderStyle.contentAreaFill = Fill.LIGHT_GRAY
folderStyle.tabFill = Fill.GRAY
state.style = folderStyle

// 'graph' is the folding enabled view graph
// 'groupNode' is the group node to set the folder style to
// 'groupNode' is contained in 'graph'

// get the folding view for the graph
const view = graph.foldingView!
// the manager which manages the views
const manager = view.manager
// get the view state from the manager using the master node of 'groupNode'
const state = manager.getFolderNodeState(view.getMasterItem(groupNode)!)
const folderStyle = new GroupNodeStyle()
folderStyle.contentAreaFill = Fill.LIGHT_GRAY
folderStyle.tabFill = Fill.GRAY
state.style = folderStyle

Note that items which are currently visible in the view graph can be modified using the usual IGraph methods. If groupNode in the above example is currently collapsed in graph the style can be set using setStyle as well:

Setting the style for a currently visible collapsed node
// 'graph' is the folding enabled view graph
// 'groupNode' is the group node to set the folder style to
// 'groupNode' is contained in 'graph' and collapsed
graph.setStyle(
  groupNode,
  new GroupNodeStyle({
    contentAreaFill: Fill.LIGHT_GRAY,
    tabFill: Fill.GRAY
  })
)

// 'graph' is the folding enabled view graph
// 'groupNode' is the group node to set the folder style to
// 'groupNode' is contained in 'graph' and collapsed
graph.setStyle(groupNode, new GroupNodeStyle({ contentAreaFill: Fill.LIGHT_GRAY, tabFill: Fill.GRAY }))

The folding states are created or updated by converters provided by the FoldingManager whenever a group is collapsed. This is described in detail for folders and their edges in the following sections.

Maintaining the Folder Node State

The instance of IFolderNodeConverter that is provided by the FoldingManager.folderNodeConverter property determines the properties of the folder node state whenever a group is collapsed.

For this, IFolderNodeConverter provides these two methods:

initializeFolderNodeState(state: FolderNodeState, foldingView: IFoldingView, viewNode: INode, masterNode: INode): void
Called when a group node is collapsed the first time, i.e. when no FolderNodeState exists, yet.
updateFolderNodeState(state: FolderNodeState, foldingView: IFoldingView, viewNode: INode, masterNode: INode): void
Called if a FolderNodeState already exists when a group node is collapsed. Used to synchronize the folding state of a folder with its potentially changed master node. Might do nothing if synchronization is not required.

DefaultFolderNodeConverter is the default implementation of IFolderNodeConverter. By default, it sets the properties of the state to the one of the original group node except for the labels. It provides plenty of configuration settings and callback methods to configure it to your needs.

Setting some defaults for DefaultFolderNodeConverter
manager.folderNodeConverter = new DefaultFolderNodeConverter({
  copyFirstLabel: true,
  folderNodeStyle: new ShapeNodeStyle({ fill: Fill.GRAY })
})

Maintaining the Folding Edge State

Similar to folder nodes, the instance of IFoldingEdgeConverter that is provided by the FoldingManager.foldingEdgeConverter property determines the properties of the folding edge state whenever a group is collapsed.

However, in addition, that converter determines how many and which folding edges should be created at all. yFiles for HTML contains the following converters:

Converter Description Example Expanded Group
DefaultFoldingEdgeConverterCreates one folding edge for each master edge that connects to the contents of the collapsed group.
MergingFoldingEdgeConverterCreates at most one folding edge (two folding edges, if edge direction is not ignored) between each pair of source node and target node. Conceptually, this implementations merges folding edges that connect the same source node and target node in a folding view into one folding edge (two folding edges, if edge direction is not ignored).
ExcludingFoldingEdgeConverterCreates no folding edges at all.

By default the DefaultFoldingEdgeConverter is used.

Both DefaultFoldingEdgeConverter and MergingFoldingEdgeConverter provide plenty of configuration settings and callback methods to configure them to your needs.

Setting some defaults for MergingFoldingEdgeConverter
manager.FoldingEdgeConverter = new MergingFoldingEdgeConverter({
  copyFirstLabel: true,
  foldingEdgeStyle: new PolylineEdgeStyle({ stroke: Stroke.GRAY })
})

Shared Folding States of Multiple Views

If you have more than one folding view for the same master, these states are shared by all views. As a consequence, view items of different views that share the same states have the same layout, geometry, style, etc.

In particular, the same FolderNodeState is shared by all folders of the same master group in all folded views.

For edges, the FoldingEdgeStateId is defined by the master edge, the source and target node, and the collapse/expand state of these nodes. In other words, two view edges of the same master edge have the same ID if both their source and target nodes have the same collapse/expand state.

FoldingEdgeStateId(IEdge, INode, boolean, INode, boolean)
Creates a new edge id defined by the master edge, the source and target node, and the collapse/expand state of these nodes.
FoldingEdgeStateId(IFoldingView, IEdge)
Creates a new edge id defined by a view edge and the folding view that created this view edge.

Since the states of labels, ports, and bends are part of the node and edge state, their states are the same if their owners share the same state.