Programmatically interacting with Nodes

This guide will show you some basic ways of programmatically interacting with GoJS nodes and model data. Throughout this guide, we will use the following diagram setup as our starting point:


var $ = go.GraphObject.make;
myDiagram = $(go.Diagram, "myDiagramDiv",
              {
                initialContentAlignment: go.Spot.Center,
                "undoManager.isEnabled": true
              });

// define a simple Node template
myDiagram.nodeTemplate =
  $(go.Node, "Auto",
    $(go.Shape, "Rectangle",
      new go.Binding("fill", "color")),
    $(go.TextBlock,
      { margin: 6, font: "18px sans-serif" },
      new go.Binding("text", "key"))
  );

myDiagram.model = new go.GraphLinksModel(
[
  { key: "Alpha", color: "lightblue" },
  { key: "Beta", color: "orange" },
  { key: "Gamma", color: "lightgreen" },
  { key: "Delta", color: "pink" }
]);
    

The code produces this Diagram:

Finding single nodes: Diagram.findNodeForKey

You can use Diagram.findNodeForKey(key) to get a reference to a Node in JavaScript. Key values in GoJS can be either strings or numbers. You can then use the Node reference to manipulate and inspect the Node.


var node = myDiagram.findNodeForKey("Alpha");

// Selects the node:
myDiagram.select(node);

// Outputs a JavaScript object in the developer console
// display of output will differ per browser, but is essentially the object:
// { key: "Alpha", color: "lightblue" }
// plus some internal implementation details.
console.log(node.data);
    

However findNodeForKey may return null if no node data uses that key value. Also, it only looks at the model data to find a node data that uses the given key value, from which it finds the corresponding Node in the Diagram. It does not look at the text values of any TextBlocks that are within the Nodes, so it can work even if no text is shown at all.

Note that there is no Node.key property. But you can get the key for a Node via someNode.data.key.

Collections of Nodes and Links

Diagrams have several properties and methods that return iterators describing collections of Parts. (Nodes and Links are kinds of Parts.) Diagram.nodes and Diagram.links return iterators of all Nodes and Links in the Diagram, respectively. Diagram.selection returns an iterator of selected Parts (both selected Nodes and selected Links).

There are also more specific methods for common operations, such as Diagram.findTreeRoots() which returns an iterator of all top-level Nodes that have no parent nodes.

This next example uses Diagram.nodes and shows how to iterate over the collection.


// wrapping code in startTransaction/commitTransaction automatically updates the display
// and allows the effects to be undone
myDiagram.startTransaction("decrease scale");

// get an iterator for all nodes
var itr = myDiagram.nodes;
while (itr.next()) {
  var node = itr.value;
  if (node.data.key === "Beta") continue; //skip Beta, just to contrast
  node.scale = 0.4; // shrink each node
}

myDiagram.commitTransaction("decrease scale");
    

As a result we have very scaled-down nodes, except for Beta:

Named GraphObjects and Panel.findObject

Often we want to manipulate a property that belongs to one of the Node's elements, perhaps an element arbitrarily deep in the template. In our example Diagram, each Node has one Shape, and if we want to change the color of this Shape directly we would need a reference to it. To make it possible to find, we can give that Shape a name:


myDiagram.nodeTemplate =
  $(go.Node, "Auto",
    $(go.Shape, "Rectangle",
      { stroke: null, name: "SHAPE" }, // added the name property
      new go.Binding("fill", "color")),
    $(go.TextBlock,
      { margin: 6, font: "18px sans-serif" },
      new go.Binding("text", "key"))
  );
    

Names allow us to easily find GraphObjects inside of Panels (all Nodes are also Panels) using Panel.findObject, which will search the visual tree of a Panel starting at that panel. So when we have a reference to a Node, we can call someNode.findObject("SomeName") to search through the node for the named object. It will return a reference to the named GraphObject if it is found, or null otherwise.

Using this, we could make an HTML button that changes the fill of the Shape inside of a selected Node:


var selectionButton = document.getElementById("selectionButton");
selectionButton.addEventListener("click", function() {
  myDiagram.startTransaction("change color");
  var it = myDiagram.selection.iterator;
  while (it.next()) {
    var node = it.value;
    var shape = node.findObject("SHAPE");
    // If there was a GraphObject in the node named SHAPE, then set its fill to red:
    if (shape !== null) {
      shape.fill = "red";
    }
  }
  myDiagram.commitTransaction("change color");
});
    

Changing Properties and Updating the Model using Data Bindings

Looking again at our Node template, we have the Shape.fill property data-bound to the "color" property of our Node data:


myDiagram.nodeTemplate =
  $(go.Node, "Auto",
    $(go.Shape, "Rectangle",
      { stroke: null, name: "SHAPE" },
      new go.Binding("fill", "color")),  // note this data binding
    $(go.TextBlock,
      { margin: 6, font: "18px sans-serif" },
      new go.Binding("text", "key"))
  );
    

Changing the Shape's fill property inside our node will not, as the Node template currently stands, update the model data.


var node = myDiagram.findNodeForKey("Alpha");
var shape = node.findObject("SHAPE");
shape.fill = "red";

// outputs "lightblue" - the model has not changed!
console.log(node.data.color);
    

This is undesirable in some cases. When we want the change to persist while saving and loading, we will want the model data updated too.

In other situations this might be a good thing. For instance if we want the color change for only cosmetic purposes, such as changing the color of a button when hovering over it with the mouse, we would not bother modifying the model data.

Suppose that we do want to update the model. The preferred way to do this is to modify the data in the model and depend on the data binding to automatically update the Shape. However, we cannot modify the data directly by just setting the JavaScript property.


var node = myDiagram.findNodeForKey("Alpha");

// DO NOT DO THIS!
// This would update the data, but GoJS would not be notified
// that this arbitrary JavaScript object has been modified,
// and the associated Node will not be updated appropriately
node.data.color = "red";
    

Instead we should set the data property using the method Model.setDataProperty(data, propertyName, propertyValue).


var node = myDiagram.findNodeForKey("Alpha");
var model = myDiagram.model;

// all model changes should happen in a transaction
model.startTransaction("change color");

// This is the safe way to change model data
// GoJS will be notified that the data has changed
// and can update the node in the Diagram
// and record the change in the UndoManager
model.setDataProperty(node.data, "color", "red");

model.commitTransaction("change color");

// outputs "red" - the model has changed!
console.log(node.data.color);
    

myDiagram.nodeTemplate =
  $(go.Node, "Auto",
    $(go.Shape, "Rectangle",
      { stroke: null },  // removed the name property
      new go.Binding("fill", "color")),
    $(go.TextBlock,
      { margin: 6, font: "18px sans-serif" },
      new go.Binding("text", "key"))
  );
    

Note that there is no longer any need to name the Shape "SHAPE", because there is no longer any need to call findObject to look for the particular Shape. Data binding will automatically update properties, so we do not have to do that ourselves.

Subjects Mentioned and Further Reading

If you are ready for a comprehensive overview of GoJS, have a look at the technical introduction. If you want to explore by example, have a look at the samples to get a feel for what's possible with GoJS.