C

OrganicLayoutData<TNode, TEdge, TNodeLabel, TEdgeLabel>

Specifies custom data for the OrganicLayout.
Inheritance Hierarchy

Members

Show:

Constructors

Parameters

Properties

Gets or sets the mapping from nodes to user-defined cluster IDs, overwriting the predefined clusteringPolicy.

If custom clusters are defined via this property, the layout's clusteringPolicy is ignored.

Nodes with equal cluster ID form a cluster, hence, will be placed closer together. The value null indicates that a node is not part of any cluster.

The specified cluster IDs are only considered for top-level nodes. Hence, all nodes within a group node (including sub-groups) are associated with the cluster ID of the corresponding top-level group node.
conversionfinal

Examples

A convenient way to define clusters is to set a delegate that returns the cluster identifier for each node, for example, using the tag of the nodes:

Using a delegate to specify clusters
// Use the node's tag for cluster assignment.
// Nodes with the same tag are part of the same cluster and, thus, placed close to each other.
layoutData.clusterIds = (node: INode) => node.tag

If the clusters are already given as a collection of the respective nodes, using the mapper is practical:

Using a mapper to set two clusters
for (const node of cluster1) {
  // First cluster: define a common string as ID for all nodes in the first collection
  layoutData.clusterIds.mapper.set(node, 'cluster1')
}
for (const node of cluster2) {
  // Second cluster: define a common string as ID for all nodes in the second collection
  layoutData.clusterIds.mapper.set(node, 'cluster2')
}

See Also

API
clusteringPolicy, CLUSTER_ID_DATA_KEY
Gets or sets the OrganicConstraintData<TNode> that allows to define additional constraints on the nodes of a graph that will be applied by the OrganicLayout during the layout calculation.
For grouped graphs, any one constraint may only be applied to nodes with the same parent.
final

See Also

Developer's Guide
Gets or sets the mapping from edges to their directedness.

This property allows the user to specify hints on the directedness of edges. More precisely, a value of 1 indicates that the edge should be considered to be directed from source to target, a value of -1 that it is directed from target to source, and a value of 0 means that it is undirected.

The specified values are considered during the detection of special substructures, see chainSubstructureStyle, cycleSubstructureStyle, parallelSubstructureStyle and starSubstructureStyle.

If left unspecified, the algorithm assumes that all edges are undirected for the substructure detection.
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 a mapping from edges to their orientation in the layout.

More precisely, a positive value indicates that the edge should have the same orientation as the layout orientation, a negative value indicates that the edge should have the opposite orientation as the layout orientation, and a value of zero means that the orientation can be chosen arbitrarily by the layout algorithm.

If the edge orientation for an edge is not explicitly mapped in the mapper, it is assumed that it can have an arbitrary orientation.

conversionfinal

Examples

Specifying the edge orientation for specific edges can be accomplished easily by using the mapper property:

Using a mapper to set different edge orientations for some edges
layoutData.edgeOrientation.mapper.set(edge1, 1.0)
layoutData.edgeOrientation.mapper.set(edge2, -1.0)
// The edge orientation for all other edges is not defined

In cases where the edge orientation 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 orientations
organicLayout.layoutOrientation = LayoutOrientation.TOP_TO_BOTTOM
layoutData.edgeOrientation = (edge) => {
  switch (edge.labels.first()!.text) {
    case 'downwards':
      return 1.0 // edges with label 'downwards' should point from top to bottom
    case 'upwards':
      return -1.0 // edges with label 'upwards' should point from bottom to top
    default:
      return 0.0 // for all other edges the algorithm can decide
  }
}

Finally, there's also the option to use the same value for all edges:

Using the layout orientation for all edges.
// all edges should have the same orientation as the layout
layoutData.edgeOrientation = 1.0

See Also

