documentationfor yFiles for HTML 2.6

Automatic Label Placement

Nodes and edges can have one or more labels that can be used to show descriptive text for the element. The label’s purpose is served best when it is both readable and also near its element, where readability most importantly results from the label being placed so that it does not overlap with other graph elements.

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

The yFiles library offers advanced labeling algorithms that automatically generate label arrangements that, whenever feasible, completely eliminate all overlaps, or minimize the number thereof otherwise. Labeling algorithms can be applied in two different scenarios: generic labeling and integrated labeling.

Labeling Scenarios: Generic vs. Integrated Labeling

Generic labeling

Generic labeling places the labels on a graph with an existing layout. It does not alter node positions, node sizes, or edge paths. Therefore, it can be applied on any kind of diagram, especially where the node positions and edge paths are fixed and should not be altered.

This is also the cause for the greatest disadvantage of generic labeling: with the fixed positions and edge paths it is not always possible to prevent overlaps completely or always place labels at the preferred positions.

Additionally, generic labeling also supports placement of rotated labels, which covers both maintaining a preset rotation angle as well as finding edge label positions where the rotation is determined by the labeling algorithm, too.

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 allows for considering labels as part of the overall layout.

Integrated labeling’s advantage is that, by design, it can prevent label overlaps completely. On the other hand, with integrated labeling it is possible that the overall layout changes after a label has been added.

The effects of the different labeling scenarios are shown in the image Comparing Generic vs. Integrated Labeling. Note that generic labeling cannot resolve all overlaps. On the other hand, integrated labeling fixes all overlaps but alters the node layout to insert space for the labels.

Comparing Generic vs. Integrated Labeling
Original
Generic labeling
Integrated labeling

Defining valid positions

It often makes sense to restrict the possible positions a label is placed on. Label models often define a set of possible positions (also known as candidates) or at least restrict the placement to some extent. It is also possible to define which among the possible candidates should be preferred over the other candidates.

Label Models

In yFiles for HTML the placement of labels is defined by a so-called label model. Whereas 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 are at the same time “candidates” among which a labeling algorithm can choose the best match to ultimately place the label.

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

For both generic labeling as well as integrated edge labeling, 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 for a node label model 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 for an edge label model, followed by the the edge segment and edge path label models.

Integrated labeling first computes an optimal location for the label, and then tries to find the best matching label position for that location in the given label model. This also works best with FreeEdgeLabelModel or SmartEdgeLabelModel, followed by the edge segment and edge path label models.

Preferred Placement of Edge Labels

The edge label models provide only a rough definition of the label placement. While a EdgePathLabelModel can define the side of the edge and the distance of the label to the edge, 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.

When calculating edge label positions, the specified settings will be adhered to as good as possible by both the generic labeling algorithms as well as by the layout algorithms that support integrated labeling.

Keep the current position of the labels

The easiest way to keep labels at their current postion 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 case, LayoutExecutor provides a global setting, labelPreferredPlacementPolicy. Policy FROM_PARAMETER prefers label placements close to their current positions.

const layout = new HierarchicLayout({ integratedEdgeLabeling: true })
await new LayoutExecutor({
  graphComponent: graphComponent,
  layout: layout,
  labelPreferredPlacementPolicy: 'from-parameter'
}).start()

Note that this setting will be overridden by a preferred placement that is defined in LayoutData as shown in the next section.

Define 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. PreferredPlacementDescriptor instances can be mapped to edges using an ItemMapping<TItem,TValue> on the algorithms' LayoutData, e.g. edgeLabelPreferredPlacement for hierarchic layout’s integrated labeling.

PreferredPlacementDescriptor.fromModel and PreferredPlacementDescriptor.fromParameter create a PreferredPlacementDescriptor 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 to place 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.

Defining the preferred placement for integrated labeling of hierarchic 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
).createDefaultParameter()

// ...

// set up layout algorithm and preferred placement
const labeling = new HierarchicLayout({ integratedEdgeLabeling: true })
const labelingData = new HierarchicLayoutData({
  // prefer placement near the source on edge for all labels
  edgeLabelPreferredPlacement: PreferredPlacementDescriptor.fromParameter(
    NinePositionsEdgeLabelModel.SOURCE_CENTERED
  )
})

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

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

