C

EdgeRouterData<TNode, TEdge, TNodeLabel, TEdgeLabel>

Specifies custom data for the EdgeRouter.
Inheritance Hierarchy

Members

Show:

Constructors

Parameters

Properties

Gets or sets the mapping from edges to their EdgeRouterBusDescriptor.

All edges associated with the same bus descriptor are routed in a bus-like style, sharing a common backbone. A bus consists of segments that are shared by multiple edges, with shorter segments connecting to the actual nodes. Note that using such a bus representation with multiple edges drawn on top of each other may obscure information like the individual edge direction. Bus routing can be very useful, for example, in parts of a diagram where each node is connected to every other node.

The EdgeRouterBusDescriptor instance allows configuring the bus formed by the associated edges. To conveniently define buses, use the add method of this property. It takes the bus descriptor as a parameter and allows defining the edges associated with the bus using the returned ItemCollection<TItem>.

Intermediate points are ignored for edges that are part of a bus.
final

Examples

Assigning a list of edges to a bus
// create a bus descriptor and, optionally, configure it
const busDescriptor = new EdgeRouterBusDescriptor({
  minimumBackboneSegmentLength: 200,
})
// retrieve an ItemCollection which defines the edges that should belong to the bus
const busEdgesCollection = edgeRouterData.buses.add(busDescriptor)
// busEdgesList is an ICollection which contains all edges that should be part of the bus
busEdgesCollection.items = busEdgesList

See Also

Developer's Guide
API
BUS_DESCRIPTOR_DATA_KEY, EdgeRouterBusDescriptor
Gets or sets the mapping of edges to their EdgeRouterEdgeDescriptor
If an edge is mapped to null, the default descriptor is used.
conversionfinal

See Also

Developer's Guide
API
EDGE_DESCRIPTOR_DATA_KEY
Gets or sets a mapping from edge labels to a crossing cost factor.
The crossing cost factor of an edge label is multiplied with the basic cost for an edge crossing the label so crossing a label with a higher cost factor is more strongly avoided than crossing one with a lower factor.
conversionfinal

Examples

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

Defining label crossing costs via a mapper
// Try harder to prevent crossings with label1
layoutData.edgeLabelCrossingCostFactors.mapper.set(label1, 1.5)
// Crossings with label2 are not as bad
layoutData.edgeLabelCrossingCostFactors.mapper.set(label2, 0.2)

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

Defining label crossing costs via a delegate
// The more upper-case letters a label has, the more important it must be
// and crossings should be avoided based on that metric
// todo
layoutData.edgeLabelCrossingCostFactors = (label: ILabel): number =>
  label.text.match(/[A-Z]/g)!.length

See Also

API
edgeLabelCrossingCost, EDGE_LABEL_CROSSING_COST_FACTOR_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 a custom comparison to define the processing order of the edges.

The processing order may affect the quality of the individual edge paths. When routing an edge, only the paths of the already routed edges and of the fixed edges (that will not be routed at all) can be considered. Therefore, edges that are processed first have to consider fewer other edge paths than the edges that are processed later, which might have to use less optimal alternative paths.

By default, i.e., if this property is null, an internal comparison instance is applied.

When defining edge or port groups, the custom processing order defined by this property is not fully obeyed. Edges that are grouped are routed together as a bundle. For example, assume that edge0 and edge2 are grouped and edge1 should be routed after edge0 but before edge2: the grouped edges are routed as a bundle, either both before or both after edge1. Note that the same is true for edges that share common bus segments, see buses.
final

Default Value

The default value is: null
There is no custom instance set and the default implementation will be applied.
Gets or sets the LayoutGrid layout data.
final

See Also

Developer's Guide
Gets or sets a mapping from node labels to a crossing cost factor.
The crossing cost factor of a node label is multiplied with the basic cost for an edge crossing the label so crossing a label with a higher cost factor is more strongly avoided than crossing one with a lower factor.
conversionfinal

Examples

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

Defining label crossing costs via a mapper
// Try harder to prevent crossings with label1
layoutData.nodeLabelCrossingCostFactors.mapper.set(label1, 1.5)
// Crossings with label2 are not as bad
layoutData.nodeLabelCrossingCostFactors.mapper.set(label2, 0.2)

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

Defining label crossing costs via a delegate
// The more upper-case letters a label has, the more important it must be
// and crossings should be avoided based on that metric
layoutData.nodeLabelCrossingCostFactors = (label: ILabel): number =>
  label.text.match(/[A-Z]/g)!.length

See Also

API
nodeLabelCrossingCost, NODE_LABEL_CROSSING_COST_FACTOR_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 sub-data that influences 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 EdgeRouter 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. In case there is no matching port, a port candidate specified for the edge is preferred.

final

See Also

Developer's Guide
Gets or sets the sub-data that provides the ability to modify the scope of the EdgeRouter with regard to the routed edges.

The routed edges can be specified using the ItemCollection properties edges and incidentNodes. Any edges, or edges incident to the nodes in those collections, are considered to be in scope by the EdgeRouter. The Item Mapping properties edgeMapping and incidentNodeMapping can be used to specify a more differentiated EdgeRouterScope for (incident) edges.

When multiple scope properties are specified for the same edge, the most permissive property is prioritized, meaning PATH is always prioritized over PATH_AS_NEEDED, which is more permissive than SEGMENTS_AS_NEEDED. IGNORE is assigned to an edge if and only if no other property is specified.

readonlyfinal

Examples

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

Setting edges as affected individually
layoutData.scope.edges.items.add(edge1)
layoutData.scope.edges.items.add(edge2)

In cases where looking at the edge itself is sufficient to determine whether an edge is in scope, it's often easier to just set a delegate instead:

Using a delegate to set edges as affected
// We assume here that all edges have a CustomData instance as their tag,
// which then has a boolean property 'IncludeInLayout'.
layoutData.scope.edges = (edge: IEdge): boolean =>
  (edge.tag as CustomData).includeInLayout

Sometimes it can be more convenient to specify the incident nodes of the affected edges:

Using a delegate to set affected edges based on their incident nodes
// We assume here that all nodes have a CustomData instance as their tag,
// which then has a boolean property 'IncludeInLayout'.
layoutData.scope.incidentNodes = (node: INode): boolean =>
  (node.tag as CustomData).includeInLayout

Finally, it's also possible to specify more differentiated policies by using the edgeMapping or incidentNodeMapping property:

Using a delegate to set specific policies for when edges should be affected
layoutData.scope.edgeMapping = (edge) => {
  if (edge == edge1) {
    //edge1 is always routed
    return EdgeRouterScope.PATH
  }
  if (edge == edge2) {
    //edge2 is only routed if necessary
    return EdgeRouterScope.PATH_AS_NEEDED
  }
  if (edge == edge3) {
    //only segments of edge3 are routed if necessary
    return EdgeRouterScope.SEGMENTS_AS_NEEDED
  }
  //other edges are not affected
  return EdgeRouterScope.IGNORE
}

See Also

Developer's Guide
Gets or sets a mapping from edges to an object representing their source edge group.
Edges sharing a source group identifier will share a common bus near the source or at a common source node if possible.
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 sharing a target group identifier will share a common bus near the target or at a common target node if possible.
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