Graph Structure

The graph model found in package com.yworks.graph.model centers around interface IGraph. It serves as a visually rich representation of the mathematical notion of a directed graph and provides geometry as well as rendering support for the items it contains.

What is a Graph?

A directed graph is defined as consisting of two sets, a node set and an edge set. A node represents any kind of entity and an edge represents a relation between any two nodes.

Directed graph means that all edges from the edge set have direction, i.e., a distinct source node and a distinct target node. The mathematical notation for an edge is a pair where the first component denotes source and the second component denotes target node: edge e = (source, target).

The yFiles FLEX graph model adds further elements to this concept: so-called ports serve as linking elements between nodes and edges. So-called bends, which are part of an edge, present a means to define a geometry for edges. Also, label elements can be used with nodes and edges and add to their visual appearance.

Nodes and edges of a graph are modeled by interfaces INode and IEdge. The ports that link nodes and edges are defined by interface IPort, the bends that an edge can contain are defined by interface IBend. Finally, interface ILabel defines generic labels for both nodes and edges.

Figure 2.2, “Graph structure” depicts the central graph structure types in yFiles FLEX. Through a set of convenient properties, IGraph makes available collection views of the model items it contains. For example, the nodes and edges properties can be used to obtain a view of all nodes and all edges, respectively.

Figure 2.2. Graph structure

Graph structure.

The types surrounding IGraph consistently provide read-only views of their data, and to change a graph element's properties, methods defined in IGraph need to be used. In other words, IGraph holds responsibility for all changes to the graph model, be it of structural nature, changes of geometry, or regarding the visual appearance of graph elements.

Figure 2.3, “Type hierarchy of model items” shows the type hierarchy of the model items in a yFiles FLEX graph model.

Figure 2.3. Type hierarchy of model items

Type hierarchy of model items.

All graph structure interfaces either directly or indirectly have interface ILookup as their base type. Consequently, the powerful look-up mechanism as discussed in the section called “Look-up Mechanism” can be used to query dynamic sets of associated types that handle specific aspects of even single instances of such interface implementations.

The look-up support provided by class DefaultGraph, the default IGraph implementation, for example, is presented in the section called “Look-up Support”. The type requests that are handled by the model item types are listed in the section called “Look-up Support”. In the section called “Customizing User Interaction Behavior”, the look-up support provided by the model item types is discussed in the context of user interaction behavior and its customization.

Figure 2.4, “Relationships between graph structure types” depicts the interrelations of graph structure elements. Nodes and edges connect to each other using ports, which logically belong to the nodes (i.e., the owner of an IPort is an INode). Edges, more precisely their paths, are defined by the ports at their ends and a sequence of zero or more bends in-between these (where each of the IBend will indicate the IEdge as its owner).

Figure 2.4. Relationships between graph structure types

Relationships between graph structure types.

The INode, IEdge, IPort, and IBend interfaces allow to obtain a graph element's geometry as well as the style object that manages visual appearance. While node geometry can be obtained directly from INode, edge geometry is available via an edge's IPort and IBend objects.

In addition to the immediate graph structure elements, there is also support for an arbitrary number of labels at both nodes and edges. The actual INode or IEdge a label belongs to, is indicated as the ILabel's owner. Figure 2.5, “ILabeledItem and ILabel” presents ILabel's relationship to other model items.

Figure 2.5. ILabeledItem and ILabel

ILabeledItem and ILabel.

Besides geometry and style, interface ILabel also grants access to label-specific data like the label's text. Label geometry is special since actual location is calculated by a so-called label model, which uses a label model parameter that is associated with the label. Label models and also the creation of valid label model parameters is explained in the section called “Label Support”.

Interface IGraph defines fundamental graph structure-related functionality including creation and deletion of graph elements, or querying of structural aspects, like, e.g., adjacency lists. Provided is also support for modifying the geometry of graph elements, i.e., their location and size, and for modifications to the visual appearance of model items.

API Excerpt 2.1, “Graph model-related methods from interface IGraph” shows IGraph's methods that deal with the creation of graph elements and modification of the graph structure.

