The LayoutGraph API - Part 2: Layout
The type LayoutGraph extends Graph and adds positional and dimensional information to the graph model. In particular, this means the location and size of a node, specified by the type INodeLayout, and the location of the source port, target port, and the control points of the path of an edge, specified by type IEdgeLayout. This extended graph type is used by the automatic layouts of yFiles.
Nodes
Interface INodeLayout is used to add the layout information to a node.
This information consists of the coordinates for the upper left corner of the node, and its width and height.
The following convenience methods defined by abstract class LayoutGraph can be used to control the layout information for nodes:
- getCenter(node: Node): YPoint
- getLocation(node: Node): YPoint
- getHeight(node: Node): number
- getWidth(node: Node): number
- Getter methods for nodes.
- setCenter(node: Node, position: YPoint): void
- setLocation(node: Node, position: YPoint): void
- setSize(node: Node, width: number, height: number): void
- Setter methods for nodes.
Edges
Interface IEdgeLayout is used to add the layout information to an edge.
This information consists of the coordinates for both starting point and end point of the edge path, as well as the coordinates for the control points in-between these two.
The coordinates for the edge’s end points are relative to the center coordinates of the edge’s source node and target node, respectively. The coordinates for the control points, in contrast, are absolute.
The following convenience methods defined by abstract class LayoutGraph can be used to control the layout information for edges. Note that the path list for an edge includes its source port, all control points, and also its target port. In this case the source and target port locations are absolute coordinates. The point list, though, holds only the edge’s control points.
Control points are also known as bends.
- getPathList(edge: Edge): YList
- getPointList(edge: Edge): YList
- getSourcePointAbs(edge: Edge): YPoint
- getTargetPointAbs(edge: Edge): YPoint
- Getter methods for edges.
- setPath(edge: Edge, path: YList): void
- setPoints(edge: Edge, points: YList): void
- setSourcePointAbs(edge: Edge, point: YPoint): void
- setTargetPointAbs(edge: Edge, point: YPoint): void
- Setter methods for edges.
Labels
Labels in a LayoutGraph are represented by implementations of the interface ILabelLayout interface. More precisely, an INodeLabelLayout defines properties for node labels and an IEdgeLabelLayout defines properties for edge labels.
To create, add, and remove labels from a LayoutGraph, use the ILabelLayoutFactory. The factory can be obtained by calling LayoutGraphUtilities.getLabelFactory. It is bound to a specific graph instance and can be used to create, add, and remove both node and edge labels in that graph. Adding labels to a LayoutGraph shows how the factory is used to create and add labels.
Defining label positions: Label Models
A label placement algorithm uses the label model that is associated with a label to get the available candidate positions. From this set of candidates it then chooses one that best matches the label position it has calculated. The label model’s model parameter is then used to encode this position.
The label models discussed here are based on interfaces INodeLabelLayoutModel and IEdgeLabelLayoutModel.
It is important to understand that the result of a labeling algorithm is one model parameter per processed label, which is directly stored with the label after calculation. This model parameter expresses the calculated label position with respect to the corresponding label model, and it is only valid in the context of this label model. To get the actual location of a label after the labeling algorithm has finished, both model parameter and corresponding label model are necessary.
It is also important to understand that the labeling algorithm can only place labels on positions which are supported by the label model. The label models DiscreteNodeLabelLayoutModel and DiscreteEdgeLabelLayoutModel support only a small set of locations. This might prevent the algorithm from removing overlaps or finding a good position. For automatic label placement it is recommended to use a label model which supports a free placement or a free placement along an edge, e.g. FreeNodeLabelLayoutModel for nodes and FreeEdgeLabelLayoutModel or SliderEdgeLabelLayoutModel for edges.
Preferred placement of a Label
The following property defined in interface IEdgeLabelLayout returns the PreferredPlacementDescriptor instance that is associated with an edge label:
By default, the PreferredPlacementDescriptor instance that is associated with actual implementations of interface IEdgeLabelLayout, like, e.g., instances of class EdgeLabelLayoutImpl, is immutable. The immutability is a direct consequence of the descriptor instance being shared among newly created edge labels.
Every attempted assignment to a property of such an immutable PreferredPlacementDescriptor instance will yield an exception.
To properly configure individual preferred placement options for an edge label, a dedicated descriptor instance needs to be associated with the label. The following code snippet shows how this can be achieved:
Using Buffered Layout
This section applies only to yFiles layout packages!The IGraph-based adapter mechanism already creates the layout graph as copy of the original IGraph and thus provides the same conceptual benefits as buffered layout. Therefore, class BufferedLayout must not be used with that mechanism.
With the yFiles for HTML 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 applyLayout method. Choosing this approach, the layout calculation is performed on the given graph, and is also immediately assigned.
Buffered layout, in contrast, utilizes class BufferedLayout, 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:
- A layout algorithm might perform badly in terms of memory consumption and execution time, due to the implementation of the graph structure. Crucial graph methods might not be optimized for layout tasks.
- Some layout algorithms need to temporarily add/remove nodes or edges to/from the given graph. Any registered graph listeners will be notified about such structural changes, which subsequently might result in unnecessary or even harmful action on a listener’s behalf.
- Even though it is guaranteed that a layout algorithm will not change a graph’s node set and edge set, it is not unusual that the ordering of nodes and/or edges is modified.
Consequently, it is not safe to rely on the index() feature of nodes or edges.
- In rare cases it might happen that a layout algorithm will crash during a calculation (due to a bug, for example). It will then return immediately and generate an exception. The input graph will be left in an intermediate, often broken state and no recovery will be possible for it.
- Directly invoking a layout algorithm’s applyLayout method will not return the calculated coordinates, but instead assign them right away to the given graph.
Consequently, any other way of coordinate assignment, e.g., in an animated fashion using coordinate interpolation, is defeated.
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.
Class BufferedLayout
The main purpose of class BufferedLayout is to create a copy of the input graph before calling its core layout algorithm. The graph structure that is used for the copied graph is optimized for layout calculation.
Class BufferedLayout must not be used with the IGraph-based adapter mechanisms. Due to its nature, this mechanism already creates the layout graph as copy of the original IGraph
The core layout algorithm 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:
- The structure of the input graph is guaranteed to not change at all.
Usually, layout providers (i.e., ILayoutAlgorithm implementations) make no guarantees on leaving the sequence of nodes or edges unchanged, which may result in unexpected side effects. One such side effect is, for example, that a layout algorithm may assign completely different layouts to a graph when being invoked twice on the same graph.
The reason for such behavior is that a layout provider’s output in general depends on the sequence of elements in the graph, but this sequence has changed with the first layout invocation.
- Calculating a layout on a copy instead of the original graph proves to be more robust. Even if there should occur an unrecoverable error in the layout process, class BufferedLayout guarantees that the structure of the input graph remains consistent.
Wrapping a layout algorithm with a BufferedLayout layout stage is as easy as shown in Using buffered layout (LayoutGraph API).