Defining the preferred placement for each label
// set up generic label placement algorithm
const labeling = new GenericLabeling({
  placeEdgeLabels: true,
  placeNodeLabels: false,
  deterministic: true
})
// set up the preferred placement descriptor
const labelingData = new LabelingData({
  edgeLabelPreferredPlacement: (label) =>
    label.tag === 'Centrality'
      ? PreferredPlacementDescriptor.fromModel(new EdgePathLabelModel(0, 0, 0, true, 'on-edge'))
      : PreferredPlacementDescriptor.fromModel(
          new EdgePathLabelModel(5, 0, 0, true, EdgeSides.RIGHT_OF_EDGE | EdgeSides.LEFT_OF_EDGE)
        )
})

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

Define PreferredPlacementDescriptor instances

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

Associating PreferredPlacementDescriptors to configure preferred placement options for edge labels
// set up generic label placement algorithm
const labeling = new GenericLabeling({
  placeEdgeLabels: true,
  placeNodeLabels: false,
  deterministic: true
})
// set up the preferred placement descriptor
const labelingData = new LabelingData({
  edgeLabelPreferredPlacement: (label) => {
    const preferredPlacementDescriptor = new PreferredPlacementDescriptor()
    if (label.tag == 'OnEdge') {
      preferredPlacementDescriptor.sideOfEdge = 'on-edge'
    } else {
      preferredPlacementDescriptor.sideOfEdge = LabelPlacements.RIGHT_OF_EDGE | LabelPlacements.LEFT_OF_EDGE
      preferredPlacementDescriptor.distanceToEdge = 5
    }

    return preferredPlacementDescriptor
  }
})

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

Preferred placement options include, for example, whether an edge label shall 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 shall 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 shall be additionally modified to achieve a mirrored orientation compared to an otherwise identically configured edge label on the left

The edge label configurations supported by class PreferredPlacementDescriptor 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.

The following property defined in interface IEdgeLabelLayout returns the PreferredPlacementDescriptor instance that is associated with an edge label:

preferredPlacementDescriptor

By default, the PreferredPlacementDescriptor instance that is associated with actual implementations of interface IEdgeLabelLayout, like, e.g., instances of class EdgeLabelLayoutImpl, is immutable. The immutability is a direct consequence of the descriptor instance being shared among newly created edge labels.

Every attempted assignment to a property of such an immutable PreferredPlacementDescriptor instance will yield an exception.

To properly configure individual preferred placement options for an edge label, a dedicated descriptor instance needs to be associated with the label. The following code snippet shows how this can be achieved:

Creating and associating a new PreferredPlacementDescriptor to configure individual preferred placement options for an edge label
// prepare the IGraph:
// register a mapper for PreferredPlacementDescriptors at the IGraph
graph.mapperRegistry.createMapper(
  ILabel.$class,
  PreferredPlacementDescriptor.$class,
  LayoutGraphAdapter.EDGE_LABEL_LAYOUT_PREFERRED_PLACEMENT_DESCRIPTOR_DP_KEY
)

// ...

// get the descriptor mapper from the IGraph
const descriptorMapper = graph.mapperRegistry.getMapper(
  LayoutGraphAdapter.EDGE_LABEL_LAYOUT_PREFERRED_PLACEMENT_DESCRIPTOR_DP_KEY
)

// create a preferred placement for the edge label...
const preferredPlacement = new PreferredPlacementDescriptor()
// ... and configure it
preferredPlacement.sideOfEdge = LabelPlacements.AT_CENTER
// ...
// Make it immutable (optional).
preferredPlacement.freeze()

// create a label and set the PreferredPlacementDescriptor
const edgeLabel = graph.addLabel(edge, 'MyLabel')
descriptorMapper.set(edgeLabel, preferredPlacement)
// prepare the IGraph:
// register a mapper for PreferredPlacementDescriptors at the IGraph
graph.mapperRegistry.createMapper(
  ILabel.$class,
  PreferredPlacementDescriptor.$class,
  LayoutGraphAdapter.EDGE_LABEL_LAYOUT_PREFERRED_PLACEMENT_DESCRIPTOR_DP_KEY
)

// ...

// get the descriptor mapper from the IGraph
const descriptorMapper = graph.mapperRegistry.getMapper<ILabel, PreferredPlacementDescriptor>(
  LayoutGraphAdapter.EDGE_LABEL_LAYOUT_PREFERRED_PLACEMENT_DESCRIPTOR_DP_KEY
)

// create a preferred placement for the edge label...
const preferredPlacement = new PreferredPlacementDescriptor()
// ... and configure it
preferredPlacement.sideOfEdge = LabelPlacements.AT_CENTER
// ...
// Make it immutable (optional).
preferredPlacement.freeze()

