documentationfor yFiles for HTML 3.0.0.1

Label Placement

Nodes and edges can have one or more labels that display descriptive text for the element. To be most effective, a label should be readable and close to its element. Readability is greatly improved when the label is placed so that it doesn’t overlap other graph elements.

Layout with and without label placement
No placement: overlaps
Label placement enabled: labels are placed without overlaps

yFiles for HTML provides advanced labeling algorithms that automatically arrange labels to eliminate overlaps whenever possible, or minimize them otherwise. Labeling algorithms can be applied in two different scenarios: generic labeling and integrated labeling.

Labeling Scenarios: Generic vs. Integrated Labeling

There are two main scenarios for labeling in yFiles: generic labeling and integrated labeling.

Generic Labeling

Generic labeling places labels on a graph that already has a layout. It does not change node positions, node sizes, or edge paths. Therefore, it can be used on any kind of diagram, especially when the node positions and edge paths are fixed and should not be changed.

However, this also means that generic labeling has a disadvantage: with fixed positions and edge paths, it might not always be possible to completely prevent overlaps or always place labels at the desired positions.

Additionally, generic labeling also supports placing rotated labels. This includes both keeping a set rotation angle and finding edge label positions where the rotation is also decided by the labeling algorithm.

Integrated Labeling

Integrated labeling is directly provided by some layout algorithms as an integrated part of the layout calculation. It is applied during the layout process and therefore considers labels as part of the overall layout.

Integrated labeling has the advantage of preventing label overlaps completely. On the other hand, with integrated labeling, the overall layout may change after a label is added.

The effects of different labeling strategies are shown in the image Comparing Generic vs. Integrated Labeling. Note that generic labeling cannot resolve all overlaps. Integrated labeling, however, fixes all overlaps but alters the node positions to make space for the labels.

Comparing Generic vs. Integrated Labeling
Original
Generic labeling
Integrated labeling

Defining Valid Positions

It often makes sense to restrict the positions where a label can be placed. Label models define a set of possible positions (sometimes called candidates) or, at a minimum, restrict the placement to some extent. For edge labels, it is also possible to define which of the possible candidates should be preferred over the other candidates.

Label Models

In yFiles for HTML the placement of labels is defined by a label model. The exact position (often relative to the label’s owner) is determined by an ILabelModelParameter. The associated ILabelModel usually defines a set of valid positions where the label can be placed relative to its respective graph element. In the context of automatic label placement, all valid positions constitute “candidates” from which a labeling algorithm can choose the best match to ultimately place the label. However, only the generic labeling supports exact candidates. Integrated labeling algorithms can instead consider preferences derived from the label model.

In terms of minimizing overlaps, more candidates mean better prospects for the outcome of a generic labeling algorithm.

Labels and label models are set up using the API provided by IGraph. See also the Label Support section in Labels.

For generic labeling around a node, FreeNodeLabelModel is a much better choice than any of the discrete node label models. However, FreeNodeLabelModel allows for unspecified label positions only, while the positions of the discrete node label models are all predefined.

For generic edge labeling, FreeEdgeLabelModel or SmartEdgeLabelModel, with their large number of nearly continuous positions, is the best choice. The edge segment and edge path label models are also good choices.

Integrated labeling first computes an optimal location for the label. After the automatic layout computation is complete, the best matching label position offered by the model is then chosen based on the final location of the label. Therefore, restrictive label models especially benefit from specifying preferences based on the label models to avoid large corrections after the layout run, while less restrictive models like FreeEdgeLabelModel or SmartEdgeLabelModel usually work best.

Preferred Placement of Edge Labels

The edge label models provide only a rough definition of the label placement. For example, the EdgePathLabelModel can define the side of the edge and the distance of the label to the edge. However, it cannot restrict the position of the label along the edge, i.e., whether the label is placed near the source or the target node. Therefore, there are additional ways to define the preferred label placement for layout algorithms.

When calculating edge label positions, the specified preferences will be adhered to as much as possible by both the generic labeling algorithm and by the layout algorithms that support integrated labeling.

The Edge Label Placement demo showcases some uses of preferred placements for different graphs.

Keep the current position of the labels

The easiest way to keep labels in their current position is to turn off all labeling options. However, one might want to use integrated labeling to make space for the edge labels, or one might want to remove overlaps but keep the overall positions. In these cases, LayoutExecutor provides a global setting, labelPlacementPolicies. Policy PREFER_PARAMETER prefers label placements close to their current positions.

const layout = new HierarchicalLayout({
  edgeLabelPlacement: EdgeLabelPlacement.INTEGRATED
})
await new LayoutExecutor({
  graphComponent: graphComponent,
  layout: layout,
  labelPlacementPolicies: LabelPlacementPolicy.PREFER_PARAMETER
}).start()

Note that this setting will be overridden by a preferred placement that is defined in LayoutData<TNode,TEdge,TNodeLabel,TEdgeLabel> as shown in the next section.

Specify the preferred placement for each label

To properly configure individual preferred placement options for an edge label, a dedicated descriptor instance needs to be associated with the label. EdgeLabelPreferredPlacement instances can be mapped to edges using an ItemMapping<TItem,TValue> on the algorithms' layout data, e.g., edgeLabelPreferredPlacements for hierarchic layout’s integrated labeling.

