documentationfor yFiles for HTML 2.6

GraphBuilder

The GraphBuilder creates general graphs from separate node and edge collections. An edge data item specifies its source and target nodes by providing their unique node ids.

Example node and edge data items may look like this:

/**
 * @typedef {Object} NodeType
 * @property {string} nodeId
 */

/**
 * @typedef {Object} EdgeType
 * @property {string} sourceNodeId
 * @property {string} targetNodeId
 */
interface NodeType {
  nodeId: string
}

interface EdgeType {
  sourceNodeId: string
  targetNodeId: string
}

Each node data item has a unique nodeId property that is referenced by the sourceNodeId and targetNodeId properties on edge data items.

To bind the node data to the graph builder, create a nodes source:

const graphBuilder = new GraphBuilder()

const nodesData = getNodeData()
const nodesSource = graphBuilder.createNodesSource(
  nodesData, // node data
  (nodeDataItem) => nodeDataItem.nodeId // node id provider
)

createNodesSource<TDataItem> creates a new NodesSource<TDataItem> that is bound to the passed nodesData and its data item type. The passed idProvider takes a data item and returns the nodeId that is used to identify a node when creating edges and during updateGraph. It is possible to use the node data item itself as the node id.

To bind the edge data to the graph builder, create an edges source:

const edgesData = getEdgesData()
const edgesSource = graphBuilder.createEdgesSource(
  edgesData,
  (edgeDataItem) => edgeDataItem.sourceNodeId,
  (edgeDataItem) => edgeDataItem.targetNodeId
)

createEdgesSource<TDataItem> creates a new EdgesSource<TDataItem> that is bound to the passed edgesData and its data item type. The passed sourceIdProvider and targetIdProvider define the id of the source and target node data items for each edge data item.

The GraphBuilder constructor alternatively allows to define all sources in a single configuration object:

const graphBuilder = new GraphBuilder({
  nodes: [
    {
      data: getNodeData(),
      id: 'nodeId'
    }
  ],
  edges: [
    {
      data: getEdgesData(),
      sourceId: 'sourceNodeId',
      targetId: 'targetNodeId'
    }
  ]
})

This feature is not available for the other graph builders.

It is possible to create multiple nodes and edges sources from different data collections and with different stylings. Edges can connect nodes from different nodes sources.

It is also possible to add group nodes from group data which is explained below.

The visual appearance, locations and labels of the nodes and edges can be customized via the NodeCreator<TDataItem> and EdgeCreator<TDataItem> as explained in the Configuring the Visual Appearance of Graph Items section.

After the sources and creators have been configured, a graph can be created and later updated if the business data has changed.

Grouping

GraphBuilder provides different ways to define Grouping Nodes. First, it supports defining a node’s parent via the NodesSource<TDataItem>'s parentIdProvider as shown in the common section.

Other than the other builders GraphBuilder allows for defining dedicated sources for group nodes via createGroupNodesSource<TDataItem>. Nodes defined here are automatically created as group nodes, their defaults are the graph’s groupNodeDefaults.

const graphBuilder = new GraphBuilder()
const groupNodesSource = graphBuilder.createGroupNodesSource(getGroupNodeData(), (data) => data.nodeId)
const nodesSource = graphBuilder.createNodesSource(getItemData(), (data) => data.nodeId)
nodesSource.parentIdProvider = (data) => data.parentId

Another common use case is that the data itself is organized in a hierarchical manner, i.e. the data object for a node has a collection of data objects which represent its child nodes. createChildNodesSource<TChildDataItem> can read such objects and create a hierarchy where the nodes are created as child nodes of the current node:

const graphBuilder = new GraphBuilder()
const nodesSource = graphBuilder.createNodesSource(nodeData, (data) => data.nodeId)
nodesSource.createChildNodesSource(
  (data) => data.children,
  (data) => data.nodeId
)

Children can define children of their own. Adding the child nodes source itself as child nodes source allows for traversing the data in a recursive manner:

const graphBuilder = new GraphBuilder()
const nodesSource = graphBuilder.createNodesSource(nodeData, (data) => data.nodeId)
const childNodesSource = nodesSource.createChildNodesSource(
  (data) => data.children,
  (data) => data.nodeId
)
// parse the children recursively
childNodesSource.addChildNodesSource((data) => data.children, childNodesSource)

A node can also define its parent node or even ancestors using another nodes source using createParentNodesSource<TParentDataItem>. If defined that way, a parent group node is automatically created using the provided NodeCreator<TDataItem>. If a node with the same ID already exists, though, that node will be set as parent, instead of creating a new node.

const graphBuilder = new GraphBuilder()
const nodesSource = graphBuilder.createNodesSource(nodeData, (data) => data.nodeId)
// create a nodes source for the node's parent
const parentNodesSource = nodesSource.createParentNodesSource(
  (data) => data.parentData,
  (data) => data.nodeId
)
// configure the node creator to style the parent nodes
parentNodesSource.nodeCreator.defaults.style = new GroupNodeStyle()

Defining a parent nodes source can also be used to define a complex hierarchy. The following example parses a string representing an IP address to create a grouping by its octets.

const graphBuilder = new GraphBuilder()
const nodesSource = graphBuilder.createNodesSource(networkObjects, (data) => data.nodeId)
// Parent data provider: parse the IP into smaller units by removing the last octet
const parentDataProvider = (ip) => {
  const separator = ip?.lastIndexOf('.')
  if (separator != -1) {
    return ip ? ip.substring(0, separator) : null
  }

  return null
}
const parentNodesSource = nodesSource.createParentNodesSource((data) => parentDataProvider(data.IP))
//Enable recursive ascent for the new parent source
parentNodesSource.addParentNodesSource(parentDataProvider, parentNodesSource)

const graphBuilder = new GraphBuilder()
const nodesSource = graphBuilder.createNodesSource(networkObjects, (data) => data.nodeId)
// Parent data provider: parse the IP into smaller units by removing the last octet
const parentDataProvider = (ip: string | null) => {
  const separator = ip?.lastIndexOf('.')
  if (separator != -1) {
    return ip ? ip.substring(0, separator) : null
  }

  return null
}
const parentNodesSource = nodesSource.createParentNodesSource((data) => parentDataProvider(data.IP))
//Enable recursive ascent for the new parent source
parentNodesSource.addParentNodesSource(parentDataProvider, parentNodesSource)

The next example shows a flat arrangement of data that is used to create a complex hierarchy. In that example, a data item represents an office with its city and country stored in separate properties.

// sample data structure

class Office {
  ID = null
  city = null
  country = null
  // other properties...
}

// sample data structure

class Office {
  ID: string | null = null
  city: string | null = null
  country: string | null = null
  // other properties...
}

const graphBuilder = new GraphBuilder()
const officeSource = graphBuilder.createNodesSource(offices, (data) => data.ID)
// the offices are grouped by their cities
const citySource = officeSource.createParentNodesSource(
  (data) => data,
  (data) => data.city
)
// the cities are grouped by their countries
const countrySource = citySource.createParentNodesSource(
  (data) => data,
  (data) => data.country
)

Note that the data item itself is passed as data item for the nodes and its ancestors. To distinguish properly between the different nodes, it is mandatory to specify an ID provider.

Tutorial Demo Code

The demos Simple Graph Builder and Graph Builder show how to use a GraphBuilder to build a graph from user data. See the multistep GraphBuilder Tutorial for in-depth explanations of the various concepts.