// create a label and set the PreferredPlacementDescriptor
const edgeLabel = graph.addLabel(edge, 'MyLabel')
descriptorMapper!.set(edgeLabel, preferredPlacement)

The properties of class PreferredPlacementDescriptor to configure preferred placement options are:

sideOfEdge
sideReference
distanceToEdge
placeAlongEdge
Preferred placement options.

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
Preferred rotation behavior.

The arrow-shaped gradient in the background of the label denotes the orientation of its box. Note that in the right figure below the text of the left label is flipped: according to its box’s orientation, the text would actually be upside-down, but to be readable it is automatically rendered correctly.

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 only or to edge labels only:

placeNodeLabels
placeEdgeLabels
Properties to restrict the set of labels.

GenericLabeling can deal with label models that support rotated labels. It supports placing rotated labels using edge label positions that retain a preset rotation angle as well as using edge label positions where the rotation is adjusted to the slope of a corresponding edge segment.

Optionally, it can also be configured to auto-flip edge labels to avoid that their text is upside-down:

autoFlipping
Configures auto-flipping for upside-down edge labels.

The labeling algorithm uses the model that is associated with a label to get the available candidate positions. From this set of candidates it then chooses one that best matches the label position it has calculated. The label model’s model parameter is then used to encode this position.

Generic labeling can be configured to place only a subset of labels by setting affectedLabels on LabelingData.

LabelingData.affectedLabels
A collection of labels that shall be placed by the algorithm.

const labeling = new GenericLabeling({
  placeNodeLabels: false,
  placeEdgeLabels: true,
  autoFlipping: true
})
const data = new LabelingData({
  affectedLabels: graphComponent.selection.selectedLabels
})

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

The base class for all major layout algorithms, class MultiStageLayout, allows to conveniently enable/disable generic labeling for each layout run. The labeling algorithm is then invoked after the actual layout algorithm has finished. Additionally, the labeling algorithm itself can also be set. Class MultiStageLayout provides the following labeling-related properties:

labelingEnabled
Allows to enable/disable generic as well as integrated labeling.
labeling
Determines the actual labeling algorithm to be used.

Using an Alternate Label Model

Instead of a label’s actual label model, the generic labeling algorithms can also use an alternate label model when calculating valid positions for it. This can be useful if the original label model is a free model, but the set of valid positions should be temporarily restricted, for example, to guarantee that an edge label is placed onto its associated edge.

To specify the alternate label models, LabelingData's properties nodeLabelModels and edgeLabelModels can be used:

Using alternate edge label models for labeling
const data = new LabelingData({
  edgeLabelModels: (label) =>
    label.layoutParameter.model instanceof FreeLabelModel
      ? new SliderEdgeLabelLayoutModel(SliderMode.CENTER)
      : null // 'null' means that the label's actual label model is used.
})

One has to use the yFiles for HTML layout label models here,i.e. one of the models that implements INodeLabelLayoutModel or IEdgeLabelLayoutModel.

Layout Data

When using the generic labeling algorithm , supplemental layout data for a graph’s elements can be specified either by using class LabelingData or by registering data providers with the graph using given look-up keys. Supplemental layout data lists all properties of LabelingData and the corresponding look-up keys that GenericLabeling tests during the layout process in order to query supplemental data.

Providing supplemental layout data is described in detail in Layout Data.

Supplemental layout data

Integrated Labeling

Integrated labeling denotes automatic edge label placement as an integrated part of a layout algorithm. All edge labels of a graph are arranged in such a manner that there are no overlaps of edge labels with each other or with graph elements.

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

Layout support for integrated labeling
Layout Style Class Name Note
HierarchicalHierarchicLayoutWith class HierarchicLayout, integrated labeling can be conveniently enabled using appropriate configuration properties. See the description of the hierarchical layout style for more information.
OrthogonalOrthogonalLayoutSee the descriptions of orthogonal layout for more information.
TreeClassicTreeLayout, TreeLayout, and BalloonLayoutIntegrated labeling can conveniently be enabled using appropriate configuration properties. See the descriptions of directed tree layout, generic tree layout, and balloon layout for more information.
Series-parallelSeriesParallelLayoutSee the description of the series-parallel layout for more information.
Polyline Edge RoutingEdgeRouterSee the description of polyline edge routing for more information.

Integrated labeling is most conveniently triggered using the corresponding properties of the layout algorithms that support it. For example:

const hierarchic = new HierarchicLayout()

// Enabling integrated edge labeling.
hierarchic.integratedEdgeLabeling = true