EdgeLabelPreferredPlacement.fromModel and EdgeLabelPreferredPlacement.fromParameter create a EdgeLabelPreferredPlacement which defines the positions defined by the label model or the label model parameter, respectively.

The following example shows a label with an EdgePathLabelModel. It is configured to allow for placing the label left, right, and on the edge, e.g., via user interaction. For the integrated labeling of the hierarchic layout algorithm, however, the preferred placement is on the edge, near the source.

Specifying the preferred placement for integrated labeling of hierarchical layout
// allow for free placement along the edge
graph.edgeDefaults.labels.layoutParameter = new EdgePathLabelModel(
  10,
  0,
  0,
  true,
  EdgeSides.LEFT_OF_EDGE | EdgeSides.ON_EDGE | EdgeSides.RIGHT_OF_EDGE
).createRatioParameter()

// ...

// set up layout algorithm and preferred placement
const layout = new HierarchicalLayout({
  edgeLabelPlacement: 'integrated'
})
const layoutData = layout.createLayoutData()

// prefer placement near the source on edge for all labels
layoutData.edgeLabelPreferredPlacements.constant =
  EdgeLabelPreferredPlacement.fromParameter(
    NinePositionsEdgeLabelModel.SOURCE_CENTERED
  )

// apply the layout
await graphComponent.applyLayoutAnimated(layout, '0.2s', layoutData)

The following code snippet shows how preferred placement can be defined individually for each label:

Specifying the preferred placement for each label
// set up the preferred placement descriptor
const centerPlacement = new EdgePathLabelModel(0, 0, 0, true, 'on-edge')
const sidePlacement = new EdgePathLabelModel(
  5,
  0,
  0,
  true,
  EdgeSides.RIGHT_OF_EDGE | EdgeSides.LEFT_OF_EDGE
)
const labeling = new GenericLabeling()
const labelingData = labeling.createLayoutData(graph)
labelingData.edgeLabelPreferredPlacements.mapperFunction = (label) =>
  label.tag === 'Centrality'
    ? EdgeLabelPreferredPlacement.fromModel(centerPlacement)
    : EdgeLabelPreferredPlacement.fromModel(sidePlacement)

// apply the layout
await graphComponent.applyLayoutAnimated(labeling, '0.2s', labelingData)

Manually configuring EdgeLabelPreferredPlacement instances

The examples above show how an EdgeLabelPreferredPlacement can be obtained from a given ILabelModel or ILabelModelParameter. Class EdgeLabelPreferredPlacement can also be instantiated directly, which provides access to more options that are not supported by label models.

Associating EdgeLabelPreferredPlacements to configure preferred placement options for edge labels
// set up generic label placement algorithm
const labeling = new GenericLabeling({
  scope: LabelingScope.EDGE_LABELS,
  deterministic: true
})
// set up the preferred placement descriptor
const labelingData = new GenericLabelingData({
  edgeLabelPreferredPlacements: (label) => {
    const edgeLabelPreferredPlacement = new EdgeLabelPreferredPlacement()
    if (label.tag == 'OnEdge') {
      edgeLabelPreferredPlacement.edgeSide = LabelEdgeSides.ON_EDGE
    } else {
      edgeLabelPreferredPlacement.edgeSide =
        LabelEdgeSides.RIGHT_OF_EDGE | LabelEdgeSides.LEFT_OF_EDGE
      edgeLabelPreferredPlacement.distanceToEdge = 5
    }

    return edgeLabelPreferredPlacement
  }
})

// apply the layout
await graphComponent.applyLayoutAnimated(labeling, '0.2s', labelingData)

Preferred placement options include, for example, whether an edge label should be positioned

  • on or to the side of the path of its edge
  • near the source node, at the center of the edge path, or near the target node

Preferred rotation behavior includes, for example, whether

  • the edge label’s rotation angle should be interpreted absolute or relative to the slope of its corresponding edge segment
  • the rotation angle of an edge label on the right side of its edge should be additionally modified to achieve a mirrored orientation compared to an otherwise identically configured edge label on the left

The rotation preferences configured by class EdgeLabelPreferredPlacement only make sense when the edge label is associated with a label model that supports label rotation and that does not restrict edge label placement to discrete positions. In particular, this applies to the "free" label models FreeEdgeLabelModel or SmartEdgeLabelModel, but also to the edge path or edge segment label models.

An edge label can be placed at the side of the path of its edge. The actual side can be specified both absolute and relative:

Absolute and relative edge label placement
Absolute placement: left of the edge path when looking at the diagram.
Relative placement: left of the edge path when looking from source to target along the edge path.

Note that the sideReference property provides the means to resolve the special case when the edge label’s side is specified as an absolute placement and its corresponding edge segment happens to be horizontal, i.e., when there is no actual left or right "side."

The properties to configure preferred rotation behavior:

angle
angleReference
angleRotationOnRightSide

The arrow-shaped gradient in the background of the label denotes the orientation of its box. In the relative rotation example, the text of the left label appears flipped because it is automatically rendered to be readable, despite the underlying box orientation.

