documentationfor yFiles for HTML 3.0.0.1

Common Features

This section describes the common features of all data binding classes.

Throughout the graph builder API, collections of business data objects are called "data" and single data objects are called "data items".

Binding the Business Data to the Graph Builders

So-called source objects bind a collection of data items to the graph builder. There are different factory methods to create source objects, e.g., createNodesSource<TDataItem>, and createEdgesSource<TDataItem>. You can create multiple nodes and edges sources, where each source may represent differently structured data collections and data items. This is especially useful when you have different types of nodes or when the business data originates from multiple different sources. All sources contribute to the same graph, and nodes from different sources may be connected by edges.

Configuring a builder with two node sources and one edge source
const graphBuilder = new GraphBuilder()
const redNodesSource = graphBuilder.createNodesSource(
  redNodes, // data collection for red nodes
  (data) => data.id // id provider
)
const blueNodesSource = graphBuilder.createNodesSource(
  blueNodes, // data collection for blue nodes
  (data) => data.id // id provider
)

const edgesSource = graphBuilder.createEdgesSource(
  edges, // data collection for edges
  (data) => data.sourceNode, // provider for the source node id
  (data) => data.targetNode // provider for the target node id
)
const graphBuilder = new GraphBuilder()
const redNodesSource = graphBuilder.createNodesSource(
  redNodes, // data collection for red nodes
  (data) => data.id! // id provider
)
const blueNodesSource = graphBuilder.createNodesSource(
  blueNodes, // data collection for blue nodes
  (data) => data.id! // id provider
)

const edgesSource = graphBuilder.createEdgesSource(
  edges, // data collection for edges
  (data) => data.sourceNode, // provider for the source node id
  (data) => data.targetNode // provider for the target node id
)

The data binding module for yFiles for HTML accepts different types of data collections. Data collections may be JavaScript arrays, JavaScript objects, iterables, or instances of an EcmaScript 2015 Map. Since IEnumerable<T> implements the iterable protocol, all yFiles for HTML collections are accepted as well.

Each data item may be identified by a unique ID. The default ID of a data item depends on the type of the data collection. If the collection is an instance of Map or a JavaScript object, the default ID is the associated key in the Map or object. Otherwise, the item itself is the default ID.

Each source type has an idProvider property (for example, idProvider) that can provide a different ID for a data item. The key in a Map or object or the index in an array or iterable is passed to the idProvider as the second canonicalId parameter.

Configuring the Visual Appearance of Graph Items

Each source object has a creator property, e.g., nodeCreator. While the source objects are responsible for building the graph structure, the creator objects manage the visual appearance of single items.

The appearance of a node is controlled by the NodeCreator<TDataItem>'s defaults and its providers, e.g., layoutProvider. The defaults determine the default properties that are shared among all nodes created by this NodeCreator<TDataItem>. The defaults cascade with the GraphBuilder’s graph, meaning that all properties that are not explicitly set on the NodeCreator’s defaults are inherited from the graph’s defaults. The providers allow for binding specific properties of a data item to properties of the node, such as its location or tag.

Similarly to nodes, edges are created by an EdgeCreator<TDataItem>.

Both node and edge creators allow for defining one or multiple labels that depend on the node or edge data items. Label creation is explained in more detail below.

Both the sources and the creators have a generic type that is bound to the data item type. The data items are passed to the various provider functions. The following tables give an overview of the available properties:

Common properties
Property Description
NodeCreator.Defaults, EdgeCreator.Defaults, LabelCreator.DefaultsDefaults for style, size, etc. These defaults cascade with the IGraph defaults. The defaults have a lower priority than the providers.
NodeCreator.StyleProvider, EdgeCreator.StyleProvider, LabelCreator.StyleProviderProvides a style instance for a data item.
NodeCreator.StyleBindings, EdgeCreator.StyleBindings, LabelCreator.StyleBindingsAllows you to reflectively set some properties on the style instance provided by the defaults or the style provider. Note that the style instance should not be shared if the style bindings depend on the data items.
NodeCreator.TagProvider, EdgeCreator.TagProvider, LabelCreator.TagProviderProvides the tag for a data item. The default returns the data item itself.

Configuring a NodeCreator shows how to configure a NodeCreator<TDataItem> to use a specific node style that is not shared between the created nodes and set several style properties depending on the node data item:

Configuring a NodeCreator
nodesSource.nodeCreator.defaults.shareStyleInstance = false
nodesSource.nodeCreator.defaults.style = new ShapeNodeStyle({
  stroke: 'darkOrange',
  fill: 'lightYellow',
  shape: 'round-rectangle'
})
nodesSource.nodeCreator.styleBindings.addBinding('stroke', (employee) =>
  employee.position.includes('Chief') ? 'darkRed' : 'darkOrange'
)
nodesSource.nodeCreator.styleBindings.addBinding('shape', (employee) =>
  employee.freelancer ? 'hexagon' : 'roundRectangle'
)

