The layout algorithms that come with the yFiles library support a number of sophisticated and powerful features for layout generation, including:
Furthermore, many of the yFiles layout algorithms provide support for advanced inremental layout and the closely related concept of "layout from sketch." An introduction to both these concepts is presented in the section called “Incremental Layout”.
The term "hierarchically organized graph" denotes a graph structure where, conceptually, nodes can be declared "children" of a common other node, their "parent." This can be exercised recursively, i.e., parents can be declared children of other parents, resulting in a hierarchy of nodes of possibly arbitrary depth.
The visual presentation of such a hierarchy is normally done by placing the children near each other and have their parent enclosing them. The parent is called a "group node," and the children are its content, they are "grouped nodes." Declaring some nodes to be children of another node is called "grouping."
Especially in the context of the yFiles Viewer distribution there is another term used for hierarchically organized graphs, they are also called "graph hierarchies." See the description of graph hierarchies.
Layout support for hierarchically organized graphs primarily means proper handling of grouped nodes and their enclosing group node. There are three different group node policies available:
Table 5.2, “Layout support for hierarchically organized graphs” lists the layout algorithms that provide support for group nodes and their content.
Table 5.2. Layout support for hierarchically organized graphs
| Layout Style | Classname | Note |
|---|---|---|
| Hierarchical | HierarchicGroupLayouter |
Support for group node policies is partly handled by prepended layout stages. See the description of hierarchical group layout for more information. |
| Organic | OrganicLayouter |
Organic layout provides direct support for all three group node policies. Class SmartOrganicLayouter uses the services of organic layout. See the descriptions of organic layout and smart organic layout for more information. |
| Orthogonal | OrthogonalGroupLayouter |
Support for group node policies is partly handled by prepended layout stages. See the description of orthogonal group layout for more information. |
Table 5.3, “Support for hierarchically organized graphs in incremental layout algorithms” lists the incremental layout algorithms that provide support for group nodes and their content. Note however that group node policies as described above are not directly supported by these algorithms.
Table 5.3. Support for hierarchically organized graphs in incremental layout algorithms
| Layout Style | Classname | Note |
|---|---|---|
| Hierarchical (incremental) | IncrementalHierarchicLayouter |
IncrementalHierarchicLayouter provides direct support for hierarchically organized graphs for both incremental as well as non-incremental layout mode. See the description of IncrementalHierarchicLayouter's advanced features for more information. |
Table 5.4, “Routing support for hierarchically organized graphs” lists the routing algorithms that provide support for hierarchically organized graphs.
Table 5.4. Routing support for hierarchically organized graphs
| Routing Style | Classname | Note |
|---|---|---|
| Orthogonal | OrthogonalEdgeRouter |
Support for routing with group nodes is partly handled by prepended layout stages. See the description of the orthogonal edge router for more information. |
In the presence of package
y.view
(i.e., with the yFiles Viewer distribution), the setup for layout calculation
of a Graph2D
that has associated a
HierarchyManager
object is a
matter of using the convenience methods of class
GroupLayoutConfigurator
as
shown in Example 5.6, “Layout preparation with classes from package y.view”.
Example 5.6. Layout preparation with classes from package y.view
// 'graph' is of type y.view.Graph2D. GroupLayoutConfigurator glc = new GroupLayoutConfigurator(graph); // Prepare all relevant information for a layout algorithm. glc.prepareAll(); // Invoke buffered layout. invokeBufferedLayout(graph, new HierarchicGroupLayouter(), false); // Restore all group node specific sizes and insets after a layout algorithm // has been run. glc.restoreAll();
Example 5.7, “Layout preparation without classes from package y.view” demonstrates how to set up a
hierarchically organized graph without the convenience functionality from
package y.view.
Basically, the data providers that hold the necessary information about the
graph's hierarchical composition have to be filled manually, and be registered
with the graph using the data provider look-up keys defined in interface
GroupingKeys
.
Example 5.7. Layout preparation without classes from package y.view
// 'graph' is of type y.layout.LayoutGraph. // Create the node maps that are to be used as data providers later on. NodeMap groupKey = graph.createNodeMap(); NodeMap nodeID = graph.createNodeMap(); NodeMap parentNodeID = graph.createNodeMap(); // Register the node maps as data providers with the graph. // Use the "well-known" look-up keys defined in interface GroupingKeys. graph.addDataProvider(Grouping.GROUP_DPKEY, groupKey); graph.addDataProvider(Grouping.NODE_ID_DPKEY, nodeID); graph.addDataProvider(Grouping.PARENT_NODE_ID_DPKEY, parentNodeID); // Now, set up the hierarchical organization of the graph, i.e., define some // of the nodes to be group nodes and others to be their content. setupHierarchicalOrganization(graph, groupKey, nodeID, parentNodeID); // Invoke buffered layout. invokeBufferedLayout(graph, new HierarchicGroupLayouter(), false);
The information for the node IDs and the parent node IDs is of symbolic nature that is used in the process of layout calculation to identify the proper parent for a given child, but also to find all children that belong to the same parent. Hence, it is important for the symbolic IDs to properly match between these two data providers, so that the graph's hierarchical composition is correctly expressed.
Example 5.8, “Encoding a graph's hierarchical composition in data providers” demonstrates possible content for the data providers. Here, the nodes themselves are used to denote symbolic IDs for both "ordinary" nodes and group nodes. Carefully observe the usage of the indirection scheme in this example for setting up the parent-child relation.
Example 5.8. Encoding a graph's hierarchical composition in data providers
// Now, set up the hierarchical organization of the graph, i.e., define some // of the nodes to be group nodes and others to be their content. for (int i = 0; i < 10; i++) { // Nodes 1, 5, and 9 are defined to be group nodes. if (i % 4 == 1) { groupKey.setBool(n[i], true); // Set a symbolic ID for the group node that is used for look-up purposes. nodeID.set(n[i], n[i]); continue; } // Set a symbolic ID for the node that is used for look-up purposes. nodeID.set(n[i], n[i]); // Node 2 is defined child of node 1; // node 6 is defined child of node 5. if (i % 4 == 2) { // Establish the relation to the parent. parentNodeID.set(n[i], nodeID.get(n[i - 1])); continue; } // Node 3 is defined child of node 1; // node 7 is defined child of node 5. if (i % 4 == 3) { // Establish the relation to the parent. parentNodeID.set(n[i], nodeID.get(n[i - 2])); } // Nodes 0, 4, and 8 remain "ordinary" nodes... }
A group node's size is determined by the bounding box that encloses its
children and additional insets that are added to each of the box's side.
To specify insets individually, a data provider can be used to hold an Insets
object for each group node.
This data provider is then registered with the graph using the look-up key
GROUP_NODE_INSETS_DPKEY
defined in interface GroupingKeys.
Example 5.9, “Defining a group node's insets” shows how to add individual Insets objects for group nodes to a node map, and how the node map is registered as a data provider with the graph.
Example 5.9. Defining a group node's insets
// 'graph' is of type y.layout.LayoutGraph. // Create the node map that is to be used as a data provider later on. NodeMap groupNodeInsets = graph.createNodeMap(); // Predefine some Insets objects. Insets in[] = new Insets[3]; in[0] = new Insets(10, 20, 30, 40); in[1] = new Insets(20, 20, 20, 20); in[2] = new Insets(40, 30, 20, 10); NodeList gnl = getListOfAllGroupNodes(graph); for (NodeCursor nc = gnl.nodes(); nc.ok(); nc.next()) { Node n = nc.node(); groupNodeInsets.set(n, in[getGroupType(n)]); } // Register the node map as a data provider with the graph. // Use the "well-known" look-up keys defined in interface GroupingKeys. graph.addDataProvider(GroupingKeys.GROUP_NODE_INSETS_DPKEY, groupNodeInsets); // Invoke buffered layout. invokeBufferedLayout(graph, new HierarchicGroupLayouter(), false);
Furthermore, a group node's size can be controlled by registering a custom
GroupBoundsCalculator
implementation replacing the default
InsetsGroupBoundsCalculator
instance.
To this end, the method from API Excerpt 5.5, “Method to set another GroupBoundsCalculator implementation” is available
with classes HierarchicGroupLayouter and OrganicLayouter.
API Excerpt 5.5. Method to set another GroupBoundsCalculator implementation
// Getter and setter for custom GroupBoundsCalculator implementation. GroupBoundsCalculator getGroupBoundsCalculator() void setGroupBoundsCalculator(GroupBoundsCalculator groupBoundsCalculator)
Class FixedGroupLayoutStage
adds support for the fixed group node policy to both hierarchical layout and
orthogonal layout.
Example 5.10, “Setup for fixed group node content” shows how the fixed group node policy is
realized as a layout stage that is prepended to the actual layout algorithm's
invocation.
Example 5.10. Setup for fixed group node content
void invokeBufferedLayout(LayoutGraph graph, Layouter layouter, boolean orthogonal) { // Create a specialized layout stage that fixes the contents of the group // nodes. FixedGroupLayoutStage fixedGroupLayoutStage = new FixedGroupLayoutStage(); if (orthogonal) fixedGroupLayoutStage.setInterEdgeRoutingStyle( FixedGroupLayoutStage.ROUTING_STYLE_ORTHOGONAL); // Prepend the stage to the given layout algorithm. layouter.prependStage(fixedGroupLayoutStage); // Invoke buffered layout for the given layout algorithm. new BufferedLayouter(layouter).doLayout(graph); // Remove the prepended layout stage. layouter.removeStage(fixedGroupLayoutStage); }
The tutorial demo application GroupingLayoutWithoutAView.java gives a detailed demonstration on how to set up a hierarchically organized graph without the functionality present in package y.view. Also, the modules that are used to set up the layout algorithms in an application context give deep insights in an algorithm's configuration:
The two ends of an edge path are also called source port and target port, respectively. A port constraint serves to pinpoint an edge's end at its source node or target node. There are two kinds of port constraints available:
Both kinds of port constraints can easily be combined to express, for example, that an edge's source port should connect to the middle of the source node's upper border.
Table 5.5, “Layout support for port constraints” lists the layout algorithms that provide support for port constraints.
Table 5.5. Layout support for port constraints
| Layout Style | Classname | Note |
|---|---|---|
| Hierarchical | HierarchicLayouter |
The algorithms of the family of hierarchical layout algorithms by default obey weak and strong port constraints as soon as they are set. See the descriptions of classic hierarchical layout and incremental hierarchical layout for more information. |
| Tree | GenericTreeLayouter |
The predefined node placer implementations that can be used with the generic tree layout algorithm by default obey strong and weak port constraints as soon as they are set. See the description of generic tree layout for more information. |
Table 5.6, “Routing support for port constraints” lists the routing algorithms that provide support for port constraints.
Table 5.6. Routing support for port constraints
| Routing Style | Classname | Note |
|---|---|---|
| Orthogonal | OrthogonalEdgeRouter |
Both OrthogonalEdgeRouter and ChannelEdgeRouter by default obey weak and strong port constraints as soon as they are set. See the descriptions of OrthogonalEdgeRouter and ChannelEdgeRouter for more information. |
Example 5.11, “Adding source port constraints to some edges” demonstrates the creation of
PortConstraint
objects, and how they
are stored in an edge map.
The edge map is then registered as a data provider with the graph using the
look-up key
SOURCE_PORT_CONSTRAINT_KEY
.
During layout calculation, an algorithm first retrieves the data provider using
the look-up key, and afterwards retrieves the contained information.
The actual coordinates of an edge end point that has a strong port constraint associated are determined at the time a layout algorithm (or a routing algorithm) processes the edge. In other words, the strong characteristic of a strong port constraint is determined by its "normal" coordinates at the time of processing. Note that the specified coordinates of such edge end points are always interpreted relative to the respective node's center.
Example 5.11. Adding source port constraints to some edges
// 'graph' is of type y.layout.LayoutGraph. // Create edge map that is used as a data provider later on. EdgeMap pcMap = graph.createEdgeMap(); // Set the coordinates for the edge's source port. (Actually, this could also // be done anywhere prior to invoking the layout algorithm.) graph.setSourcePointRel(edge3, new YPoint(-10, 20)); // Create PortConstraint objects: // 1) Port constraint that allows an edge end to connect to any side of its // respective node. PortConstraint pc1 = PortConstraint.create(PortConstraint.ANY_SIDE); // 2) Port constraint that determines an edge end to connect *anywhere* to the // upper (NORTH) side of its respective node. PortConstraint pc2 = PortConstraint.create(PortConstraint.NORTH); // 3) Strong port constraint that determines an edge end to connect to the // lower (SOUTH) side of its respective node. The actual end point is at a // fixed coordinate. PortConstraint pc3 = PortConstraint.create(PortConstraint.SOUTH, true); // Establish a mapping from edges to port constraints. pcMap.set(edge1, pc1); pcMap.set(edge2, pc2); pcMap.set(edge3, pc3); // Register the edge map as a data provider with the graph. // Use the "well-known" look-up key defined in interface PortConstraintKeys. // Note that the above defined port constraints are set so that they apply to // the source ends of their respective edges only. graph.addDataProvider(PortConstraint.SOURCE_PORT_CONSTRAINT_KEY, pcMap); // Invoke buffered layout. invokeBufferedLayout(graph, new HierarchicLayouter());
The tutorial demo application LayoutWithoutAView.java demonstrates how to set up port constraints (both weak and strong) for edge end points without the functionality present in package y.view.
The concept of port candidates is an extension to that of classic port constraints as described above. Port candidates can be used in conjunction with both nodes and edges, however, unlike port constraints, they present a node-centric approach. Primarily, port candidates provide a means to:
Class PortCandidate
enables definition
of port candidates that conceptually correspond to either weak port
constraints, i.e., effectively describe side constraints, or strong port
constraints, which encode specific anchor locations at a node.
Note that port candidates that correspond to strong port constraints directly
include the coordinates for the actual anchor locations.
PortCandidate additionally allows to associate costs with a given port candidate, which can be used to establish an order of precedence among multiple port candidates. When a given edge port is being assigned to any of the available port candidates at a node, those with low costs are favored compared to other port candidates with higher costs associated.
To define the set of side constraints and anchor locations at a node, multiple
port candidates can easily be combined using the services of class
PortCandidateSet
.
When a PortCandidate object is added to an instance of PortCandidateSet, the
capacity of the port candidate can optionally be configured.
The capacity of a given port candidate (sometimes also referred to as
"cardinality") specifies the allowed number of connecting edges at that side or
anchor location.
The PortCandidateSet objects for the node set of a graph can be registered by
means of a data provider using the look-up key
NODE_DP_KEY
.
Matching port candidates means the process of distributing a node's edges to the available port candidates. All edges connecting to a node that has a set of port candidates associated with it via a PortCandidateSet object are distributed among the available port candidates with respect to:
For example, when the limit of allowed edges for a given port candidate with costs k is reached, i.e., the given port candidate is said to be "saturated," then the next least expensive port candidate among the remaining ones is chosen to connect edges to.
To influence the matching process, a subset of the PortCandidate objects used
for a node can additionally be associated with the respective ports of its
connecting edges.
The subset then defines a restricted set of desired port candidates an edge
prefers to connect to.
The PortCandidate objects can be combined using
java.util.Collection objects which are stored by means
of data providers.
The data providers are registered with the graph using the look-up keys
SOURCE_PCLIST_DPKEY
and
TARGET_PCLIST_DPKEY
.
Table 5.7, “Layout support for port candidates” lists the layout algorithms that provide support for port candidates.
Table 5.7. Layout support for port candidates
| Layout Style | Classname | Note |
|---|---|---|
| Hierarchical | IncrementalHierarchicLayouter |
Incremental hierarchical layout supports port candidates as soon as they are set. See the description of incremental hierarchical layout for more information. |
Table 5.8, “Routing support for port candidates” lists the routing algorithms that provide support for port candidates.
Table 5.8. Routing support for port candidates
| Routing Style | Classname | Note |
|---|---|---|
| Orthogonal | OrthogonalEdgeRouter |
Both OrthogonalEdgeRouter and ChannelEdgeRouter by default support port candidates as soon as they are set. See the descriptions of OrthogonalEdgeRouter and ChannelEdgeRouter for more information. |
Example 5.12, “Creating port candidate sets for nodes” demonstrates how a port candidate
set is declared, and how a data provider (adapter) is registered with the graph
using the look-up key
NODE_DP_KEY
.
During layout calculation, an algorithm first retrieves the data provider using
the look-up key, and afterwards retrieves the contained information.
Example 5.12. Creating port candidate sets for nodes
// 'graph' is of type y.layout.LayoutGraph. // Define a port candidate set that is used for all nodes. final PortCandidateSet pcs = new PortCandidateSet(); // Create port candidates that conceptually correspond to classic side // constraints (weak constraints) and add them to the set. // North side: no costs associated; allows two edges to connect. pcs.add(PortCandidate.createCandidate(PortConstraint.NORTH), 2); // East side: costs of 1.0; allows four edges to connect. pcs.add(PortCandidate.createCandidate(PortConstraint.EAST, 1.0), 4); // West side: costs of 2.0, but allows an unlimited number of edges to connect. pcs.add(PortCandidate.createCandidate(PortConstraint.WEST, 2.0), Integer.MAX_VALUE); // Create a data provider (adapter) that returns the port candidate set for // each node. DataProvider dp = new DataProviderAdapter() { public Object get(Object dataHolder) { return ((dataHolder instanceof Node) ? pcs : null); } }; // Register the data provider (adapter) with the graph. // Use the "well-known" look-up key defined in class PortCandidateSet. graph.addDataProvider(PortCandidateSet.NODE_DP_KEY, dp); // Invoke buffered layout. invokeBufferedLayout(graph, new IncrementalHierarchicLayouter());
Edge grouping means bundling of a set of edges to be treated in a common manner regarding some aspects of edge path generation. Specifically, if edges at a common source node, for example, are declared an edge group at their source ends, then their source ports will be anchored at the same location.
Additionally, all grouped edges will also be routed in bus-style, i.e., their paths will share a common edge segment. If edges at different source (target) nodes are declared an edge group at their source (target) ends, then they will be routed in bus-style only.
Edge grouping is also referred to as port grouping sometimes. If edges from an edge group have associated inconsistent, or even contradicting port constraints, then the location of the common port is not guaranteed to obey any of them.
Table 5.9, “Layout support for edge groups” lists the layout algorithms that provide support for edge groups.
Table 5.9. Layout support for edge groups
| Layout Style | Classname | Note |
|---|---|---|
| Hierarchical | HierarchicLayouter |
The hierarchical layout algorithm by default generates edge/port groups as soon as they are declared. See the descriptions of classic hierarchical layout and incremental hierarchical layout for more information. |
| Orthogonal | DirectedOrthogonalLayouter |
The directed orthogonal layout algorithm by default generates edge/port groups as soon as they are declared. See the description of directed orthogonal layout for more information. |
Table 5.10, “Routing support for edge groups” lists the routing algorithms that provide support for edge groups.
Table 5.10. Routing support for edge groups
| Routing Style | Classname | Note |
|---|---|---|
| Orthogonal | OrthogonalEdgeRouter |
Support for edge/port groups is partly handled by prepended layout stages. See the description of the orthogonal edge router for more information. |
Example 5.13, “Creating an edge group at a common target node” demonstrates how edge groups are
declared, and how an edge map is registered as a data provider with the graph
using the look-up key
TARGET_GROUPID_KEY
.
During layout calculation, an algorithm first retrieves the data provider using
the look-up key, and afterwards retrieves the contained information.
Example 5.13. Creating an edge group at a common target node
// 'graph' is of type y.layout.LayoutGraph. // Create an edge map that is used as a data provider later on. EdgeMap egMap = graph.createEdgeMap(); // Declare an edge group for the target end of all incoming edges at a specific // node. String targetEdgeGroupID = "All my grouped edges."; for (EdgeCursor ec = specificNode.inEdges(); ec.ok(); ec.next()) { egMap.set(ec.edge(), targetEdgeGroupID); } // Register the edge map as a data provider with the graph. // Use the "well-known" look-up key defined in interface PortConstraintKeys. graph.addDataProvider(PortConstraintKeys.TARGET_GROUPID_KEY, egMap); // Invoke buffered layout. invokeBufferedLayout(graph, new HierarchicLayouter());
|
Copyright ©2004-2008, yWorks GmbH. All rights reserved. |