Managing Graph Hierarchies

The management of a graph hierarchy is a multi-faceted undertaking that is handled by a small number of classes. Adding support for grouping to a "flat" graph can easily be achieved with little effort. Setting up the model-view relationship for additional folding support using a grouped graph as the model as well.

Class DefaultGraph, the default IGraph implementation used in yFiles for Silverlight, allows to conveniently enable grouping support by means of a property. The same effect can also be achieved by directly creating the GroupedGraph instance that ultimately handles the graph hierarchy.

Folding support is enabled using the FoldingManager class and creating managed views.

Grouping Support in DefaultGraph

Class DefaultGraph provides convenient high-level support in order to enable grouping support for the graph structure. Instead of explicitly creating a GroupedGraph instance for a graph (as described in the section called “Class GroupedGraph”), the GroupingSupported property allows to easily enable grouping support for a graph. Example 5.1, “Enabling grouping support with a DefaultGraph instance” presents how grouping support is enabled for a DefaultGraph instance.

Example 5.1. Enabling grouping support with a DefaultGraph instance

// 'graph' is of type yWorks.yFiles.UI.Model.DefaultGraph.

graph.GroupingSupported = true;

// after grouping is enabled an IGroupedGraph instance
// can be retrieved from the graph
IGroupedGraph groupedGraph = graph.GetGroupedGraph();

The hierarchy is managed by implementations of the interface IGroupedGraph as mentioned below. Once grouping is enabled IGraph's method GetGroupedGraph returns the IGroupedGraph instance which manages the hierarchy for this graph.

Tutorial demo application GraphEditorWindow presents an extensive example of an application that handles grouped graphs.

Class GroupedGraph

Class GroupedGraph is an implementation of interface IGroupedGraph. It maintains the model that is behind a graph hierarchy and also manages changes relevant to this model. GroupedGraph uses an implementation of generic interface IHierarchy that holds the actual hierarchy of nodes. This IHierarchy implementation binds the type parameter to model item type INode.

Figure 5.6. Type hierarchy for class GroupedGraph

Type hierarchy for class GroupedGraph.

GroupedGraph's responsibilities include, e.g., creating group nodes and grouping and ungrouping of nodes. Also, re-parenting, i.e., setting another parent node for a node is handled by GroupedGraph, too. The necessary low-level changes that correspond to the provided functionality are then invoked on the IHierarchy implementation.

Adding grouping support to a graph is achieved by creating a GroupedGraph instance for it. Example 5.2, “Adding grouping support to a graph” demonstrates how to add a GroupedGraph instance to a yet "flat" graph, i.e., one that lacks support for grouping.

Example 5.2. Adding grouping support to a graph

// 'flatGraph' is of type yWorks.yFiles.UI.Model.IGraph.

// Create grouping support for the given IGraph.
GroupedGraph grouped = new GroupedGraph(flatGraph);

Basically, this has the same effect as the line in Example 5.1, “Enabling grouping support with a DefaultGraph instance”.

The following methods defined in interface IGroupedGraph (or available via the .NET extension methods mechanism) allow to create and modify the hierarchical organization of a grouped graph. Group nodes can be created both explicitly as well as implicitly when grouping a set of nodes. Un-grouping of nodes is achieved by means of the general re-parenting functionality.

Conceptually, grouped nodes are one level below their containing group node in the graph hierarchy. Technically, however, they are in the same graph as their enclosing group node. Example 5.3, “Grouping nodes” shows how nodes are grouped in a common group node which is created implicitly.

Example 5.3. Grouping nodes

// 'grouped' is of type yWorks.yFiles.UI.Model.IGroupedGraph.

// Create a new top-level group node that has two children.
INode groupNode = grouped.GroupNodes(new INode[] {someNode, someOtherNode});

Interface IGroupedGraph also provides support for the notion of default settings for graph elements as laid out in interface IGraph. Specifically, this also covers default styles for group nodes which are specified using the INodeDefaults interface.

The Style property of the IGroupedGraph's GroupNodeDefaults can be used to set the default style for group nodes. Style sharing semantics for group nodes are controlled using the ShareStyleInstance property.

Note

Initially, ShapeNodeStyle is used as the default group node style.

Node styles are described in the section called “Visual Representation of Graph Elements”.

Look-up Modifications

