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.

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():INode
createEdge(sourcePort:IPort, targetPort:IPort, style:IEdgeStyle = null):IEdge
// INode is also IPortOwner.
addPort(portOwner:IPortOwner, x:Number, y:Number):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.

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 is maintained by an actual instance. 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 {}

Table 2.1, “Supported type requests with Lookup method” presents a sample of types that are supported with look-up operations initiated on a DefaultGraph instance.

Table 2.1. Supported type requests with Lookup method

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.)

The ILookupDecorator that is returned when requesting this type in an invocation of DefaultGraph's Lookup method 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.1, “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.1. 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.2, “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.2. 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.createEdge(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.3, “Creating a graph”.

Example 2.3. 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.4, “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.4. 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; it.hasNext(); ) {
  var node: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()
  for (; jt.hasNext(); ) {
    var edge:IEdge = jt.next();
    // If the edge is outgoing at the current node, then reverse it.
    if (edge.sourcePort.owner == node) {
      var tmpPort:IPort = edge.sourcePort;
      edge.sourcePort = edge.targetPort;
      edge.targetPort = tmpPort;
    }
  }
}

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

Example 2.5. 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 = it.next();
  for (var jt:Iterator = node.ports.iterator(); jt.hasNext(); ) {
    var port: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.6, “Iterating over graph elements (cont.)” shows iteration over all bends of a set of edges.

Example 2.6. Iterating over graph elements (cont.)

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

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

Example 2.7. 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 = it.next();
  trace(nodeLabel.owner.toString() + ": '" + nodeLabel.text + "'");
}