The task of a layout algorithm is a major undertaking that involves arbitrarily complex logic. However, there can be identified a number of well-defined (sub-)tasks that even completely different algorithms do in a similar manner. Factoring out such tasks so that they can be reused in varying contexts, greatly reduces the complexity of any layout algorithm.
The yFiles library allows to formulate complex layout processes by plugging together so-called "layout stages" that, among other things, can be used to handle such well-defined (sub-)tasks.
A layout stage serves as a kind of container that encapsulates arbitrary layout functionality, and provides a general means to string together multiple stages into a compound layout process. Interface LayoutStage defines the basis for a layout stage. It is a specialization of interface Layouter, and thus can be used as a stand-alone layout provider as well as a part of a larger layout process.
The methods of interface LayoutStage are used to establish a relationship to another Layouter implementation, the so-called "core layouter." The core layouter's invocation is entirely bracketed by the layout stage's logic.
When used in the context of a larger layout process, the layout stage can easily be used to simplify the core layouter's task. It performs preprocessing steps on the input graph before the core layouter's invocation, and postprocessing steps thereafter.
Table 5.1, “Predefined layout stages” lists some of the predefined LayoutStage implementations, most of them being part of the default compound layout process as described below. Further layout stages are described in the section called “Layout Stages”.
Table 5.1. Predefined layout stages
Classname | Description |
---|---|
BufferedLayouter | Duplicates the graph to be processed, so that the layout cannot destroy the original data. See also the description of buffered layout calculation below. |
SelfLoopLayouter | Calculates orthogonal edge paths for a graph's self-loops (reflexive edges). |
ParallelEdgeLayouter | Calculates edge paths for all edges with identical source node and target node. (Self-loops are not processed.) |
OrientationLayouter | Changes the orientation of a computed layout. |
SubgraphLayouter | Reduces the original graph to the subgraph that is induced by selected nodes. |
Figure 5.7, “Layout stages complex” shows the class hierarchy for the layout stages.
Except for class BufferedLayouter, all layout stages from Table 5.1, “Predefined layout stages” are part of the compound layout process defined by CanonicMultiStageLayouter. The following methods of class CanonicMultiStageLayouter can be used for configuring the layout process:
void appendStage(LayoutStage stage) void prependStage(LayoutStage stage) void removeStage(LayoutStage stage) |
|
Description | Methods to add and remove individual layout stages. |
void enableOnlyCore() boolean isComponentLayouterEnabled() boolean isSelfLoopLayouterEnabled() void setComponentLayouterEnabled(boolean enabled) void setSelfLoopLayouterEnabled(boolean enabled) |
|
Description | Methods for enabling and disabling the predefined layout stages, and also for controlling their enabled state. (Excerpt.) |
LayoutStage getComponentLayouter() LayoutStage getSelfLoopLayouter() void setComponentLayouter(LayoutStage layouter) void setSelfLoopLayouter(LayoutStage layouter) |
|
Description | Getter and setter methods for predefined layout stages. (Excerpt.) |
With the yFiles layout algorithms it is possible to have a graph layout calculated using two different approaches, namely "unbuffered" layout or "buffered" layout. Unbuffered layout means to directly invoke a layout algorithm's doLayout method. Choosing this approach, the layout calculation is performed on the given graph, and is also immediately assigned. Buffered layout, in contrast, utilizes class BufferedLayouter, which creates a copy of the original graph that is then used for layout calculation.
Unbuffered layout has some severe drawbacks that should be observed:
With these drawbacks in mind, it is almost always a good idea to choose buffered layout instead. It facilitates many sophisticated features, like, e.g., layout morphing, and at the same time increases an application's robustness.
The main purpose of class BufferedLayouter is to create a copy of the input graph before calling its core layouter. The graph structure that is used for the copied graph is optimized for layout calculation.
The core layouter subsequently executes on the copy and calculates a new layout, which is then transferred to the original graph. There are several beneficial aspects of this functionality:
Wrapping a layout algorithm with a BufferedLayouter layout stage is as easy as shown in Example 5.3, “Using buffered layout”.
Example 5.3. Using buffered layout
// 'graph' is of type y.layout.LayoutGraph. // Run organic layout by implicitly wrapping its invocation using the services // of class BufferedLayouter. new BufferedLayouter(new SmartOrganicLayouter()).doLayout(graph);
Alternatively, class BufferedLayouter allows to get the calculated graph layout as a separate object. This is demonstrated in Example 5.4, “Buffered layout with deferred coordinate assignment”.
Example 5.4. Buffered layout with deferred coordinate assignment
// 'graph' is of type y.layout.LayoutGraph. // Run organic layout by implicitly wrapping its invocation using the services // of class BufferedLayouter. // The result of the layout is returned separately as a GraphLayout object, // i.e., the original graph's layout information is not changed. GraphLayout gl = new BufferedLayouter(new SmartOrganicLayouter()).calcLayout(graph);
The yFiles layout algorithms support advanced functionality that can take into account even individual information for single graph elements. However, individual setup like, e.g., attaching a preferred edge length to each edge, is beyond the means a graph structure provides. Instead, the data accessor concept as described in the section called “Binding Data to Graph Elements” is utilized to provide the individual setup as supplemental information to a layout algorithm.
The supplemental data for the graph elements is bound to the graph using a so-called "data provider key." During layout calculation, the algorithm then retrieves the data provider that contains the data from the graph by using the same key.
Depending on the data provider key, the algorithm expects the returned data provider to hold values of specific type for either nodes or edges of the graph. Example 5.5, “Binding supplemental data” shows the use of an implicit data provider that returns an integral value for each edge indicating the edge's preferred length to the organic layout algorithm.
Example 5.5. Binding supplemental data
// 'graph' is of type y.layout.LayoutGraph. // Create an implicit data provider and register it with the graph using a // so-called data provider look-up key. // The key is then used by the layout algorithm to retrieve the supplemental // data. graph.addDataProvider(SmartOrganicLayouter.PREFERRED_EDGE_LENGTH_DATA, new DataProviderAdapter() { public int getInt(Object o) { return (int)(200 * getLength((Edge)o)); } }); // Invoke organic layout on the graph. SmartOrganicLayouter sol = new SmartOrganicLayouter(); sol.doLayout(graph); // Remove the data provider from the graph. graph.removeDataProvider(SmartOrganicLayouter.PREFERRED_EDGE_LENGTH_DATA);
Copyright ©2004-2012, yWorks GmbH. All rights reserved. |