Iteration Mechanisms

Iterating over a collection of graph elements can be done using language-specific iteration idioms, like the foreach construct of C#, as well as using the iteration mechanisms provided by the yFiles library itself.

Language Constructs for Iteration

The C# foreach construct allows to conveniently iterate over collections of graph elements. Example 12.3, “Iteration using foreach construct” presents iteration over the elements of a graph using the Nodes and Edges properties.

Example 12.3. Iteration using foreach construct

// 'graph' is of type yWorks.yFiles.Algorithms.Graph.

// Iterating over all nodes of a graph.
foreach (Node n in graph.Nodes) {
  // Do something with Node 'n'.
}

// Iterating over all edges of a graph.
foreach (Edge e in graph.Edges) {
  // Do something with Edge 'e'.
}

Cursors

The yFiles cursor hierarchy with interface ICursor and its typed sub-interfaces INodeCursor and IEdgeCursor provides bidirectional iteration over a sequence of objects in a uniform fashion. Figure 12.4, “The yFiles cursor hierarchy” depicts the inheritance relationship along with all relevant methods.

Figure 12.4. The yFiles cursor hierarchy

The yFiles cursor hierarchy.

A cursor offers access to the object it currently points to, it tells whether the current position is valid or "out of range," and finally a cursor gives the size of the sequence it iterates over. Upon creation, a cursor by default points to the first object of the underlying sequence. Moving the cursor position is possible both absolutely, i.e., to the first or last object of the sequence and relatively, i.e., to the next or the previous object. Note that the typing of the sub-interfaces takes place at the access methods, i.e., these return references of type Node and Edge, respectively.

// 'graph' is of type yWorks.yFiles.Algorithms.Graph.

// Get a cursor to iterate over all edges from the edge set of the graph.
IEdgeCursor ec = graph.GetEdgeCursor();

// Ask for the size of the underlying sequence of edges.
int size = ec.Size;

// Move the cursor to the last position.
ec.ToLast();

A cursor cannot change the sequence it iterates over, particularly, it cannot remove elements from the underlying container. So effectively, a cursor constitutes a read-only view on the sequence it iterates over. To actually remove an element from the underlying container, the container itself must be accessible. See the section called “Iteration and Element Removal” for more details.

Figure 12.5, “Types that provide cursors” shows the most prominent yFiles types that provide cursors to iterate over object, node, or edge sequences, respectively. These types are YList, NodeList, EdgeList, IGraphInterface, Graph, and Node.

Figure 12.5. Types that provide cursors

Types that provide cursors.

Example 12.4, “Forward iteration with various cursor types” and Example 12.5, “Backward iteration” show the usage of cursors in conjunction with loops to iterate over a sequence of either typed or untyped objects. Note the additional cursor movement to the last position of the sequence with backward iteration.

Example 12.4. Forward iteration with various cursor types

// 'graph' is of type yWorks.yFiles.Algorithms.Graph.
// 'list' is of type yWorks.yFiles.Algorithms.YList.

// Forward iterate over all objects from the list.
for (ICursor c = list.Cursor(); c.Ok; c.Next()) {
  object obj = c.Current;
}

// Forward iterate over all nodes of the node set from the graph.
for (INodeCursor nc = graph.GetNodeCursor(); nc.Ok; nc.Next()) {
  Node n = nc.Node;
}

// Forward iterate over all edges from the edge set of the graph.
for (IEdgeCursor ec = graph.GetEdgeCursor(); ec.Ok; ec.Next()) {
  Edge e = ec.Edge;
}

Example 12.5. Backward iteration

// Get a cursor to iterate over the list.
ICursor c = list.Cursor();

// Move the cursor to the last position of the sequence.
// Backward iterate over all objects.
for (c.ToLast(); c.Ok; c.Prev()) {
  object obj = c.Current;
}

Iteration and Element Removal

Interface ICursor does not provide a method to remove an element from an underlying container. Instead, any elements must be removed from the container itself.

Example 12.6. Removing elements from an underlying container

// 'graph' is of type yWorks.yFiles.Algorithms.Graph.

// Remove unwanted nodes from the node set of the graph.
for (INodeCursor nc = graph.GetNodeCursor(); nc.Ok; nc.Next()) {
  if (IsUnwanted(nc.Node)) {
    // The graph is asked to remove the specified node.
    graph.RemoveNode(nc.Node);
  }
}

However, when an element that is pointed to by a cursor gets removed from the underlying container, the cursor does not change state, i.e., it still points to the same, now removed, element. Moving the cursor thereafter either forward or backward has the same effect as if the element was in the container, i.e., the cursor subsequently points to the next, respectively previous element from the foregoing sequence.

Once the cursor has been moved away from the removed element's position, though, there is no way to return to that position again. In conclusion, a simple call sequence as in Example 12.7, “Call sequence to move a cursor” does not necessarily return the cursor to the originating position in the sequence.

Example 12.7. Call sequence to move a cursor

// 'graph' is of type yWorks.yFiles.Algorithms.Graph.

// Get a cursor to iterate over all nodes of the node set from the graph.
INodeCursor nc = graph.GetNodeCursor();

// The cursor points to the first position of the sequence, i.e., to the first 
// node of the node set.
Node firstN = nc.Node;
nc.Next();                           // Cursor now points to the second node.
Node nextN = nc.Node;
// The simple call sequence: move forward and immediately backward.
nc.Next();                           // Cursor now points to the third node.
nc.Prev();                           // Cursor now points to the second node.
// Test for consistency.
if (nextN != nc.Node) {
  throw new System.ApplicationException("Inconsistent cursor.");
}

// Move the cursor back to the first node.
nc.ToFirst();

firstN = nc.Node;
nc.Next();                           // Cursor now points to the second node.
nextN = nc.Node;
// Now, remove the second node from the node set.
graph.RemoveNode(nc.Node);
// The simple call sequence: move forward and immediately backward.
nc.Next();                           // Cursor now points to the third node.
nc.Prev();                           // Cursor now points to the first node.
// Test for consistency.
if (nextN != nc.Node) {
  throw new System.ApplicationException("Inconsistent cursor.");
// This time an exception will be thrown that the cursor has become 
// inconsistent. 
}

Alternative Iteration Techniques

Additionally to iteration using cursors, there is another possibility to specifically iterate over the edges adjacent to a node. Example 12.8, “Non-cursor iteration over a node's edges” demonstrates this technique.

Example 12.8. Non-cursor iteration over a node's edges

// 'node' is of type yWorks.yFiles.Algorithms.Node.

// Retrieve the initial incoming edge from the node.
Edge e = node.FirstInEdge;
while (e != null) {
  // Successively retrieve the next incoming edge.
  e = e.NextInEdge;
}

// Retrieve the initial outgoing edge from the node.
e = node.FirstOutEdge;
while (e != null) {
  // Successively retrieve the next outgoing edge.
  e = e.NextOutEdge;
}