When a GroupedGraph is created, the look-up behavior of the given IGraph instance is modified. More precisely, the types listed in Table 5.1, “Additional types supported with look-up operations” are additionally supported with look-up operations initiated on the IGraph instance using the Lookup method.

Table 5.1. Additional types supported with look-up operations

Requested Type Description
IGroupedGraph The returned IGroupedGraph implementation can be used to manipulate the hierarchical organization of the graph.
IHierarchy<INode> The returned IHierarchy implementation is bound to model item type INode. It presents a view of the actual hierarchical structure of the nodes. (See below for the description of interface IHierarchy.)

Note

Via the .NET extension methods mechanism, the IGroupedGraph implementation that is in the look-up of an IGraph instance is also conveniently accessible using the GetGroupedGraph method.

Note that by default the IGroupedGraph instance and the GroupedGraph instance that are returned when requesting the respective type are in fact the GroupedGraph instance that has been created to add grouping support to the graph. Example 5.4, “Retrieving the GroupedGraph instance that is associated with a graph” shows how to get the IGroupedGraph instance using a convenience extension method.

Example 5.4. Retrieving the GroupedGraph instance that is associated with a graph

// 'graph' is of type yWorks.yFiles.UI.Model.IGraph.

// Retrieve the IGroupedGraph implementation that adds grouping support to the
// graph.
IGroupedGraph grouped = graph.GetGroupedGraph();

if (grouped != null) {
  // Do something with the IGroupedGraph.
}

In order to revert any changes that were introduced by creating grouping support for a graph, a GroupedGraph instance needs to be properly disposed of. To this end, the following clean-up method is provided:

Note

When grouping support has been enabled using the GroupingSupported property of DefaultGraph, disposing of the GroupedGraph instance will be handled automatically.

Interface IHierarchy

Generic interface IHierarchy<T> defines the basis for a data structure that holds a hierarchical organization of elements of a given type T. It models a tree-like hierarchy that allows to observe structural changes.

In conjunction with a GroupedGraph, an implementation of IHierarchy, where the type parameter is bound to type INode, is used to model the actual hierarchy of nodes for the IGraph that has grouping support enabled. This IHierarchy instance handles the low-level changes to the hierachy of nodes.

The following methods from generic interface IHierarchy allow to query and modify a hierarchical structure of arbitrary type.

Important

The AddChild method is not supported with the IHierarchy instance which is used together with the IGroupedGraph instance that adds grouping support to a graph. Instead, this method should be invoked on the IGroupedGraph instance.

The Root property returns the value for the root of the hierarchy. For a hierarchy of nodes that is used by a GroupedGraph, this value is normally null.

Via the .NET extension methods mechanism, there are further utility methods available that allow to conveniently query information about the hierarchy held by an IHierarchy implementation:

Class FoldingManager

Class FoldingManager allows to conveniently add folding support to a grouped graph. It provides and handles the so-called managed views that enable folding operations in a graph hierarchy. Among other things, this includes:

  • creating the graph structure that will be presented in a managed view: initially, all representative graph elements are copies of their corresponding original graph elements
  • synchronizing between model and views: any changes to an original graph element or its representative (style, geometry, label text, etc.) are instantly propagated to the corresponding other element
  • keeping separate folding-related state for dummy elements that need to exist in managed views due to folding operations
  • synchronizing the folding-related state across all managed views: any changes to dummy graph elements (style, geometry, label text, etc.) are instantly propagated to all other managed views
  • setting up folding operation support: an implementation of interface IFoldedGraph enables collapsing/expanding group nodes in a managed view

In addition, FoldingManager also synchronizes data that is bound to model items using the ITagOwner interface. Specifically, this enables using either original or representative graph elements in order to access the same data.

Important

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

The code in Example 5.5, “Creating a managed view” presents how to use FoldingManager to create a managed view.

Example 5.5. Creating a managed view

// 'gc' is of type yWorks.yFiles.UI.GraphControl.

FoldingManager fm = new FoldingManager();
// Creating a managed view.
IFoldedGraph fg = fm.CreateManagedView();

// Showing the folding-enabled graph in a GraphControl.
gc.Graph = fg.Graph;

There can be an arbitrary number of managed views for a master graph, and managed views can present different parts of a graph hierarchy. For example, one managed view may present the entire hierarchy of nodes, while another only presents the contents of a given group node.

The overloads of FoldingManager's CreateManagedView method enable creating graph structures for managed views that show a subset of the master graph only. Also supported is a way to specify the initial collapse/expand state of representatives for group nodes from the master graph.

