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 FLEX, 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 3.1, “Enabling grouping support with a DefaultGraph instance” presents how grouping support is enabled for a DefaultGraph instance.

Example 3.1. Enabling grouping support with a DefaultGraph instance

// 'graph' is of type com.yworks.graph.model.DefaultGraph.

graph.groupingSupported = true;

Tutorial demo application GroupingDemo presents an extensive example of an application that handles hierarchically organized 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 interface INodeHierarchy that holds the actual hierarchy of nodes. This INodeHierarchy implementation binds the type parameter to model item type INode.

Figure 3.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 INodeHierarchy implementation.

Adding grouping support to a graph is achieved by creating a GroupedGraph instance for it. Example 3.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 3.2. Adding grouping support to a graph

// 'flatGraph' is of type com.yworks.graph.model.IGraph.

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

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

API Excerpt 3.1, “Structure-related methods” lists the methods defined in interface IGroupedGraph that 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.

API Excerpt 3.1. Structure-related methods

// Re-parenting, i.e., changing the parent node of a given INode.
setParent(node:INode, parent:INode):void

// Grouping nodes.
groupNodes(children:Iterable, parent:INode = null):INode

// Creating group nodes.
createGroupNode(parent:INode = null, bounds:IRectangle = null, 
                style:INodeStyle = null):INode

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 3.3, “Grouping nodes” shows how nodes are grouped in a common group node which is created implicitly.

Example 3.3. Grouping nodes

// 'grouped' is of type com.yworks.graph.model.IGroupedGraph.

// Create a new top-level group node that has two children.
var groupNode:INode = 
  grouped.groupNodes(new YList(new Array(someNode, someOtherNode)));

Class GroupedGraph also provides support for the notion of default styles as laid out in interface IGraph. Property defaultGroupNodeStyle can be used to set the default style for group nodes. Style sharing semantics are controlled using property shareDefaultGroupNodeStyleInstance.

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 3.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 3.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.
GroupedGraph The returned GroupedGraph can be used to manipulate the hierarchical organization of the graph as well as to control group node style-related settings.
INodeHierarchy The returned INodeHierarchy implementation presents a view of the actual hierarchical structure of the nodes. (See below for the description of interface INodeHierarchy.)

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 3.4, “Retrieving the GroupedGraph instance that is associated with a graph” shows how to get the IGroupedGraph instance from the graph's look-up.

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

// 'graph' is of type com.yworks.graph.model.IGraph.

// Retrieve the IGroupedGraph implementation that adds grouping support to the
// graph.
var grouped:IGroupedGraph = graph.lookup(IGroupedGraph) as IGroupedGraph;

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 method in API Excerpt 3.2, “Clean-up method for GroupedGraph” is provided.

API Excerpt 3.2. Clean-up method for GroupedGraph

dispose():void

Note

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

Interface INodeHierarchy

Interface INodeHierarchy defines the basis for a data structure that holds a hierarchical organization of nodes. It models a tree-like hierarchy that allows to observe structural changes.

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

API Excerpt 3.3, “Methods defined in INodeHierarchy” lists the methods from interface INodeHierarchy that allow to query and modify a hierarchical structure of nodes.

Important

On the INodeHierarchy instance, which is used together with the GroupedGraph object that adds grouping support to a graph, no methods should be invoked that modify a hierarchy. Instead, all modifications should be carried out using the GroupedGraph instance.

API Excerpt 3.3. Methods defined in INodeHierarchy

contains(item:INode):Boolean

addChild(parent:INode, child:INode):void
remove(child:INode):void

getChildCount(parent:INode):int
getChildren(item:INode):Iterable

isLeaf(item:INode):Boolean
setLeaf(item:INode, leaf:Boolean):void

getParent(child:INode):INode
setParent(child:INode, parent:INode):void

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

Related Classes

Static class Hierarchies provides utility methods that allow to conveniently query information about a hierarchy held by an INodeHierarchy implementation. API Excerpt 3.4, “Utility methods in Hierarchies” lists some of the methods.

API Excerpt 3.4. Utility methods in Hierarchies

static elements(hierarchy:INodeHierarchy):Iterable

static isDescendant(hierarchy:INodeHierarchy, node:INode, parent:INode):Boolean
static getDescendants(hierarchy:INodeHierarchy, root:INode):Iterable