Developer's Guide
API
EDGE_ORIENTATION_DATA_KEY
Gets or sets the layoutGridData.
A node is placed inside the columns/rows defined by the corresponding LayoutGridCellDescriptor. Instances can be shared among multiple nodes, but don't have to be shared.
For common nodes (i.e. non-group nodes) the OrganicLayout only considers single layout grid cells (i.e., no multi-cell descriptors). Furthermore, the algorithm will throw an ArgumentError if there is a layout grid and the descendants of a group node are assigned to different layout grid cells or if there are group nodes that are associated with a GroupNodeHandlingPolicy other than FREE. In addition, the OrganicLayout doesn't support multiple grids, i.e., all nodes have to be mapped to cells of the same layoutGrid.
final

Examples

The following sample shows how to assign nodes to layout grid cells simply via cell indices:

Assigning nodes to layout grid cells via indices
// Create four nodes and place them in grid cells in the following way
// +---+---+---+
// | 1 | 2 | 3 |
// +---+---+---+
//     | 4 |
//     +---+

const gridData = layoutData.layoutGridData

const node1 = graph.createNode()
const node2 = graph.createNode()
const node3 = graph.createNode()
const node4 = graph.createNode()

// Assign the nodes to their rows and columns.
// Note that you don't have to create or use LayoutGrid directly in this case.
// Setting the indices is enough.
gridData.rowIndices.mapper.set(node1, 0)
gridData.rowIndices.mapper.set(node2, 0)
gridData.rowIndices.mapper.set(node3, 0)
gridData.rowIndices.mapper.set(node4, 1)
gridData.columnIndices.mapper.set(node1, 0)
gridData.columnIndices.mapper.set(node2, 1)
gridData.columnIndices.mapper.set(node3, 2)
gridData.columnIndices.mapper.set(node4, 1)

graph.applyLayout(new OrganicLayout(), layoutData)

When used this way there is no need to create a LayoutGrid instance or work with it directly. For more flexibility, e.g. to use cells that span multiple columns or rows, the LayoutGrid can be used as well:

Assigning nodes to layout grid cells via factory methods on the grid
// Create three nodes and place them in grid cells in the following way
// +---+---+
// | 1 | 2 |
// +---+---+
// |   3   |
// +---+---+

const gridData = layoutData.layoutGridData

const node1 = graph.createNode(new Rect(0, 0, 50, 50))
const node2 = graph.createNode(new Rect(0, 0, 50, 50))
const node3 = graph.createNode(new Rect(0, 0, 125, 50))
// Create a new LayoutGrid with two rows and two columns
const grid = new LayoutGrid(2, 2)
// Assign the nodes to their cells
const gridCells = new Mapper<INode, LayoutGridCellDescriptor>()
gridCells.set(node1, grid.createCellDescriptor(0, 0))
gridCells.set(node2, grid.createCellDescriptor(0, 1))
gridCells.set(node3, grid.createRowSpanDescriptor(1))
gridData.layoutGridCellDescriptors = gridCells

graph.applyLayout(new OrganicLayout(), layoutData)

See Also

Developer's Guide
Gets or sets a mapping from edges to their minimum lengths.
If the minimum length for an edge is not explicitly mapped in the mapper, it is assumed that it is 0.
conversionfinal

Examples

Specifying a minimum length for specific edges can be accomplished easily by using the mapper property:

Using a mapper to set different minimum lengths for some edges
layoutData.minimumEdgeLengths.mapper.set(edge1, 120)
layoutData.minimumEdgeLengths.mapper.set(edge2, 75)
// All other edges not set in the mapper implicitly get the default value 0

In cases where the minimum length 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 minimum edge lengths
// Ensure that there is enough space for the edge's label.
// We'll assume that every edge has a label here.
layoutData.minimumEdgeLengths = (edge) =>
  edge.labels.first()!.preferredSize.width * 1.2

Finally, there's also the option to use the same value for all edges:

Using a constant minimum edge length
layoutData.minimumEdgeLengths = 120

See Also

Developer's Guide
API
MINIMUM_EDGE_LENGTH_DATA_KEY
Gets or sets a mapping from nodes to their minimum distances to other nodes around them.

