Skip to main content
  1. Index

Data Binding

In GoJS, a Model holds your data, and the Diagram displays that data. Templates (Diagram.nodeTemplate, Diagram.linkTemplate) connect the two, describing how to render each Node or Link from a data object.

Templates have data bindings to connect your model data to the properties on your templates that you need to change on each node. Below is a Node template containing a Shape and a TextBlock, each of which have a data binding via GraphObject.bind:

Kinds of Bindings

You can make a binding by writing new go.Binding(...), but the more common way is to call one of these methods on a GraphObject:

  • .bind() - GraphObject.bind is used to set a GraphObject property from the Part's data in the model. For example, setting color or text, like above.
  • .bindObject() - GraphObject.bindObject is used to set a GraphObject property from another GraphObject property. For example we might want to set the color of a GraphObject based on whether or not Part.isSelected is currently true or false.
  • .bindModel() - GraphObject.bindModel is used to set a GraphObject property from the Model.modelData.
  • .bindTwoWay() - GraphObject.bindTwoWay is used to set a GraphObject property from the data, and also modifies the data if the GraphObject property changes. For example, it is common to use a two-way binding on Part.location to make saving location changes easier. Use two-way bindings sparingly - only when you are sure you want this functionality.
  • .theme() - GraphObject.theme is used to bind a GraphObject property from a Theme source. This is an advanced topic, you can read more about theming at the learn page on theming.

Data bindings are used to keep GraphObject properties in sync with their Part's data's properties. They are not used to establish or maintain relationships between Parts. Each kind of Model has its own methods for identifying parts and declaring the relationships between them.

Trying to bind a non-existent property of a GraphObject will probably result in a warning or error that you can see in the console log. Providing a new value of the wrong type for a property may also result in a warning or error. Always check the console log for any kinds of potential exceptions.

Binding object properties such as Part.location

You can data bind properties that have values that are JS objects. For example it is common to data bind the Part.location property.

The value of Part.location is of type Point, so in this example the data property value must be an instance of Point.

js

But this is not ideal, since it means we'd have go.Point in our model data. Strings are more easily read and written in JSON and XML, so GoJS bindings routinely use conversion functions to keep model data cleaner.

Conversion functions

The Point class has a static function, Point.parse, that makes conversion from a string to a Point object easy. It expects two numbers representing the Point.x and Point.y values, and returns a Point object.

You can pass a conversion function as the third argument to the Binding constructor. In this case it is Point.parse. This allows the location to be specified as a string (such as "100 50"). For data properties on model objects, you will often want to use strings as the representation of Points, Sizes, Rects, Margins, and Spots, rather than references to objects of those classes.

js

Conversion functions can be named or anonymous functions. They should not have any side-effects. They may get called any number of times in any order, so they should be efficient.

Here is an example that has several Shape properties data-bound to the same boolean data property named "highlight". Each conversion function takes the boolean value and returns the appropriate value for the property that is data-bound. This makes it trivial to control the appearance of each node from the data by setting the "highlight" data property to be either false or true.

Note that a conversion function can only return property values. You cannot return GraphObjects to replace objects in the visual tree of the Part. If you need to show different GraphObjects based on bound data, you can bind the GraphObject.visible or the GraphObject.opacity property. If you really want different visual structures you can use multiple templates (see Template Maps).

Updating model data values

The examples above all depend on the data bindings being evaluated when the Part has been created and its Panel.data property is set to refer to the corresponding node or link data. These actions occur automatically when the Diagram creates diagram parts for the data in the model upon setting Diagram.model.

However, GoJS cannot know when the data property of an arbitrary JavaScript object has been modified. If you want to change some data object in a model and have the diagram be automatically updated, what you should do depends on the nature of the property that you are changing.

For most data properties, ones that the model does not treat specially but are data-bound, you can just call Model.set within a transaction. In this example we modify the value of "highlight" on a node data object. For fun, this modification occurs about twice a second.

Changing graph structure

Data binding is not used to establish relationships between parts. For data properties that a particular model knows about, such as "to" or "from" for link data in a GraphLinksModel, you must call the appropriate model methods in order to modify the data property. Modifying a data property directly without calling the appropriate model method may cause inconsistencies or undefined behavior.

For node data, the model methods are

For link data, the model methods are

