Iteration Mechanisms

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

Figure 7.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 com.yworks.yfiles.base.Graph.

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

// Ask for the size of the underlying sequence of edges.
var size:int = 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 7.5, “Classes that provide cursors” shows the most prominent yFiles classes that provide cursors to iterate over object, node, or edge sequences, respectively. These classes are YList, NodeList, EdgeList, GraphInterface, Graph, and Node.

Figure 7.5. Classes that provide cursors

Classes that provide cursors.

Example 7.3, “Forward iteration with various cursor types” and Example 7.4, “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 7.3. Forward iteration with various cursor types

// 'graph' is of type com.yworks.yfiles.base.Graph.
// 'list' is of type com.yworks.yfiles.base.YList.

// Forward iterate over all objects from the list.
for (var c:YCursor = list.cursor(); c.ok(); c.next()) {
  var obj:Object = c.current();
}

// Forward iterate over all nodes of the node set from the graph.
for (var nc:NodeCursor = graph.nodes(); nc.ok(); nc.next()) {
  var n:Node = nc.node();
}

// Forward iterate over all edges from the edge set of the graph.
for (var ec:EdgeCursor = graph.edges(); ec.ok(); ec.next()) {
  var e:Edge = ec.edge();
}

Example 7.4. Backward iteration

// Get a cursor to iterate over the list.
var c:YCursor = list.cursor();

// Move the cursor to the last position of the sequence.
// Backward iterate over all objects.
for (c.toLast(); c.ok(); c.prev()) {
  var obj:Object = c.current();
}

Iteration and Element Removal

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

Example 7.5. Removing elements from an underlying container

// 'graph' is of type com.yworks.yfiles.base.Graph.

// Remove unwanted nodes from the node set of the graph.
for (var nc:NodeCursor = graph.nodes(); 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 7.6, “Call sequence to move a cursor” does not necessarily return the cursor to the originating position in the sequence.

Example 7.6. Call sequence to move a cursor

// 'graph' is of type com.yworks.yfiles.base.Graph.

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

// The cursor points to the first position of the sequence, i.e., to the first
// node of the node set.
var firstN:Node = nc.node();
nc.next();                           // Cursor now points to the second node.
var nextN:Node = 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 Error("Inconsistent cursor.");
}

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

var firstN:Node = nc.node();
nc.next();                           // Cursor now points to the second node.
var nextN:Node = 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 Error("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 7.7, “Non-cursor iteration over a node's edges” demonstrates this technique.

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

// 'node' is of type com.yworks.yfiles.base.Node.

// Retrieve the initial incoming edge from the node.
var e:Edge = 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();
}