C

PortData<TNode, TEdge, TNodeLabel, TEdgeLabel>

Specifies custom data for constraining the port placement.
Inheritance Hierarchy

Members

Show:

Constructors

Parameters

Properties

Gets or sets a mapping from nodes to their NodePortCandidates.
If candidates are assigned to nodes (with this property) and to edges (sourcePortCandidates or targetPortCandidates), then the layout algorithm tries to match them. The exact procedure depends on the layout algorithm.
conversionfinal

Examples

The simplest way to define node port candidates is to use the same NodePortCandidates for all nodes, if suitable for the use case:

Defining the same port candidates for all nodes
// All nodes get only a candidate for the lower (Bottom) side with capacity 10
const constantPortCandidates = new NodePortCandidates().addFreeCandidate(
  PortSides.BOTTOM,
  10,
)
layoutData.ports.nodePortCandidates = constantPortCandidates

The same effect can be achieved with a delegate as well. However, a more useful way to use a delegate would be to decide whether a node should get node port candidates based on some data at the node, e.g., found in its tag:

Defining port candidate sets with a delegate
// Create a PortCandidateSet with capacity 2 for the upper side (Top) and capacity 1 for the lower side (Bottom)
const pcs = new NodePortCandidates()
  .addFreeCandidate({
    side: PortSides.TOP,
    capacity: 2,
  })
  .addFreeCandidate(PortSides.BOTTOM)
// Specify the node port candidates for diamond nodes, i.e. for nodes with the string "diamond" in their tag
layoutData.ports.nodePortCandidates = (node) =>
  node.tag === 'diamond' ? pcs : null!

If specific nodes should get certain NodePortCandidates, it may sometimes be easier to use the mapper to set them:

Defining port candidate sets with a mapper
// Create node port candidate set with capacity 2 for the top side and capacity 1 for the bottom side
const pcs1 = new NodePortCandidates()
  .addFreeCandidate({ side: PortSides.TOP, capacity: 2 })
  .addFreeCandidate(PortSides.BOTTOM)
// Create another set with candidates for the left and right side
const pcs2 = new NodePortCandidates()
  .addFreeCandidate({
    side: PortSides.RIGHT,
    capacity: 4,
  })
  .addFreeCandidate({
    side: PortSides.LEFT,
    capacity: 4,
  })
// Specify the port candidates for two specific nodes
layoutData.ports.nodePortCandidates.mapper.set(node1, pcs1)
layoutData.ports.nodePortCandidates.mapper.set(node2, pcs2)

See Also

API
NODE_PORT_CANDIDATES_DATA_KEY
Gets or sets a mapping from edges to an object representing their source port alignment group.

The layout algorithm places all edges with the same alignment ID at a node such that they either share the same port location or are placed exactly opposite. Port alignment is prioritized over constraints given by sourcePortCandidates, targetPortCandidates, and nodePortCandidates.

The exact procedure of how the ports are aligned and the restrictions on the alignment depends on the layout algorithm.

conversionfinal

Examples

The simplest way to use source port alignment group is to use the same object as alignment ID for all edges. Since aligning is done per node, this has the effect of aligning all edges with the same source node:

Aligning all edges on the source side
layoutData.ports.sourcePortAlignmentIds = {}

The same effect can be achieved with a delegate as well, by returning the source node as alignment ID for each edge. However, a more useful way to use a delegate here would be grouping edges by some commonality, such as the same color:

Aligning edges by color
layoutData.ports.sourcePortAlignmentIds = (edge: IEdge) => {
  const style = edge.style as PolylineEdgeStyle
  if (style != null) {
    return style.stroke!.fill
  }
  return null
}

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

Aligning certain edges with a mapper
edgePortAlignment.forEach((group) => {
  // Use the collection as group ID, since it's common to all edges in it
  group.forEach((edge) => {
    layoutData.ports.sourcePortGroupIds.mapper.set(edge, group)
  })
})

See Also

API
targetPortAlignmentIds, sourcePortGroupIds, SOURCE_PORT_ALIGNMENT_ID_DATA_KEY
Gets or sets a mapping from edges to their source port candidates.

LayoutPortCandidates allow to define where an edge can connect to its source node and allow fine control over port placement.

If candidates are assigned to edges (e.g. with this property) and to nodes (nodePortCandidates), the layout algorithm tries to match them. The exact procedure depends on the layout algorithm.