IFoldedGraph CreateManagedView()
IFoldedGraph CreateManagedView(INode root)
Description Overloaded method for creating managed views.
IFoldedGraph CreateManagedView(INode root, Predicate<INode> expandedPredicate)
Description The predicate allows to specify the initial collapse/expand state of group node representatives.

The initial collapse/expand state of group nodes representatives can also be set using the SetInitiallyExpanded method.

For each dummy graph element in a managed view, FoldingManager keeps a separate folding-related state, which it synchronizes across all its managed views. When dummy elements are first created, so-called converters are queried for the dummy element's initial state. Dummy node converters and dummy edge converters can be set using the DummyNodeConverter and DummyEdgeConverter property of class FoldingManager, respectively.

By default, FoldingManager uses classes DefaultDummyNodeConverter and DefaultDummyEdgeConverter. The default behavior for dummy node creation is to use all state from the original group node. The default behavior for dummy edge creation is to use all state from the original edge, and to create exactly one representing dummy edge.

Dummy element converters can be widely customized. In the section called “Converters and Callbacks” all converter-related aspects are discussed.

Note

The FoldingManager's dummy element converters are queried for each transition where graph elements in a managed view become dummy elements. However, if there is already folding-related state held for the dummy elements, then this state will be used.

Look-up Modifications

FoldingManager modifies the look-up behavior of the IGraph instance in a managed view. More precisely, the types listed in Table 5.2, “Additional types supported with look-up operations” are additionally supported with look-up operations initiated on a managed view's IGraph instance.

Table 5.2. Additional types supported with look-up operations

Requested Type Description
IFoldedGraph The returned IFoldedGraph implementation can be used to execute folding operations in the graph. Also, it provides access to the FoldingManager that is responsible for the graph's managed view.
IGroupedGraph The returned IGroupedGraph implementation can be used to manipulate the hierarchical organization of the graph.

Note

Via the .NET extension methods mechanism, both the IFoldedGraph implementation and the IGroupedGraph implementation that are in the look-up of an IGraph instance are also conveniently accessible using the GetFoldedGraph and GetGroupedGraph methods.

Note that the IFoldedGraph implementation available through the look-up operation is the same as the one returned by the CreateManagedView call that created the managed view.

Also, the IMapperRegistry that is available via the IGraph's MapperRegistry property provides convenient direct access to any IMapper implementations from the master graph using their respective tag. If another IMapper is registered in a managed view using the tag from a master graph's IMapper, no direct access is possible. Instead, the same tag refers to different IMapper instances for the managed view's graph and the master graph.

Setting up folding support using FoldingManager is illustrated in detail in the tutorial demo application FoldingWindow.

Interface IFoldedGraph

Interface IFoldedGraph defines the contract for working with the folding-enabled graph in a managed view, and also for specifying the subset of a graph hierarchy to build the view's graph from.

The actual IFoldedGraph implementation that is associated with the graph in a specific managed view is returned when creating that view using one of FoldingManager's CreateManagedView methods, but it can also be queried from that graph's look-up, if necessary.

Example 5.6. Getting the IFoldedGraph implementation from the folding-enabled graph's look-up

// 'gc' is of type yWorks.yFiles.UI.GraphControl.

// Retrieve the IFoldedGraph implementation that enables folding operations.
IFoldedGraph fg = gc.Graph.GetFoldedGraph();

if (fg != null) {
  // Do something with the IFoldedGraph implementation.
}

Working with a folding-enabled graph mainly means collapsing and expanding group nodes, which can be done using the following methods:

IFoldedGraph also provides several methods that allow to query, for example, whether a given group node in the managed view is currently expanded or not, or what the collapse/expand state for a group node that is currently not visible in the managed view would be. The following methods from IFoldedGraph allow to query state aspects of graph elements in a managed view:

bool IsInitiallyExpanded(INode masterGroupNode)
bool IsExpanded(INode groupNode)
Description Methods for querying state aspects of elements in a managed view. Collapsed/expanded state for group nodes in a managed view.
bool IsDummy(IModelItem item)
Description Dummy state for graph elements in a managed view.

Provided by IFoldedGraph are further methods that allow to get original graph elements for given graph elements from a managed view, or vice versa, to get the representative graph elements for given graph elements from the master graph:

