gojs
Version:
Interactive diagrams, charts, and graphs, such as trees, flowcharts, orgcharts, UML, BPMN, or business diagrams
399 lines (381 loc) • 18.1 kB
HTML
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS Debugging Suggestions-- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="../release/go.js"></script>
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Debugging Suggestions</h1>
<p>
Developing a diagramming app involves a lot more than just writing some JavaScript code that uses the <b>GoJS</b> library.
</p>
<ul>
<li>You will need to be familiar with HTML DOM and CSS.</li>
<li>You will need to test your app on many different devices using many different browsers.</li>
<li>You will need to be familiar with your JavaScript framework (if any).</li>
<li>You will need to know how to use each browser's development facilities, especially the console window and debugger.</li>
</ul>
<h3 id="UseGoDebugJSLibrary">Use the <code>go-debug.js</code> library</h3>
<p>
While developing your app make sure you use the debug library, <code>go-debug.js</code>, rather than the <code>go.js</code> 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.
</p>
<h3 id="UseDocumentedAPI">Use the documented API</h3>
<p>
Try to limit your code to only use documented classes, properties, and methods, as listed in the
<a href="../api/index.html" target="api">API</a> reference or in the TypeScript definition file,
<a href="../release/go.d.ts" target="_blank">go.d.ts</a>.
</p>
<p>
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 <a>Point</a>, <a>Rect</a>, <a>Spot</a>, and <a>LayoutVertex</a> instances
and the <a>InputEvent.up</a> property.
</p>
<p>
Do not modify the prototypes of any of the <b>GoJS</b> classes.
If you modify the built-in classes, we cannot support you.
The way to modify the behavior of the <b>GoJS</b> classes is via the techniques discussed at
<a href="extensions.html">Extensions</a>.
However most of the <b>GoJS</b> classes cannot be subclassed and most of the documented methods cannot be overridden.
Generally the <a>Tool</a> and <a>Layout</a> classes and the <a>CommandHandler</a> and <a>Link</a> classes may be subclassed;
look at the API documentation to see if a method may be overridden.
</p>
<h2 id="UsingConsoleWindow">Using the Console window</h2>
<p>
First you will need to get a reference to your <a>Diagram</a> object in the Console window or the Debugger window.
</p>
<p>
One way to do that is by remembering it in your code.
You can set a property on the <code>window</code> object to refer to the Diagram that you create.
Many of the samples do this just by leaving out the <code>var</code> declaration:
<pre class="lang-js"> myDiagram = $(go.Diagram, "myDiagramDiv", . . .);</pre>
</p>
<p>
Alternatively, in the console, if you know the name of the HTML DIV element,
you can call the static function <a>Diagram,fromDiv</a> to get the <a>Diagram</a> object:
<pre class="lang-js">> myDiagram = go.Diagram.fromDiv("myDiagramDiv");</pre>
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 <a>Diagram,fromDiv</a> on that element to get the corresponding Diagram object.
</p>
<p>
Then in the console you can use the <code>myDiagram</code> reference to the <a>Diagram</a> object. Some examples:
</p>
<p>
<pre class="lang-js">> myDiagram.nodes.size</pre>
returns the number of <a>Node</a>s in the Diagram.
</p>
<p>
<pre class="lang-js">> myDiagram.model.nodeDataArray[0]</pre>
returns the first node data object in the diagram's model's <a>Model.nodeDataArray</a>.
</p>
<p>
<pre class="lang-js">> myDiagram.layoutDiagram(true)</pre>
forces all layouts to happen, rearranging the nodes and routing the links.
</p>
<p>
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:
<pre class="lang-js">> myNode = myDiagram.findNodeForKey("Omega");
> myNode.isSelected = true
> myDiagram.commandHandler.scrollToPart(myNode)</pre>
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 <a>Diagram.findNodesByExample</a> method might also be useful.
</p>
<h3 id="ExaminingSelectedNode">Examining a selected Node</h3>
<p>
<pre class="lang-js">> myDiagram.selection.first()</pre>
returns the first selected <a>Part</a>, which might be either a <a>Node</a>, a <a>Link</a>,
or null if nothing is selected.
</p>
<p>
If you remember the selected Node or Link, you can then examine it further more easily. For example:
<pre class="lang-js">> myNode = myDiagram.selection.first()
> myNode.data.key</pre>
remembers the first selected Node and returns the key of the node data.
You might want to look at all of the properties of the <pre class="lang-js">myNode.data</pre> object.
</p>
<p>
You could also look at other properties of the Node and call its methods. For example:
<pre class="lang-js">> myNode.location</pre>
returns a <a>Point</a> whose properties the debugger may show. Or call:
<pre class="lang-js">> myNode.location.toString()</pre>
to see a human-readable textual rendering of that Point object.
</p>
<p>
As another example, you can print out all of the nodes the selected node is connected to:
<pre class="lang-js">myNode.findNodesOutOf().each(function(n) { console.log(n.data.key); })</pre>
You can find more examples of iterating at <a href="collections.html#MoreIterationExamples">Collections</a>
</p>
<p>
You can also look at the structure of the visual tree of a node. With this recursive function:
<pre class="lang-js">> function walk(x, level, index) {
> console.log(level + "," + index + ": " + x.toString());
> if (!(x instanceof go.Panel)) return;
> for (var i = 0; i < x.elements.size; i++) walk(x.elt(i), level+1, i);
> }</pre>
you could call
<pre class="lang-js">> walk(myNode, 0, 0)</pre>
and in the Org Chart sample get results such as:
<pre class="lang-js">
0,0: Node#653(Kensaku Tamaki)
1,0: Shape(Rectangle)#656
1,1: Panel(Panel.Table)#657
2,0: TextBlock("Kensaku Tamaki")
2,1: Picture(https://www.nwoods.com/go/Flags/japan-flag.Png)#664
2,2: TextBlock("Title: Vice Chairman"...)</pre>
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.
</p>
<h2 id="DebuggingNodePanelDesigns">Debugging Node Panel designs</h2>
<p>
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:
</p>
<ul>
<li><a href="https://gojs.net/latest/intro/buildingObjects.html">Building with GraphObjects</a></li>
<li><a href="https://gojs.net/latest/intro/textBlocks.html">TextBlocks</a></li>
<li><a href="https://gojs.net/latest/intro/shapes.html">Shapes</a></li>
<li><a href="https://gojs.net/latest/intro/pictures.html">Pictures</a></li>
<li><a href="https://gojs.net/latest/intro/panels.html">Panels</a></li>
<li><a href="https://gojs.net/latest/intro/tablePanels.html">Table Panels</a></li>
<li><a href="https://gojs.net/latest/intro/sizing.html">Sizing of GraphObjects</a></li>
</ul>
<p>
Say that you want a node consisting of two TextBlocks, one above the other. You might start off with:
</p>
<pre class="lang-js" id="first">
diagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, { fill: "white" }),
$(go.Panel, "Vertical",
{ margin: 3 },
$(go.TextBlock,
new go.Binding("text", "t1")),
$(go.TextBlock,
new go.Binding("text", "t2"))
)
);
diagram.model.nodeDataArray = [{ t1: "Top", t2: "Bottom"}];
</pre>
<script>goCode("first", 500, 140)</script>
<p>
But wait -- you want the node to be a fixed size. So you set the node's width and height:
</p>
<pre class="lang-js" id="second">
diagram.nodeTemplate =
$(go.Node, "Auto",
{ width: 80, height: 100 },
$(go.Shape, { fill: "white" }),
$(go.Panel, "Vertical",
{ margin: 3 },
$(go.TextBlock,
new go.Binding("text", "t1")),
$(go.TextBlock,
new go.Binding("text", "t2"))
)
);
diagram.model.nodeDataArray = [{ t1: "Top", t2: "Bottom"}];
</pre>
<script>goCode("second", 500, 140)</script>
<p>
That looks better, but you are suprised that both TextBlocks are near the center. Why is that?
For debugging purposes let's change the <a>GraphObject.background</a> colors of each TextBlock and the nested Panel.
</p>
<pre class="lang-js" id="third">
diagram.nodeTemplate =
$(go.Node, "Auto",
{ width: 80, height: 100 },
$(go.Shape, { fill: "white" }),
$(go.Panel, "Vertical", { background: "red" },
{ margin: 3 },
$(go.TextBlock, { background: "lime" },
new go.Binding("text", "t1")),
$(go.TextBlock, { background: "cyan" },
new go.Binding("text", "t2"))
)
);
diagram.model.nodeDataArray = [{ t1: "Top", t2: "Bottom"}];
</pre>
<script>goCode("third", 500, 140)</script>
<p>
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.
</p>
<p>
So you think that you just need to <a>GraphObject.stretch</a> the panel.
</p>
<pre class="lang-js" id="fourth">
diagram.nodeTemplate =
$(go.Node, "Auto",
{ width: 80, height: 100 },
$(go.Shape, { fill: "white" }),
$(go.Panel, "Vertical", { background: "red" },
{ margin: 3, stretch: go.GraphObject.Fill },
$(go.TextBlock, { background: "lime" },
new go.Binding("text", "t1")),
$(go.TextBlock, { background: "cyan" },
new go.Binding("text", "t2"))
)
);
diagram.model.nodeDataArray = [{ t1: "Top", t2: "Bottom"}];
</pre>
<script>goCode("fourth", 500, 140)</script>
<p>
Now 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.
</p>
<p>
If you want the text to be spaced evenly vertically,
you might think you only need to stretch those two TextBlocks.
</p>
<pre class="lang-js" id="fifth">
diagram.nodeTemplate =
$(go.Node, "Auto",
{ width: 80, height: 100 },
$(go.Shape, { fill: "white" }),
$(go.Panel, "Vertical", { background: "red" },
{ margin: 3, stretch: go.GraphObject.Fill },
$(go.TextBlock, { background: "lime" },
{ stretch: go.GraphObject.Fill },
new go.Binding("text", "t1")),
$(go.TextBlock, { background: "cyan" },
{ stretch: go.GraphObject.Fill },
new go.Binding("text", "t2"))
)
);
diagram.model.nodeDataArray = [{ t1: "Top", t2: "Bottom"}];
</pre>
<script>goCode("fifth", 500, 140)</script>
<p>
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.
</p>
<p>
Instead of a Vertical Panel we should use a Table Panel.
This requires assigning the <a>GraphObject.row</a> on each element (i.e. each TextBlock).
</p>
<pre class="lang-js" id="sixth">
diagram.nodeTemplate =
$(go.Node, "Auto",
{ width: 80, height: 100 },
$(go.Shape, { fill: "white" }),
$(go.Panel, "Table", { background: "red" },
{ margin: 3, stretch: go.GraphObject.Fill },
$(go.TextBlock, { background: "lime" },
{ row: 0 },
new go.Binding("text", "t1")),
$(go.TextBlock, { background: "cyan" },
{ row: 1 },
new go.Binding("text", "t2"))
)
);
diagram.model.nodeDataArray = [{ t1: "Top", t2: "Bottom"}];
</pre>
<script>goCode("sixth", 500, 140)</script>
<p>
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 <a>Panel.defaultAlignment</a> or <a>Panel.defaultStretch</a>.)
</p>
<p>
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.
</p>
<p>
But to demonstrate one more debugging technique, we'll make the Node <a>Part.resizable</a>.
You can interactively resize the node (the whole node because we haven't set <a>Part.resizeObjectName</a>)
so you can see how the nested Panel and the TextBlocks handle constrained sizing.
</p>
<pre class="lang-js" id="seventh">
diagram.nodeTemplate =
$(go.Node, "Auto", { resizable: true },
{ width: 80, height: 100 },
$(go.Shape, { fill: "white" }),
$(go.Panel, "Table", { background: "red" },
{ margin: 3, stretch: go.GraphObject.Fill },
$(go.TextBlock, { background: "lime" },
{ row: 0 },
new go.Binding("text", "t1")),
$(go.TextBlock, { background: "cyan" },
{ row: 1 },
new go.Binding("text", "t2"))
)
);
diagram.model.nodeDataArray = [{ t1: "Top String", t2: "Bottom String"}];
diagram.findNodeForData(diagram.model.nodeDataArray[0]).isSelected = true;
</pre>
<script>goCode("seventh", 500, 140)</script>
<p>
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.
</p>
<p>
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 <a>TextBlock.textAlign</a> to "center".
</p>
<pre class="lang-js" id="eighth">
diagram.nodeTemplate =
$(go.Node, "Auto", { resizable: true },
{ width: 80, height: 100 },
$(go.Shape, { fill: "white" }),
$(go.Panel, "Table", { background: "red" },
{ margin: 3, stretch: go.GraphObject.Fill,
defaultStretch: go.GraphObject.Horizontal },
$(go.TextBlock, { background: "lime" },
{ row: 0, textAlign: "center" },
new go.Binding("text", "t1")),
$(go.TextBlock, { background: "cyan" },
{ row: 1, textAlign: "center" },
new go.Binding("text", "t2"))
)
);
diagram.model.nodeDataArray = [{ t1: "Top String", t2: "Bottom String"}];
diagram.findNodeForData(diagram.model.nodeDataArray[0]).isSelected = true;
</pre>
<script>goCode("eighth", 500, 140)</script>
<p>
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.
</p>
<p>
Now we just need to get rid of the colored backgrounds and resizable-ness used for debugging
and assign the desired colors and fonts.
</p>
<pre class="lang-js" id="ninth">
diagram.nodeTemplate =
$(go.Node, "Auto",
{ width: 80, height: 100 },
$(go.Shape, { fill: "white" }),
$(go.Panel, "Table",
{ margin: 3, stretch: go.GraphObject.Fill,
defaultStretch: go.GraphObject.Horizontal, background: "purple" },
$(go.TextBlock,
{ row: 0, textAlign: "center", stroke: "white", font: "bold 11pt sans-serif" },
new go.Binding("text", "t1")),
$(go.TextBlock,
{ row: 1, textAlign: "center", stroke: "white", font: "bold 11pt sans-serif" },
new go.Binding("text", "t2"))
)
);
diagram.model.nodeDataArray = [{ t1: "Top String", t2: "Bottom String"}];
</pre>
<script>goCode("ninth", 500, 140)</script>
</div>
</div>
</body>
</html>