Developing a diagramming app involves a lot more than just writing some JavaScript code that uses the GoJS library.
go-debug.js
library
While developing your app make sure you use the debug library, go-debug.js
, rather than the go.js
library.
The debug library does more error checking of property values and method arguments, and it detects more unusual situations.
Most warning and errors will be written to the console window. Always check it for messages. We have tried to make them informative.
For version 3 and later versions of 2.3 we have enhanced the package.json
file so that tools that
observe the "development" condition automatically make use of go-debug.js
or go-debug.mjs
as appropriate for the module system being used. Look at the GoJS package.json
for details.
Try to limit your code to only use documented classes, properties, and methods, as listed in the API reference or in the TypeScript definition file, go.d.ts.
Please do not refer to some minified property name, which will only be one or two letters long. In another version of the library the minified names will be different, so such code would no longer work. Basically: never use one or two letter property names except for "x" and "y" on Point, Rect, Spot, and LayoutVertex instances and the InputEvent.up property.
Do not modify the prototypes of any of the GoJS classes. If you modify the built-in classes, we cannot support you. The way to modify the behavior of the GoJS classes is via the techniques discussed at Extensions. However most of the GoJS classes cannot be subclassed and most of the documented methods cannot be overridden. Generally the Tool and Layout classes and the CommandHandler and Link classes may be subclassed; look at the API documentation to see if a method may be overridden.
First you will need to get a reference to your Diagram object in the Console window or the Debugger window.
One way to do that is by remembering it in your code.
You can set a property on the window
object to refer to the Diagram that you create.
Many of the samples do this just by leaving out the var
declaration:
myDiagram = new go.Diagram("myDiagramDiv", . . .);
Alternatively, in the console, if you know the name of the HTML DIV element, you can call the static function Diagram.fromDiv to get the Diagram object:
myDiagram = go.Diagram.fromDiv("myDiagramDiv");
If that DIV element is not named, perhaps you have some other way of getting a reference to the DIV element.
That may depend on the framework that you are using.
You can still call Diagram.fromDiv on that element to get the corresponding Diagram object.
Then in the console you can use the myDiagram
reference to the Diagram object. Some examples:
myDiagram.nodes.count; // returns the number of Nodes in the Diagram.
myDiagram.links.count; // returns the number of Links.
myDiagram.model.nodeDataArray[0]; // returns the first node data object in the diagram's model's Model.nodeDataArray.
myDiagram.layoutDiagram(true); // forces all layouts to happen, rearranging the nodes and routing the links.
The code that you execute in the console can be more complicated too. For example, you can find, select, and scroll to a particular node:
myNode = myDiagram.findNodeForKey("Omega");
myNode.isSelected = true;
myDiagram.commandHandler.scrollToPart(myNode); // scrolls to myNode
If you don't know the key for the node that you want to see in the viewport, perhaps you know how to find the node data object in the model. The Diagram.findNodesByExample method might also be useful.
myDiagram.selection.first()
returns the first selected Part, which might be either a Node, a Link,
or null if nothing is selected.
If you remember the selected Node or Link, you can then examine it further more easily. For example:
myNode = myDiagram.selection.first(); // get a reference to the first selected Part
myNode.data.key // inspect its data.key
myNode.data // or look at the whole data object
You could also look at other properties of the Node and call its methods. For example:
myNode.location // returns a Point whose properties the debugger may show
myNode.location.toString(); // More readable Point
As another example, you can print out all of the nodes the selected node is connected to:
myNode.findNodesOutOf().each(n => console.log(n.data.key))
You can find more examples of iterating at Collections
You can also look at the structure of the visual tree of a node. With this recursive function:
function walk(x, level, index) {
console.log(level + "," + index + ": " + x.toString());
if (!(x instanceof go.Panel)) return;
for (let i = 0; i < x.elements.count; i++) walk(x.elt(i), level+1, i);
}
you could call
walk(myNode, 0, 0)
and in the Org Chart sample get results such as:
0,0: Node#653(Kensaku Tamaki)
1,0: Shape(Rectangle)#656
1,1: Panel(Table)#657
2,0: TextBlock("Kensaku Tamaki")
2,1: Picture(https://nwoods.com/go/Flags/japan-flag.Png)#664
2,2: TextBlock("Title: Vice Chairman"...)
So you can see how the Node is a panel composed of Shape surrounding a nested Table Panel, which in turn is composed of two TextBlocks and a Picture.
When building your own node template, there may be times when the objects in the node are not sized and positioned the way that you would like. It is important that you understand how objects may be assembled within panels. You will want to re-read:
Say that you want a node consisting of two TextBlocks, one above the other, spaced nicely. You might start off with:
diagram.nodeTemplate =
new go.Node("Auto")
.add(
new go.Shape({ fill: "white" }),
new go.Panel("Vertical", { margin: 3 })
.add(
new go.TextBlock()
.bind("text", "t1"),
new go.TextBlock()
.bind("text", "t2")
)
);
diagram.model.nodeDataArray = [{ t1: "Top", t2: "Bottom"}];
But wait -- you want the node to be a fixed size. So you set the node's width and height:
diagram.nodeTemplate =
new go.Node("Auto", { width: 80, height: 100 })
.add(
new go.Shape({ fill: "white" }),
new go.Panel("Vertical", { margin: 3 })
.add(
new go.TextBlock()
.bind("text", "t1"),
new go.TextBlock()
.bind("text", "t2")
)
);
diagram.model.nodeDataArray = [{ t1: "Top", t2: "Bottom"}];
That looks better, but you are surprised that both TextBlocks are near the center. Why is that? For debugging purposes let's change the GraphObject.background colors of each TextBlock and the nested Panel.
diagram.nodeTemplate =
new go.Node("Auto", { width: 80, height: 100 })
.add(
new go.Shape({ fill: "white" }),
new go.Panel("Vertical", { margin: 3, background: "red" })
.add(
new go.TextBlock({ background: "lime" })
.bind("text", "t1"),
new go.TextBlock({ background: "cyan" })
.bind("text", "t2")
)
);
diagram.model.nodeDataArray = [{ t1: "Top", t2: "Bottom"}];
It is now clear that the TextBlocks are no bigger than they need to be to hold the text, and that the Panel is also no bigger than need be to hold the two TextBlocks.
So you think that you just need to GraphObject.stretch the panel.
diagram.nodeTemplate =
new go.Node("Auto", { width: 80, height: 100 })
.add(
new go.Shape({ fill: "white" }),
new go.Panel("Vertical", { margin: 3, stretch: go.Stretch.Fill, background: "red" })
.add(
new go.TextBlock({ background: "lime" })
.bind("text", "t1"),
new go.TextBlock({ background: "cyan" })
.bind("text", "t2")
)
);
diagram.model.nodeDataArray = [{ t1: "Top", t2: "Bottom"}];
The Panel with the red background indeed fills up the whole outer Auto Panel, inside its main Shape acting as a border. But the lime green and cyan blue TextBlocks are still only their natural heights.
If you want the text to be spaced evenly vertically, you might think you only need to stretch those two TextBlocks.
diagram.nodeTemplate =
new go.Node("Auto", { width: 80, height: 100 })
.add(
new go.Shape({ fill: "white" }),
new go.Panel("Vertical", { margin: 3, stretch: go.Stretch.Fill, background: "red" })
.add(
new go.TextBlock({ stretch: go.Stretch.Fill, background: "lime" })
.bind("text", "t1"),
new go.TextBlock({ stretch: go.Stretch.Fill, background: "cyan" })
.bind("text", "t2")
)
);
diagram.model.nodeDataArray = [{ t1: "Top", t2: "Bottom"}];
Now the TextBlocks are stretching horizontally but not vertically! The reason is that a Vertical Panel never stretches its elements vertically. It always stacks its elements on top of each other with their natural heights. When a Vertical Panel is taller than the stack of its elements, there is extra space at the bottom. (Similarly, a Horizontal Panel never stretches its elements horizontally.)
Instead of a Vertical Panel we should use a Table Panel. This requires assigning the GraphObject.row on each element (i.e. each TextBlock).
diagram.nodeTemplate =
new go.Node("Auto", { width: 80, height: 100 })
.add(
new go.Shape({ fill: "white" }),
new go.Panel("Table", { margin: 3, stretch: go.Stretch.Fill, background: "red" })
.add(
new go.TextBlock({ row: 0, background: "lime" })
.bind("text", "t1"),
new go.TextBlock({ row: 1, background: "cyan" })
.bind("text", "t2")
)
);
diagram.model.nodeDataArray = [{ t1: "Top", t2: "Bottom"}];
Because by default elements are centered within the cells of a Table Panel, no stretching of the TextBlocks is needed. (You could change that by setting Panel.defaultAlignment or Panel.defaultStretch.)
Are we all done? Maybe. What happens when the text changes size? One way to test that is to create a bunch of nodes using different model data, using short and long strings.
But to demonstrate one more debugging technique, we'll make the Node Part.resizable. You can interactively resize the node (the whole node because we haven't set Part.resizeObjectName) so you can see how the nested Panel and the TextBlocks handle constrained sizing.
diagram.nodeTemplate =
new go.Node("Auto", { width: 80, height: 100, resizable: true })
.add(
new go.Shape({ fill: "white" }),
new go.Panel("Table", { margin: 3, stretch: go.Stretch.Fill, background: "red" })
.add(
new go.TextBlock({ row: 0, background: "lime" })
.bind("text", "t1"),
new go.TextBlock({ row: 1, background: "cyan" })
.bind("text", "t2")
)
);
diagram.model.nodeDataArray = [{ t1: "Top String", t2: "Bottom String"}];
diagram.findNodeForData(diagram.model.nodeDataArray[0]).isSelected = true;
Note how when the node becomes narrow, it clips the text rather than make the text wrap. Let's say that you would rather that the text wrap.
This can be implemented by stretching the TextBlocks horizontally, which will define their widths, forcing the text to wrap. But text normally is drawn at the left side of the bounds of the TextBlock when the text direction is left-to-right. If you want each TextBlock to be centered within its bounds, you'll need to set TextBlock.textAlign to "center".
diagram.nodeTemplate =
new go.Node("Auto", { width: 80, height: 100, resizable: true })
.add(
new go.Shape({ fill: "white" }),
new go.Panel("Table", {
margin: 3,
stretch: go.Stretch.Fill,
defaultStretch: go.Stretch.Horizontal,
background: "red"
})
.add(
new go.TextBlock({ row: 0, background: "lime" })
.bind("text", "t1"),
new go.TextBlock({ row: 1, background: "cyan" })
.bind("text", "t2")
)
);
diagram.model.nodeDataArray = [{ t1: "Top String", t2: "Bottom String"}];
diagram.findNodeForData(diagram.model.nodeDataArray[0]).isSelected = true;
The TextBlocks can be seen to stretch across the width of the available area. Note how the text wraps as the node becomes narrow, causing the TextBlocks to become more narrow. Of course when there's not enough room to render all of the text, the TextBlocks will be clipped.
Now we just need to get rid of the colored backgrounds and resizable-ness used for debugging and assign the desired colors and fonts.
diagram.nodeTemplate =
new go.Node("Auto", { width: 80, height: 100 })
.add(
new go.Shape({ fill: "white" }),
new go.Panel("Table", {
margin: 3,
stretch: go.Stretch.Fill,
defaultStretch: go.Stretch.Horizontal,
background: "purple"
})
.add(
new go.TextBlock({ row: 0, textAlign: "center", stroke: "white", font: "bold 11pt sans-serif" })
.bind("text", "t1"),
new go.TextBlock({ row: 1, textAlign: "center", stroke: "white", font: "bold 11pt sans-serif" })
.bind("text", "t2")
)
);
diagram.model.nodeDataArray = [{ t1: "Top String", t2: "Bottom String"}];