In addition to the properties shared by all creator types, there are further properties available on NodeCreator<TDataItem>, EdgeCreator<TDataItem> and LabelCreator<TDataItem>:

Additional NodeCreator properties
Property Description
layoutProviderDetermines a node’s size and location.
layoutBindingsAllows to reflectively set some properties like x and y on the layout provided by the defaults or the layout provider.
isGroupPredicateDetermines if a node is created as group node or regular node.
Additional EdgeCreator properties
Property Description
bendsProviderDetermines the points to create bends at.
Additional LabelCreator properties
Property Description
textProviderDetermines a label’s text.
preferredSizeProviderDetermines a label’s preferredSize.
preferredSizeBindingsAllows to reflectively set width and height of the preferred size which were automatically computed internally or are provided by the preferred size provider.
layoutParameterProviderDetermines a label’s layoutParameter.

Note that NodeCreator<TDataItem>, EdgeCreator<TDataItem>, and LabelCreator<TDataItem> can be used independently of any graph builder. This might come in handy if the business data structure does not fit any of the graph builders' requirements.

Adding labels

Labels can be added using the node and edge creators. When the number of labels per node or edge is known beforehand and the labels do not have their own data objects, createLabelBinding will create a LabelCreator<TDataItem> with the node’s or edge’s data item. Otherwise, createLabelsSource<TLabelDataItem> creates a LabelsSource<TDataItem> that allows for creating a dynamic amount of labels per node or edge with their own data objects.

createLabelBinding returns a LabelCreator<TDataItem> whose textProvider is preconfigured with the text provider passed as parameter to createLabelBinding.

A simple label using the graph’s default style
nodesSource.nodeCreator.createLabelBinding((employee) => employee.name)

The LabelCreator<TDataItem> can be configured further to adjust the styling and position according to the given data item. Note that with createLabelBinding the data item is of the same type as the item of the owning node or edge.

Adding labels with a configured label creator
const labelCreator = nodesSource.nodeCreator.createLabelBinding(
  (employee) => employee.name
)
labelCreator.styleProvider = (employee) =>
  employee.department == 'IT' ? itLabelStyle : logisticsLabelStyle

const labelCreator: LabelCreator<Employee> =
  nodesSource.nodeCreator.createLabelBinding((employee) => employee.name)
labelCreator.styleProvider = (employee) =>
  employee.department == 'IT' ? itLabelStyle : logisticsLabelStyle

Adding two labels
const creator1 = nodesSource.nodeCreator.createLabelBinding(
  (employee) => employee.name
)
creator1.defaults.layoutParameter = InteriorNodeLabelModel.TOP
const creator2 = nodesSource.nodeCreator.createLabelBinding(
  (employee) => employee.department
)
creator2.defaults.layoutParameter = InteriorNodeLabelModel.BOTTOM

createLabelsSource<TLabelDataItem> returns a LabelsSource<TDataItem> whose labelCreator has to be configured further. Note that the data provider which is passed as parameter is expected to return an enumerable of data items where each item represents one label.

Adding labels with a labels source
const labelsSource = nodesSource.nodeCreator.createLabelsSource(
  (employee) => employee.labels
)
labelsSource.labelCreator.styleProvider = (labelObject) =>
  labelObject.style == 'Orange' ? orangeLabelStyle : defaultLabelStyle
labelsSource.labelCreator.textProvider = (labelObject) => labelObject.text

const labelsSource: LabelsSource<LabelObject> =
  nodesSource.nodeCreator.createLabelsSource(
    (employee) => employee.labels
  )
labelsSource.labelCreator.styleProvider = (labelObject) =>
  labelObject.style == 'Orange' ? orangeLabelStyle : defaultLabelStyle
labelsSource.labelCreator.textProvider = (labelObject) => labelObject.text

Labels can also be used to provide icons instead of text, using an IconLabelStyle and configuring the LabelCreator<TDataItem> accordingly:

Adding icons
const labelCreator = nodesSource.nodeCreator.createLabelBinding()
// to avoid empty labels to be created return null for missing image URL
labelCreator.textProvider = (employee) =>
  employee.imageUrl != null ? ' ' : null
labelCreator.styleProvider = (employee) =>
  new IconLabelStyle({
    href: employee.imageUrl,
    iconSize: new Size(16, 16),
    iconPlacement: ExteriorNodeLabelModel.LEFT
  })

Creating And Updating Graphs

Each graph builder manages an IGraph instance that can be passed as a constructor parameter. If no graph is passed, the graph builder creates a new Graph. Creating the initial graph and updating it with changed data isn’t automatic; you must trigger it explicitly.

  • buildGraph populates the managed graph instance with items generated from the bound data. It preserves existing items and does not clear the graph.
  • updateGraph updates the graph to reflect changes in the bound data by adding new elements and removing obsolete ones. You can set updated data collections using the various setData<TDataItem> methods. This is only required if the instances of the data collections have changed.