This setting only has an effect when allowNodeOverlaps is false and only applies to the post-processing step that moves overlapping nodes. So the distances from a node that never had overlaps to begin with, may not follow this setting.

Minimum distance values must be greater than 0.

conversionfinal

Examples

Specifying a minimum node distance for specific nodes, overriding the default setting on OrganicLayout can be accomplished easily by using the mapper property:

Using a mapper to set different minimum distances for some nodes
layoutData.minimumNodeDistances.mapper.set(node1, 25)
layoutData.minimumNodeDistances.mapper.set(node2, 45)
// All other edges not set in the mapper implicitly get the default value
// from OrganicLayout.minimumNodeDistance.

In cases where the minimum distance can be determined by looking at the node itself it's often easier to just set a delegate instead of preparing a mapper:

Using a delegate to determine minimum node distances
// Scale the minimum distance around a node by the node size.
// This ensures that larger nodes get more space.
layoutData.minimumNodeDistances = (node) =>
  Math.max(node.layout.width, node.layout.height)

Finally, there's also the somewhat nonsensical option to use the same value for all edges, which, as the comment notes, can be achieved easier by just setting the respective property on OrganicLayout:

Using a constant minimum node distance
layoutData.minimumNodeDistances = 25
// This is equivalent to the following property on OrganicLayout:
organicLayout.defaultMinimumNodeDistance = 25

Sample Graphs

ShownSetting: Preferred minimum node distance 40

See Also

API
MINIMUM_DISTANCE_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 star, parallel, chain, and cycle substructures.

If node types are defined, only nodes of the same type or nodes which all have no type can form a substructure.

For parallel substructures, property parallelSubstructureTypeSeparation controls whether parallel substructures are strictly separated by type or if a structure may contain nodes of different types but the substructure layout itself takes care that different types are visually separated (e.g. by placing nodes of the same type closer together or on the same circle).

The same does property starSubstructureTypeSeparation for star substructures.

conversionfinal

Sample Graphs

ShownSetting: Without node types

See Also

Developer's Guide
API
NODE_TYPE_DATA_KEY, parallelSubstructureTypeSeparation, starSubstructureTypeSeparation
Gets or sets the collection of nodes that are allowed to overlap with other nodes.
This property allows to individually specify nodes that are ignored when overlaps between nodes are resolved.
This selection of nodes is only considered when allowNodeOverlaps is false .
conversionfinal

Examples

Specifying the property for specific nodes, overriding the default setting on OrganicLayout can be accomplished easily by using the items property:

Using items to specify nodes that can overlap.
layoutData.overlappingNodes.items.add(node1)
layoutData.overlappingNodes.items.add(node2)

In cases where the property can be determined by looking at the node itself, it's often easier to just set a delegate instead of adding it to the items:

Using a delegate to specify nodes that can overlap.
// Only nodes with a specific area are considered when resolving overlaps.
layoutData.overlappingNodes = (node) =>
  node.layout.width * node.layout.height <= 10

See Also

API
allowNodeOverlaps
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 OrganicLayout 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.
final
Gets or sets a mapping from edges to their preferred lengths.
If the preferred length for an edge is not explicitly mapped in the mapper, or mapped to 0 or a negative value, the default value from defaultPreferredEdgeLength will be used instead.
conversionfinal

Examples

Specifying a preferred length for specific edges, overriding the default setting on OrganicLayout can be accomplished easily by using the mapper property:

Using a mapper to set different preferred lengths for some edges
layoutData.preferredEdgeLengths.mapper.set(edge1, 120)
layoutData.preferredEdgeLengths.mapper.set(edge2, 75)
// All other edges not set in the mapper implicitly get the default value
// from OrganicLayout.preferredEdgeLength.

In cases where the preferred length 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 preferred edge lengths
// Ensure that there is enough space for the edge's label.
// We'll assume that every edge has a label here.
layoutData.preferredEdgeLengths = (edge) =>
  edge.labels.get(0).preferredSize.width * 1.2