API Excerpt 2.1. Graph model-related methods from interface IGraph

// Creation of graph elements.
createNode( bounds:IRectangle=null, style:INodeStyle=null ):INode
createNodeAt( x:Number, y:Number ):INode
createEdge(sourcePort:IPort, targetPort:IPort, style:IEdgeStyle = null ):IEdge
createEdgeBetweenNodes( sourceNode:INode, targetNode:INode, 
                                          style:IEdgeStyle=null ):IEdge
// INode is also IPortOwner.
addPort(portOwner:IPortOwner, x:Number, y:Number):IPort
addPortWithParameter(portOwner:IPortOwner,
                     modelParameter:IPortLocationModelParameter):IPort
addBend(edge:IEdge, index:int, x:Number, y:Number):IBend 
// INode and IEdge are also ILabeledItem.
addLabel(item:ILabeledItem, text:String, 
         labelModelParameter:ILabelModelParameter = null, 
         style:ILabelStyle = null):ILabel

Methods that handle the deletion of graph elements are listed in API Excerpt 2.2, “Graph model-related deletion methods from interface IGraph”.

API Excerpt 2.2. Graph model-related deletion methods from interface IGraph

// Deletion of graph elements.
clear():void
clearBends(edge:IEdge):void

// Single graph elements.
removeNode(node:INode):void
removeEdge(edge:IEdge):void
removePort(port:IPort):void
removeBend(bend:IBend):void
removeLabel(label:ILabel):void

IGraph methods that allow to query structural aspects of a graph, for example, the set of edges adjacent at a given node, are listed in API Excerpt 2.3, “Further graph structure-related methods”.

API Excerpt 2.3. Further graph structure-related methods

// Querying structural aspects of a graph.
contains(item:IModelItem):Boolean

edgesAtPort(port:IPort):Iterable
// INode is also IPortOwner.
edgesAtPortOwner(portOwner:IPortOwner):Iterable

The geometry of graph elements can be altered by means of the methods listed in API Excerpt 2.4, “Geometry-related methods”. They allow to directly change the sizes of nodes and labels, and the locations of nodes, ports, and bends.

Note that IGraph provides the defaultNodeSize property, which holds a default size for nodes that is automatically used at creation time when no explicit size is specified.

API Excerpt 2.4. Geometry-related methods

// Convenience methods for changing the geometry/location of nodes, bends, 
// ports, and labels.
setBounds(node:INode, x:Number, y:Number, width:Number, height:Number):void
setBendLocation(bend:IBend, x:Number, y:Number):void
setPortLocation(port:IPort, x:Number, y:Number):void
setPreferredSize(label:ILabel, width:Number, height:Number):void

Modifying the visual appearance of model items is supported by the IGraph methods presented in API Excerpt 2.5, “Styles-related methods from interface IGraph”. They can be used to conveniently handle styles, which are responsible for providing the visual presentation of graph elements (see also the section called “Visual Representation of Graph Elements”).

API Excerpt 2.5. Styles-related methods from interface IGraph

// Style setters. 
setNodeStyle(node:INode, style:INodeStyle):void
setEdgeStyle(edge:IEdge, style:IEdgeStyle):void
setLabelStyle(label:ILabel, style:ILabelStyle):void
setPortStyle(port:IPort, style:IPortStyle):void

However, IGraph also provides the necessary support for a so-called "default style" mechanism that automatically binds styles to newly created graph elements. This is achieved with the help of the defaultNodeStyle, defaultEdgeStyle, etc. set of properties, which allow both read and write access.

Events

IGraph dispatches events to notify interested parties of structural graph changes. The information is conveyed using objects of type GraphEvent, and covers element insertion and removal as well as element modifications like, e.g., changing an edge's end nodes. Listeners have to listen to the event type GRAPH_CHANGED. The affected model item can be retrieved from the event's item property. The kind of change is determined by the event's kind property as defined in one of the GraphEventKind constants.

Example 2.1. Listening for structural changes

// add a listener for graph changes
// graph is of type IGraph
graph.addEventListener(GraphEvent.GRAPH_CHANGED, onGraphChanged);