Note that neither method triggers an automatic layout. If a layout is required, you must apply it in a subsequent step.

Building and Updating a graph
// Build a graph initially
graphBuilder.buildGraph()
// Apply a layout in a subsequent step
graph.applyLayout(new OrganicLayout())

// ...

// Set the new data collections:
graphBuilder.setData(redNodesSource, newRedNodes)
graphBuilder.setData(blueNodesSource, newBlueNodes)
graphBuilder.setData(edgesSource, newEdges)
// Update a graph after the business data has changed
graphBuilder.updateGraph()
// Apply a layout in a subsequent step
graph.applyLayout(new OrganicLayout())

The updateGraph methods only perform structural updates on the graph by default.

If the data for existing nodes might change and require updating (e.g., style or labels), you must update the graph item explicitly. The NodeCreator<TDataItem> raises an node-updated event for each node that has been kept in the graph and might require updating. Developers can use the various update methods provided by NodeCreator<TDataItem> to use the binding mechanism for node and edge creation to update various properties on existing nodes. For edges, EdgeCreator<TDataItem> provides similar methods that can be called upon the edge-updated event.

Updating any aspect of node and edge creation
nodeCreator.addEventListener('node-updated', (evt) => {
  // evaluate the providers and apply respective bindings
  nodeCreator.updateLayout(evt.graph, evt.item, evt.dataItem)
  nodeCreator.updateStyle(evt.graph, evt.item, evt.dataItem)
  nodeCreator.updateTag(evt.graph, evt.item, evt.dataItem)
  nodeCreator.updateLabels(evt.graph, evt.item, evt.dataItem)
})
edgeCreator.addEventListener('edge-updated', (evt) => {
  // evaluate the providers and apply respective bindings
  edgeCreator.updateBends(evt.graph, evt.item, evt.dataItem)
  edgeCreator.updateStyle(evt.graph, evt.item, evt.dataItem)
  edgeCreator.updateTag(evt.graph, evt.item, evt.dataItem)
  edgeCreator.updateLabels(evt.graph, evt.item, evt.dataItem)
})

graphBuilder.updateGraph()

Only add update methods for properties that might change. Properties that are known not to change or are not affected by the data do not need their update method called.

The same applies to labels: if the text, style, or other properties of an existing label might change, developers must add update methods to eachLabelCreator<TDataItem> instance.

Updating existing labels
const labelCreator1 = nodeCreator.createLabelBinding((item) => item.name)
labelCreator1.addEventListener('label-updated', (evt) => {
  // update everything which might change
  labelCreator1.updateStyle(evt.graph, evt.item, evt.dataItem)
  labelCreator1.updateText(evt.graph, evt.item, evt.dataItem)
  labelCreator1.updateTag(evt.graph, evt.item, evt.dataItem)
  labelCreator1.updateLayoutParameter(evt.graph, evt.item, evt.dataItem)
  labelCreator1.updatePreferredSize(evt.graph, evt.item, evt.dataItem)
})
const labelCreator2 = nodeCreator.createLabelBinding(
  (item) => item.position
)
labelCreator2.addEventListener('label-updated', (evt) => {
  // update only properties which might change
  labelCreator2.updateText(evt.graph, evt.item, evt.dataItem)
})

Update only the properties that might change.

Grouping

All graph builders fully support the creation of group nodes from data. Group nodes can appear anywhere regular nodes can. This especially means that group nodes can be recursively nested, and edges can connect to group nodes.

The isGroupPredicate determines whether a node is created as a group node. Each graph builder provides a group source factory method that conveniently creates a source object for only group nodes (e.g. createGroupNodesSource<TDataItem>). These sources have the isGroupPredicate set to a function that always returns true and have defaults that cascade with the groupNodeDefaults.

You can set a custom isGroupPredicate when the NodesSource<TDataItem> should create a mix of group and normal nodes. Note, however, that all nodes share the same defaults of their NodesSource<TDataItem>.

Similar to the IGraph API, the relationship between a node and its group is determined by the parentIdProvider property, which determines the ID of a node’s parent group node.

An example where a graph with group nodes is built from hierarchical data
const nodesData = getNodesData()
const groupNodesData = getGroupNodesData()

const graphBuilder = new GraphBuilder()

// create a nodes source for the regular nodes
const nodesSource = graphBuilder.createNodesSource(
  nodesData,
  (item) => item.id
)
// set a parent provider that associates a node with its parent group
nodesSource.parentIdProvider = (nodeDataItem) => nodeDataItem.parentId

// create a nodes source for the group nodes
const groupNodesSource = graphBuilder.createGroupNodesSource(
  groupNodesData,
  (groupDataItem) => groupDataItem.id
)
// groups may be nested within each other -> set a parent provider that
// associates a node with its parent group
groupNodesSource.parentIdProvider = (groupDataItem) =>
  groupDataItem.parentId