Learn to build a basic GoJS diagram application with customizable nodes, dynamic templates, and interactive features in this step-by-step guide.
In this article, I will present the basic capabilities of GoJS by creating an application with very simple code. It will be designed for drawing uncomplicated diagrams.
GoJS is a JavaScript library that takes complex data and runs it through flexible modeling options to create visualizations that are easy to understand and put into action.
The starting point for us is the HTML file where we import the library. We can download it directly from the website, from the npm, or we can use the CDN. The next step is to add the div element – the future location of the diagram. NOTE: We don’t create the canvas ourselves, as the library does it for us. However, the div itself may be freely refined to make it match the website. The file should look more or less like this:
Listing 1. Initial appearance of the index.html file
<!DOCTYPE html><html><head><script src="https://cdnjs.cloudflare.com/ajax/libs/gojs/1.8.12/go.js"></script></head><body><div id="diagram-content" style="height: 800px; border: 1px solid black;"></div></body></html>
view rawindex.html hosted with ❤ by GitHub
The next step is to write JavaScript code that creates the diagram. We create a diagram.js file where we will put the entire logic of the application. We can create it in any convention - GoJS doesn’t dictate any specific way of building sources. For the purposes of this example, I will create an IIFE (Immediately Invoked Function Expression), which will be a named diagram with the initDiagram function available externally. By using IIFE, we will be able to hide a part of our implementation from outside access.
In the case of GoJS, our starting point for creating all objects is the go.GraphObject.make function, which calls the factory method of the selected class. It’s used so often that we should assign it to a variable with a shorter name. The generally accepted convention is to use the dollar sign for this reason. However, we need to be careful in the case of parallel use of jQuery. As the first argument, this function accepts the class whose instance is supposed to be created, while the values of individual fields of the object are accepted as subsequent arguments. It’s worth emphasizing that usually the string is given as a second argument with the value of one selected field defined in the object. Generally, it’s the field whose value is most important in the given context, such as the type of created figure.
The first thing that we want to create is the diagram itself. It is located under the go.Diagram class. The most important parameter is the id of the element it’s supposed to be placed in. This is why we call it $(go.Diagram, 'diagram-content').
In order to better specify the operation of the diagram object, check what fields it has in the documentation. For instance, we may assign the value go.Spot.Center to the field initialContentAlignment. This initially places the diagram’s elements at its center (by default they are in the upper left corner) of the application. Let's put it into the initDiagram. We have to remember to call this function in the HTML file. We should get the following JS and HTML files:
Listing 2. diagram.js skeleton and creation of an empty diagram
diagram = (function(){var $ = go.GraphObject.make;var diagram;var initDiagram = function () {diagram = $(go.Diagram,'diagram-content', {initialContentAlignment: go.Spot.Center});}return { initDiagram };})();
view rawdiagram.js hosted with ❤ by GitHub
<!DOCTYPE html><html><head><script src="https://cdnjs.cloudflare.com/ajax/libs/gojs/1.8.12/go.js"></script><script src="diagram.js"></script></head><body><div id="diagram-content" style="height: 800px; border: 1px solid black;"></div><script>window.onload = function() {diagram.initDiagram();};</script></body></html>
view rawindex.html hosted with ❤ by GitHub
The diagram is empty, so unfortunately we won’t see any action yet. The only thing that will show us that GoJS is working is the default action lock of clicking the right mouse button on the diagram.
The basis for the diagram is the creation of a data model for it to operate on. The model consists of two tables – nodeDataArray (data of nodes) and linkDataArray (data of edges). Nodes are the main elements of a diagram that graphically represent the objects (for example, an entity in a diagram of relations of entities). Links (edges) are the connections between nodes (directed or not), which, depending on the type of diagram, may have different meanings (such as the relation between entities in an ERD diagram).
We define the individual nodes as objects where the only required field is key, for example, a unique identifier. In addition, we can optionally define the category of objects, as well as complete them with any data which will be stored along with the node in the diagram. Edges are also defined as objects having two required fields – from and to, whose values determine which nodes are connected. Like in the case of nodes, we may also add our own additional data.
The diagram model is located under the model field of the diagram object. It's best to define it by creating a go.GraphLinksModel object we pass the nodes and edges to. Here’s an example of what a model definition might look like:
Listing 3. Definition of the diagram model
diagram.model = $(go.GraphLinksModel, {nodeDataArray: [{key: 1, category: 'first'},{key: 2, category: 'second'},{key: 3, category: 'second'},{key: 4, category: 'third'}],linkDataArray: [{from: 1, to: 2},{from: 2, to: 3},{from: 1, to: 3},{from: 3, to: 4}]});
view rawdiagram.js hosted with ❤ by GitHub
After placing it in the initDiagram and running it, we should get a very simple diagram. It will show us the values entered in key fields connected by edges.
The next step is to provide the objects with an appearance. The appearance is set by creating a go.Node object in which we subsequently create objects defining the shape (go.Shape). The simplest definition of the object’s appearance (template) would look like this:
Listing 4. The simplest definition of the object’s appearance
diagram.nodeTemplate = $(go.Node, 'Auto', $(go.Shape, 'Circle'));
view rawdiagram.js hosted with ❤ by GitHub
In this case, we will create a black circle to represent the object. Auto means the arrangement of individual shapes. In the case of Auto, each shape defined inside go.Node will be placed in layers at the center of the object. In the case of shapes, Circle was selected here, but there are many others available here. We could also set field values in the shape object – this would let us specify elements like: fill, stroke or size. In addition, we can load these values from the data model using go.Binding.
Another way to define the appearance is to create a shape using the SVG path definition. Then, we use this path as the value of the geometryString field. Here’s an example of what the template definition would look like in such a case (we’ll also set the size and color of the fill):
Listing 5. Definition of appearance using the SVG path
diagram.nodeTemplate = $(go.Node,'Auto',$(go.Shape, {geometryString: 'F M0 0 L100 0 Q150 50 100 100 L0 100 Q50 50 0 0z',fill: 'white',width: 100,height: 100}));
view rawdiagram.js hosted with ❤ by GitHub
It’s also possible to define the appearance using images in any format (including SVG). In such cases, instead of go.Shape we have to use go.Picture. Interestingly, go.Picture can store not only images, but also other canvas-type elements. This allows us to create even more advanced visualizations.
Another way of defining the appearance of objects is to combine many different shapes into one. The advantage of this approach is that after dividing the object into several smaller ones, we can define different behaviors for each part (for example, one of the shapes may be a button on which we define a click action – in this case, only this fragment will have this event assigned). In order to create complex objects, it’s worth learning about methods for arranging shapes on an object. We’ve used Auto, which means placing everything in the middle. We also have other options, the most basic of which include: Horizontal (from left to right), Vertical (from top to bottom), Spot (placement on predetermined positions like Center, Top, Bottom, etc.). Subsequent shapes are passed as the next arguments of the function creating the Node, and they are placed in this order. For example, two rectangles, each with a different appearance and placed one on top of the other, would be defined in like this:
Listing 6. Definition of appearance consisting of several shapes
diagram.nodeTemplate = $(go.Node,'Vertical',$(go.Shape,'Rectangle', {width: 50,height: 50,strokeWidth: 0,fill: 'yellow',}),$(go.Shape,'Rectangle', {width: 100,height: 50,strokeWidth: 5,stroke: 'red',fill: 'green'}));
view rawdiagram.js hosted with ❤ by GitHub
However, what if we want different types of objects? To do this, instead of assigning the appearance to nodeTemplate, we complete the map – nodeTemplateMap, where the key is the category of the object (category, which we have defined in the model), while template is the value. For the purposes of this example, let’s assume that we’ll use the definitions given above for the categories: first will be a circle, second will be SVG shape, third will be two rectangles. However, for practical reasons, we shouldn’t assign template directly to the map, but rather create the function that will return the template we will assign to which category, and then supplement the map - nodeTemplateMap using the data from it. Note that this map should be completed before we supply the diagram with the model. For example, it could look like this (some fragments of the code have been left out for clarity):
Listing 7. Suggestion for using nodeTemplateMap
var getTemplates = function () {return [{category: 'first',template: $(go.Node, 'Auto', $(go.Shape, 'Circle'))}, {category: 'second',template: $(go.Node,'Auto',$(go.Shape, {// ...}))}, {category: 'third',template: $(go.Node,'Vertical',// ...}))}];}var initDiagram = function () {// ...getTemplates().forEach(x => diagram.nodeTemplateMap.add(x.category, x.template));diagram.model = $(go.GraphLinksModel, {// ...});}
view rawdiagram.js hosted with ❤ by GitHub
Writing this should generate the following effect:
As you can see, the diagram is beginning to take shape. In addition, it’s worth mentioning that besides the appearance of nodes, you can also determine the appearance of edges. This is defined in an analogical manner, with the difference that instead of nodeTemplate and nodeTemplateMap, we have linkTemplate and linkTemplateMap, while during the definition of a template, instead of creating a go.Node object we create a go.Link object
Curious about the next step in creating a GoJS diagram? Follow our blog and stay up-to-date with every new article!
Also check out how to create real-time visualizations using GoJS and SignalR.
This post was also published on ITNEXT.
You can see the result of this tutorial here: