C

GenericLabelingData<TNode, TEdge, TNodeLabel, TEdgeLabel>

Specifies custom data for the GenericLabeling.
Inheritance Hierarchy

Members

Show:

Constructors

Parameters

Properties

Gets or sets a mapping from edge labels to a delegate for modifying the weights of their LabelCandidates.

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.

final

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:

Adjusting the weight to a previously unknown layout result
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:

Combining custom weights and built-in weight 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:

Adjusting the weight based on view properties of the label
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

API
nodeLabelCandidateProcessors
Gets or sets a mapping from edge labels to their EdgeLabelCandidates.
The candidates provided by edgeLabelCandidates are used by the GenericLabeling algorithm and specify valid candidate positions for the label placement.
conversionfinal

Examples

Edge label candidates offer different convenience methods to specify candidates for the labels. You can use configurable methods like addDiscreteCandidates:

Setting a centered label candidate
const centerCandidate = new EdgeLabelCandidates().addDiscreteCandidates(
  DiscreteEdgeLabelPositions.CENTER,
)
layoutData.edgeLabelCandidates = centerCandidate

Alternatively, you can specify your own custom candidate positions for a label:

Setting a custom candidate position for one 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:

Setting composite factories to provide fallback candidates
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

Developer's Guide
API
EdgeLabelCandidates
Gets a mapper from edge labels to the LabelCandidates chosen by the labeling algorithm.
This property can be used after layout to retrieve the chosen candidates for each edge label.
readonlyfinal

Examples

Retrieving chosen candidates after the labeling
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

API
nodeLabelCandidatesResult
Gets or sets a mapping from edge labels to their LabelingCosts.
The GenericLabeling algorithm chooses from the set of valid label positions. The LabelingCosts can be used to specify costs for layout properties of different positions. For example, a high cost can be specified for positions where a label would cross an edge, making such positions less likely to be chosen by the algorithm.
The costs are interpreted relative to one another and can therefore only be used to prioritize desired layout qualities over other, less desired, qualities.
conversionfinal

Examples

The easiest option is to set the same costs for all edge labels, by setting a constant LabelingCosts instance:

Using constant costs for all labels
layoutData.edgeLabelingCosts = new LabelingCosts(
  LabelingOptimizationStrategy.LABEL_OVERLAPS,
)

Handling only certain labels differently can be done easily by using the mapper property:

Using a mapper to set costs for certain labels
// 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:

Using a delegate to determine costs for all labels
// Retrieve the penalties of an edge label from its tag property
layoutData.edgeLabelingCosts = (label) => label.tag as LabelingCosts

See Also

API
LabelingCosts, EdgeLabelCandidates
Gets or sets the mapping that provides an EdgeLabelPreferredPlacement instance for edge labels.
conversionfinal

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:

Setting the same EdgeLabelPreferredPlacement for all labels
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:

Customizing the placement of certain labels individually via a mapper
// 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:

Configuring preferred placement for all labels with a delegate
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:

Configuring preferred placement from a label model parameter
layoutData.edgeLabelPreferredPlacements =
  EdgeLabelPreferredPlacement.fromParameter(
    NinePositionsEdgeLabelModel.CENTER_CENTERED,
  )

See Also

API
EdgeLabelPreferredPlacement, EDGE_LABEL_PREFERRED_PLACEMENT_DATA_KEY
Gets or sets a mapping from node labels to a delegate for modifying the weights of their LabelCandidates.

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.

final

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:

Adjusting the weight to a previously unknown layout result
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:

Combining custom weights and built-in weight 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:

Adjusting the weight based on view properties of the label
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

API
edgeLabelCandidateProcessors
Gets or sets a mapping from node labels to their NodeLabelCandidates.
The candidates provided by nodeLabelCandidates are used by the GenericLabeling algorithm and specify valid candidate positions for the label placement.
conversionfinal

Examples

Node label candidates offer different convenience methods to specify candidates for the labels. You can add candidates using configurable methods like addDiscreteCandidates:

Setting a centered label candidate
const centerCandidate = new NodeLabelCandidates().addDiscreteCandidates(
  DiscreteNodeLabelPositions.CENTER,
)
layoutData.nodeLabelCandidates = centerCandidate

