Where to Find Up-to-date yFiles Information

This page is from the outdated yFiles for Java 2.13 documentation. You can find the most up-to-date documentation for all yFiles products on the yFiles documentation overview page.

Please see the following links for more information about the yFiles product family of diagramming programming libraries and corresponding yFiles products for modern web apps, for cross-platform Java(FX) applications, and for applications for the Microsoft .NET environment.

More about the yFiles product family Close X

Graph Structure Functionality

The yFiles graph structure implementation as depicted in Figure 4.1, “The basic graph structure” shows the main classes together with their associations and multiplicity. A graph can have an arbitrary number of nodes and edges, every node and edge, though, has exactly one graph. A node can have an arbitrary number of edges, but an edge always has two nodes (its source node and its target node).

Figure 4.1. The basic graph structure

The yFiles basic graph structure.

More About Class Graph

In addition to the functionality presented in the section called “Creating Graphs and Graph Elements”, which covers simple graph element creation, class Graph also provides methods that offer more control in specific situations.

For example, in addition to the most frequently used simple edge creation, it is also possible to specify the exact place where a newly created edge should be inserted into both the sequence of outgoing edges at its source node and the sequence of incoming edges at its target node. Furthermore, an already existing edge can be completely remodeled, i.e., both its end nodes as well as the insertion places at either end node can be changed. Reversing an edge, as another variant of changing an edge, is also supported.

Example 4.1, “Exact edge insertion” shows the respective line of code for specifying the exact place of edge insertion.

Example 4.1. Exact edge insertion

// 'graph' is of type y.base.Graph. 
// 'source' and 'target' are of type y.base.Node. 

// Create a new edge at the second position of all incoming edges at the target 
// node. 
graph.createEdge(source, source.firstOutEdge(), 
                 target, target.firstInEdge(), graph.AFTER, graph.AFTER);

Figure 4.2, “Exact edge insertion” shows the resulting graph. The newly created edge is inserted after the first incoming edge at the target node. Observe how the sequence of incoming edges (indicated by the numbers near the arrowheads) changes with the insertion.

Figure 4.2. Exact edge insertion

Three incoming edges at target node.
Fourth incoming edge inserted at place two.
(a) Graph before... (b) ... and after edge insertion.

The analogous operation to graph element creation is their removal. Class Graph offers methods to remove all graph elements at once, or to remove only a single node or a single edge. When removing either an instance of type Node or an instance of type Edge their reference to the containing graph is cleared.

// 'graph' is of type y.base.Graph. 

// Remove single graph elements from the graph. 
// Note that all edges adjacent to the given node are removed prior to the node 
// itself. 
graph.removeNode(graph.firstNode());
graph.removeEdge(graph.lastEdge());

// Remove all graph elements from the graph at once. 
graph.clear();

Note

When removing a single node from a graph, all its connecting edges are also removed. Technically, they are even removed before the node is, since an edge requires both its source node and target node to reside in the same graph.

An alternative to removing graph elements is to hide them. Hiding is a technique to only temporarily remove graph elements, i.e., taking them away from the graph structure to perform some task on the remaining graph, and thereafter putting them back into again, unhiding them. Note that both removing and hiding a graph element only takes them away from the graph, the objects themselves still exist.

// 'graph' is of type y.base.Graph. 

// Hide single graph elements from the graph. 
// Note that all edges adjacent to 'exampleNode' are hidden prior to the node 
// itself. 
Node exampleNode = graph.firstNode();
graph.hide(exampleNode);

// Unhide single graph elements from the graph. 
// Note that *only* 'exampleNode' is unhidden, while its formerly adjacent edges 
// are not. 
graph.unhide(exampleNode);

In order to be able to unhide previously hidden graph elements, it is necessary to retain them, for example, in a data structure. Hiding of graph elements can also be performed utilizing specialized classes which automatically keep track of all hidden elements. These classes are described in the section called “Advanced Topics”.