T GetMaster<T>(T item) where T : class, IModelItem
IEnumerable<IEdge> GetMasterEdges(IEdge dummyEdge)
Description Methods for mapping between graph elements in the model (master graph) and a managed view. Getting original graph elements (if any) for graph elements in the managed view.
T GetRepresentative<T>(T item) where T : class, IModelItem
Description Getting representing graph elements (if any) for original graph elements.

It is important to understand that there is not necessarily a one-to-one mapping between graph elements from the master graph and graph elements in a managed view. For example, for an edge in a managed view that connects to a collapsed group node, there can be several original edges in the master graph.

Figure 5.7, “Edge mappings according to the dummy edge policy” illustrates two possible situations that result when a group node in a managed view is collapsed. Edges in the master graph (left) that connect to nodes inside that group node can be retained in a managed view (middle) or can be merged into a single representative edge (right). A third alternative, where all edges are cleared, is also possible.

Figure 5.7. Edge mappings according to the dummy edge policy

Edges in master graph.
1:1 mapping in managed view.
Merging policy.
Edges in master graph 1:1 mapping in managed view Merging policy

Interface IFoldedGraph provides support for a managed view to present arbitrary parts of the master graph's hierarchy of nodes. The LocalRoot property can be conveniently used to specify a group node from the master graph which is taken as the root for the subset of the graph hierarchy that should be presented. Effectively, this means that a managed view always presents either a specific group node's contents or the root of the hierarchy of nodes.

In case that the group node that is given as the root is removed from the master graph, the managed 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.

Usage of interface IFoldedGraph can be observed in tutorial demo application FoldingWindow.

Converters and Callbacks

FoldingManager uses so-called converters to handle the transition for graph elements in a managed view that become dummy elements. Converters determine what state (style, geometry, label text, etc.) should be used for a dummy graph element, if there is not already folding-related state held for them by FoldingManager.

Whenever a group node gets collapsed, the IDummyNodeConverter implementation that is set with FoldingManager via the DummyNodeConverter property is queried to determine the actual state that should be used for the dummy node.

Figure 5.8. Hierarchy of dummy node converters

Hierarchy of dummy node converters.

Table 5.3, “Predefined dummy node converter implementations” lists the predefined dummy node converter implementations present in the yWorks.yFiles.UI.Model namespace.

Table 5.3. Predefined dummy node converter implementations

Type Name Description
DefaultDummyNodeConverter By default, creates/updates dummy nodes using all state from the original group node.

DefaultDummyNodeConverter provides properties to conveniently specify state aspects for a new dummy node. In addition, there is also a variety of callbacks available that easily allow to customize creating and updating dummy nodes.

Even more control can be achieved by directly implementing the IDummyNodeConverter interface. Most importantly with the interface's methods is that all modifications of the dummy node's state need to be carried out using the IChangeDummyNodeAppearanceCallback implementation.

Whenever a group node gets collapsed, and there would connect dummy edges to the collapsed group node, the IDummyEdgeConverter implementation that is set with FoldingManager via the DummyEdgeConverter property is queried to determine which dummy edge should be created and what state should be used for that dummy edge.

Figure 5.9. Hierarchy of dummy edge converters

Hierarchy of dummy edge converters.

Table 5.4, “Predefined dummy edge converter implementations” lists the predefined dummy edge converter implementations present in the yWorks.yFiles.UI.Model namespace.

Table 5.4. Predefined dummy edge converter implementations

Type Name Description
DefaultDummyEdgeConverter Creates a corresponding dummy edge for each edge in the master graph that connects to a node whose representative is hidden in a managed view.
MergingDummyEdgeConverter Creates at most one dummy edge (two dummy edges, if edge direction is not ignored) between each pair of source node and target node. Conceptionally, this implementations merges dummy edges that connect the same source node and target node in a managed view into one dummy edge (two dummy edges).
ExcludingDummyEdgeConverter Creates no dummy edges at all.

Both DefaultDummyEdgeConverter and MergingDummyEdgeConverter provide properties to conveniently specify state aspects for a new dummy edge that is created using either policy. In addition, there is also a variety of callbacks available that easily allow to customize creating and updating dummy edges.

Even more control can be achieved by directly subclassing AbstractDummyEdgeConverter. Most importantly with that class's single abstract method is that the dummy edge creation policy needs to be specified using the provided IAddDummyEdgeCallback implementation. Also, any modifications of the dummy edge's state need to be carried out using that callback, too.