Specifies custom data for the GenericLabeling.
Examples
The following example shows how to create a new instance of GenericLabelingData<TNode,TEdge,TNodeLabel,TEdgeLabel> and use it with a GenericLabeling:
const layoutData = new GenericLabelingData()
layoutData.scope.nodeLabels = graphComponent.selection.labels.filter(
(label) => label.owner instanceof INode,
)
layoutData.scope.edgeLabels = graphComponent.selection.labels.filter(
(label) => label.owner instanceof IEdge,
)
layoutData.edgeLabelPreferredPlacements = (
label: ILabel,
): EdgeLabelPreferredPlacement =>
new EdgeLabelPreferredPlacement({
// Place short labels horizontally, and others along the edge
angleReference:
label.text.length < 5
? LabelAngleReferences.ABSOLUTE
: LabelAngleReferences.RELATIVE_TO_EDGE_FLOW,
})
graphComponent.graph.applyLayout(new GenericLabeling(), layoutData)
In many cases the complete initialization of GenericLabelingData<TNode,TEdge,TNodeLabel,TEdgeLabel> can also be done in a single object initializer:
const layoutData = new GenericLabelingData({
edgeLabelPreferredPlacements: (
label: ILabel,
): EdgeLabelPreferredPlacement =>
new EdgeLabelPreferredPlacement({
// Place short labels horizontally, and others along the edge
angleReference:
label.text.length < 5
? LabelAngleReferences.ABSOLUTE
: LabelAngleReferences.RELATIVE_TO_EDGE_FLOW,
}),
})
layoutData.scope.nodeLabels = graphComponent.selection.labels.filter(
(label) => label.owner instanceof INode,
)
layoutData.scope.edgeLabels = graphComponent.selection.labels.filter(
(label) => label.owner instanceof IEdge,
)
graphComponent.graph.applyLayout(new GenericLabeling(), layoutData)
Type Parameters
- TNode
- TEdge
- TNodeLabel
- TEdgeLabel
Type Details
- yFiles module
- algorithms
Constructors
Parameters
A map of options to pass to the method.
- edgeLabelingCosts - ItemMapping<TEdgeLabel,LabelingCosts>
- A mapping from edge labels to their LabelingCosts. This option either sets the value directly or recursively sets properties to the instance of the edgeLabelingCosts property on the created object.
- nodeLabelingCosts - ItemMapping<TNodeLabel,LabelingCosts>
- A mapping from node labels to their LabelingCosts. This option either sets the value directly or recursively sets properties to the instance of the nodeLabelingCosts property on the created object.
- edgeLabelCandidates - ItemMapping<TEdgeLabel,EdgeLabelCandidates>
- A mapping from edge labels to their EdgeLabelCandidates. This option either sets the value directly or recursively sets properties to the instance of the edgeLabelCandidates property on the created object.
- nodeLabelCandidates - ItemMapping<TNodeLabel,NodeLabelCandidates>
- A mapping from node labels to their NodeLabelCandidates. This option either sets the value directly or recursively sets properties to the instance of the nodeLabelCandidates property on the created object.
- edgeLabelCandidateProcessors - ItemMapping<TEdgeLabel,function(IEnumerable<LabelCandidate>,LayoutEdgeLabel):void>
- A mapping from edge labels to a delegate for modifying the weights of their LabelCandidates. This option either sets the value directly or recursively sets properties to the instance of the edgeLabelCandidateProcessors property on the created object.
- nodeLabelCandidateProcessors - ItemMapping<TNodeLabel,function(IEnumerable<LabelCandidate>,LayoutNodeLabel):void>
- A mapping from node labels to a delegate for modifying the weights of their LabelCandidates. This option either sets the value directly or recursively sets properties to the instance of the nodeLabelCandidateProcessors 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.
- 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.
- 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.
Properties
edgeLabelCandidateProcessors
: ItemMapping<TEdgeLabel,function(IEnumerable<LabelCandidate>,LayoutEdgeLabel):void>Gets or sets a mapping from edge labels to a delegate for modifying the weights of their LabelCandidates.
Remarks
The candidates created for an edge label are used by the GenericLabeling algorithm and specify valid candidate positions for the label placement. Their weights are used to determine which candidate position is chosen for a given label.
When the delegate is evaluated, the weight property of the label candidates contains the result of the weight calculation performed by the labeling algorithm.
Examples
The delegate can be helpful when the priorities of label candidates rely on the result of another layout that has not yet run at the time when candidates are specified:
layoutData.edgeLabelCandidateProcessors.constant = (
candidates: IEnumerable<LabelCandidate>,
_: ILabel | LayoutEdgeLabel,
) => {
candidates.forEach((labelCandidate) => {
//avoid label positions that moved below the x-axis, e.g., because the owner was moved by a preceding layout run
labelCandidate.weight = labelCandidate.layout.anchorY < 0 ? 1.0 : 0.2
})
}
Since the label candidates contain the weight calculated by the GenericLabeling algorithm when the delegate is evaluated, you can also combine a custom weight calculation with the result of the built-in calculations:
const weightRatio = 0.8
layoutData.edgeLabelCandidateProcessors.constant = (
candidates: IEnumerable<LabelCandidate>,
label: ILabel | LayoutEdgeLabel,
) => {
candidates.forEach((labelCandidate) => {
const customWeight = calculateCustomWeight(label, labelCandidate)
labelCandidate.weight =
labelCandidate.weight * (1 - weightRatio) +
customWeight * weightRatio
})
}
Sometimes, the weight of a candidate can also depend on view-only properties of its label. In such cases, a mapping from ILabel to the delegate can be used:
layoutData.edgeLabelCandidateProcessors.mapperFunction = (
viewLabel: ILabel,
) => {
return (
candidates: IEnumerable<LabelCandidate>,
_label: ILabel | LayoutEdgeLabel,
) => {
candidates.forEach((labelCandidate) => {
const candidateBox = labelCandidate.layout
if (
viewLabel.text == 'Horizontal label' &&
!(candidateBox.upX == 0 && candidateBox.upY == -1)
) {
//non-horizontal candidates are very undesirable for certain labels
labelCandidate.weight = 0.0
}
//else: keep weight
})
}
}
See Also
Gets or sets a mapping from edge labels to their EdgeLabelCandidates.
Remarks
Examples
Edge label candidates offer different convenience methods to specify candidates for the labels. You can use configurable methods like addDiscreteCandidates:
const centerCandidate = new EdgeLabelCandidates().addDiscreteCandidates(
DiscreteEdgeLabelPositions.CENTER,
)
layoutData.edgeLabelCandidates = centerCandidate
Alternatively, you can specify your own custom candidate positions for a label:
const topCenter = new OrientedRectangle(100, 0, 50, 20)
layoutData.edgeLabelCandidates = (label) =>
label.text == 'Title'
? new EdgeLabelCandidates().addFixedCandidate(topCenter)
: new EdgeLabelCandidates().addFreeCandidates()
Finally, you can combine and prioritize different sets of candidates, e.g. to provide fallbacks if the preferred position is unavailable:
const candidates = new EdgeLabelCandidates()
//Add primary candidate with high weight
candidates.addSliderCandidates({
mode: EdgeLabelSliderMode.CENTER,
weight: 1.0,
})
//Add fallback candidates with lower weight
candidates.addDiscreteCandidates({
labelPositions: DiscreteEdgeLabelPositions.SIX_POS,
weight: 0.5,
})
layoutData.edgeLabelCandidates = candidates
See Also
Gets a mapper from edge labels to the LabelCandidates chosen by the labeling algorithm.
Remarks
Examples
graph.applyLayout(new HierarchicalLayout(), layoutData)
graph.labels.forEach((label) => {
if (label.owner instanceof IEdge) {
console.log(
`Candidate ${layoutData.edgeLabelCandidatesResult.get(label)} has been chosen for label ${label}`,
)
}
})
See Also
Gets or sets a mapping from edge labels to their LabelingCosts.
Remarks
Examples
The easiest option is to set the same costs for all edge labels, by setting a constant LabelingCosts instance:
layoutData.edgeLabelingCosts = new LabelingCosts(
LabelingOptimizationStrategy.LABEL_OVERLAPS,
)
Handling only certain labels differently can be done easily by using the mapper property:
// edgeLabel1 avoids overlapping other labels as much as possible
layoutData.edgeLabelingCosts.mapper.set(
edgeLabel1,
new LabelingCosts(LabelingOptimizationStrategy.LABEL_OVERLAPS),
)
// edgeLabel2 avoids overlapping edges as much as possible
layoutData.edgeLabelingCosts.mapper.set(
edgeLabel2,
new LabelingCosts(LabelingOptimizationStrategy.EDGE_OVERLAPS),
)
// all other edge labels will default to the GenericLabeling.DefaultEdgeLabelingPenalties
In cases where the LabelingCosts for each label can be determined by looking at the label itself it's often easier to just set a delegate instead of preparing a mapper:
// Retrieve the penalties of an edge label from its tag property
layoutData.edgeLabelingCosts = (label) => label.tag as LabelingCosts
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 preferred placement 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
nodeLabelCandidateProcessors
: ItemMapping<TNodeLabel,function(IEnumerable<LabelCandidate>,LayoutNodeLabel):void>Gets or sets a mapping from node labels to a delegate for modifying the weights of their LabelCandidates.
Remarks
The candidates created for a node label are used by the GenericLabeling algorithm and specify valid candidate positions for the label placement. Their weights are used to determine which candidate position is chosen for a given label.
When the delegate is evaluated, the weight property of the label candidates contains the result of the weight calculation performed by the GenericLabeling algorithm.
Examples
The delegate can be helpful when the priorities of label candidates rely on the result of another layout that has not yet run at the time when candidates are specified:
layoutData.nodeLabelCandidateProcessors.constant = (
candidates: IEnumerable<LabelCandidate>,
_: LayoutNodeLabel,
) => {
candidates.forEach((labelCandidate) => {
//avoid label positions that moved below the x-axis, e.g., because the owner was moved by a preceding layout run
labelCandidate.weight = labelCandidate.layout.anchorY < 0 ? 1.0 : 0.2
})
}
Since the label candidates contain the weight calculated by the GenericLabeling algorithm when the delegate is evaluated, you can also combine a custom weight calculation with the result of the built-in calculations:
const weightRatio = 0.8
layoutData.nodeLabelCandidateProcessors.constant = (
candidates: IEnumerable<LabelCandidate>,
label: LayoutNodeLabel,
) => {
candidates.forEach((labelCandidate) => {
const customWeight = calculateCustomWeight(label, labelCandidate)
labelCandidate.weight =
labelCandidate.weight * (1 - weightRatio) +
customWeight * weightRatio
})
}
Sometimes, the weight of a candidate can also depend on view-only properties of its label. In such cases, a mapping from ILabel to the delegate can be used:
layoutData.nodeLabelCandidateProcessors.mapperFunction = (
viewLabel: ILabel,
) => {
return (
candidates: IEnumerable<LabelCandidate>,
label: LayoutNodeLabel,
) => {
candidates.forEach((labelCandidate) => {
const candidateBox = labelCandidate.layout
if (
viewLabel.text == 'Horizontal label' &&
!(candidateBox.upX == 0 && candidateBox.upY == -1)
) {
//non-horizontal candidates are very undesirable for certain labels
labelCandidate.weight = 0.0
}
//else: keep calculated weight
})
}
}
See Also
Gets or sets a mapping from node labels to their NodeLabelCandidates.
Remarks
Examples
Node label candidates offer different convenience methods to specify candidates for the labels. You can add candidates using configurable methods like addDiscreteCandidates:
const centerCandidate = new NodeLabelCandidates().addDiscreteCandidates(
DiscreteNodeLabelPositions.CENTER,
)
layoutData.nodeLabelCandidates = centerCandidate
Alternatively, you can specify your own custom candidate positions for a label:
const topCenter = new OrientedRectangle(100, 0, 50, 20)
layoutData.nodeLabelCandidates = (label) =>
label.text == 'Title'
? new NodeLabelCandidates().addFixedCandidate(topCenter)
: new NodeLabelCandidates().addFreeCandidates()
Finally, you can combine and prioritize different sets of candidates, e.g. to provide fallbacks if the preferred position is unavailable:
const candidates = new NodeLabelCandidates()
//add primary candidate with high weight
candidates.addDiscreteCandidates({
labelPositions: DiscreteNodeLabelPositions.CENTER,
weight: 1.0,
})
//add fallback candidates with lower weight
candidates.addDiscreteCandidates({
labelPositions: DiscreteNodeLabelPositions.CORNERS,
weight: 0.5,
})
layoutData.nodeLabelCandidates = candidates
See Also
Gets a mapper from node labels to the LabelCandidates chosen by the labeling algorithm.
Remarks
Examples
graph.applyLayout(new HierarchicalLayout(), layoutData)
graph.labels.forEach((label) => {
if (label.owner instanceof INode) {
console.log(
`Candidate ${layoutData.nodeLabelCandidatesResult.get(label)} has been chosen for label ${label}`,
)
}
})
See Also
Gets or sets a mapping from node labels to their LabelingCosts.
Remarks
Examples
The easiest option is to set the same costs for all node labels, by setting a constant LabelingCosts instance:
layoutData.nodeLabelingCosts = new LabelingCosts(
LabelingOptimizationStrategy.LABEL_OVERLAPS,
)
Handling only certain labels differently can be done easily by using the mapper property:
// edgeLabel1 avoids overlapping other labels as much as possible
layoutData.nodeLabelingCosts.mapper.set(
edgeLabel1,
new LabelingCosts(LabelingOptimizationStrategy.LABEL_OVERLAPS),
)
// edgeLabel2 avoids overlapping edges as much as possible
layoutData.nodeLabelingCosts.mapper.set(
edgeLabel2,
new LabelingCosts(LabelingOptimizationStrategy.NODE_OVERLAPS),
)
// all other edge labels will default to the GenericLabeling.DefaultNodeLabelingPenalties
In cases where the LabelingCosts for each label can be determined by looking at the label itself it's often easier to just set a delegate instead of preparing a mapper:
// Retrieve the descriptor of an edge label from its tag property
layoutData.nodeLabelingCosts = (label) => label.tag as LabelingCosts
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 sub-data that provides the possibility of modifying the scope of the GenericLabeling algorithm with respect to the placed labels.
Remarks
Examples
The scope can be specified using the items property when only a few known labels should be affected:
const affectedEdgeLabels = layoutData.scope.edgeLabels.items
affectedEdgeLabels.add(edgeLabel1)
affectedEdgeLabels.add(edgeLabel2)
In cases where the scope of a label can be determined by examining the label itself, it is often easier to set a delegate instead:
// All node labels should be affected unless they're marked as "Fixed"
layoutData.scope.nodeLabels = (label) => label.tag != 'Fixed'
Sometimes it can be more convenient to specify the owners of the affected edges:
// All labels owned by the selected edges should be affected
layoutData.scope.edges = graphComponent.selection.edges
Finally, it's also possible to specify the owners of the affected labels and further restrict them based on a property of the label itself:
// The labels of all selected nodes should be affected
layoutData.scope.nodes = graphComponent.selection.nodes
// But only if they are not marked as "Fixed"
layoutData.scope.nodeLabels = (label) => label.tag != 'Fixed'
Gets or sets a mapping from edges to an object representing their source edge group.
Remarks
See Also
Gets or sets a mapping from edges to an object representing their target edge group.
Remarks
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.