conversionfinal

Examples

Source port candidates are effectively a collection of possible port placements with different costs and the layout algorithm is free to choose the candidate that fits best into the overall layout, while also preferring candidates with a lower cost. To set the same candidate list for all edges, it's easiest to use the constant property:

Setting the same set of source port candidates for all edges
// Prefer the center position and route the edge downwards if possible (no cost for this candidate)
const candidates = new EdgePortCandidates().addFixedCandidate(
  PortSides.BOTTOM,
  Point.ORIGIN,
)
// Alternatively, use any position at the bottom border of the node, but with a higher cost
candidates.addFreeCandidate(PortSides.BOTTOM, 1)
// If that fails, use either the left or the right side
candidates
  .addFreeCandidate(PortSides.LEFT, 2)
  .addFreeCandidate(PortSides.RIGHT, 2)
layoutData.ports.sourcePortCandidates = candidates

If certain edges need specific port candidates, it's usually convenient to use the mapper property:

Setting different source port candidates for certain edges
// edge1 should leave the source node preferably on the node side in flow direction,
// but can also use a fixed port location on the left of the node.
layoutData.ports.sourcePortCandidates.mapper.set(
  edge1,
  new EdgePortCandidates()
    .addFreeCandidate(PortSides.END_IN_FLOW)
    .addFixedCandidate(PortSides.LEFT, new Point(-0.5, 0.25), 1),
)

// edge2 should leave the source node anywhere on the upper side of the node
layoutData.ports.sourcePortCandidates.mapper.set(
  edge2,
  new EdgePortCandidates().addFreeCandidate(PortSides.TOP),
)

For cases when the desired configuration of port candidates can be readily created from the edge itself, the mapperFunction is often the most convenient option:

Setting different source port candidates for each edge with a delegate
// Assume that edges may have "yes" or "no" labels and specify port candidates based on label text
layoutData.ports.sourcePortCandidates = (edge) => {
  switch (edge.labels.at(0)?.text) {
    case 'Yes':
      return new EdgePortCandidates().addFreeCandidate(PortSides.RIGHT)
    case 'No':
      return new EdgePortCandidates().addFreeCandidate(PortSides.LEFT)
    default:
      return new EdgePortCandidates()
        .addFreeCandidate(PortSides.TOP)
        .addFreeCandidate(PortSides.BOTTOM)
  }
}

Sample Graphs

ShownSetting: Example of using edge port candidates. Edge port candidates are colored uniquely based on the edge with which they are associated.

See Also

API
SOURCE_PORT_CANDIDATES_DATA_KEY
Gets or sets a mapping from edges to an object representing their source port group.
All edges with the same port group ID at a node will share the same port location. Port grouping is prioritized over other constraints, for example, if two port-grouped edges do not have at least one common LayoutPortCandidate, then the port of one of the edges will be chosen for both, violating the constraint of the other edge.
conversionfinal

Examples

The simplest way to use source port groups is to use the same object as port group ID for all edges. Since grouping is done per node, this has the effect of grouping all edges with the same source node together:

Grouping all edges on the source side
layoutData.ports.sourcePortGroupIds = {}

The same effect can be achieved with a delegate as well, by returning the source node as the port group ID for each edge. However, a more useful way to use a delegate here would be grouping edges by some commonality, such as the same color:

Grouping edges by color
layoutData.ports.sourcePortGroupIds = (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 port group IDs:

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

See Also

Developer's Guide
API
targetPortGroupIds, SOURCE_PORT_GROUP_ID_DATA_KEY
Gets or sets a mapping from edges to an object representing their target port alignment group.

The layout algorithm places all edges with the same alignment ID at a node such that they either share the same port location or be placed exactly opposite. Port alignment is prioritized over constraints given by sourcePortCandidates, targetPortCandidates, and nodePortCandidates.

The exact procedure how the ports are aligned and the restrictions on the alignment depends on the layout algorithm.

conversionfinal

Examples

The simplest way to use target port alignment group is to use the same object as alignment ID for all edges. Since aligning is done per node, this has the effect of aligning all edges with the same target node:

Aligning all edges on the source side
layoutData.ports.targetPortAlignmentIds = {}

The same effect can be achieved with a delegate as well, by returning the target node as alignment ID for each edge. However, a more useful way to use a delegate here would be grouping edges by some commonality, such as the same color:

Aligning edges by color
layoutData.ports.targetPortAlignmentIds = (edge: IEdge) => {
  const style = edge.style as PolylineEdgeStyle
  if (style != null) {
    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 alignment IDs:

Aligning certain edges with a mapper
edgePortAlignment.forEach((group) => {
  // Use the collection as group ID, since it's common to all edges in it
  group.forEach((edge) => {
    layoutData.ports.targetPortGroupIds.mapper.set(edge, group)
  })
})

See Also

API
sourcePortAlignmentIds, targetPortGroupIds, TARGET_PORT_ALIGNMENT_ID_DATA_KEY
Gets or sets a mapping from edges to their target port candidates.

LayoutPortCandidates allow to define where an edge can connect to its target node and allow fine control over port placement.

If candidates are assigned to edges (e.g. with this property) and to nodes (nodePortCandidates), the layout algorithm tries to match them. The exact procedure depends on the layout algorithm.

conversionfinal

Examples

Target port candidates are effectively a collection of possible port placements with different costs and the layout algorithm is free to choose the candidate that fits best into the overall layout, while also preferring candidates with a lower cost. To set the same candidate list for all edges, it's easiest to use the constant property:

Setting the same set of target port candidates for all edges
const candidates = new EdgePortCandidates()
// Prefer the center position and enter the node from the top if possible (no cost for this candidate)
new EdgePortCandidates()
  .addFixedCandidate(PortSides.TOP, [0, 0])
  // Alternatively, use any position at the top border of the node, but with a higher cost
  .addFreeCandidate(PortSides.TOP, 1)
  // If that fails, use either the left or the right side
  .addFreeCandidate(PortSides.LEFT, 2)
  .addFreeCandidate(PortSides.RIGHT, 2)

If certain edges need specific port candidates, it's usually convenient to use the mapper property:

Setting different target port candidates for certain edges
// edge1 should enter the target node preferably on the node side in flow direction,
// but can also use a fixed port location on the left of the node.
layoutData.ports.targetPortCandidates.mapper.set(
  edge1,
  new EdgePortCandidates()
    .addFreeCandidate(PortSides.END_IN_FLOW)
    .addFixedCandidate(PortSides.LEFT, new Point(-0.5, -0.25), 1),
)

// edge2 should enter the target node anywhere on the bottom side of the node
layoutData.ports.targetPortCandidates.mapper.set(
  edge2,
  new EdgePortCandidates().addFreeCandidate(PortSides.BOTTOM),
)

For cases when the desired configuration of port candidates can be readily created from the edge itself, the mapperFunction is often the most convenient option:

Setting different target port candidates for each edge with a delegate
// Assume that edges may have "yes" or "no" labels and specify port candidates based on label text
layoutData.ports.targetPortCandidates = (edge) => {
  switch (edge.labels.at(0)?.text) {
    case 'Yes':
      return new EdgePortCandidates().addFreeCandidate(PortSides.RIGHT)
    case 'No':
      return new EdgePortCandidates().addFreeCandidate(PortSides.LEFT)
    default:
      return new EdgePortCandidates()
        .addFreeCandidate(PortSides.TOP)
        .addFreeCandidate(PortSides.BOTTOM)
  }
}

Sample Graphs

ShownSetting: Example of using edge port candidates. Edge port candidates are colored uniquely based on the edge with which they are associated.

See Also

API
TARGET_PORT_CANDIDATES_DATA_KEY
Gets or sets a mapping from edges to an object representing their target port group.
All edges with the same port group ID at a node will share the same port location. The PortPlacementStage assigns the same port to edges that are port-grouped at a common node. Importantly, port grouping is prioritized over other constraints, for example, if two port-grouped edges do not have at least one common LayoutPortCandidate, then the port of one of the edges will be chosen for both, violating the constraint of the other edge.
conversionfinal

Examples

The simplest way to use target port groups is to use the same object as port group ID for all edges. Since grouping is done per node, this has the effect of grouping all edges with the same target node together:

Grouping all edges on the target side
layoutData.ports.targetPortGroupIds = {}

The same effect can be achieved with a delegate as well, by returning the target node as the port group ID for each edge. However, a more useful way to use a delegate here would be grouping edges by some commonality, such as the same color:

Grouping edges by color
layoutData.ports.targetPortGroupIds = (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 port group IDs:

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

See Also

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