// the listener:
private function onGraphChanged(event:GraphEvent):void {
  switch (event.kind) {
    case GraphEventKind.NODE_CREATED:
      var node:INode = event.item as INode;
      // do something with the newly created node
      break;
    case GraphEventKind.EDGE_CREATED:
      var edge:IEdge = event.item as IEdge;
      // do something with the newly created edge
      break;
  }
}

For performance reasons, layout changes are not reported by events but using a reporter-listener mechanism. A INodeBoundsChangedReporter for a graph can be retrieved using the look-up mechanism. The reporter can be used to add an INodeBoundsChangedListener which gets notified upon node layout changes.

Example 2.2. Listening for layout changes

// get the graph's INodeBoundsChangedReporter instance
var reporter:INodeBoundsChangedReporter
    = graph.lookup(INodeBoundsChangedReporter) as INodeBoundsChangedReporter;
// listener is an implementor of INodeBoundsChangedListener
reporter.addNodeBoundsChangedListener(listener);

// the listener implementation:
public class NodeBoundsChangedListener implements INodeBoundsChangedListener {
  public function nodeBoundsChanged( node:INode, oldBounds:IRectangle ):void {
    var newBounds:IRectangle = node.layout;
    // the node's bounds have been changed: do something
  }
}

Look-up Support

All graph structure types support the look-up mechanism through their Lookup method. Table 2.1, “Supported type requests with look-up operations” lists the various type requests that can be initiated on the model items.

Table 2.1. Supported type requests with look-up operations

Model Item Type Requested Type Description
all model items IMementoSupport The returned IMementoSupport implementation can be used to create IUndoUnit objects.
IClipboardHelper The returned IClipboardHelper implementation allows to augment clipboard-related actions like Cut, Copy, and Paste.
IHandleProvider Holds a collection of IHandle implementations.
IPositionHandler, IReshapeHandleProvider, and ISelectionPaintable See the section called “Customizing User Interaction Behavior”.
ITagOwner Allows tagging model items and associating arbitrary data using the tags. See the section called “User Tags”.
SnapContext Allows using the support for interactive snapping of graph elements. See the section called “Support for Interactive Snapping of Graph Elements”.
IMarqueeTestable The returned IMarqueeTestable implementation can be used to test whether a model item is inside a given marquee rectangle.
IHighlightPaintableInstaller and IFocusPaintableInstaller The returned IHighlightPaintableInstaller / IFocusPaintableInstaller implementation can be used to install visual decorations for a model item in the canvas that serve as a highlight/focus indication. See also the section called “Customizing User Interaction Behavior”.
INode IPortCandidateProvider and ISizeConstraintProvider See the section called “Customizing User Interaction Behavior”.
IGroupBoundsCalculator and INodeInsetsProvider Implementations for these interfaces are in the look-up for non-leaf nodes in a hierarchically organized graph.
IShapeGeometry The returned IShapeGeometry implementation describes the geometry of the node's shape.
IEdge IEdgePortCandidateProvider See the section called “Customizing User Interaction Behavior”.
IBendCreator The returned IBendCreator implementation helps in creating new bends in an IEdge.
IBendSelectionTester Helps in getting the IBend objects at specified coordinates in the canvas.
IPort IHandle The returned IHandle implementation represents the UI element that is displayed in the canvas for moving the IPort.
IEdgeIntersectionCalculator Enables calculating the visible portion of an IEdge's path.
IBend IHandle The returned IHandle implementation represents the UI element that is displayed in the canvas for moving the IBend.
ILabel ILabelModelParameterProvider Provides possible label candidates for an ILabel according to the label's model.
ILabelModelParameterFinder Enables finding the best matching label model parameter for an ILabel's position according to the label's model.