Alternatively, you can specify your own custom candidate positions for a label:

Setting a custom candidate position for one 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:

Setting composite factories to provide fallback candidates
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

Developer's Guide
API
NodeLabelCandidates
Gets a mapper from node labels to the LabelCandidates chosen by the labeling algorithm.
This property can be used after layout to retrieve the chosen candidates for each node label.
readonlyfinal

Examples

Retrieving chosen candidates after the labeling
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

API
edgeLabelCandidatesResult
Gets or sets a mapping from node labels to their LabelingCosts.
The GenericLabeling algorithm chooses from the set of valid label positions. The LabelingCosts can be used to specify costs for layout properties of different positions. For example, a high cost can be specified for positions where a label would cross an edge, making such positions less likely to be chosen by the algorithm.
The costs are interpreted relative to one another and can therefore only be used to prioritize desired layout qualities over other, less desired, qualities.
conversionfinal

Examples

The easiest option is to set the same costs for all node labels, by setting a constant LabelingCosts instance:

Using constant costs for all labels
layoutData.nodeLabelingCosts = new LabelingCosts(
  LabelingOptimizationStrategy.LABEL_OVERLAPS,
)

Handling only certain labels differently can be done easily by using the mapper property:

Using a mapper to set costs for certain labels
// 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:

Using a delegate to determine costs for all labels
// Retrieve the descriptor of an edge label from its tag property
layoutData.nodeLabelingCosts = (label) => label.tag as LabelingCosts

See Also

API
LabelingCosts, nodeLabelCandidates
Gets or sets the mapping from nodes to their margins.
Node margins allow to reserve space around nodes.
conversionfinal

Examples

The easiest option is to reserve the same space around all nodes, by setting a constant value:

Using constant space around all nodes
layoutData.nodeMargins = new Insets(20)

Handling only certain nodes differently can be done easily by using the mapper property:

Using a mapper to set margins for certain nodes
// 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:

Using a delegate to determine margins for all nodes
// Retrieve the space around the node from its tag property
layoutData.nodeMargins = (node: INode): Insets =>
  new Insets(parseFloat(node.tag))

See Also

API
NODE_MARGIN_DATA_KEY
Gets or sets the sub-data that provides the possibility of modifying the scope of the GenericLabeling algorithm with respect to the placed labels.
The labels to be placed can be specified using the ItemCollection properties nodeLabels and edgeLabels.
The GenericLabeling's scope property can be used to conveniently restrict the scope to only LayoutNodeLabels and LayoutEdgeLabels. If combined with this scope specification, the scope policy acts as an additional restriction, further reducing the labels that are in scope.
readonlyfinal

Examples

The scope can be specified using the items property when only a few known labels should be affected:

Setting labels as affected individually
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:

Using a delegate to set labels as affected
// 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:

Using a delegate to specify owners of affected labels
// 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:

Specifying affected labels based on their owners and 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.
Source and target edge groups are only used for labeling in conjunction with the intersectsOwnEdgeGroup indicating if a label candidate overlaps an edge from the same group. Edges don't have to share parts of the same path for this purpose (as would be the result from other layout algorithms that use edge grouping). Specifying source and target groups will not change edge paths, nor will the labels be placed any differently. It is mainly a tool to allow selective label/edge overlaps in certain cases.
conversionfinal

See Also

API
SOURCE_EDGE_GROUP_ID_DATA_KEY
Gets or sets a mapping from edges to an object representing their target edge group.
Source and target edge groups are only used for labeling in conjunction with the intersectsOwnEdgeGroup indicating if a label candidate overlaps an edge from the same group. Edges don't have to share parts of the same path for this purpose (as would be the result from other layout algorithms that use edge grouping). Specifying source and target groups will not change edge paths, nor will the labels be placed any differently. It is mainly a tool to allow selective label/edge overlaps in certain cases.
conversionfinal

See Also

API
TARGET_EDGE_GROUP_ID_DATA_KEY

Methods

Combines this instance with the given layout data.
This keeps the current instance unmodified and instead returns a new instance that dynamically combines the contents of all involved instances.
final

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