Absolute and relative edge label rotation
Absolute rotation: 0 degree when looking at the diagram.
Relative rotation: 0 degree added to the slope of the edge segment that is nearest to the edge label.

Generic Labeling

A GenericLabeling computes label positions for the labels from a given graph so that they ideally do not overlap with each other or with graph elements. It does so without altering nodes or edges in any way.

The set of labels that a generic labeling algorithm processes can be restricted to node labels, edge labels, or even custom subsets of labels:

scope
The GenericLabeling’s scope property can restrict the affected labels to either node labels or edge labels.
scope
The GenericLabelingData’s scope property allows creating custom subsets of affected labels.

Specifying affected labels
const labeling = new GenericLabeling()
labeling.scope = LabelingScope.EDGE_LABELS
Specifying affected labels using LayoutData
const data = labeling.createLayoutData(graph)

// Set all selected node labels as affected
data.scope.nodeLabels.source = graphComponent.selection.labels.filter(
  (label) => label.owner instanceof INode
)
// Set all labels of selected edges as affected
data.scope.edgeLabels.source = graphComponent.selection.edges.flatMap(
  (edge) => edge.labels
)

await graphComponent.applyLayoutAnimated(labeling, '0.2s', data)

The labeling algorithm places labels based on candidate positions, choosing, for each label, the candidate that allows for the best overall arrangement of labels. If models are defined for the labels of the IGraph, the valid label positions are automatically retrieved from those label models unless custom candidate positions are specified. If neither custom candidates nor models are available to provide valid candidate positions, the labels are placed as if they had a FreeNodeLabelModel or FreeEdgeLabelModel respectively.

All major layout algorithms have a property LayoutStages through which a generic labeling post-processing step can be enabled, disabled, or modified. Additionally, the generic labeling can be conveniently enabled by setting the algorithm’s NodeLabelPlacement and/or EdgeLabelPlacement property to "generic":

Enabling the generic labeling stage
//this hierarchical layout should place edge labels but let a GenericLabeling stage place the node labels
const hierarchicalLayout = new HierarchicalLayout()
hierarchicalLayout.nodeLabelPlacement = NodeLabelPlacement.GENERIC
hierarchicalLayout.edgeLabelPlacement = EdgeLabelPlacement.INTEGRATED

Configuring the generic labeling stage
const labeling = layoutStages.get(GenericLabeling)
if (labeling) {
  labeling.reduceLabelOverlaps = true
  labeling.enabled = true
}

The labeling algorithm is then invoked after the actual layout algorithm has finished.

Specifying Candidate Positions

Instead of relying on the automatically retrieved candidates from a label’s model, the generic labeling algorithm can also use specified NodeLabelCandidates and EdgeLabelCandidates. This approach can be useful in scenarios such as:

  • Working with a LayoutGraph that does not support label models.
  • When the label model is a free model, but the set of valid positions should be temporarily restricted.

To specify candidate sets, the GenericLabelingData<TNode,TEdge,TNodeLabel,TEdgeLabel>'s properties nodeLabelCandidates and edgeLabelCandidates can be used.

Specifying edge label candidates for generic labeling
const data = new GenericLabelingData({
  edgeLabelCandidates: (label) =>
    label.layoutParameter.model instanceof FreeLabelModel
      ? new EdgeLabelCandidates().addSliderCandidates(
          EdgeLabelSliderMode.CENTER
        )
      : null // 'null' means that the label's actual label model is used.
})

Integrated Labeling

Integrated labeling refers to automatic label placement that is an integral part of a layout algorithm. Most algorithms support either integrated placement of node labels or edge labels. All placed labels of the graph are arranged to avoid overlaps between edge labels and other graph elements.

Layout support for integrated labeling lists the major layout algorithms that support integrated labeling.

Layout support for integrated labeling
Layout Style Class Name Note
HierarchicalHierarchicalLayoutSupports integrated edge labeling. See the description of the hierarchical layout style for more information.
OrthogonalOrthogonalLayoutSupports integrated edge labeling. See the description of the orthogonal layout for more information.
TreeTreeLayout, andSupports integrated edge labeling. See the description of the tree layout
Radial TreeRadialTreeLayoutSupports integrated node labeling and integrated edge labeling. See the description of the radial tree layout for more information.
CircularCircularLayoutRadialLayoutSupports integrated node labeling. See the descriptions of the circular layout and radial layout for more information.
Series-parallelSeriesParallelLayoutSupports integrated edge labeling. See the description of the series-parallel layout for more information.
Edge RoutingEdgeRouterSupports integrated edge labeling. See the description of edge routing for more information.

Integrated labeling is enabled using the corresponding nodeLabelPlacement and edgeLabelPlacement properties of the layout algorithms that support it. Note that integrated node labeling is usually offered in multiple styles such as "horizontal", "ray-like" or "ray-like leaves". For example:

// Enabling integrated node and edge labeling.
const radialLayout = new RadialTreeLayout()
radialLayout.nodeLabelPlacement = 'ray-like'
radialLayout.edgeLabelPlacement = 'integrated'