Finally, there's also the somewhat nonsensical option to use the same value for all edges, which, as the comment notes, can be achieved easier by just setting the respective property on OrganicLayout:

Using a constant preferred edge length
layoutData.preferredEdgeLengths = 120
// This is equivalent to the following property on OrganicLayout:
organicLayout.defaultPreferredEdgeLength = 120

Sample Graphs

ShownSetting: Preferred edge length 40

See Also

Developer's Guide
API
PREFERRED_EDGE_LENGTH_DATA_KEY
Gets or sets the sub-data that specifies the subset of nodes that is moved by the OrganicLayout.
readonlyfinal

Examples

Defining the subset of nodes that should be laid out can be done in various ways, mostly depending on which option is more convenient for a particular use case. You can use the ItemCollection<TItem>'s source property to use any .NET collection or IEnumerable<T>:

Setting a collection of affected nodes
const layoutData = new OrganicLayoutData()
layoutData.scope.nodes = graphComponent.selection.nodes

Alternatively, ItemCollection<TItem> also has an items property, which is a collection that already exists, in case the items may have to be added one by one. This can be more convenient than defining an own list and setting it to source:

Adding individual nodes as affected nodes
for (const edge of graphComponent.selection.edges) {
  layoutData.scope.nodes.items.add(edge.sourceNode)
  layoutData.scope.nodes.items.add(edge.targetNode)
}

A powerful option that doesn't use a collection is to use the predicate to set a custom delegate that returns for every node whether it is contained in the set or not:

Using a delegate to determine whether a node is affected or not
// We assume here that all nodes have a CustomData instance as their tag,
// which then has a boolean property 'IncludeInLayout'.
layoutData.scope.scopeModes = (node) =>
  (node.tag as CustomData).includeInLayout
    ? OrganicScope.AFFECTED
    : OrganicScope.FIXED

See Also

Developer's Guide
API
SCOPE_DATA_KEY
Gets or sets a mapping from edges to an object representing their source edge group.
For normal structures, the organic layout does not support the classical edge grouping where edges at a common source node share a segment or a bus. The edge groups are only considered in the cases described below.
  • When the star substructure style SEPARATED_RADIAL is applied, edges grouped at the root node are drawn in a grouped routing style.
  • When one of the parallel substructure styles RECTANGULAR, RADIAL or STRAIGHT_LINE is applied, then edges of the parallel structures may be grouped at their outer nodes.
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
substructureTargetGroupIds, SOURCE_EDGE_GROUP_ID_DATA_KEY
Gets or sets a mapping from edges to an object representing their target edge group.
For normal structures, the organic layout does not support the classical edge grouping where edges at a common target node share a segment or a bus. The edge groups are only considered in the cases described below.
  • When the star substructure style SEPARATED_RADIAL is applied, edges grouped at the root node are drawn in a grouped routing style.
  • When one of the parallel substructure styles RECTANGULAR, RADIAL or STRAIGHT_LINE is applied, then edges of the parallel structures may be grouped at their outer nodes.
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
substructureSourceGroupIds, TARGET_EDGE_GROUP_ID_DATA_KEY
Gets a mapper from non-group nodes to the computed center z-coordinate in case that a 3D layout was created.

The mapper is filled by the layout with the computed z-coordinates of the non-group nodes. The node's world coordinates provide the matching x and y coordinates.

This is only required if create3DLayout is enabled. Otherwise, the algorithm produces a 2D result.

readonlyfinal

Examples

Retrieving the z-coordinates of nodes after a layout
const layoutData = new OrganicLayoutData()

const layout = new OrganicLayout()
// Enable creating a 3D result
layout.create3DLayout = true

graph.applyLayout(layout, layoutData)

for (const node of graph.nodes) {
  // Print z-coordinate for all non-group nodes
  if (!graph.isGroupNode(node)) {
    console.log(
      `Node ${node} has z-coordinate ${layoutData.zCoordinatesResult.get(node)}`,
    )
  }
}

See Also

API
Z_COORDINATE_RESULT_DATA_KEY, create3DLayout

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