C

OrthogonalLayoutData<TNode, TEdge, TNodeLabel, TEdgeLabel>

Specifies custom data for the OrthogonalLayout.
Inheritance Hierarchy

Members

Show:

Constructors

Parameters

Properties

Gets or sets the mapping from edges to their bend cost.
The bend costs of the edges are considered during the bend minimization phase. Edges with lower costs are more likely to have bends. The default bend cost of an edge is 1, which is used for edges that do not have an individual bend cost.
The implemented bend minimization is a heuristic and, thus, does not guarantee an optimal solution.
Avoid using very large cost values. Due to the fact that costs need to be multiplied and summed up, this might lead to overflows and undesired results.
conversionfinal

Examples

When there are only a few edges to customize bend costs for, the easiest way is usually to use the mapper:

Defining edge bend costs via a mapper
// Try harder to prevent bends for edge1
layoutData.edgeBendCosts.mapper.set(edge1, 1.5)
// Bends for edge2 are not as bad
layoutData.edgeBendCosts.mapper.set(edge2, 0.2)

If the bend cost can readily be computed from the edge itself, the mapperFunction is often the more convenient option:

Defining edge bend costs via a delegate
// The more labels an edge has, the more important it is to
// prevent bends
layoutData.edgeBendCosts = (edge: IEdge): number => 1 + edge.labels.size

See Also

Developer's Guide
API
EDGE_BEND_COST_DATA_KEY
Gets or sets the mapping from edges to their crossing cost.
Edges with lower costs are more likely to have crossings. A crossing with an edge that has a cost of zero is cost-free and such a crossing will therefore not be avoided. The default crossing cost of an edge is 1, which is used for edges which do not have an individual crossing cost.
The implemented crossing minimization is a heuristic. Therefore, there is no guarantee that crossing a certain edge is actually avoided, even though it has high crossing costs.
Avoid using very large cost values. Due to the fact that costs need to be multiplied and summed up, this might lead to overflows and undesired results.
conversionfinal

Examples

When there are only a few edges to customize crossing costs for, the easiest way is usually to use the mapper:

Defining edge crossing costs via a mapper
// Try harder to prevent crossings with edge1
layoutData.edgeCrossingCosts.mapper.set(edge1, 1.5)
// Crossings with edge2 are not as bad
layoutData.edgeCrossingCosts.mapper.set(edge2, 0.2)

If the crossing cost can readily be computed from the edge itself, the mapperFunction is often the more convenient option:

Defining edge crossing costs via a delegate
// The more labels an edge has, the more important it is to
// prevent crossing with it
layoutData.edgeCrossingCosts = (edge: IEdge): number =>
  1 + edge.labels.size

See Also

Developer's Guide
API
EDGE_CROSSING_COST_DATA_KEY
Gets or sets the mapping from edges to their OrthogonalLayoutEdgeDescriptor.
If an edge is mapped to null, the default descriptor is used.
Minimum edge length constraints can only be considered when using layoutMode STRICT.
conversionfinal

See Also

Developer's Guide
API
OrthogonalLayoutEdgeDescriptor, EDGE_DESCRIPTOR_DATA_KEY
Gets or sets the mapping from edges to their directedness, which is considered for the detection of substructures.

The directedness is only considered for the detection of substructures in the input graph i.e., trees, chains, and cycles. A substructure is only identified as such if all edges are either undirected or consistently directed with respect to the specified directedness.

  • A directedness value of 1 indicates that the edge is considered to be directed from source to target.
  • A directedness value of -1 indicates that the edge is considered to be directed from target to source.
  • A directedness value of 0 indicates that the edge is considered to be undirected.

All edges are undirected by default.

If left unspecified, the algorithm assumes that all edges have directedness 0.
The edge directedness should not be confused with the definition of edgeOrientation. The latter one marks edges that are directed orthogonal and shall point in the specified layoutOrientation. On the other hand, the directedness solely affects the detection of substructures.
conversionfinal

Examples

The easiest option is to define all edges with the same directedness:

Treating all edges as directed
layoutData.edgeDirectedness = 1

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

Using a mapper to set directedness for certain edges
// edge1 should be considered directed
layoutData.edgeDirectedness.mapper.set(edge1, 1)
// edge2 should be considered directed against the flow
layoutData.edgeDirectedness.mapper.set(edge2, -1)
// All other edges not set in the mapper are treated as undirected

In cases where the directedness for each edge can be determined by looking at the edge itself it's often easier to just set a delegate instead of preparing a mapper:

Using a delegate to determine edge directedness
// Treat edges as directed or undirected based on their style's arrowhead
layoutData.edgeDirectedness = (edge: IEdge): 0 | 1 | -1 => {
  const style = edge.style as PolylineEdgeStyle
  // edges with either no arrows on both ends or an arrow on both ends should be considered undirected
  if (
    (style.sourceArrow === null && style.targetArrow === null) ||
    (style.sourceArrow !== null && style.targetArrow !== null)
  ) {
    return 0
  }
  // edges with only a target arrow are directed from source to target
  if (style.targetArrow !== null) {
    return 1
  }
  // edges with only a source arrow are directed from target to source
  if (style.sourceArrow !== null) {
    return -1
  }
  return 0
}

