Specifies custom data for the TreeLayout.
Examples
The following example shows how to create a new instance of TreeLayoutData<TNode,TEdge,TNodeLabel,TEdgeLabel> and use it with an TreeLayout:
const layoutData = new TreeLayoutData()
layoutData.nodeMargins = new Insets(15)
layoutData.treeRoot.item = rootNode
graph.applyLayout(new TreeLayout(), layoutData)
In many cases the complete initialization of TreeLayoutData<TNode,TEdge,TNodeLabel,TEdgeLabel> can also be done in a single object initializer:
const layoutData = new TreeLayoutData({
nodeMargins: new Insets(15),
treeRoot: (node) => node === rootNode,
})
graph.applyLayout(new TreeLayout(), layoutData)
Type Parameters
- TNode
- TEdge
- TNodeLabel
- TEdgeLabel
Type Details
- yFiles module
- algorithms
Constructors
Parameters
A map of options to pass to the method.
- subtreePlacers - ItemMapping<TNode,ISubtreePlacer>
- The mapping from nodes to their ISubtreePlacer. This option either sets the value directly or recursively sets properties to the instance of the subtreePlacers property on the created object.
- portAssigners - ItemMapping<TNode,ITreeLayoutPortAssigner>
- The mapping from nodes to their ITreeLayoutPortAssigner. This option either sets the value directly or recursively sets properties to the instance of the portAssigners property on the created object.
- childOrder - ChildOrderData<TNode,TEdge>
- The ChildOrderData<TNode,TEdge> which specifies how child nodes are to be sorted. This option either sets the value directly or recursively sets properties to the instance of the childOrder 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 child nodes such that those with 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.
- assistantNodes - ItemCollection<TNode>
- The collection of nodes the AssistantSubtreePlacer considers as assistants. This option either sets the value directly or recursively sets properties to the instance of the assistantNodes property on the created object.
- singleSplitSubtreePlacerPrimaryNodes - ItemCollection<TNode>
- The collection of nodes the SingleSplitSubtreePlacer places with its primaryPlacer. This option either sets the value directly or recursively sets properties to the instance of the singleSplitSubtreePlacerPrimaryNodes property on the created object.
- leftRightSubtreePlacerLeftNodes - ItemCollection<TNode>
- The set of nodes that are placed on the left side of the bus. This option either sets the value directly or recursively sets properties to the instance of the leftRightSubtreePlacerLeftNodes property on the created object.
- multiLayerSubtreePlacerLayerIndices - ItemMapping<TNode,number>
- The mapping from nodes to the index of the local layer the MultiLayerSubtreePlacer shall place the node in. This option either sets the value directly or recursively sets properties to the instance of the multiLayerSubtreePlacerLayerIndices property on the created object.
- multiParentDescriptors - ItemMapping<TNode,MultiParentDescriptor>
- The mapping of nodes to their MultiParentDescriptor. This option either sets the value directly or recursively sets properties to the instance of the multiParentDescriptors property on the created object.
- treeRoot - ItemCollection<TNode>
- The mapping for marking the node that will be used as the root node of the tree. This option either sets the value directly or recursively sets properties to the instance of the treeRoot property on the created object.
- edgeLabelPreferredPlacements - ItemMapping<TEdgeLabel,EdgeLabelPreferredPlacement>
- The mapping that provides an EdgeLabelPreferredPlacement instance for edge labels. This option either sets the value directly or recursively sets properties to the instance of the edgeLabelPreferredPlacements property on the created object.
- compactSubtreePlacerStrategyMementos - IMapper<TNode,any>
- A mapper from nodes to a strategy memento object for the CompactSubtreePlacer. This option either sets the value directly or recursively sets properties to the instance of the compactSubtreePlacerStrategyMementos 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.
- nonTreeEdges - ItemCollection<TEdge>
- The collection of edges explicitly marked as not belonging to a tree. This option either sets the value directly or recursively sets properties to the instance of the nonTreeEdges property on the created object.
- edgeBundleDescriptors - ItemMapping<TEdge,EdgeBundleDescriptor>
- The mapping of edges to their EdgeBundleDescriptor. This option either sets the value directly or recursively sets properties to the instance of the edgeBundleDescriptors property on the created object.
- ports - BasicPortData<TNode,TEdge,TNodeLabel,TEdgeLabel>
- The sub-data that influences 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.
Properties
Gets or sets the collection of nodes the AssistantSubtreePlacer considers as assistants.
Remarks
Examples
Specifying which nodes to lay out as assistant nodes can be done in various ways, depending on which option is more convenient at the moment. For example, if you already have a collection or IEnumerable<T> somewhere that contains all nodes that should be treated as assistant nodes, then the easiest option usually is to use the source property:
layoutData.assistantNodes = graphComponent.selection.nodes
In some cases only some nodes may need to be directly set as assistant nodes, but are not already in a collection, in which case the items property can be used directly to add them:
const layoutData = new TreeLayoutData()
layoutData.assistantNodes.items.add(assistantNode1)
layoutData.assistantNodes.items.add(assistantNode2)
In cases where it's directly apparent from a node whether to lay it out as an assistant node, the best option usually is to use a delegate:
layoutData.assistantNodes = (node: INode): boolean => node.tag.isAssistant
See Also
Sample Graphs
Gets or sets the ChildOrderData<TNode,TEdge> which specifies how child nodes are to be sorted.
Examples
The ChildOrderData<TNode,TEdge> class provides two primary methods for determining the order of a parent's child nodes: either by directly sorting the child nodes using targetNodeComparators or targetNodeComparables, or by sorting the outgoing edges from the parent node to its child nodes using outEdgeComparators or outEdgeComparables. Setting a comparable value (e.g. a number or a string) for all nodes means that the child nodes of all parents in the graph are ordered using those values.
// Use the node height as comparable to sort the child nodes by their height
layoutData.childOrder.targetNodeComparables = (child) =>
child.layout.height
Using a comparison delegate for specific nodes is convenient if the child nodes should be ordered only for particular parent nodes.
layoutData.childOrder.targetNodeComparators = (parent: INode) => {
if (parent == specificNode) {
// Define a comparison function only for a specific parent node
// ... and sort its child nodes based on an order value specified in their tags
return (child1: INode, child2: INode) =>
child1.tag.sequenceOrder.compareTo(child2.tag.sequenceOrder)
}
// No ordering for the edges around the other nodes
return null
}
Setting a comparable function for all edges means that the outgoing edges of all parents in the graph are ordered using that function.
// Use the label count of the out edges as comparable value to sort them
layoutData.childOrder.outEdgeComparables = (edge) => edge.labels.size
Setting a comparable value (e.g. a number or a string) for all edges means that the outgoing edges of all parents in the graph are ordered using those values.
layoutData.childOrder.outEdgeComparators = (parent: INode) => {
if (parent == specificNode) {
// Define a comparison function only for a specific parent node
// ... and sort its outgoing edges based on an order value specified in their tags
return (edge1: IEdge, edge2: IEdge) =>
edge1.tag.sequenceOrder.compareTo(edge2.tag.sequenceOrder)
}
// No ordering for the edges around the other nodes
return null
}
See Also
Gets or sets a mapper from nodes to a strategy memento object for the CompactSubtreePlacer.
Remarks
This property has an effect only if CompactSubtreePlacer is selected as subtree placer for at least one subtree of the input graph. Then, given that this property is set, the subtree placer stores the applied placement strategy for the nodes' children and keeps it in subsequent layout runs. This is useful to maintain similar layout styles over subsequent runs. Otherwise, even for small changes in the graph structure, the results produced by CompactSubtreePlacer can drastically change.
The actual values stored in this mapper are opaque objects only intended for internal use by CompactSubtreePlacer.
Examples
layoutData.compactSubtreePlacerStrategyMementos = new Mapper()
See Also
Gets or sets a mapping from edges to their priority to be a 'critical' edge.
Remarks
The layout tries to align each node pair that is connected by a critical edge (value > 0).
Critical edges highlight different edge paths that are relevant for a user. The layout tries to 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
Gets or sets the mapping of edges to their EdgeBundleDescriptor.
Remarks
This property only has an effect when the treeReductionStage is enabled, which is the default setting.
Bundling together multiple edges means that their common parts are to some degree merged into a bundled part. At the source and target point, the edges are again clearly split.
If an edge is mapped to null
, the default descriptor is used.
See Also
Gets or sets the mapping that provides an EdgeLabelPreferredPlacement instance for edge labels.
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 set of nodes that are placed on the left side of the bus.
Remarks
Examples
When customizing left/right placement for nodes it's common to have some sort of telling from the node where it should be placed. The simplest way to use this would be the mapperFunction:
layoutData.leftRightSubtreePlacerLeftNodes = (node) =>
(node.tag as CustomData).placement === 'left'
In other cases, the mapper option allows more flexibility over its use:
// Place up to three nodes to the left, and the others to the right
for (const node of graph.nodes) {
let i = 0
for (const n of graph.successors(node).takeWhile(() => i++ < 3)) {
layoutData.leftRightSubtreePlacerLeftNodes.mapper!.set(n, true)
}
// Placement to the right is the default, so nothing else is necessary here
}
See Also
Sample Graphs
Gets or sets the mapping from nodes to the index of the local layer the MultiLayerSubtreePlacer shall place the node in.
Remarks
See Also
Gets or sets the mapping of nodes to their MultiParentDescriptor.
Remarks
A MultiParentDescriptor is used by TreeLayout to determine the desired layout of nodes that constitute a multi-parent structure. All nodes of such a structure are placed side by side, and the incident edges are routed over common points for incoming edges and for outgoing edges.
If all nodes that belong to a multi-parent structure are mapped to null
, an instance with default settings is used.
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 mapping from nodes to an object defining the node type, which influences the ordering of child nodes such that those with the same type are preferably placed next to each other.
Remarks
See Also
Sample Graphs
Gets or sets the collection of edges explicitly marked as not belonging to a tree.
Remarks
See Also
Gets or sets the mapping from nodes to their ITreeLayoutPortAssigner.
Remarks
Examples
Depending on the customization needed, ItemMapping<TItem,TValue> has several options that work well in different situations. To change the default only for some nodes that are known beforehand, it's often easiest to just use the mapper:
// Place ports at node1 at the center of the node
layoutData.portAssigners.mapper.set(node1, new TreeLayoutPortAssigner())
// Distribute ports on node2 on the border of the node
layoutData.portAssigners.mapper.set(
node2,
new TreeLayoutPortAssigner(TreeLayoutPortAssignmentMode.DISTRIBUTED),
)
In case the desired configuration can readily be inferred from each node, the most convenient option usually is to use a delegate:
layoutData.portAssigners = (node) => {
const customData = node.tag as CustomData
if (customData.portPlacement == 'center') {
return new TreeLayoutPortAssigner()
}
if (customData.portPlacement == 'border') {
return new TreeLayoutPortAssigner(
TreeLayoutPortAssignmentMode.DISTRIBUTED,
)
}
// Fall back to TreeLayout.DefaultPortAssigner for other cases
return null
}
See Also
Gets or sets the sub-data that influences 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.
In addition, it is possible to specify that ports should be grouped at the source or target.
If both EdgePortCandidates and NodePortCandidates are specified, the layout algorithm tries to match them. 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 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. When there is no match, the port candidate with the lowest costs specified for the edge is chosen.
Gets or sets the collection of nodes the SingleSplitSubtreePlacer places with its primaryPlacer.
Remarks
See Also
Gets or sets the mapping from nodes to their ISubtreePlacer.
Remarks
null
, then the defaultSubtreePlacer applies for that node.Examples
Depending on the customization needed, ItemMapping<TItem,TValue> has several options that work well in different situations. To change the default only for some nodes that are known beforehand, it's often easiest to just use the mapper:
// Arrange node1's children below the node and to the right
layoutData.subtreePlacers.mapper.set(
node1,
new SingleLayerSubtreePlacer(
SubtreeTransform.ROTATE_LEFT_FLIP_Y,
SingleLayerSubtreePlacerRootAlignment.LEFT,
10,
10,
),
)
// Arrange node2's children as compactly as possible via CompactSubtreePlacer
layoutData.subtreePlacers.mapper.set(node2, new CompactSubtreePlacer())
In case the desired configuration can readily be inferred from each node, the most convenient option usually is to use a delegate:
layoutData.subtreePlacers = (node: INode): ISubtreePlacer => {
// If the node has too many children, switch to CompactSubtreePlacer
const successors = graph.successors(node).toList()
if (successors.size > 10) {
return new CompactSubtreePlacer()
}
// Otherwise if there are assistant children, we use AssistantSubtreePlacer
if (successors.some((n: INode): boolean => n.tag.isAssistant)) {
return new AssistantSubtreePlacer()
}
// In all other cases use the default configured on TreeLayout
return null!
}
See Also
Gets or sets the mapping for marking the node that will be used as the root node of the tree.
Remarks
Examples
The simplest way to set a custom root node for the layout, if you already have a TNode
for that, would be to use the item to specify it as the root:
layoutData.treeRoot.item = root
In cases where it's easier to look at a TNode
to determine whether it should be the root node or not, it's often most convenient to use the predicate property:
// This is equivalent to the declaration above
layoutData.treeRoot = (node) => node === root
// Or use a different way of choosing the root node, e.g. by taking the node whose label
// says 'Root'
layoutData.treeRoot = (node) => node.labels.get(0).text === 'Root'
Sometimes there might also be a collection that usually holds just the single node that should be the root, in which case source can be useful:
// Use the first selected node as the root
// This will be evaluated anew each time the layout is applied
layoutData.treeRoot = graphComponent.selection.nodes
See Also
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.