Important

Hiding a graph element only differs from removing a graph element in that there is no graph event fired that signals the structural change. (Please see the section called “Events and Listeners” for a description of graph events.) As a consequence, any listeners to such graph events won't be notified when either node or edge is hidden, which can easily result in data structures getting out of sync with the actual structure of the graph.

The most notable listener that strongly depends on proper notification of any structural changes to a graph is class HierarchyManager. It is responsible for managing essential aspects of grouped graphs, and uses graph events to synchronize its own data structures. (Please see Chapter 7, Graph Hierarchies for the description of grouped graphs.)

Therefore, when working with graph hierarchies graph elements must not be hidden, but instead should be removed.

In addition to the number of nodes and the number of edges an instance of type Graph also knows whether a single graph element belongs to it or if there is an edge connecting two nodes. The latter feature is especially useful if there is no reference of an edge at hand, for instance.

If a graph element has been hidden it does not belong to any graph until it is unhidden again.

// 'graph' is of type y.base.Graph

// Get the number of nodes in the graph. 
// (Both methods are equivalent.) 
int nodeCount = graph.nodeCount();
int N = graph.N();

// Get the number of edges in the graph. 
// (Both methods are equivalent.) 
int edgeCount = graph.edgeCount();
int E = graph.E();

// Check if the graph is empty. 
boolean isEmpty = graph.isEmpty();

// Check if the first node belongs to the graph. 
boolean containsNode = graph.contains(graph.firstNode());

// Check if there is an edge between first and last node of the graph. 
boolean containsEdge = graph.containsEdge(graph.firstNode(), graph.lastNode());

Class Graph offers access to its graph elements in several different ways. It returns either cursors to iterate over the respective sets of graph elements, or creates arrays containing references to all nodes or all edges from the graph. As an additional convenience, there are methods to get the first, respectively last element of any of the two sets. While the returned cursors by definition have read-only behavior on the underlying container, the returned arrays actually are copies of the respective sets of elements from the graph at a certain point in time. In effect, this means that, for example, a returned node array can be modified in any way, i.e., nodes might be removed from the array or the sequence of nodes might be changed, without affecting the node set from the graph.

To change the sequence of any of the graph element sets class Graph has methods to move an element to the first or last position, respectively.

// 'graph' is of type y.base.Graph. 

// Get the first and last node of the node set from the graph. 
Node firstNode = graph.getFirstNode();
Node lastNode = graph.getLastNode();

// Exchange first and last node of the node set. 
graph.moveToLast(firstNode);
graph.moveToFirst(lastNode);

// Get the first and last edge of the edge set from the graph. 
Edge firstEdge = graph.getFirstEdge();
Edge lastEdge = graph.getLastEdge();

// Exchange first and last edge of the edge set. 
graph.moveToLast(firstEdge);
graph.moveToFirst(lastEdge);

// Get a cursor of all nodes from the graph. 
NodeCursor nc = graph.nodes();

// Get a cursor of all edges from the graph. 
EdgeCursor ec = graph.edges();

// Get an array of all nodes from the graph. 
Node nodes[] = graph.getNodeArray();

// Get an array of all edges from the graph. 
Edge edges[] = graph.getEdgeArray();

Graph Elements

Every instance of a graph element, i.e., every instance of type Node and type Edge holds a reference to the instance of type Graph it belongs to. This reference is cleared when the graph element has been removed or hidden from its graph.

// 'node' is of type y.base.Node.

// Get the graph the node belongs to.
Graph graph = node.getGraph();

Furthermore, every graph element knows the position it has in the respective set of elements within its graph. More precisely, when an instance of type Graph is asked for an array of its nodes, for example, this array reflects the node iteration sequence exactly, i.e., a node at position 6 in the array will return the value 6 when asked for its position.

Important

The position of a removed graph element is undefined. The same holds for a hidden graph element.