static getNearestCommonAncestor(hierarchy:INodeHierarchy, items:Iterator):INode

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.

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

Example 3.5. Creating a managed view

// 'gc' is of type com.yworks.ui.GraphCanvasComponent

var fm:FoldingManager = new FoldingManager();
// Creating a managed view.
var fg:IFoldedGraph = 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.

FoldingManager's createManagedView() method enables 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.

API Excerpt 3.5. Methods for creating managed views

// The predicate allows to specify the initial collapse/expand state of group
// node representatives.
function createManagedView( root:INode=null,
                            expandedPredicate:Function=null ):IFoldedGraph

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 3.2, “Additional types supported with look-up operations” are additionally supported with look-up operations initiated on a managed view's IGraph instance.

Table 3.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, ît 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 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 both via IGraph's look-up as well as through the 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 FolderDemo.

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 3.6. Getting the IFoldedGraph implementation from the folding-enabled graph's look-up

// 'gc' is of type com.yworks.ui.GraphCanvasComponent.

// Retrieve the IFoldedGraph implementation that enables folding operations.
var fg:IFoldedGraph = gc.graph.lookup(IFoldedGraph) as IFoldedGraph;

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 methods listed in API Excerpt 3.6, “Collapsing and expanding group nodes”.

API Excerpt 3.6. Collapsing and expanding group nodes

function collapse( groupNode:INode ):void
function expand( groupNode:INode ):void

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. API Excerpt 3.7, “Querying state aspects of elements in a managed view” lists the methods from IFoldedGraph that allow to query state aspects of graph elements in a managed view.

API Excerpt 3.7. Querying state aspects of elements in a managed view

// Collapsed/expanded state for group nodes in a managed view.
function isInitiallyExpanded(masterGroupNode:INode):Boolean
function isExpanded( groupNode:INode ):Boolean

// Dummy state for graph elements in a managed view.
function isDummy( item:IModelItem ):Boolean

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. API Excerpt 3.8, “Mapping between graph elements in the model (master graph) and a managed view” lists these methods.

API Excerpt 3.8. 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.
function getMaster( item:IModelItem ):IModelItem
function getMasterNode( node:INode ):INode
function getMasterEdge( edge:IEdge ):IEdge
function getMasterLabel( label:ILabel ):ILabel
function getMasterPort( port:IPort):IPort
function getMasterBend( bend:IBend ):IBend
function getMasterEdges( dummyEdge:IEdge ):Iterable

// Getting representing graph elements (if any) for original graph elements.
function getRepresentative( modelItem:IModelItem ):IModelItem
function getRepresentativeNode( node:INode ):INode
function getRepresentativeEdge( edge:IEdge ):IEdge
function getRepresentativeLabel( label:ILabel ):ILabel
function getRepresentativePort( port:IPort ):IPort
function getRepresentativeBend( bend:IBend ):IBend

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 3.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 3.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 FoldingDemo.

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 3.8. Hierarchy of dummy node converters

Hierarchy of dummy node converters.

Table 3.3, “Predefined dummy node converter implementations” lists the predefined dummy node converter implementations present in the com.yworks.graph.model namespace.

Table 3.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.

API Excerpt 3.9. IDummyNodeConverter interface methods

// Customizing creation of collapsed group nodes.
function createDummyNodeAppearance(
                    callback:IChangeDummyNodeAppearanceCallback,
                    foldedGraph:IFoldedGraph,
                    dummyNode:INode,
                    masterNode:INode ):void
// Customizing update of collapsed group nodes.
function changeDummyNodeAppearance(
                    callback:IChangeDummyNodeAppearanceCallback,
                    foldedGraph:IFoldedGraph,
                    dummyNode:INode,
                    masterNode:INode ):void

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 3.9. Hierarchy of dummy edge converters

Hierarchy of dummy edge converters.

Table 3.4, “Predefined dummy edge converter implementations” lists the predefined dummy edge converter implementations present in the com.yworks.graph.model namespace.

Table 3.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.

API Excerpt 3.10. Abstract method in AbstractDummyEdgeConverter

// Customizing dummy edge creation.
function addDummyEdge( callback:IAddDummyEdgeCallback,
                       foldedGraph:IFoldedGraph,
                       masterEdge:IEdge,
                       localSourceNode:INode,
                       sourceDummy:Boolean,
                       localTargetNode:INode,
                       targetDummy:Boolean ):IEdge