See Also

Developer's Guide
API
EDGE_DIRECTEDNESS_DATA_KEY
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 descriptor 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 the mapping from edges to their orientation, specifying how they should be routed with respect to the main layout direction.

The orientation of an edge is 1 if it should be routed in the main layout direction, -1 if it should be routed against the main layout direction, or 0 if it should be routed independently of the main layout direction.

The main layout orientation can be set using method layoutOrientation.

The edge orientation will be ignored if group nodes exist in the graph.
conversionfinal

Examples

The easiest option is to define all edges with the same orientation:

// Specify all edges to be oriented in the main layout direction
layoutData.edgeOrientation = 1

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

layoutData.edgeOrientation.mapper.set(edge1, 1)
layoutData.edgeOrientation.mapper.set(edge2, -1)

In cases where the directedness for each edge can be determined by looking at the edge itself, it's often easier to just set a delegate instead of preparing a mapper:

layoutData.edgeOrientation = (edge) => edge.tag.edgeOrientation

See Also

Developer's Guide
API
EDGE_ORIENTATION_DATA_KEY
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

Developer's Guide
API
NODE_MARGIN_DATA_KEY
Gets or sets the mapping from nodes to an object defining the node type, which is considered for the detection of tree, chain, and cycle substructures.

If node types are defined, only nodes of the same type can form a substructure.

Node types are only considered when using the substructure features treeSubstructureStyle, chainSubstructureStyle, or cycleSubstructureStyle.

conversionfinal

See Also

Developer's Guide
API
NODE_TYPE_DATA_KEY
Gets or sets the sub-data that provides a way of influencing the placement of the ports.

The port placement can be influenced by specifying EdgePortCandidates for the source and target of an edge, as well as by specifying NodePortCandidates at the nodes.

In addition, it is possible to specify that ports should be grouped at the source or target.

If both EdgePortCandidates and NodePortCandidates are specified, the layout algorithm tries to match them. An edge port candidate matches a node port candidate if

  • their matchingIds are equal or one type is null,
  • they belong to a common side or one side is ANY, and
  • if both candidates are fixed, they describe the same positions.

The position of a port candidate is defined by offset or the actual offset of the edge endpoint for fixed-from-sketch candidates. When there is no match, the port candidate with the lowest costs specified for the edge is chosen.

The OrthogonalLayout does not natively support port placement constraints. Support is provided through additional pre- and postprocessing by the PortPlacementStage, which is added to the layoutStages and enabled by default. To further improve the outcome you may consider applying an edge routing algorithm as an additional post-processing step.
final
Gets or sets a mapping from edges to an object representing their source edge group.
Edges can be grouped so that they share common segments at the beginning or end of their routes. Although a graph may contain source and target grouped edges, an edge can only be part of either a source or a target group.
Edge groups will be ignored if group nodes exist in the graph.
conversionfinal

Examples

One simple way to use source groups is to use the edge's source node as group ID which effectively groups all edges with the same source together:

Grouping all edges on the source side
layoutData.sourceGroupIds = (edge: IEdge) => edge.sourceNode

Another useful way to use a delegate here would be grouping edges by some commonality, such as the same color:

Grouping edges by color
layoutData.sourceGroupIds = (edge: IEdge) => {
  const style = edge.style
  if (style instanceof PolylineEdgeStyle) {
    return style.stroke!.fill
  }
  return null
}

If only certain edges should be grouped it may sometimes be easier to use the mapper to set the group IDs:

Grouping certain edges with a mapper
for (const group of edgeGroups) {
  for (const edge of group) {
    // Use the collection as group ID, since it's common to all edges in it
    layoutData.sourceGroupIds.mapper.set(edge, group)
  }
}

See Also

Developer's Guide
API
targetGroupIds, SOURCE_EDGE_GROUP_ID_DATA_KEY
Gets or sets a mapping from edges to an object representing their target edge group.
Edges can be grouped so that they share common segments at the beginning or end of their routes. Although a graph may contain source and target grouped edges, an edge can only be part of either a source or a target group.
Edge groups will be ignored if group nodes exist in the graph.
conversionfinal

Examples

One simple way to use source groups is to use the edge's target node as group ID which effectively groups all edges with the same target together:

Grouping all edges on the target side
layoutData.targetGroupIds = (edge: IEdge) => edge.targetNode

Another useful way to use a delegate here would be grouping edges by some commonality, such as the same color:

Grouping edges by color
layoutData.targetGroupIds = (edge: IEdge) => {
  const style = edge.style
  if (style instanceof PolylineEdgeStyle) {
    return style.stroke!.fill
  }
  return null
}

If only certain edges should be grouped it may sometimes be easier to use the mapper to set the group IDs:

Grouping certain edges with a mapper
for (const group of edgeGroups) {
  for (const edge of group) {
    // Use the collection as group ID, since it's common to all edges in it
    layoutData.targetGroupIds.mapper.set(edge, group)
  }
}

See Also

Developer's Guide
API
sourceGroupIds, 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