HierarchicalLayoutData<TNode extends TNodeEdge,TEdge extends TNodeEdge,TNodeEdge,TNodeLabel,TEdgeLabel>
Specifies custom data for the HierarchicalLayout.
Default Values of Properties
Examples
The following example shows how to create a new instance of HierarchicalLayoutData<TNode,TEdge,TNodeEdge,TNodeLabel,TEdgeLabel> and use it with an HierarchicalLayout:
const hierarchicalLayout = new HierarchicalLayout()
const layoutData = new HierarchicalLayoutData()
// group all edges on their source side
layoutData.sourceGroupIds = (edge: IEdge) => edge.sourceNode
// Use the stroke width of the style as edge thickness
layoutData.edgeThickness = (edge: IEdge) =>
edge.style instanceof PolylineEdgeStyle
? edge.style.stroke!.thickness
: 1
graphComponent.graph.applyLayout(hierarchicalLayout, layoutData)
In many cases, the complete initialization of HierarchicalLayoutData<TNode,TEdge,TNodeEdge,TNodeLabel,TEdgeLabel> can also be done in a single object initializer:
const layoutData = new HierarchicalLayoutData({
// group all edges on their source side
sourceGroupIds: (edge: IEdge): any => edge.sourceNode,
// Use the stroke width of the style as edge thickness
edgeThickness: (edge: IEdge): number =>
(edge.style as PolylineEdgeStyle).stroke!.thickness,
})
graphComponent.graph.applyLayout(new HierarchicalLayout(), layoutData)
Type Parameters
- TNode: TNodeEdge
- TEdge: TNodeEdge
- TNodeEdge
- TNodeLabel
- TEdgeLabel
Type Details
- yFiles module
- algorithms
Constructors
Parameters
A map of options to pass to the method.
- alternativeEdgePaths - ItemMapping<TEdge,IEnumerable<IPoint>>
- A mapping from edges to alternative paths for edges connecting to groups, group content, or folder nodes. This option either sets the value directly or recursively sets properties to the instance of the alternativeEdgePaths property on the created object.
- alternativeGroupBounds - ItemMapping<TNode,IRectangle>
- A mapping from group/folder nodes to alternative bounds for these nodes. This option either sets the value directly or recursively sets properties to the instance of the alternativeGroupBounds property on the created object.
- criticalEdgePriorities - ItemMapping<TEdge,number>
- A mapping from edges to their priority to be a 'critical' edge. This option either sets the value directly or recursively sets properties to the instance of the criticalEdgePriorities property on the created object.
- reduceCriticalEdgeCrossings - boolean
- Whether or not critical edges automatically get crossing costs assigned based on their critical edge priorities. This option sets the reduceCriticalEdgeCrossings property on the created object.
- folderNodes - ItemCollection<TNode>
- The collection of folder nodes used for recursive edge styles in incremental layout mode. This option either sets the value directly or recursively sets properties to the instance of the folderNodes property on the created object.
- edgeDescriptors - ItemMapping<TEdge,HierarchicalLayoutEdgeDescriptor>
- The mapping of edges to their HierarchicalLayoutEdgeDescriptor. This option either sets the value directly or recursively sets properties to the instance of the edgeDescriptors property on the created object.
- nodeDescriptors - ItemMapping<TNode,HierarchicalLayoutNodeDescriptor>
- The mapping of nodes to their HierarchicalLayoutNodeDescriptor This option either sets the value directly or recursively sets properties to the instance of the nodeDescriptors property on the created object.
- nodeMargins - ItemMapping<TNode,Insets>
- The mapping from nodes to their margins. This option either sets the value directly or recursively sets properties to the instance of the nodeMargins property on the created object.
- nodeTypes - ItemMapping<TNode,any>
- The mapping from nodes to an object defining the node type, which influences the ordering of nodes during the sequencing such that nodes of the same type are preferably placed next to each other. This option either sets the value directly or recursively sets properties to the instance of the nodeTypes property on the created object.
- subcomponents - ItemCollectionMapping<TNode,HierarchicalLayoutSubcomponentDescriptor>
- The mapping of nodes to a subcomponent which is arranged by the layout algorithm defined by property layoutAlgorithm. This option sets the subcomponents property on the created object.
- ports - PortData<TNode,TEdge,TNodeLabel,TEdgeLabel>
- The sub-data that provides a way of influencing the placement of the ports. This option either sets the value directly or recursively sets properties to the instance of the ports property on the created object.
- sourceGroupIds - ItemMapping<TEdge,any>
- A mapping from edges to an object representing their source edge group. This option either sets the value directly or recursively sets properties to the instance of the sourceGroupIds property on the created object.
- targetGroupIds - ItemMapping<TEdge,any>
- A mapping from edges to an object representing their target edge group. This option either sets the value directly or recursively sets properties to the instance of the targetGroupIds property on the created object.
- gridComponents - ItemCollectionMapping<TEdge,GridComponentDescriptor>
- The mapping from edges to their GridComponentDescriptor which defines the grid component they belong to. This option sets the gridComponents property on the created object.
- gridComponentRootOffsets - ItemMapping<TNode,number>
- The mapping from nodes to their layer index relative to the root node of the grid component they belong to. This option either sets the value directly or recursively sets properties to the instance of the gridComponentRootOffsets property on the created object.
- nodesBeforeBus - ItemCollection<TNode>
- The collection of grid component nodes that should be placed before the common bus segment. This option either sets the value directly or recursively sets properties to the instance of the nodesBeforeBus property on the created object.
- layerConstraints - LayerConstraintData<TNode>
- The layout data to specify layer constraints. This option either sets the value directly or recursively sets properties to the instance of the layerConstraints property on the created object.
- sequenceConstraints - SequenceConstraintData<TNode,TEdge,TNodeEdge>
- The layout data to specify sequence constraints. This option sets the sequenceConstraints property on the created object.
- incrementalNodes - ItemCollection<TNode>
- A collection of nodes that should be layered incrementally. This option either sets the value directly or recursively sets properties to the instance of the incrementalNodes property on the created object.
- incrementalEdges - ItemCollection<TEdge>
- A collection of edges that should be sequenced incrementally. This option either sets the value directly or recursively sets properties to the instance of the incrementalEdges property on the created object.
- layoutGridData - LayoutGridData<TNode,TEdge,TNodeLabel,TEdgeLabel>
- The layoutGridData. This option either sets the value directly or recursively sets properties to the instance of the layoutGridData property on the created object.
- bfsLayerAssignerCoreNodes - ItemCollection<TNode>
- The collection of core nodes used by the BfsLayerAssigner. This option either sets the value directly or recursively sets properties to the instance of the bfsLayerAssignerCoreNodes property on the created object.
- givenLayersIndices - ItemMapping<TNode,number>
- The mapping from nodes to their layer index when using the GivenLayersAssigner. This option either sets the value directly or recursively sets properties to the instance of the givenLayersIndices property on the created object.
- edgeDirectedness - ItemMapping<TEdge,number>
- The mapping from edges to their directedness. This option either sets the value directly or recursively sets properties to the instance of the edgeDirectedness property on the created object.
- edgeThickness - ItemMapping<TEdge,number>
- The mapping from edges to their thickness. This option either sets the value directly or recursively sets properties to the instance of the edgeThickness property on the created object.
- edgeCrossingCosts - ItemMapping<TEdge,number>
- The mapping from edges to their crossing cost. This option either sets the value directly or recursively sets properties to the instance of the edgeCrossingCosts property on the created object.
- groupBorderCrossingCosts - ItemMapping<TNode,number>
- The mapping from group nodes to the costs for a crossing with the group node border. This option either sets the value directly or recursively sets properties to the instance of the groupBorderCrossingCosts property on the created object.
- edgeLabelPreferredPlacements - ItemMapping<TEdgeLabel,EdgeLabelPreferredPlacement>
- The mapping that provides an EdgeLabelPreferredPlacement instance for edge ILabels. This option either sets the value directly or recursively sets properties to the instance of the edgeLabelPreferredPlacements property on the created object.
- uniformPortAssignmentGroups - ItemCollection<TNode>
- The collection of group nodes for which the ports of the adjacent edges are uniformly distributed. This option either sets the value directly or recursively sets properties to the instance of the uniformPortAssignmentGroups property on the created object.
- tabularGroups - ItemCollection<TNode>
- The collection of tabular group nodes whose children are arranged in a tabular fashion. This option either sets the value directly or recursively sets properties to the instance of the tabularGroups property on the created object.
- tabularGroupChildComparators - ItemMapping<TNode,function(TNode,TNode):number>
- The mapping from tabular group nodes to comparison functions used to sort the child nodes of the group. This option either sets the value directly or recursively sets properties to the instance of the tabularGroupChildComparators property on the created object.
Properties
Gets or sets a mapping from edges to alternative paths for edges connecting to groups, group content, or folder nodes.
Remarks
When running in incremental layout mode, the alternative edge paths are considered during the routing of fixed (i.e., non-incremental) edges.
The alternative paths should be used in conjunction with alternative group bounds to achieve more stable layout results when collapsing/expanding a group node as follows:
- Collapsing: edges adjacent to the group itself and edges where one of the endpoints (source/target) lies inside the group should get the path before collapsing the group as an alternative path. If both endpoints are either inside or outside the group, no alternative path is required.
- Expanding: edges adjacent to the expanded folder node (which is now a group) should get the path before expanding as an alternative path.
See Also
Gets or sets a mapping from group/folder nodes to alternative bounds for these nodes.
Remarks
See Also
Gets or sets the collection of core nodes used by the BfsLayerAssigner.
Remarks
The core nodes will be placed in the first layer. If there are no core nodes, then nodes with in-degree 0
are considered to be the core nodes.
The BfsLayerAssigner is used when fromScratchLayeringStrategy is set to BFS.
See Also
Gets or sets a mapping from edges to their priority to be a 'critical' edge.
Remarks
The layout tries to vertically align each node pair that is connected by a critical edge. Critical edges are marked by having a priority greater than 0
; edges with a zero or negative priority are not considered critical.
Critical edges highlight different edge paths that are relevant for a user. The layout algorithm tries to vertically align each node pair that is connected by a critical edge. Conflicts between different critical edges are always resolved in favor of the higher priority.
Examples
Critical edges are often determined by some external data source and are often part of a larger path. The following example shows how two different such paths can be made 'critical' with different priorities:
// Set two different critical edge paths with different priorities
for (const edge of path1) {
layoutData.criticalEdgePriorities.mapper.set(edge, 5)
}
// Lower priority for the second path, which then may deviate from a straight line
for (const edge of path2) {
layoutData.criticalEdgePriorities.mapper.set(edge, 2)
}
Alternatively, a delegate may be used as well, if determining edge criticality is easier when looking at the edge itself:
// Assume that the edge's tag holds an instance that has an 'IsCritical' property
layoutData.criticalEdgePriorities = (edge: IEdge): 0 | 1 =>
edge.tag.isCritical ? 1 : 0
See Also
Sample Graphs
Gets or sets the mapping from edges to their crossing cost.
Remarks
1
, which is used for edges which do not have an individual crossing cost.Examples
When there are only a few edges to customize crossing costs for, the easiest way is usually to use the mapper:
const layoutData = new HierarchicalLayoutData()
// Try harder to prevent crossings with edge1
layoutData.edgeCrossingCosts.mapper.set(edge1, 1.5)
// Crossings with edge2 are not as bad
layoutData.edgeCrossingCosts.mapper.set(edge2, 0.2)
If the crossing cost can readily be computed from the edge itself, the mapperFunction is often the more convenient option:
// The more labels an edge has, the more important it is to
// prevent crossing with it
layoutData.edgeCrossingCosts = (edge: IEdge): number =>
1 + edge.labels.size
See Also
Gets or sets the mapping of edges to their HierarchicalLayoutEdgeDescriptor.
Remarks
null
, the default descriptor is used.Examples
This property allows changing how edges are routed for each edge individually. To just change a few edges that deviate from the default style, using the mapper is probably the best option:
// Ensure larger lengths for the first and last segments of edge1
layoutData.edgeDescriptors.mapper.set(
edge1,
new HierarchicalLayoutEdgeDescriptor({
minimumFirstSegmentLength: 30,
minimumLastSegmentLength: 50,
}),
)
// Change the routing style to octilinear for edge2
layoutData.edgeDescriptors.mapper.set(
edge2,
new HierarchicalLayoutEdgeDescriptor({
routingStyleDescriptor: new RoutingStyleDescriptor(
HierarchicalLayoutRoutingStyle.OCTILINEAR,
),
}),
)
// All other edges not set in the mapper use the default EdgeLayoutDescriptor
// set on HierarchicalLayout.
If a HierarchicalLayoutEdgeDescriptor is easier to create from every edge itself, using a delegate is often easier:
// Use a property from a custom business object in the edge's tag as the minimum length
layoutData.edgeDescriptors = (edge) => {
const customData = edge.tag as CustomData
return new HierarchicalLayoutEdgeDescriptor({
minimumLength: customData.minimumLength,
routingStyleDescriptor: new RoutingStyleDescriptor(
customData.shouldUseOctilinearRouting
? HierarchicalLayoutRoutingStyle.OCTILINEAR
: HierarchicalLayoutRoutingStyle.POLYLINE,
),
})
}
See Also
Gets or sets the mapping from edges to their directedness.
Remarks
1
means that the edge should fully comply, a value of -1
means that it should comply inversely (the edge should point against the main layout direction), and a value of 0
means that the direction doesn't matter at all and the endpoints of the edges may be placed at the same layer. If there are conflicting preferences, edges with higher absolute values are more likely to point in the desired direction.Examples
The easiest option is to define all edges with the same directedness:
layoutData.edgeDirectedness = 1
Handling only certain edges differently can be done easily by using the mapper property:
// edge1 should be considered directed
layoutData.edgeDirectedness.mapper.set(edge1, 1)
// edge2 should be considered directed against the flow
layoutData.edgeDirectedness.mapper.set(edge2, -1)
// All other edges not set in the mapper are treated as undirected
In cases where the directedness for each edge can be determined by looking at the edge itself it's often easier to just set a delegate instead of preparing a mapper:
// Treat edges as directed or undirected based on their style's arrowhead
layoutData.edgeDirectedness = (edge: IEdge): 0 | 1 | -1 => {
const style = edge.style as PolylineEdgeStyle
// edges with either no arrows on both ends or an arrow on both ends should be considered undirected
if (
(style.sourceArrow === null && style.targetArrow === null) ||
(style.sourceArrow !== null && style.targetArrow !== null)
) {
return 0
}
// edges with only a target arrow are directed from source to target
if (style.targetArrow !== null) {
return 1
}
// edges with only a source arrow are directed from target to source
if (style.sourceArrow !== null) {
return -1
}
return 0
}
See Also
Sample Graphs
1
.Gets or sets the mapping that provides an EdgeLabelPreferredPlacement instance for edge ILabels.
Examples
Depending on how much customization is needed, some ways of setting EdgeLabelPreferredPlacements are more convenient than others. For example, to set the same descriptor for all labels, you can just use the constant property:
layoutData.edgeLabelPreferredPlacements = new EdgeLabelPreferredPlacement(
{
// Place labels along the edge
angleReference: LabelAngleReferences.RELATIVE_TO_EDGE_FLOW,
angle: 0,
// ... on either side
edgeSide: LabelEdgeSides.LEFT_OF_EDGE | LabelEdgeSides.RIGHT_OF_EDGE,
// ... with a bit of distance to the edge
distanceToEdge: 5,
},
)
If some labels should use custom placement or this has to be configured ahead of time, you can use the mapper instead:
// Place label1 orthogonal to the edge anywhere on it
layoutData.edgeLabelPreferredPlacements.mapper.set(
label1,
new EdgeLabelPreferredPlacement({
placementAlongEdge: LabelAlongEdgePlacements.ANYWHERE,
angleReference: LabelAngleReferences.RELATIVE_TO_EDGE_FLOW,
angle: Math.PI / 2,
}),
)
// Place label2 near the edge's source on either side of it, and make it parallel to the edge
layoutData.edgeLabelPreferredPlacements.mapper.set(
label2,
new EdgeLabelPreferredPlacement({
placementAlongEdge: LabelAlongEdgePlacements.AT_SOURCE,
edgeSide: LabelEdgeSides.RIGHT_OF_EDGE | LabelEdgeSides.LEFT_OF_EDGE,
angleReference: LabelAngleReferences.RELATIVE_TO_EDGE_FLOW,
angle: 0,
}),
)
When the preferred placement can be inferred from the label itself, a delegate is usually the easiest choice:
layoutData.edgeLabelPreferredPlacements = (
label: ILabel,
): EdgeLabelPreferredPlacement => {
const customData = label.tag as CustomData
return new EdgeLabelPreferredPlacement({
angle: 0,
angleReference: LabelAngleReferences.RELATIVE_TO_EDGE_FLOW,
// If the tag says to place the label in the center, put it in the center parallel to the edge's path
// All other labels can be placed anywhere, but on the side of the edge.
placementAlongEdge: customData.placeInCenter
? LabelAlongEdgePlacements.AT_CENTER
: LabelAlongEdgePlacements.ANYWHERE,
edgeSide: customData.placeInCenter
? LabelEdgeSides.ON_EDGE
: LabelEdgeSides.LEFT_OF_EDGE | LabelEdgeSides.RIGHT_OF_EDGE,
})
}
Note that the preferred placement can also be inferred from an arbitrary ILabelModelParameter:
layoutData.edgeLabelPreferredPlacements =
EdgeLabelPreferredPlacement.fromParameter(
NinePositionsEdgeLabelModel.CENTER_CENTERED,
)
See Also
Gets or sets the mapping from edges to their thickness.
Remarks
0
.Examples
The easiest option is to define the same thickness for all edges:
layoutData.edgeThickness = 3
Handling only certain edges differently can be done easily by using the mapper property:
layoutData.edgeThickness.mapper.set(edge1, 2)
layoutData.edgeThickness.mapper.set(edge2, 5)
// All other edges not set in the mapper have no defined thickness
In cases where the thickness for each edge can be determined by looking at the edge itself it's often easier to just set a delegate instead of preparing a mapper:
// Use the style's pen thickness as edge thickness
layoutData.edgeThickness = (edge: IEdge): number => {
const style = edge.style
if (style instanceof PolylineEdgeStyle) {
return style.stroke!.thickness
}
return 0
}
See Also
Sample Graphs
Gets or sets the collection of folder nodes used for recursive edge styles in incremental layout mode.
Remarks
Examples
Using the folderNodes property is probably easiest with a delegate that checks for each node whether it's a folder node or not:
layoutData.folderNodes = (node) =>
graph.foldingView!.isInFoldingState(node)
However, the other options are also still there, such as using an IEnumerable<T> via source:
layoutData.folderNodes = graph.nodes.filter((node) =>
graph.foldingView!.isInFoldingState(node),
)
or explicitly using the items collection to add or remove items, perhaps based on the user collapsing/expanding group nodes:
// When collapsing a node, mark it as a folder node for layout
graphEditorInputMode.navigationInputMode.addEventListener(
'group-collapsed',
(evt) => layoutData.folderNodes.items.add(evt.item),
)
// When expanding the node again, we unmark it again
graphEditorInputMode.navigationInputMode.addEventListener(
'group-expanded',
(evt) => layoutData.folderNodes.items.remove(evt.item),
)
See Also
Gets or sets the mapping from nodes to their layer index when using the GivenLayersAssigner.
Remarks
If the GivenLayersAssigner is used, the nodes are assigned to layers in the following way:
- Nodes with the same index are placed in the same layer.
- A node with a smaller index is placed in a layer above that of a node with a larger layer index ("above" with respect to the given LayoutOrientation, i.e., on a smaller y-coordinate for orientation top-to-bottom).
- The given layer indices are normalized such that there are no empty layers and the smallest layer has value
0
.
The GivenLayersAssigner is used when fromScratchLayeringStrategy is set to USER_DEFINED.
See Also
Gets or sets the mapping from nodes to their layer index relative to the root node of the grid component they belong to.
Remarks
i
specified for a grid node is interpreted relative to the root node as follows, assuming that the root node is in layer x
.- If the grid component is placed in the layers after the root, the grid node will be placed in layer
x + i
- If the grid component is placed in the layers before the root, the grid node will be placed in layer
x - i
Examples
When the layer offsets can be readily inferred from the nodes, then the easiest option is usually to use the mapperFunction:
layoutData.gridComponentRootOffsets = (node: INode): number =>
node.tag.offset
In case only some nodes need to be mapped to their respective layer offset, it's sometimes easier to use the mapper:
layoutData.gridComponentRootOffsets.mapper.set(node1, 0)
layoutData.gridComponentRootOffsets.mapper.set(node2, 1)
See Also
Gets or sets the mapping from edges to their GridComponentDescriptor which defines the grid component they belong to.
Remarks
All edges with the same GridComponentDescriptor adjacent to a specific node (the root) and having the same direction are arranged on a grid with a central bus structure. The edgeDirectedness is also considered here. The direction defines the placement of the nodes in the following way:
- Grid nodes connected by out-edges of the root are placed in layers starting with the layer immediately after the layer that contains the root (e.g. below the root for top-to-bottom layout orientation).
- Grid nodes connected by in-edges of the root are placed in the layers ending with the layer immediately before the layer that contains the root (e.g. above the root for top-to-bottom layout orientation).
The grid components are arranged using a style that yields more compact layout results. Edges to the child nodes, the so-called grid nodes, are routed using a shared segment that connects to the common root node. The grid nodes are arranged using several layers above or below the root node such that the whole substructure has a compact area. By default, each grid layer contains equally many grid nodes, grid nodes of adjacent layers are center-aligned and the shared bus segment is routed in the middle of the nodes. This produces compact, symmetric arrangements of the grid components.
The arrangement can be customized for each grid component individually, by using the settings offered by GridComponentDescriptor. It allows to configure the number of grid nodes before and after the common bus segment.
Some restrictions apply for grid components:
- Only nodes that are in the same LayoutGridCellDescriptor as the root node are included in the grid component.
- The grid nodes must all belong to the same group node. Also, a group node can not be part of a grid component; neither as grid element nor as the root.
- Grid structures can not be nested. That means that if a node is a grid node it can not be a root node of another grid component at the same time.
- For bus edges, the integrated edge labeling feature does not place port labels as close to the actual port as it does for other edges (see AT_SOURCE_PORT and AT_TARGET_PORT).
- Edge grouping constraints at the side of the grid root node are ignored for bus edges; the grouping induced by the grid component is considered to be stronger.
- If in incremental layout mode, the bus edges are always treated in an incremental way such that the bus-style routing remains consistent (e.g. if a fixed bus edge contains bends that suggest another route, they are ignored). The reason is that the bus-style layout of the grid components has a higher priority. Furthermore, the location of the bus segment relative to the elements is not derived from the sketch but depends on the settings maximumNodesBeforeBus and maximumNodesAfterBus as well as the before/after constraints defined by nodesBeforeBus. This means that it can happen that a fixed grid node which was placed before the bus in the input drawing may be placed after the bus if the bus was enlarged due to the insertion of incremental grid nodes.
- It is not recommended to define grid components inside a group node which has an incremental group hint. Fixed grid nodes inside such a group are not necessarily kept fixed but are treated like incrementally inserted nodes.
Examples
Grid components can be configured by first adding a new GridComponentDescriptor and saving the resulting ItemCollection<TItem>. That one can then be used to define the structure of the grid:
// Retrieve an ItemCollection<IEdge> which defines all edges
// of a grid component. This component will be arranged according to the GridComponentDescriptor
const grid = layoutData.gridComponents.add(
new GridComponentDescriptor(),
)
// You can use this ItemCollection<IEdge> as usual, using either one of
// the Source, Items, Mapper, or Delegate properties.
// Here, we simply use the selected edges:
grid.source = graphComponent.selection.edges
Since adding a new GridComponentDescriptor for a grid returns the ItemCollection<TItem>, configuring grid components can also be chained conveniently:
// component1Edges, component2Edges, component3Edges are of type IEnumerable.<IEdge>
layoutData.gridComponents.add(new GridComponentDescriptor()).source =
component1Edges
layoutData.gridComponents.add(new GridComponentDescriptor()).source =
component2Edges
layoutData.gridComponents.add(new GridComponentDescriptor()).source =
component3Edges
See Also
Sample Graphs
Gets or sets the mapping from group nodes to the costs for a crossing with the group node border.
Remarks
5
. This means that by default, a crossing between an edge and a group node border is more expensive than a crossing between two edges. Also note that a crossing with a group node border that has a cost of zero is cost-free and such a crossing will therefore not be avoided.Examples
When there are only a few group nodes to customize crossing costs for, the easiest way is usually to use the mapper:
// Try harder to prevent crossings with group node1
layoutData.groupBorderCrossingCosts.mapper.set(node1, 7)
// Crossings with group node2 are not as bad
layoutData.groupBorderCrossingCosts.mapper.set(node2, 3)
If the crossing cost can readily be computed from the group node itself, the mapperFunction is often the more convenient option:
// The more labels a group node has, the more important it is to
// prevent crossing with it
layoutData.groupBorderCrossingCosts = (node: INode): number =>
5 + node.labels.size
See Also
Gets or sets a collection of edges that should be sequenced incrementally.
Remarks
Examples
Elements which should be placed incrementally can be provided with a delegate:
// create a HierarchicalLayout which rearranges
// only the incremental graph elements
const hl = new HierarchicalLayout({ fromSketchMode: true })
// provide additional data to configure the HierarchicalLayout
const hlData = new HierarchicalLayoutData()
// specify the nodes to rearrange
hlData.incrementalNodes = (node: INode): boolean => isIncremental(node)
// specify the nodes and edges to rearrange
hlData.incrementalNodes = (item): boolean => isIncremental(item)
hlData.incrementalEdges = (item): boolean => isIncremental(item)
graph.applyLayout(hl, hlData)
More specific hints can be created using IncrementalNodeHint:
// create a HierarchicalLayout which rearranges
// only the incremental graph elements
const hl = new HierarchicalLayout({ fromSketchMode: true })
// provide additional data to configure the HierarchicalLayout for incremental edges
const hlData = new HierarchicalLayoutData({
// sequence hint for incremental edges
incrementalEdges: incrementalEdges,
})
// provide additional GenericLayoutData to configure incremental nodes
const glData = new GenericLayoutData<INode, IEdge, ILabel, ILabel>()
const nodeIncrementalHintsMapping = (glData.addItemMapping(
HierarchicalLayout.INCREMENTAL_NODE_HINTS_DATA_KEY,
).mapperFunction = (node) => {
if (incrementalNodes.includes(node)) {
// create a hint for incremental nodes
if (graph.isGroupNode(node)) {
// special hint for groups
return IncrementalNodeHint.INCREMENTAL_GROUP
}
if (fixedNodes.includes(node)) {
// exact layer for the fixedNodes
return IncrementalNodeHint.USE_EXACT_LAYER_COORDINATES
}
// simple layer hint for all other nodes
return IncrementalNodeHint.LAYER_INCREMENTALLY
}
return IncrementalNodeHint.NONE
})
// combine both layout data instances
const layoutData = hlData.combineWith(glData)
graph.applyLayout(hl, layoutData)
See Also
Gets or sets a collection of nodes that should be layered incrementally.
Remarks
Examples
Elements which should be placed incrementally can be provided with a delegate:
// create a HierarchicalLayout which rearranges
// only the incremental graph elements
const hl = new HierarchicalLayout({ fromSketchMode: true })
// provide additional data to configure the HierarchicalLayout
const hlData = new HierarchicalLayoutData()
// specify the nodes to rearrange
hlData.incrementalNodes = (node: INode): boolean => isIncremental(node)
// specify the nodes and edges to rearrange
hlData.incrementalNodes = (item): boolean => isIncremental(item)
hlData.incrementalEdges = (item): boolean => isIncremental(item)
graph.applyLayout(hl, hlData)
More specific hints can be created using IncrementalNodeHint:
// create a HierarchicalLayout which rearranges
// only the incremental graph elements
const hl = new HierarchicalLayout({ fromSketchMode: true })
// provide additional data to configure the HierarchicalLayout for incremental edges
const hlData = new HierarchicalLayoutData({
// sequence hint for incremental edges
incrementalEdges: incrementalEdges,
})
// provide additional GenericLayoutData to configure incremental nodes
const glData = new GenericLayoutData<INode, IEdge, ILabel, ILabel>()
const nodeIncrementalHintsMapping = (glData.addItemMapping(
HierarchicalLayout.INCREMENTAL_NODE_HINTS_DATA_KEY,
).mapperFunction = (node) => {
if (incrementalNodes.includes(node)) {
// create a hint for incremental nodes
if (graph.isGroupNode(node)) {
// special hint for groups
return IncrementalNodeHint.INCREMENTAL_GROUP
}
if (fixedNodes.includes(node)) {
// exact layer for the fixedNodes
return IncrementalNodeHint.USE_EXACT_LAYER_COORDINATES
}
// simple layer hint for all other nodes
return IncrementalNodeHint.LAYER_INCREMENTALLY
}
return IncrementalNodeHint.NONE
})
// combine both layout data instances
const layoutData = hlData.combineWith(glData)
graph.applyLayout(hl, layoutData)
See Also
Gets or sets the layout data to specify layer constraints.
Remarks
Layer constraints affect the assignment of nodes to layers. A layer constraint can be specified either explicitly for a given node or by mapping some or all nodes to an IComparable, e.g., a number, with the nodeComparables property.
In the latter case, the comparables are used to create placeInOrder constraints between one node and another, if the one precedes the other. These constraints work just like explicitly created constraints. Note that equal values do not result in placeInSameLayer constraints, and no constraints are added for nodes mapped to null
.
Examples
One way to map each node to an IComparable is to use the mapperFunction:
// Map each node to an IComparable, in this case taken from a custom object stored in the tag
layoutData.layerConstraints.nodeComparables = (node) => {
const data = node.tag
if (data !== null) {
return data.layerOrder
}
// Don't constrain layer placement if the tag doesn't contain our custom object.
return null
}
If only some nodes should be constrained, the mapper can be used instead:
// Define layer constraints for a few nodes via a mapper. Nodes that are not
// explicitly mapped don't have constraints on their layer placement.
const mapper = layoutData.layerConstraints.nodeComparables
.mapper as IMapper<INode, any>
// Place both node1 and node2 in the same layer by assigning the same value
mapper.set(node1, 5)
mapper.set(node2, 5)
// Place node3 somewhere above node1 and node2 by assigning a smaller value
mapper.set(node3, 2)
// Place node4 somewhere below node1 and node2 by assigning a larger value
mapper.set(node4, 8)
// The exact values don't matter much they just define an ordering. However, all
// values must have the same type you cannot mix int and string or double, for example.
If those mappings have been prepared beforehand, e.g. in a HashMap<TKey,TValue> or IMapper<K,V>, that property on the ItemMapping<TItem,TValue> can also be set:
layoutData.layerConstraints.nodeComparables = layerOrderMapper
// Or create the mapping from a Map, instead:
layoutData.layerConstraints.nodeComparables = layerOrderMap
IComparable is only the most natural option if such an object can be readily produced from a node, or there already exists such a mapping from nodes to something that can be compared. In many cases it can be easier to construct constraints manually with the respective methods on LayerConstraintData<TNode>:
// Place all nodes in sameLayerNodes into the same layer
const firstSameLayerNode = sameLayerNodes.first()!
for (const node of sameLayerNodes) {
layoutData.layerConstraints.placeInSameLayer(firstSameLayerNode, node)
}
// Ensure that node2 is placed two layers below node1
layoutData.layerConstraints.placeInOrder(node1, node2, 2)
// Also place another node in the top layer
layoutData.layerConstraints.placeAtTop(nodeAtTop)
Sample Graphs
Gets a mapper from nodes to the index of their layer.
Remarks
Examples
const layoutData = new HierarchicalLayoutData()
graph.applyLayout(new HierarchicalLayout(), layoutData)
for (const node of graph.nodes) {
console.log(
`Node ${node} has been placed in layer ${layoutData.layerIndicesResult.get(node)}`,
)
}
See Also
Gets or sets the layoutGridData.
Examples
The following sample shows how to assign nodes to layout grid cells simply via cell indices:
// Create four nodes and place them in grid cells in the following way
// +---+---+---+
// | 1 | 2 | 3 |
// +---+---+---+
// | 4 |
// +---+
const gridData = layoutData.layoutGridData
const node1 = graph.createNode()
const node2 = graph.createNode()
const node3 = graph.createNode()
const node4 = graph.createNode()
// Assign the nodes to their rows and columns.
// Note that you don't have to create or use LayoutGrid directly in this case.
// Setting the indices is enough.
gridData.rowIndices.mapper.set(node1, 0)
gridData.rowIndices.mapper.set(node2, 0)
gridData.rowIndices.mapper.set(node3, 0)
gridData.rowIndices.mapper.set(node4, 1)
gridData.columnIndices.mapper.set(node1, 0)
gridData.columnIndices.mapper.set(node2, 1)
gridData.columnIndices.mapper.set(node3, 2)
gridData.columnIndices.mapper.set(node4, 1)
graph.applyLayout(new HierarchicalLayout(), layoutData)
If used this way there is no need to create a LayoutGrid instance or work with it directly. For more flexibility, e.g., to use cells that span multiple columns or rows, the LayoutGrid can be used as well:
// Create three nodes and place them in grid cells in the following way
// +---+---+
// | 1 | 2 |
// +---+---+
// | 3 |
// +---+---+
const gridData = layoutData.layoutGridData
const node1 = graph.createNode(new Rect(0, 0, 50, 50))
const node2 = graph.createNode(new Rect(0, 0, 50, 50))
const node3 = graph.createNode(new Rect(0, 0, 125, 50))
// Create a new LayoutGrid with two rows and two columns
const grid = new LayoutGrid(2, 2)
// Assign the nodes to their cells
const gridCells = new Mapper<INode, LayoutGridCellDescriptor>()
gridCells.set(node1, grid.createCellDescriptor(0, 0))
gridCells.set(node2, grid.createCellDescriptor(0, 1))
gridCells.set(node3, grid.createRowSpanDescriptor(1))
gridData.layoutGridCellDescriptors = gridCells
graph.applyLayout(new HierarchicalLayout(), layoutData)
Gets or sets the mapping of nodes to their HierarchicalLayoutNodeDescriptor
Remarks
null
, the default descriptor is used.Examples
This property allows changing how nodes are laid out within their layer for each node individually. To just change a few nodes that deviate from the default handling, using the mapper is probably the best option:
// Set a custom minimum distance of node1 to other element
layoutData.nodeDescriptors.mapper.set(
node1,
new HierarchicalLayoutNodeDescriptor({ minimumDistance: 50 }),
)
// Align node2 at the bottom of its layer
layoutData.nodeDescriptors.mapper.set(
node2,
new HierarchicalLayoutNodeDescriptor({ layerAlignment: 1 }),
)
// All other nodes not set in the mapper use the default NodeLayoutDescriptor
// set on HierarchicalLayout.
If a HierarchicalLayoutNodeDescriptor is easier to create from every node itself, using a delegate is often easier:
// Use a property from a custom business object in the node's tag as the minimum length
layoutData.nodeDescriptors = (node) => {
const customData = node.tag as CustomData
const descriptor = new HierarchicalLayoutNodeDescriptor({
minimumDistance: 5,
})
switch (customData.alignment) {
case ItemPlacement.TOP:
descriptor.layerAlignment = 0
break
case ItemPlacement.CENTER:
descriptor.layerAlignment = 0.5
break
case ItemPlacement.BOTTOM:
descriptor.layerAlignment = 1
break
}
return descriptor
}
See Also
Gets or sets the mapping from nodes to their margins.
Remarks
Examples
The easiest option is to reserve the same space around all nodes, by setting a constant value:
layoutData.nodeMargins = new Insets(20)
Handling only certain nodes differently can be done easily by using the mapper property:
// node1 only reserves space above and below
layoutData.nodeMargins.mapper.set(node1, new Insets(20, 10, 0, 0))
// node2 has space all around
layoutData.nodeMargins.mapper.set(node2, new Insets(25))
// all other nodes don't get extra space
In cases where the nodeMargins for each node can be determined by looking at the node itself it's often easier to just set a mapperFunction instead of preparing a mapper:
// Retrieve the space around the node from its tag property
layoutData.nodeMargins = (node: INode): Insets =>
new Insets(parseFloat(node.tag))
See Also
Gets or sets the collection of grid component nodes that should be placed before the common bus segment.
Remarks
Examples
When the placement can be readily inferred from the nodes, then the easiest option is usually to use the predicate:
layoutData.nodesBeforeBus = (node) =>
graphComponent.selection.nodes.includes(node)
Alternatively, an existing IEnumerable<T> containing the nodes can be passed via source:
layoutData.folderNodes = graphComponent.selection.nodes
or items can be added or removed one-by-one using the items collection:
layoutData.nodesBeforeBus.items.add(node1)
layoutData.nodesBeforeBus.items.add(node2)
See Also
Gets or sets the mapping from nodes to an object defining the node type, which influences the ordering of nodes during the sequencing such that nodes of the same type are preferably placed next to each other.
Remarks
See Also
Sample Graphs
Gets or sets the sub-data that provides a way of influencing the placement of the ports.
Remarks
The port placement can be influenced by specifying EdgePortCandidates for the source and target of an edge, as well as by specifying NodePortCandidates at the nodes.
If an edge that has EdgePortCandidates connects to a node with NodePortCandidates, the HierarchicalLayout tries to match both collections to find an appropriate port. An edge port candidate matches a node port candidate if
- Their matchingIds are equal or one type is
null
, - They belong to a common side or at least one side is ANY, and
- If both candidates are fixed, they describe the same positions.
The position of a port candidate is defined by offset or the actual offset of the edge endpoint for fixed-from-sketch candidates. In case there is no matching port, a port candidate specified for the edge is preferred.
The HierarchicalLayout does not support LayoutPortCandidates with multiple sides (e.g. RIGHT or LEFT). To model that an edge should connect at one of several sides, define multiple candidates instead, where each candidate has a single side.
In addition, it is possible to specify that ports should be grouped at the source or target. In addition, it is possible to specify that ports should be aligned at the source or target.
Gets or sets whether or not critical edges automatically get crossing costs assigned based on their critical edge priorities.
Remarks
1
as the crossing cost (which is the default for it). If disabled, the critical edge priorities do not automatically affect the crossing minimization. The user can still provide higher crossing costs manually, of course.Default Value
true
.Critical edges automatically get crossing costs based on their priorities
See Also
Gets or sets the layout data to specify sequence constraints.
Examples
One way to set sequence constraints is by mapping nodes or edges to an IComparable (e.g. a number) which define the order in which those nodes are placed within the same layer. This can be done with the itemComparables property. One way to map each item to such an IComparable is to use the mapperFunction:
// Map each item to an IComparable, in this case taken from a custom object stored in the tag
layoutData.sequenceConstraints.itemComparables = (item) => {
const data = item.tag
if (data !== null) {
return data.sequenceOrder
}
// Don't constrain sequencing if the tag doesn't contain our custom object.
return null
}
If only some nodes should be constrained, the mapper can be used instead:
// Define sequence constraints for a few nodes via a mapper. Nodes that are not
// explicitly mapped don't have constraints on their placement within the layer.
const mapper = layoutData.sequenceConstraints.itemComparables
.mapper as IMapper<INode, any>
// Place node1 before node2
mapper.set(node1, 1)
mapper.set(node2, 3)
// Place node3 between both of them
mapper.set(node3, 2)
// Place node4 after the others by assigning a larger value
mapper.set(node4, 8)
// The exact values don't matter much they just define an ordering. However, all
// values must have the same type you cannot mix int and string or double, for example.
If those mappings have been prepared beforehand, e.g. in a HashMap<TKey,TValue> or IMapper<K,V>, that property on the ItemMapping<TItem,TValue> can also be set:
layoutData.sequenceConstraints.itemComparables = sequenceMapper
// Or create the mapping from a Map, instead:
layoutData.sequenceConstraints.itemComparables = sequenceMap
IComparable is only the most natural option if such an object can be readily produced from a node, or there already exists such a mapping from nodes to something that can be compared. In many cases it can be easier to construct constraints manually with the respective methods on SequenceConstraintData<TNode,TEdge,TItem>:
// Ensure that node1 is placed before node2
layoutData.sequenceConstraints.placeNodeBeforeNode(node1, node2)
// Also place another node at the very start
layoutData.sequenceConstraints.placeNodeAtHead(firstNodeInLayer)
Sample Graphs
Gets a mapper from nodes to the sequence index in their layer.
Remarks
true
. This will skip the remaining stages of the layout and therefore reduce the runtime.Examples
const layoutData = new HierarchicalLayoutData()
graph.applyLayout(new HierarchicalLayout(), layoutData)
for (const node of graph.nodes) {
console.log(
`Node ${node} has been placed in layer ${layoutData.layerIndicesResult.get(
node,
)} at position ${layoutData.sequenceIndicesResult.get(node)}`,
)
}
See Also
Gets or sets a mapping from edges to an object representing their source edge group.
Remarks
Edges sharing a source group identifier will share a common bus near the source or at a common source node if possible.
The HierarchicalLayout is able to group incoming and outgoing edges. Therefore, the source group identifier of the outgoing edges of a node has to match with the target group identifier of the incoming edges.
Examples
One simple way to use source groups is to use the edge's source node as group ID which effectively groups all edges with the same source together:
layoutData.sourceGroupIds = (edge: IEdge) => edge.sourceNode
Another useful way to use a delegate here would be grouping edges by some commonality, such as the same color:
layoutData.sourceGroupIds = (edge: IEdge) => {
const style = edge.style
if (style instanceof PolylineEdgeStyle) {
return style.stroke!.fill
}
return null
}
If only certain edges should be grouped it may sometimes be easier to use the mapper to set the group IDs:
for (const group of edgeGroups) {
for (const edge of group) {
// Use the collection as group ID, since it's common to all edges in it
layoutData.sourceGroupIds.mapper.set(edge, group)
}
}
See Also
Gets or sets the mapping of nodes to a subcomponent which is arranged by the layout algorithm defined by property layoutAlgorithm.
Remarks
The nodes forming a subcomponent are arranged by another layout algorithm and that sub-layout is finally embedded into the global hierarchical layout. This feature allows, for example, to highlight special sub-structures by arranging them in a different fashion. Note that subcomponents may not be nested, that is, a subcomponent is not allowed to contain nodes that belong to yet another subcomponent. Nesting of subcomponents results in an exception.
To conveniently define a subcomponent as well as the layout algorithm responsible for it use the add method of this property. It takes the HierarchicalLayoutSubcomponentDescriptor as parameter and allows to define the nodes associated to it using the returned ItemCollection<TItem>.
A subcomponent can be connected to nodes of another subcomponent as well as to nodes that are not part of any subcomponent. Edges that model these connections are so-called inter-edges. For inter-edges there apply some smaller restrictions with respect to the supported features (see the documentation of SUBCOMPONENT_DESCRIPTOR_DATA_KEY for details).
If all inter-edges of a subcomponent connect to the same external node, that single external so-called connector node is included in the calculation of the component layout when using placement policy AUTOMATIC or ALWAYS_INTEGRATED. In case the result is feasible, this results in a better and more compact integration of the component in the main layout. In that case the sub-layout is fully responsible for the routing of inter-edges (meaning the restrictions below may not apply anymore).
Subcomponents can be defined on grouped graphs with the following restrictions:
- All subcomponent nodes must be on the same hierarchy level if the group node itself is not part of the subcomponent.
- If a group is assigned to a subcomponent, all its descendants (including other group nodes) must be in that component, too.
When running in incremental mode, it is still up to the sub-layout algorithm to decide whether to treat a subcomponent element as incremental or fixed. If the given layout of the subcomponent should be considered, then the from-sketch mode of the used layout algorithm needs to be enabled (if such a mode is supported).
Examples
Subcomponents can be configured by first adding a new HierarchicalLayoutSubcomponentDescriptor and saving the resulting ItemCollection<TItem>. That one can then be used to define the subset of nodes which the layout should process:
// Retrieve an ItemCollection<INode> which defines all nodes
// of a subcomponent. This subcomponent will be arranged by an OrganicLayout
const organicSubset = layoutData.subcomponents.add(
new HierarchicalLayoutSubcomponentDescriptor(new OrganicLayout()),
)
// You can use this ItemCollection<INode> as usual, using either one of
// the Source, Items, Mapper, or Delegate properties.
// Here, we simply use the selected nodes:
organicSubset.source = graphComponent.selection.nodes
Since adding a new subcomponent returns the ItemCollection<TItem>, configuring subcomponents can also be conveniently chained:
layoutData.subcomponents.add(
new HierarchicalLayoutSubcomponentDescriptor(
new OrganicLayout(),
HierarchicalLayoutSubcomponentPlacementPolicy.ISOLATED,
),
).source = organicNodes
layoutData.subcomponents.add(
new HierarchicalLayoutSubcomponentDescriptor(new TreeLayout()),
).source = treeNodes
layoutData.subcomponents.add(
new HierarchicalLayoutSubcomponentDescriptor(new OrthogonalLayout()),
).source = orthogonalNodes
See Also
Gets or sets the mapping from tabular group nodes to comparison functions used to sort the child nodes of the group.
Remarks
Examples
layoutData.tabularGroupChildComparators = (
child1: INode,
child2: INode,
) => child1.labels.get(0).text.localeCompare(child2.labels.get(0).text)
// for the comparison, we assume that child nodes have a custom data tag which holds a column property
layoutData.tabularGroupChildComparators.mapper.set(
tabularGroupNode1,
(child1: INode, child2: INode) =>
child1.tag.column.CompareTo(child2.tag.column),
)
See Also
Gets or sets the collection of tabular group nodes whose children are arranged in a tabular fashion.
Remarks
Children of tabular groups are laid out in a compact tabular fashion as shown in the figure below. Groups contained in a tabular group behave like tabular groups as well.
Child nodes are placed next to each other within the same layer with a distance defined by tabularGroupChildDistance, which is zero by default.
There are some setups for which a tabular group may not be tight (i.e., maximally compact):
- If edges directly connect to a tabular group (not to its content), then it is not guaranteed that the group becomes maximally compact. Children may be placed a little further apart to fulfill the defined edgeDistance.
- The same is true if there are children with self-loops. Such self-loops are always drawn within the tabular group.
- In addition, labels of edges connecting two children of the same tabular group may intersect with other edge segments.
Besides, the table groups may not be tight if there are user specified constraints like node margins, node labels that exceed the size of the associated child node, and port candidates connecting to a side orthogonal to the layout orientation.
Examples
layoutData.tabularGroups.predicate = (node) => graph.isGroupNode(node)
// assume that the tag of a node contains a flag indicating whether a group is a tabular group
layoutData.tabularGroups.predicate = (node) => node.tag.isTabularGroup
layoutData.tabularGroups.mapper = new Mapper()
layoutData.tabularGroups.mapper.set(tabularGroupNode1, true)
layoutData.tabularGroups.mapper.set(tabularGroupNode2, true)
// directly assign a collection of tabular group nodes (e.g. of type List<INode>)
layoutData.tabularGroups.items = tabularGroupNodes
See Also
Sample Graphs
Gets or sets a mapping from edges to an object representing their target edge group.
Remarks
Edges sharing a target group identifier will share a common bus near the target or at a common target node if possible.
The HierarchicalLayout is able to group incoming and outgoing edges. Therefore, the target group identifier of the incoming edges of a node has to match with the source group identifier of the outgoing edges.
Examples
One simple way to use source groups is to use the edge's target node as group ID which effectively groups all edges with the same target together:
layoutData.targetGroupIds = (edge: IEdge) => edge.targetNode
Another useful way to use a delegate here would be grouping edges by some commonality, such as the same color:
layoutData.targetGroupIds = (edge: IEdge) => {
const style = edge.style
if (style instanceof PolylineEdgeStyle) {
return style.stroke!.fill
}
return null
}
If only certain edges should be grouped it may sometimes be easier to use the mapper to set the group IDs:
for (const group of edgeGroups) {
for (const edge of group) {
// Use the collection as group ID, since it's common to all edges in it
layoutData.targetGroupIds.mapper.set(edge, group)
}
}
See Also
Gets or sets the collection of group nodes for which the ports of the adjacent edges are uniformly distributed.
Remarks
By default, there is no guarantee that the ports at group nodes are uniformly distributed. This setting only affects the two sides of a group node that are in flow and against the flow of the layout orientation. For example, for orientation TOP_TO_BOTTOM only the edges incident to the top and bottom group node side are considered but not the edges at the left and right side.
The uniform group port assignment considers the edgeDistance such that two ports keep the specified distance to each other. Group nodes may in consequence be enlarged to satisfy that constraints, potentially generating less compact layouts. The distance from the group node border to the first/last port on each group side may be influenced by using group node padding.
Importantly, it is not guaranteed that the group node ports of a specific side can be uniformly distributed. In the following cases ports of a specific side are not uniformly distributed:
- There are edges that cross the group node border because they connect to a child node of the group.
- There is a self-loop at the group node which has its source and target port at the same group node side.
Examples
layoutData.uniformPortAssignmentGroups = (node: INode): boolean =>
graph.isGroupNode(node)
layoutData.uniformPortAssignmentGroups = graph.nodes.filter((node) =>
graph.isGroupNode(node),
)
See Also
Sample Graphs
Methods
combineWith
(data: LayoutData<TNode,TEdge,TNodeLabel,TEdgeLabel>…) : LayoutData<TNode,TEdge,TNodeLabel,TEdgeLabel>Combines this instance with the given layout data.
Remarks
Parameters
A map of options to pass to the method.
- data - LayoutData<TNode,TEdge,TNodeLabel,TEdgeLabel>
- The LayoutData<TNode,TEdge,TNodeLabel,TEdgeLabel> to combine this instance with.
Returns
- ↪LayoutData<TNode,TEdge,TNodeLabel,TEdgeLabel>
- The combined layout data.