Members
Constructors
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.
edgeLabelCandidateProcessors
: ItemMapping<TEdgeLabel, function(IEnumerable<LabelCandidate>, LayoutEdgeLabel): void>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.
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 = centerCandidateAlternatively, 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 = candidatesSee Also
Developer's Guide
API
- EdgeLabelCandidates
Gets a mapper from edge labels to the LabelCandidates chosen by the labeling algorithm.
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.
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.DefaultEdgeLabelingPenaltiesIn 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 LabelingCostsSee 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.
nodeLabelCandidateProcessors
: ItemMapping<TNodeLabel, function(IEnumerable<LabelCandidate>, LayoutNodeLabel): void>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.
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 = centerCandidateAlternatively, 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 = candidatesSee Also
Developer's Guide
API
- NodeLabelCandidates
Gets a mapper from node labels to the LabelCandidates chosen by the labeling algorithm.
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.
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.DefaultNodeLabelingPenaltiesIn 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 LabelingCostsSee Also
Gets or sets the mapping from nodes to their margins.
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 spaceIn 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.
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.edgesFinally, 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'See Also
Developer's Guide
Gets or sets a mapping from edges to an object representing their source edge group.
See Also
Gets or sets a mapping from edges to an object representing their target edge group.
See Also
Methods
combineWith
(data: LayoutData<TNode, TEdge, TNodeLabel, TEdgeLabel>): LayoutData<TNode, TEdge, TNodeLabel, TEdgeLabel>Combines this instance with the given layout data.
combineWith
(data: LayoutData<TNode, TEdge, TNodeLabel, TEdgeLabel>): LayoutData<TNode, TEdge, TNodeLabel, TEdgeLabel>Parameters
- data: LayoutData<TNode, TEdge, TNodeLabel, TEdgeLabel>
- The LayoutData<TNode, TEdge, TNodeLabel, TEdgeLabel> to combine this instance with.
Return Value
- LayoutData<TNode, TEdge, TNodeLabel, TEdgeLabel>
- The combined layout data.
See Also
Developer's Guide
API
- CompositeLayoutData, GenericLayoutData