// 'graph' is of type y.base.Graph. 
// 'node' is of type y.base.Node. 

// Get the node's position. 
int index = node.index();

// Get an array of all nodes from the graph. 
Node nodes[] = graph.getNodeArray();
// Check the positions of all nodes. 
for (int i = 0; i < graph.N(); i++)
{
  if (nodes[i].index() != i)
    throw new RuntimeException("Mismatch at position " + i + ".");
  else
    System.out.println("All is well at position " + i + ".");
}

Class Node

Class Node is responsible for everything that is related to its connecting edges. More specifically, an instance of type Node knows the overall number of connecting edges (the so-called degree of the node) as well as the division into the number of incoming edges (in-degree) and the number outgoing edges (out-degree).

// 'node' is of type y.base.Node.

// Get the number of edges at a node. 
int degree = node.degree();

// Get the number of incoming edges at a node. 
int inDegree = node.inDegree();

// Get the number of outgoing edges at a node. 
int outDegree = node.outDegree();

A node also provides methods to easily test whether there is a connecting edge to another node.

// 'graph' is of type y.base.Graph. 

Node firstNode = graph.firstNode();
Node lastNode = graph.lastNode();

// Check whether there is an edge between first and last node of the graph. 
// First check if there is a connecting edge outgoing to 'lastNode'. 
Edge e = firstNode.getEdgeTo(lastNode);
// If not, then check if there is a connecting edge incoming from 'lastNode'. 
if (e == null)
  e = firstNode.getEdgeFrom(lastNode);

Furthermore, it can give cursors to iterate over all connecting edges, iterate over all incoming edges, and iterate over all outgoing edges. When iterating over all edges incoming edges appear first, outgoing edges last.

// 'node' is of type y.base.Node. 

// Get a cursor to iterate over all edges at a node. 
EdgeCursor edges = node.edges();

// Get a cursor to iterate over all incoming edges at a node. 
EdgeCursor inEdges = node.inEdges();

// Get a cursor to iterate over all outgoing edges at a node. 
EdgeCursor outEdges = node.outEdges();

Important

Since a self-loop is both an incoming and an outgoing edge at its node, it appears twice when iterating over all edges of the node. Also, a self-loop counts twice when asking for the degree of a node.

As a convenience, class Node furthermore provides cursors to iterate over all its neighbors, to iterate over all its predecessors, or to iterate over all its successors. Predecessors and successors mean the nodes at the opposite of an incoming edge, and an outgoing edge, respectively.

// 'node' is of type y.base.Node.

// Get a cursor to iterate over all neighbor nodes. 
NodeCursor neighbors = node.neighbors();

// Get a cursor to iterate over the source nodes of all incoming edges. 
// These nodes are called predecessors.
NodeCursor predecessors = node.predecessors();

// Get a cursor to iterate over the target nodes of all outgoing edges. 
// These nodes are called successors.
NodeCursor successors = node.successors();

Class Edge

The most important information an instance of type Edge provides is its source node and its target node. Closely related is the method to get the opposite when holding one of the edge's end nodes.

// 'edge' is of type y.base.Edge. 

// Get the two end nodes of an edge. 
Node source = edge.source();
Node target = edge.target();

// Getting the opposite when holding one of either source or target node. 
Node opposite = edge.opposite(source);

Note

Although a hidden edge holds no reference to a graph, it can still provide access to its source and target node, independent of their status.

As a convenience, class Edge additionally offers information whether it is a self-loop.

// 'edge' is of type y.base.Edge. 

// Ask the edge whether it is a self-loop. 
boolean isSelfLoop = edge.isSelfLoop();

Note

Ignoring the directedness of the yFiles graph structure implementation and instead interpreting it undirected would be done using method neighbors of class Node, or a combination of methods edges of class Node and opposite of class Edge.

Complexity