Through the GraphDecorator class specialized decorators for the graph element types are available. These decorators provide convenient access to the most common decorations for an actual graph element type (which largely match the table's content). The GraphDecorator can be retrieved from the graph's lookup as shown in Example 2.3, “Getting the GraphDecorator for a graph”.

Example 2.3. Getting the GraphDecorator for a graph

// graph is of type IGraph
var decorator:GraphDecorator = graph.lookup(GraphDecorator) as GraphDecorator;

Note that the main purpose of the available decorations is to enable customization of specific aspects of a graph element's look and feel as described in the section called “Look-up Support”, but with respect to quickly seeing what is "in the look-up" of a given graph element, the decorators are also useful.

Class DefaultGraph

Class DefaultGraph is the default IGraph (see above) implementation used in yFiles FLEX. It holds the central graph structure that is displayed by the canvas, i.e., class GraphCanvasComponent.

Class DefaultGraph allows to conveniently create a graph model consisting of nodes, edges, ports, etc., connect them appropriately, or again remove any element from the graph. Querying structural aspects of the graph model or changing the geometry of model items is easily achieved. Also, the visual appearance for each kind of graph element or each individual model item can be set using styles.

For example, the methods listed in API Excerpt 2.6, “Graph structure-related methods”, which add to the graph structure methods already defined in IGraph, can be used to obtain all incoming and outgoing edges at a given node.

API Excerpt 2.6. Graph structure-related methods

inEdgesAtPort(port:IPort):Iterable
// INode is also IPortOwner.
inEdgesAtPortOwner(portOwner:IPortOwner):Iterable

outEdgesAtPort(port:IPort):Iterable
// INode is also IPortOwner.
outEdgesAtPortOwner(portOwner:IPortOwner):Iterable

DefaultGraph fully supports the notion of default styles as laid out in interface IGraph. Any created graph element is automatically associated with a fresh instance of its respective style type. The actual default node style, default edge style, etc. instances that are used for this mechanism can be set in advance.

As an option, DefaultGraph can also be configured to apply style sharing semantics when automatically associating default style instances with model items. When either of the shareDefaultNodeStyleInstance, shareDefaultEdgeStyleInstance, etc. properties is set to true, the respective default style instance is associated with a model item by reference instead of being cloned.

Note

Initially, classes ShapeNodeStyle and PolylineEdgeStyle are used as the default node and edge styles for the default style support of class DefaultGraph. Similarly, SimpleLabelStyle is used as the default node label and default edge label style.

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

Look-up Support

Class DefaultGraph supports the look-up mechanism as described in the section called “Look-up Mechanism” by means of a look-up chain (see the section called “Look-up Chaining”) that it is maintaining. The look-up chain can be easily customized using the methods listed in API Excerpt 2.7, “Methods for modifying the look-up chain of class DefaultGraph”.

API Excerpt 2.7. Methods for modifying the look-up chain of class DefaultGraph

addLookup(lookup:IContextLookupChainLink):void 
removeLookup(lookup:IContextLookupChainLink):void 

Additionally, DefaultGraph also directly maintains look-up chains for the graph element types, i.e., for nodes, edges, labels, etc. These look-up chains can be customized using an implementation of interface ILookupDecorator as described below.

Table 2.2, “Supported type requests with look-up operations” presents a sample of types that are supported with look-up operations initiated on a DefaultGraph instance. Further types that are supported when grouping support is enabled are listed in the section called “Look-up Modifications”.

Table 2.2. Supported type requests with look-up operations

Requested Type Description
ILookupDecorator The returned ILookupDecorator enables convenient manipulation of look-up chains maintained for the graph elements of the queried DefaultGraph, i.e., for all its nodes, edges, labels, etc. As a convenience, manipulation of the graph's own look-up chain is also supported. (See below for a description on using ILookupDecorator.)
IUndoSupport The returned IUndoSupport implementation allows to indicate to the undo engine any pending modifications to items in the canvas.
IGraphUndoUnitSupport The returned IGraphUndoUnitSupport implementation can be easily wrapped in order to customize the undoability mechanism, if necessary.
INodeBoundsChangedReporter The returned INodeBoundsChangedReporter implementation allows to register an event listener that signals changes in the layout of INode model items.
IBendLocationChangedReporter The returned IBendLocationChangedReporter implementation allows to register an event listener that signals changes in the location of IBend model items.
ILabelTextChangedReporter The returned ILabelTextChangedReporter implementation allows to register an event listener that signals text changes of ILabel model items.
IPreferredSizeChangedReporter The returned IPreferredSizeChangedReporter implementation allows to register an event listener that signals changes in the preferred size of ILabel model items.
ITagChangedReporter The returned ITagChangedReporter implementation allows to register an event listener that signals changes of the tag object of model items.

The ILookupDecorator that is returned when querying DefaultGraph's look-up for this type makes available further look-up chains for decoration. These look-up facilities are maintained for the model items in the DefaultGraph, i.e., specifically look-up operations initiated on the following types can be decorated: INode, IEdge, IPort, IBend, and ILabel. Example 2.4, “Modifying the look-up chain for the nodes of a DefaultGraph” shows the typical pattern that is used to modify the look-up chain for the items of a DefaultGraph.

Example 2.4. Modifying the look-up chain for the nodes of a DefaultGraph

function prependMyCustomLookupBehavior( graph:IGraph ):void {
  // Get the graph's ILookupDecorator that enables manipulation of all look-up 
  // operations for the contained graph elements. 
  var decorator:ILookupDecorator = 
    graph.lookup( ILookupDecorator ) as ILookupDecorator;
  if( null != decorator ) {
    // Test whether decoration of look-up operations initiated on type INode is 
    // supported.
    if( decorator.canDecorate( INode ) ) {
      // Prepend a custom look-up provider.
      decorator.addLookup( INode, new MySelectionPaintableChainLink() );
    }
  }
}

The CustomStyle demo application shows how selection painting of all nodes can be customized using the lookup decorator mechanism.

Working with DefaultGraph

Example 2.5, “Creating a graph” shows how to create a graph and how to populate it with newly created graph elements. The example also demonstrates how to add labels to both nodes and edges.

Example 2.5. Creating a graph

var graph:IGraph = new DefaultGraph();

// Create 10 nodes.
var nodes:Array = new Array(10);
for (var i:int = 0; i < nodes.length; i++) {
  nodes[i] = graph.createNode();
  graph.addLabel(nodes[i], "Node #" + i.toString());
}

// Create 5 edges. Each edge has "even" source node and "odd" target node. 
var edges:Array = new Array(5);
for (var i:int = 0, j:int = 0; i < nodes.length - 1; i += 2, j++) {
  edges[j] = graph.createEdgeBetweenNodes(nodes[i], nodes[i + 1]);
  graph.addLabel(edges[j], "Edge #" + j.toString());
}

Connecting edges to the ports of some nodes and also adding bends to edges is presented in Example 2.6, “Creating a graph”.

Example 2.6. Creating a graph

var graph:IGraph = new DefaultGraph();
// Node style configuration.
ShapeNodeStyle( graph.defaultNodeStyle ).shapeNodeShape = 
    ShapeNodeShape.ROUND_RECT;
graph.defaultNodeSize = com.yworks.canvas.geom.Size.create(80, 40);

// Create two nodes.
var fst:INode = graph.createNodeAt(100, 200), 
    snd:INode = graph.createNodeAt(300, 200);
// Add a port to each node.
var fstSouth:IPort = graph.addPort(fst, 120, 220), 
    sndNorth:IPort = graph.addPort(snd, 280, 180);

// Create two edges connecting the ports.
var back:IEdge = graph.createEdge(sndNorth, fstSouth), 
    forth:IEdge = graph.createEdge(fstSouth, sndNorth);
// Add some bends to each edge.
graph.addBend(back, 0, 240, 100);
graph.addBend(back, 1, 160, 300);

graph.addBend(forth, 0, 120, 300);
graph.addBend(forth, 1, 200, 300);
graph.addBend(forth, 2, 200, 100);
graph.addBend(forth, 3, 280, 100);

Iterating over the elements of a graph is supported in a number of ways. Example 2.7, “Iterating over graph elements”, for example, presents iteration over the nodes of a graph and iteration over the edges adjacent at a node.

Example 2.7. Iterating over graph elements

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

// Iterate over all nodes in the graph.
for (var it:Iterator = graph.nodes.iterator(); it.hasNext(); ) {
  var node:INode = INode( it.next() );
  if (!fulfillsSomeCondition(node)) {
    continue;
  }
  
  // Iterate over all adjacent edges at nodes that fulfill some condition.
  // (Technically, the edges are adjacent to an IPortOwner.)
  var jt:Iterator = graph.edgesAtPortOwner(node).iterator();
  while( jt.hasNext() ) {
    var edge:IEdge = IEdge( jt.next() );
    // If the edge is outgoing at the current node, then reverse it.
    if (edge.sourcePort.owner == node) {
    	graph.setPorts( edge, edge.targetPort, edge.sourcePort );
    }
  }
}

Getting all edges that connect to some ports from a given set of nodes is shown in Example 2.8, “Iterating over graph elements (cont.)”.

Example 2.8. Iterating over graph elements (cont.)

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

for (var it:Iterator = myNodeList.iterator(); it.hasNext(); ) {
  var node:INode = INode( it.next() );
  for (var jt:Iterator = node.ports.iterator(); jt.hasNext(); ) {
    var port:IPort = IPort( jt.next() );
    if (!fulfillsSomeCondition(port)) {
      continue;
    }
    
    // Iterate over all adjacent edges at ports that fulfill some condition.
    for (var kt:Iterator = graph.edgesAtPort(port).iterator(); kt.hasNext(); ) {
      doSomethingWithThisEdge(kt.next());
    }
  }
}

Example 2.9, “Iterating over graph elements (cont.)” shows iteration over all bends of a set of edges.

Example 2.9. Iterating over graph elements (cont.)

for (var it:Iterator = myEdgeList.iterator(); it.hasNext(); ) {
  var edge:IEdge = IEdge( it.next() );
  // Iterate over all bends of the current edge.
  for (var jt:Iterator = edge.bends.iterator(); jt.hasNext(); ) {
    var bend:IBend = IBend( jt.next() );
    trace("bend location: x=" + bend.x + " y=" + bend.y);
  }
}

The code snippet in Example 2.10, “Iterating over graph elements (cont.)” iterates over all node labels in the graph and prints out their content.

Example 2.10. Iterating over graph elements (cont.)

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

// Iterating over all node labels from the graph.
for (var it:Iterator = graph.nodeLabels.iterator(); it.hasNext(); ) {
  var nodeLabel:ILabel = ILabel( it.next() );
  trace(nodeLabel.owner.toString() + ": '" + nodeLabel.text + "'");
}

Example 2.11, “Getting an edge's source and target node” shows how to get the source and target node of an edge.

Example 2.11. Getting an edge's source and target node

// get the source node of an edge
var sourceNode:INode = edge.sourcePort.owner as INode;
// get the target node of an edge
var targetNode:INode = edge.targetPort.owner as INode;

Example 2.12, “Getting a node's neighbors” shows how to get a node's predecessor and successor nodes, i.e., the nodes at the other end of incoming and outgoing edges, respectively.

Example 2.12. Getting a node's neighbors

// get the predecessors of a node
for (var it:Iterator = DefaultGraph(graph).inEdgesAtPortOwner(node).iterator(); 
     it.hasNext(); ) {
  var predNode:INode = IEdge(it.next()).sourcePort.owner as INode;
  
  // do something with this node...
}		

// get the successors of a node
for (var it:Iterator = DefaultGraph(graph).outEdgesAtPortOwner(node).iterator(); 
     it.hasNext(); ) {
  var succNode:INode = IEdge(it.next()).targetPort.owner as INode;
  
  // do something with this node...
}

Example 2.13, “Getting a node's in degree and out degree” shows how to get the number of incoming and outgoing edges at a node, i.e., the node's in degree and out degree, respectively.

Example 2.13. Getting a node's in degree and out degree

// determine the in degree of a node
var inDegree:int = 0;
for (var it:Iterator = DefaultGraph(graph).inEdgesAtPortOwner(node).iterator(); 
     it.hasNext(); ) {
  inDegree++;
}

// determine the out degree of a node
var outDegree:int = 0;
for (var it:Iterator = DefaultGraph(graph).outEdgesAtPortOwner(node).iterator(); 
     it.hasNext(); ) {
  outDegree++;
}