This example changes the "to" property of a link data, causing the link to connect to a different node. This example uses the default Link template, which does not have any relevant data bindings. The change in the link relationship is accomplished by calling a model method, not via a data binding.

Binding to GraphObject sources

Instead of binding to model data, you may want to bind to a property of the Part or a named GraphObject in the same Part. The source property must be a settable property of the class, and the binding is evaluated when the property is set to a new value.

One common use of such a binding is to change the appearance of a Part when the Part.isSelected is changed. Call GraphObject.bindObject to cause the Binding to use the object whose GraphObject.name is the given name. Use the empty string, "", or no argument, to refer to the whole Part itself. This is a convenience so that you do not need to name the whole Part. "bindObject" really means "from the GraphObject named ...", as found by Panel.findObject when there is a string argument.

In the example below, the Shape.fill is bound to the Part.isSelected property. When the node is selected or de-selected, the Part.isSelected property changes value, so the binding is evaluated. The conversion function gets a boolean value and returns the desired brush color to be used as the shape's fill. This example also turns off selection adornments, so that the only visual way to tell that a node is selected is by the shape's fill color.

Caution: do not declare cycles of binding dependencies -- that will result in undefined behavior. GraphObject binding sources also require the Part to be bound to data (i.e. Part.data must be non-null). The property on the GraphObject must be settable, so it does not work on read-only properties such as ones that return computed values (e.g. Part.isTopLevel) or Iterators (e.g. Node.linksConnected).

Binding to the shared Model data source

The binding source object may be a third kind of source, besides the Panel.data or some GraphObject within the panel. It can also be the JavaScript Object that is the shared Model.modelData object. This permits binding of Node or Link element properties to shared properties in the model that will exist and may be modified even though no nodes or links exist in the model.

In the example below, the Shape.fill is bound to the "color" property on the Model.modelData object. As you click the button the changeColor function modifies the modelData object by calling Model.set.

Two-way data binding

Two-way data bindings allow us to edit GraphObject properties and automatically save those edits to the model.

A conversion function lets us take model data (like a string) and convert it to another type (like a Point), but we might also want to do the reverse, converting a Point to a string. Two-way bindings allow us to use both a conversion and a back-conversion function. They are declared with bindTwoWay. The conversion function goes from source (model) to target (diagram) and the back-conversion function goes from target to source. This is commonly used with "position" or "location" to store a more redable string representation in the model rather than a Point:

js

Two-way bindings make it easy for user interaction, like dragging a Node, or using the the TextEditingTool, to update the model automatically. Try dragging this node around, or selecting it, clicking on the text, and editing.

Reasons for TwoWay Bindings

The basic reason for using a TwoWay Binding on a settable property is to make sure that any changes to that property will be copied to the corresponding model data. By making sure that the Model is up-to-date, you can easily "save the diagram" just by saving the model and "loading a diagram" is just a matter of loading a model into memory and setting Diagram.model. If you are careful to only hold JSON-serializable data in the model data, you can just use the Model.toJson and Model.fromJson methods for converting a model to and from a textual representation.

Most bindings do not need to be TwoWay. For performance reasons you should not make a Binding be TwoWay unless you actually need to propagate changes back to the data. Most settable properties are only set on initialization and then never change.

Settable properties only change value when some code sets them. That code might be in code that you write as part of your app. Or it might be in a command (see Commands) or a tool (see Tools). Here is a list of properties for which a TwoWay Binding is plausible because one of the predefined commands or tools modify them:

You probably will not need a TwoWay binding on any other properties unless you write code to modify them. And even then it is sometimes better to write the code to modify the model data directly by calling Model.set, depending on a OneWay Binding to update the GraphObject property.

It is also possible to use TwoWay Bindings where the source is a GraphObject rather than model data. This is needed less frequently, when you do not want to have the state stored in the model, but you do want to synchronize properties of GraphObjects within the same Part.

Use TwoWay Bindings sparingly.

You must not have a TwoWay binding on the node data property that is the "key" property. (That defaults to the name "key" but is actually the value of Model.nodeKeyProperty.) That property value must always be unique among all node data within the model and is known by the Model. A TwoWay binding might change the value, causing a multitude of problems. Similarly, the Node.key property is read-only, to prevent accidental changes of the key value.

Theming

The ability to automatically update properties from Theme objects is basically provided by data binding using a subclass of Binding. However, theme binding is OneWay only. Read more about theming at the learn page on theming.