Table 4.1, “Time complexities in package y.base” shows time complexities for several of the most frequent tasks when working with the yFiles graph structure implementation.

Creation of graph elements takes constant time as does the removal of an edge. Removing a node, though, implies the removal of all its connecting edges first, so this task takes linear time, i.e., time proportional to the number of connecting edges. Also, all iteration is done in linear time. Checking whether a given node or edge belong to a graph is done in constant time, while testing if there is an edge connecting two nodes takes linear time.

Table 4.1. Time complexities in package y.base

Task Involved Class(es) Complexity
Creating a node Graph O(1)
Creating an edge Graph O(1)
Removing/hiding a node Graph O(Node.degree())
Removing/hiding an edge Graph O(1)
Clearing the graph Graph O(Graph.N() + Graph.E())
Iterating over the node set Graph, NodeCursor O(Graph.N())
Iterating over the edge set Graph, EdgeCursor O(Graph.E())
Iterating over all edges connecting to a node Node, EdgeCursor O(Node.degree())
Iterating over all incoming edges Node, EdgeCursor O(Node.inDegree())
Iterating over all outgoing edges Node, EdgeCursor O(Node.outDegree())
Checking if a node belongs to a graph Graph O(1)
Checking if an edge belongs to a graph Graph O(1)
Checking if there is an edge connecting two nodes Graph O(min(source.outDegree(), target.inDegree()))

Advanced Topics

Hiding Graph Elements

Support for hiding graph elements is further completed by classes GraphHider and GraphPartitionManager from package y.util. In addition to the methods from class Graph, GraphHider offers various methods to hide arbitrary collections of graph elements or even all elements from the graph at once. Moreover, it automatically keeps track of all elements hidden as a result of such method calls.

To unhide graph elements, class GraphHider provides a small set of methods that collectively employ all-at-once behavior when unhiding, i.e., all elements previously hidden are unhidden at once.

Class GraphPartitionManager extends the idea from class GraphHider in a way so that it is possible to mark parts of the graph as so-called "partitions" and hide/unhide these. Example 4.18, “Making a graph connected” is a rather extensive code snippet where an instance of GraphPartitionManager is used to hide/unhide connected components.

Important

When working with graph hierarchies graph elements must not be hidden, but instead should be removed.

Copying a Graph

Class GraphCopier provides convenient support for copying an object of type y.base.Graph. It enables the complete range from copying the entire Graph to copying only sets of graph elements. Optionally, any data associated to either nodes or edges by means of data accessors, i.e., data providers or node maps and edge maps, respectively, can also be copied.

GraphCopier relies on implementations of static inner interface GraphCopier.CopyFactory to delegate the actual work of copying nodes and edges from one graph to another to. A simple default implementation for a copy factory that is capable of handling a Graph object is provided by class GraphCopyFactory. Initially, i.e., when no other copy factory has been set, class Graph returns a similar implementation when its getGraphCopyFactory method is invoked.

Example 4.2. Creating a GraphCopier

// 'graph' is of type y.base.Graph.

// Create a new GraphCopier that uses the copy factory which is registered with 
// the graph.
GraphCopier gc = new GraphCopier(graph.getGraphCopyFactory());

The following methods from class GraphCopier can be used to copy either an entire Graph object or the subgraph that is induced by a given set of nodes from a given source graph:

By default, copying a Graph object or a set of nodes does not include any data associated with either the nodes or edges. The following methods can be used to control whether the contents of data accessors, i.e., both data providers and node maps and edge maps, respectively, that are registered with the source graph are copied over to the target graph, too.

void setDataProviderContentCopying(boolean dataProviderContentCopying)
Description Controls whether to copy data provider contents.
void setNodeMapCopying(boolean nodeMapCopying)
void setEdgeMapCopying(boolean edgeMapCopying)
Description Determines whether to copy the contents of node maps and edge maps registered with the source graph.

Data accessors are presented in the section called “Binding Data to Graph Elements”.