documentationfor yFiles for HTML 2.6

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>. It is possible to create multiple nodes and edges sources, where each source may represent differently structured data collections and data items. This is especially useful when having different types of nodes or when the business data originates from multiple different sources. All sources end up in the same graph and nodes of different sources may be connected by edges.

Configuring a builder with two nodes sources and one edges 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 (e.g. 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 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 over 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 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 = InteriorLabelModel.NORTH
const creator2 = nodesSource.nodeCreator.createLabelBinding((employee) => employee.department)
creator2.defaults.layoutParameter = InteriorLabelModel.SOUTH

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({
    icon: employee.imageUrl,
    iconSize: new Size(16, 16),
    iconPlacement: ExteriorLabelModel.WEST
  })

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 DefaultGraph. The initial graph creation as well as updating the graph for changed data doesn’t happen automatically, it has to be triggered explicitly:

  • buildGraph populates the maintained graph instance with the items generated from the bound data. It does not clear the graph and preserves existing items.
  • updateGraph updates the graph. It reflects changes in the bound data by adding new elements and removing obsolete elements. Updated data collections can be set via 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 required, the layout has to be applied 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 per default only perform structural updates on the graph.

In cases where the data for existing nodes might change and require updating e.g. style or labels, one has to update the graph item explicitly. The NodeCreator<TDataItem> raises an NodeUpdated event for each node which has been kept in the graph and hence might require updating. Developers can use the various update methods provided by NodeCreator<TDataItem> to use the binding mechanism for node and edge creation for updating various properties on existing nodes. For edges, EdgeCreator<TDataItem> provides similar methods which can be called upon the EdgeUpdated event.

Updating any aspect of node and edge creation
nodeCreator.addNodeUpdatedListener((sender, 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.addEdgeUpdatedListener((sender, 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()

Note that it is only necessary to add update methods for properties which might change. Properties which are known not to change or which are not affected by the data do not need to have their update method called.

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

Updating existing labels
const labelCreator1 = nodeCreator.createLabelBinding((item) => item.name)
labelCreator1.addLabelUpdatedListener((sender, 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.addLabelUpdatedListener((sender, evt) => {
  // update only properties which might change
  labelCreator2.updateText(evt.graph, evt.item, evt.dataItem)
})

Note that only properties which might change have to be updated.

Grouping

All graph builders fully support the creation of group nodes from data. Group nodes may appear at all places where regular nodes may appear. This especially means that group nodes can be recursively nested and edges may be connected to group nodes.

The isGroupPredicate determines if a node shall be created as group node. On each graph builder there is 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.

A custom isGroupPredicate can be set when the NodesSource<TDataItem> shall 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