UNPKG

50.8 kBHTMLView Raw
1<!DOCTYPE html>
2<html lang="en">
3 <head>
4<meta charset="utf-8"/>
5<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, viewport-fit=cover"/>
6
7<link rel="stylesheet" href="../assets/css/style.css"/>
8<!-- Copyright 1998-2021 by Northwoods Software Corporation. --> <title> GoJS Coordinate Systems-- Northwoods Software </title>
9 <link rel="stylesheet" href="../assets/css/prism.css" />
10 </head>
11 <script>
12
13 window.diagrams = [];
14 window.goCode = function(pre, w, h, parentid, animation) {
15 window.diagrams.push([pre, w, h, parentid, animation]);
16 }
17 </script>
18 <body>
19 <nav id="navTop" class="w-full z-30 top-0 text-white bg-nwoods-primary">
20 <div class="w-full container max-w-screen-lg mx-auto flex flex-wrap sm:flex-nowrap items-center justify-between mt-0 py-2">
21 <div class="md:pl-4">
22 <a class="text-white hover:text-white no-underline hover:no-underline
23 font-bold text-2xl lg:text-4xl rounded-lg hover:bg-nwoods-secondary " href="../">
24 <h1 class="mb-0 p-1 ">GoJS</h1>
25 </a>
26 </div>
27 <button id="topnavButton" class="rounded-lg sm:hidden focus:outline-none focus:ring" aria-label="Navigation">
28 <svg fill="currentColor" viewBox="0 0 20 20" class="w-6 h-6">
29 <path id="topnavOpen" fill-rule="evenodd" d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM9 15a1 1 0 011-1h6a1 1 0 110 2h-6a1 1 0 01-1-1z" clip-rule="evenodd"></path>
30 <path id="topnavClosed" class="hidden" fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
31 </svg>
32 </button>
33 <div id="topnavList" class="hidden sm:block items-center w-auto mt-0 text-white p-0 z-20">
34 <ul class="list-reset list-none font-semibold flex justify-end flex-wrap sm:flex-nowrap items-center px-0 pb-0">
35 <li class="p-1 sm:p-0"><a class="topnav-link" href="../learn/">Learn</a></li>
36 <li class="p-1 sm:p-0"><a class="topnav-link" href="../samples/">Samples</a></li>
37 <li class="p-1 sm:p-0"><a class="topnav-link" href="../intro/">Intro</a></li>
38 <li class="p-1 sm:p-0"><a class="topnav-link" href="../api/">API</a></li>
39 <li class="p-1 sm:p-0"><a class="topnav-link" href="https://www.nwoods.com/products/register.html">Register</a></li>
40 <li class="p-1 sm:p-0"><a class="topnav-link" href="../download.html">Download</a></li>
41 <li class="p-1 sm:p-0"><a class="topnav-link" href="https://forum.nwoods.com/c/gojs/11">Forum</a></li>
42 <li class="p-1 sm:p-0"><a class="topnav-link" href="https://www.nwoods.com/contact.html"
43 target="_blank" rel="noopener" onclick="getOutboundLink('https://www.nwoods.com/contact.html', 'contact');">Contact</a></li>
44 <li class="p-1 sm:p-0"><a class="topnav-link" href="https://www.nwoods.com/sales/index.html"
45 target="_blank" rel="noopener" onclick="getOutboundLink('https://www.nwoods.com/sales/index.html', 'buy');">Buy</a></li>
46 </ul>
47 </div>
48 </div>
49 <hr class="border-b border-gray-600 opacity-50 my-0 py-0" />
50 </nav>
51
52 <div class="md:flex flex-col md:flex-row md:min-h-screen w-full max-w-screen-xl mx-auto">
53
54 <div id="navSide" class="flex flex-col w-full md:w-40 lg:w-48 text-gray-700 bg-white flex-shrink-0">
55 <div class="flex-shrink-0 px-8 py-4">
56 <button id="navButton" class="rounded-lg md:hidden focus:outline-none focus:ring" aria-label="Navigation">
57 <svg fill="currentColor" viewBox="0 0 20 20" class="w-6 h-6">
58 <path id="navOpen" fill-rule="evenodd" d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM9 15a1 1 0 011-1h6a1 1 0 110 2h-6a1 1 0 01-1-1z" clip-rule="evenodd"></path>
59 <path id="navClosed" class="hidden" fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
60 </svg>
61 </button>
62 </div>
63 <nav id="navList" class="min-h-screen hidden md:block sidebar-nav flex-grow px-1 lg:px-4 pb-4 md:pb-0 md:overflow-y-auto break-words">
64 <a href="index.html">Basics</a>
65 <a href="buildingObjects.html">Building Parts</a>
66 <a href="usingModels.html">Using Models</a>
67 <a href="dataBinding.html">Data Binding</a>
68 <a href="react.html">GoJS with React</a>
69 <a href="angular.html">GoJS with Angular</a>
70 <a href="textBlocks.html">TextBlocks</a>
71 <a href="shapes.html">Shapes</a>
72 <a href="pictures.html">Pictures</a>
73 <a href="panels.html">Panels</a>
74 <a href="tablePanels.html">Table Panels</a>
75 <a href="brush.html">Brushes</a>
76 <a href="sizing.html">Sizing Objects</a>
77 <a href="itemArrays.html">Item Arrays</a>
78 <a href="changedEvents.html">Changed Events</a>
79 <a href="transactions.html">Transactions</a>
80 <a href="viewport.html">Coordinates</a>
81 <a href="initialView.html">Initial View</a>
82 <a href="collections.html">Collections</a>
83 <a href="links.html">Links</a>
84 <a href="linkLabels.html">Link Labels</a>
85 <a href="connectionPoints.html">Link Points</a>
86 <a href="ports.html">Ports</a>
87 <a href="nodes.html">Nodes</a>
88 <a href="debugging.html">Debugging</a>
89 <a href="layouts.html">Layouts</a>
90 <a href="trees.html">Trees</a>
91 <a href="subtrees.html">SubTrees</a>
92 <a href="groups.html">Groups</a>
93 <a href="subgraphs.html">SubGraphs</a>
94 <a href="sizedGroups.html">Sized Groups</a>
95 <a href="selection.html">Selection</a>
96 <a href="highlighting.html">Highlighting</a>
97 <a href="animation.html">Animation</a>
98 <a href="toolTips.html">ToolTips</a>
99 <a href="contextmenus.html">Context Menus</a>
100 <a href="events.html">Diagram Events</a>
101 <a href="tools.html">Tools</a>
102 <a href="commands.html">Commands</a>
103 <a href="permissions.html">Permissions</a>
104 <a href="validation.html">Validation</a>
105 <a href="HTMLInteraction.html">HTML Interaction</a>
106 <a href="layers.html">Layers &amp; Z-ordering</a>
107 <a href="palette.html">Palette</a>
108 <a href="overview.html">Overview</a>
109 <a href="resizing.html">Resizing Diagrams</a>
110 <a href="replacingDeleting.html">Replacing and Deleting</a>
111 <a href="buttons.html">Buttons</a>
112 <a href="templateMaps.html">Template Maps</a>
113 <a href="legends.html">Legends and Titles</a>
114 <a href="extensions.html">Extensions</a>
115 <a href="geometry.html">Geometry Strings</a>
116 <a href="grids.html">Grid Patterns</a>
117 <a href="graduatedPanels.html">Graduated Panels</a>
118 <a href="makingImages.html">Diagram Images</a>
119 <a href="makingSVG.html">Diagram SVG</a>
120 <a href="printing.html">Printing</a>
121 <a href="serverSideImages.html">Server-side Images</a>
122 <a href="nodeScript.html">GoJS in Node.js</a>
123 <a href="testing.html">Testing</a>
124 <a href="storage.html">Storage</a>
125 <a href="performance.html">Performance</a>
126 <a href="source.html">Building from Source</a>
127 <a href="platforms.html">Platforms</a>
128 <a href="deployment.html">Deployment</a>
129 </nav>
130 </div>
131 <div class="pt-4 px-2 md:px-0 lg:px-4 pb-16 w-full overflow-hidden">
132
133<h1>Coordinate Systems</h1>
134<p>
135A <a>Diagram</a> uses two major coordinate systems when drawing <a>Part</a>s: document coordinates and view coordinates.
136Furthermore each <a>Panel</a> within a <a>Part</a> has its own coordinate system that its elements use.
137</p>
138<p>
139All coordinate systems in <b>GoJS</b> have <a>Point</a>s with increasing values of X going rightwards and
140increasing values of Y going downwards.
141</p>
142
143<h2 id="DocumentAndViewCoordinates">Document and View coordinates</h2>
144<p>
145The <a>Part.location</a> and <a>GraphObject.actualBounds</a> and <a>GraphObject.position</a> of Parts are in document coordinates.
146Thus the <a>Point</a> that may be saved for a Node's location in the model's node data object are normally in document coordinates:
147</p>
148<pre class="lang-js"><code>
149 diagram.model.nodeDataArray = [
150 { key: "Alpha", loc: "0 0" },
151 { key: "Beta", loc: "100 50" }
152 ];
153</code></pre>
154<p>
155The union of all of the Nodes and Links in a Diagram form the <a>Diagram.documentBounds</a>.
156This <a>Rect</a> has values that are in document coordinates.
157Depending on where the Nodes and Links are, the document bounds may cover a very large area.
158The range of the document bounds might be entirely positive x and y values, or that might be entirely negative,
159or more likely they may cover both negative and positive values.
160</p>
161<p>
162But a Part with a <a>Part.location</a> of (0, 0) in document coordinates is not always drawn at the top-left corner of
163the HTML Div element that the user sees in the page.
164When the user scrolls the diagram the part will need to be drawn elsewhere on the canvas.
165And if the user zooms in to make the parts appear larger, the parts will be drawn at different points in the canvas.
166Yet the <a>Part.location</a> does not change value as the user scrolls or zooms the diagram.
167</p>
168<p>
169The <i>viewport</i> is the area of the document that is visible in the canvas.
170That area is available as the <a>Diagram.viewportBounds</a>.
171Note that the viewport bounds is in document coordinates, not in view coordinates!
172The top-left corner of the viewport is (0,0) in view coordinates but is at <a>Diagram.position</a> in document coordinates.
173The bottom-right corner of the viewport is at the canvas's (width,height) in view coordinates.
174The bottom-right corner of the viewport in document coordinates depends on the <a>Diagram.scale</a>.
175</p>
176<p>
177Points in the canvas are in view coordinates, which are independent of document coordinates.
178View coordinates are distances from the top-left corner of the canvas in device-independent pixels.
179The differences between document coordinates and view coordinates are primarily controlled by two <a>Diagram</a> properties:
180<a>Diagram.position</a> and <a>Diagram.scale</a>.
181Scrolling and panning change the Diagram.position.
182Zooming in or out changes the Diagram.scale.
183You can also convert between coordinate systems by calling <a>Diagram.transformDocToView</a> and <a>Diagram.transformViewToDoc</a>.
184However very few properties or method arguments or return values are in view coordinates -- almost everything is in document
185coordinates or in panel coordinates.
186</p>
187<p>
188As an example of showing the viewport in the context of the whole document, an <a>Overview</a> does exactly that.
189Take a look at the overview that is in the <a href="../samples/orgChartStatic.html">Org Chart sample</a>.
190The overview shows the whole document of the main diagram.
191The magenta box shows the main diagram's viewport within the whole document.
192As you scroll or pan the main diagram, the viewport moves.
193As you zoom out, the viewport gets larger.
194</p>
195<p>
196To better understand the difference between document and viewport coordinates, look at this diagram:
197</p>
198<pre class="lang-js" id="diffCoordSystems" style="display:none"><code>
199 diagram.nodeTemplate =
200 $(go.Node, "Auto",
201 { scale : 1.3},
202 new go.Binding("location", "loc", gridPointParse),
203 new go.Binding("scale", "scale"),
204 { locationSpot: go.Spot.Center, portId: "NODE" },
205 $(go.Shape, "RoundedRectangle",
206 { fill: "white", portId: "SHAPE" },
207 new go.Binding("fill", "color"),
208 new go.Binding("strokeWidth", "strokeW")),
209 $(go.TextBlock,
210 { margin: 4, portId: "TEXTBLOCK" },
211 new go.Binding("text", "text"),
212 new go.Binding("stroke", "textColor"))
213 );
214
215 diagram.linkTemplate =
216 $(go.Link,
217 $(go.Shape, { stroke: "darkgray", strokeWidth: 2 }),
218 $(go.Shape, { toArrow: "Standard", stroke: "darkgray", fill: "darkgray" })
219 );
220
221 // colors
222 var docGridStroke = "rgba(70, 130, 180, 0.5)";
223 var viewGridStroke = "rgba(255, 128, 128, 1)";
224 var commentStroke = "brown";
225
226 var cellSide = 20; // side length of one grid cell
227 var cellSize = new go.Size(cellSide * 5, cellSide * 5);
228 var pointSize = 7;
229
230 function gridSizeParse(size) {
231 if (!(size instanceof go.Size)) {
232 size = go.Size.parse(size);
233 }
234 size.setTo(size.width * cellSide, size.height * cellSide);
235 return size;
236 }
237 function gridPointParse(point) {
238 if (!(point instanceof go.Point)) {
239 point = go.Point.parse(point);
240 }
241 point.setTo(point.x * cellSide, point.y * cellSide);
242 return point;
243 }
244
245 function LabelledPoint(x, y, label) {
246 return {
247 x: x,
248 y: y,
249 label: label
250 }
251 }
252
253 diagram.nodeTemplateMap.add("Description", // Template for comment node
254 $(go.Node, "Auto",
255 new go.Binding("location", "loc", gridPointParse),
256 new go.Binding("scale", "scale"),
257 { locationSpot: go.Spot.Center, portId: "NODE" },
258 $(go.Shape, "RoundedRectangle",
259 { fill: "white", portId: "SHAPE" },
260 new go.Binding("fill", "color"),
261 new go.Binding("strokeWidth", "strokeW")),
262 $(go.Panel, "Vertical",
263 $(go.TextBlock,
264 {font: "bold 11pt sans-serif", margin: new go.Margin(3, 0, 0, 0)},
265 new go.Binding("text", "header"),
266 new go.Binding("stroke", "textColor")),
267 $(go.TextBlock,
268 { margin: 3, portId: "TEXTBLOCK" },
269 new go.Binding("text", "text"),
270 new go.Binding("stroke", "textColor"))
271 )
272 ));
273
274 diagram.nodeTemplateMap.add("DeltaDescription", // Template for comment node
275 $(go.Node, "Auto",
276 new go.Binding("location", "loc", gridPointParse),
277 new go.Binding("scale", "scale"),
278 { locationSpot: go.Spot.Center, portId: "NODE" },
279 $(go.Shape, "RoundedRectangle",
280 { fill: "white", portId: "SHAPE" },
281 new go.Binding("fill", "color"),
282 new go.Binding("strokeWidth", "strokeW")),
283 $(go.Panel, "Vertical",
284 $(go.TextBlock,
285 {font: "bold 11pt sans-serif", margin: new go.Margin(3, 0, 0, 0)},
286 new go.Binding("text", "header"),
287 new go.Binding("stroke", "textColor")),
288 $(go.TextBlock,
289 { margin: 3, portId: "TEXTBLOCK", alignment: go.Spot.Left },
290 new go.Binding("text", "text"),
291 new go.Binding("stroke", "textColor")),
292 $(go.TextBlock,
293 { margin: 3, portId: "TEXTBLOCK", font: "italic 10pt sans-serif" },
294 new go.Binding("text", "desc"),
295 new go.Binding("stroke", "textColor"))
296 )
297 ));
298
299 diagram.nodeTemplateMap.add("Point", // template for denoting points on the grid
300 $(go.Node, "Vertical",
301 { movable: false },
302 new go.Binding("location", "point", function gridPointLocation(point) {
303 label = point.label;
304 // measure the longest line's width
305 textLines = label.split("\n");
306 width = 0
307 textLines.forEach(function(text) {
308 var textBlock = $(go.TextBlock, { text: text, font: "bold 10pt sans-serif"});
309 if (textBlock.naturalBounds.right > width) {
310 width = textBlock.naturalBounds.right;
311 }
312 });
313
314 // text block with entire string to measure height
315 var textBlock = $(go.TextBlock, { text: label, font: "bold 10pt sans-serif" });
316 point = new go.Point(point.x, point.y);
317
318 // convert from grid coordinates to diagram coordinates
319 gridPointParse(point);
320
321 // offset
322 point.setTo(point.x - width / 2, point.y - pointSize / 2 - textBlock.naturalBounds.bottom); // align to center of circle to intersection instead of top left corner
323 return point;
324 }),
325
326 $(go.TextBlock,
327 {position: new go.Point(0, -pointSize - 7), textAlign: "center", font : "bold 10pt sans-serif"},
328 new go.Binding("text", "point", function getLabel(point) {
329 return point.label;
330 }), new go.Binding("margin", "margin")),
331 $(go.Shape,
332 "Circle",
333 {width: pointSize, height: pointSize, alignment: go.Spot.Center})
334 ));
335
336 diagram.linkTemplateMap.add("Comment", // Template for links from comments
337 $(go.Link,
338 { curve: go.Link.Bezier },
339 new go.Binding("curviness"),
340 new go.Binding("fromSpot", "fromSpot"),
341 new go.Binding("toSpot", "toSpot"),
342 $(go.Shape, { stroke: commentStroke },
343 new go.Binding("stroke", "stroke")),
344 $(go.Shape, { toArrow: "OpenTriangle", stroke: commentStroke },
345 new go.Binding("stroke", "stroke"))
346 ));
347
348 diagram.groupTemplateMap.add("Grid",
349 $(go.Group, "Position",
350 { movable: false },
351 $(go.Shape, "Rectangle", { fill: "transparent", strokeWidth: 2},
352 new go.Binding("fill", "fill"),
353 new go.Binding("stroke", "border"),
354 new go.Binding("desiredSize", "size", gridSizeParse).makeTwoWay(go.Size.stringify)),
355 $(go.Panel, "Grid",
356 { name: "DOCGRID", desiredSize: cellSize, gridCellSize: new go.Size(cellSide, cellSide) },
357 new go.Binding("desiredSize", "size", gridSizeParse).makeTwoWay(go.Size.stringify),
358 new go.Binding("gridCellSize", "cell", go.Size.parse).makeTwoWay(go.Size.stringify),
359 $(go.Shape, "LineV",
360 new go.Binding("stroke")),
361 $(go.Shape, "LineH",
362 new go.Binding("stroke"))
363 ),
364 new go.Binding("location", "loc", gridPointParse)
365 ));
366
367 diagram.initialContentAlignment = go.Spot.Center;
368
369 var model = new go.GraphLinksModel();
370 model.linkFromPortIdProperty = "fPID";
371 model.linkToPortIdProperty = "tPID"
372
373 model.nodeDataArray = [
374 { key: "docGrid", isGroup: true, category: "Grid", stroke: docGridStroke, fill: "transparent", size: "24 20", border: docGridStroke },
375 { key: "viewGrid", isGroup: true, group: "docGrid", category: "Grid", fill: "rgb(248,248,248)",stroke: viewGridStroke, size: "13.7 9.95", loc: "5.8 5.2", cell: "25 25", border: viewGridStroke},
376 { key: "alpha", group: "docGrid", text: "Alpha", loc: "1.95 6.95"},
377 { key: "beta", group: "docGrid", text: "Beta", loc: "12.4 1.25"},
378 { key: "gamma", group: "docGrid", text: "Gamma", loc: "10 7.95"},
379 { key: "delta", group: "docGrid", text: "Delta", loc: "14.5 11.95"},
380 { key: "epsilon", group: "docGrid", text: "Epsilon", loc: "7.9 17.95"},
381 { key: "zeta", group: "docGrid", text: "Zeta", loc: "12.35 18.7"},
382 { key: "eta", group: "docGrid", text: "Eta", loc: "22.5 11.95"},
383 { key: "point1", group: "docGrid", category: "Point", point: LabelledPoint(5.8, 5.2, "(300, 250) Document Coordinates\n(0, 0) Viewport Coordinates")},
384 { key: "point2", group: "docGrid", category: "Point", point: LabelledPoint(19.3, 14.9, "(850, 650) Document Coordinates\n(550, 400) Viewport Coordinates"), margin: new go.Margin(0, 0, 4, 0)},
385 { key: "point1", group: "docGrid", category: "Point", point: LabelledPoint(0, 0, "(0, 0) Document Coordinates")},
386 { key: "point1", group: "docGrid", category: "Point", point: LabelledPoint(24, 20, "(1200, 1000) Document Coordinates")},
387 { key: "viewportDesc", category: "Description", header: "Viewport", text: "position: (300, 250)\nviewportBounds: (550, 400)\nscale: 1.25", textColor: "brown", loc: "0 17"},
388 { key: "documentDesc", category: "Description", header: "Document", text: "documentBounds: (1200, 1000)\npadding: (5, 5, 5, 5)", textColor: "rgb(50, 120, 160)", loc: "22 -3"},
389 { key: "deltaDesc", category: "DeltaDescription", header: "Delta", text: "location: (650, 550)", desc: "Location is in document\ncoordinates, and does not\nchange with viewport\nmovement or scaling.", textColor: "black", loc: "26 6"}
390 ];
391 model.linkDataArray = [
392 { to: "viewGrid", from: "viewportDesc", category: "Comment", stroke: "brown"},
393 { to: "docGrid", from: "documentDesc", category: "Comment", stroke: "rgb(50, 120, 160)"},
394 { to: "delta", from: "deltaDesc", category: "Comment", stroke: "black", curviness: -10},
395 { to: "gamma", from: "alpha"},
396 { to: "gamma", from: "beta"},
397 { to: "delta", from: "gamma"},
398 { to: "epsilon", from: "delta"},
399 { to: "zeta", from: "delta"},
400 { to: "eta", from: "delta"}
401 ];
402
403 diagram.model = model;
404
405
406 // Formatting
407 function headerStyle() {
408 return {
409 margin: 3,
410 font: "bold 12pt sans-serif",
411 minSize: new go.Size(140, 16),
412 maxSize: new go.Size(120, NaN),
413 textAlign: "center"
414 };
415 }
416 function textStyle() {
417 return {
418 margin: 3,
419 font: "italic 10pt sans-serif",
420 minSize: new go.Size(16, 16),
421 maxSize: new go.Size(160, NaN),
422 textAlign: "left"
423 };
424 }
425</code></pre>
426<script>goCode("diffCoordSystems", 750, 650)</script>
427
428<h2 id="CoordinateSystemsExample">Coordinate systems example</h2>
429<p>
430This example shows three Parts at three different locations in document coordinates.
431Pass the mouse over each of the parts to see where those locations are in view coordinates.
432Initially you will see that the only difference between document and view coordinates are a constant offset.
433That offset is due to the <a>Diagram.padding</a> that puts a little space between the edge of the canvas and
434the edge of where the diagram's objects are.
435It is also due to <a>Part.locationSpot</a> having the location be at the center of the "+" Shape,
436not at the top-left corner of the whole Part.
437</p>
438<pre class="lang-js" id="coordsystems"><code>
439 // read-only to avoid accidentally moving any Part in document coordinates
440 diagram.isReadOnly = true;
441
442 diagram.nodeTemplate =
443 $(go.Part, // no links or grouping, so use the simpler Part class instead of Node
444 {
445 locationSpot: go.Spot.Center, locationObjectName: "SHAPE",
446 layerName: "Background",
447 mouseOver: function (e, obj) { showPoint(obj.part.location); },
448 click: function (e, obj) { showPoint(obj.part.location); }
449 },
450 new go.Binding("location", "loc", go.Point.parse),
451 $(go.Shape, "PlusLine",
452 { name: "SHAPE", width: 8, height: 8 }),
453 $(go.TextBlock,
454 { position: new go.Point(6, 6), font: "8pt sans-serif" },
455 new go.Binding("text", "loc"))
456 );
457
458 diagram.model.nodeDataArray = [
459 { loc: "0 0" },
460 { loc: "100 0" },
461 { loc: "100 50" }
462 ];
463
464 function showPoint(loc) {
465 var docloc = diagram.transformDocToView(loc);
466 var elt = document.getElementById("Message1");
467 elt.textContent = "Selected node location,\ndocument coordinates: " + loc.x.toFixed(2) + " " + loc.y.toFixed(2) +
468 "\nview coordinates: " + docloc.x.toFixed(2) + " " + docloc.y.toFixed(2);
469 }
470 // make accessible to the HTML buttons:
471 myDiagram = diagram;
472</code></pre>
473<script>goCode("coordsystems", 300, 150)</script>
474<textarea id="Message1" style="width: 300px; height: 70px">(move mouse over node to see points in document and in view coordinates)</textarea>
475<input id="ZoomOut" type="button" onclick="myDiagram.commandHandler.decreaseZoom()" value="Zoom Out" />
476<input id="ZoomIn" type="button" onclick="myDiagram.commandHandler.increaseZoom()" value="Zoom In" />
477<p>
478Then try scrolling or zooming in and looking at the locations of those parts in view coordinates.
479Zooming in increases the <a>Diagram.scale</a> by a small factor.
480That changes the locations in view coordinates, even though the locations in document coordinates did not change.
481</p>
482<p class="box bg-info">
483 To "move" a node one must change its <a>GraphObject.position</a> or <a>Part.location</a> in document coordinates.
484 To "scroll" a diagram one must change the <a>Diagram.position</a>.
485 Either way will cause a node to appear at a different point in the viewport.
486</p>
487
488<h2 id="DocumentBounds">Document bounds</h2>
489<p>
490All of the <a>Part</a>s of a diagram have positions and sizes (i.e. their <a>GraphObject.actualBounds</a>) in document coordinates.
491The union of all of those parts' actualBounds constitutes the <a>Diagram.documentBounds</a>.
492If all of the parts are close together, the document bounds might be small.
493If some or all of the parts are far apart from each other, the document bounds might be large, even if there are only two parts
494or if there is just one really large part.
495The <a>Diagram.documentBounds</a> value is independent of the <a>Diagram.viewportBounds</a>.
496The former only depends on the bounds of the parts; the latter only depends on the size of the canvas and the diagram's
497position and scale.
498</p>
499<p>
500<a>Diagram.computeBounds</a>, which is responsible for the bounds computation,
501also adds the <a>Diagram.padding</a> Margin so that no Parts appear directly up against the edge of the diagram when scrolled to that side.
502You may want to keep some parts, particularly background decorations, from being included in the document bounds computation.
503Just set <a>Part.isInDocumentBounds</a> to false for such parts.
504</p>
505<p>
506The diagram does not compute a new value for <a>Diagram.documentBounds</a> immediately upon any change to any part
507or the addition or removal of a part.
508Thus the <a>Diagram.documentBounds</a> property value may not be up-to-date until after a transaction completes.
509</p>
510<p>
511The relative sizes of the <a>Diagram.documentBounds</a> and <a>Diagram.viewportBounds</a> control whether or not
512scrollbars are needed.
513You can set <a>Diagram.hasHorizontalScrollbar</a> and/or <a>Diagram.hasVerticalScrollbar</a> to false to
514make sure no scrollbar appears even when needed.
515</p>
516<p>
517If you do not want the <a>Diagram.documentBounds</a> to always reflect the sizes and locations of all of the nodes and links,
518you can set the <a>Diagram.fixedBounds</a> property.
519However if there are any nodes that are located beyond the fixedBounds, the user may be unable to scroll the diagram to see them.
520</p>
521<p>
522If you want to be notified whenever the document bounds changes, you can register a "DocumentBoundsChanged" <a>DiagramEvent</a> listener.
523</p>
524
525<h2 id="ViewportBounds">Viewport bounds</h2>
526<p>
527The <a>Diagram.viewportBounds</a> always has x and y values that are given by the <a>Diagram.position</a>.
528It always has width and height values that are computed from the canvas size and the <a>Diagram.scale</a>.
529</p>
530<p>
531Users can scroll the document contents using keyboard commands, scrollbars or panning.
532Programmatically, you can scroll using several means:
533</p>
534<ul>
535 <li>setting <a>Diagram.position</a></li>
536 <li>calling <a>Diagram.scrollToRect</a> or <a>Diagram.centerRect</a> or <a>Diagram.scroll</a></li>
537 <li>calling <a>Diagram.alignDocument</a></li>
538 <li>setting <a>Diagram.contentAlignment</a></li>
539 <li>calling <a>CommandHandler.scrollToPart</a></li>
540</ul>
541<p>
542Furthermore, scrolling may happen automatically as nodes or links are added to or removed from or change visibility in the diagram.
543Also, zooming will typically result in scrolling as well.
544</p>
545<p>
546When scrolling, the <a>Diagram.position</a> normally will be limited to the range specified by the <a>Diagram.documentBounds</a>.
547The short or "line" scrolling distance is controlled by <a>Diagram.scrollHorizontalLineChange</a> and <a>Diagram.scrollVerticalLineChange</a>.
548The long or "page" scrolling distance is controlled by the size of the viewport.
549If you want to control the precise values that the <a>Diagram.position</a> may have,
550you can specify a <a>Diagram.positionComputation</a> function. See the example below.
551</p>
552<p>
553User can zoom in or out using keyboard commands, mouse wheel, or pinching.
554Programmatically, you can zoom using several means:
555</p>
556<ul>
557 <li>setting <a>Diagram.scale</a></li>
558 <li>calling <a>Diagram.zoomToFit</a> or <a>Diagram.zoomToRect</a></li>
559 <li>setting <a>Diagram.autoScale</a></li>
560 <li>calling <a>CommandHandler.decreaseZoom</a>, <a>CommandHandler.increaseZoom</a>, <a>CommandHandler.resetZoom</a>, or
561 <a>CommandHandler.zoomToFit</a></li>
562</ul>
563<p>
564When zooming in or out, the <a>Diagram.scale</a> normally will be limited to the range given by <a>Diagram.minScale</a> and <a>Diagram.maxScale</a>.
565If you want to control the precise values that the <a>Diagram.scale</a> may have,
566you can specify a <a>Diagram.scaleComputation</a> function. See the example below.
567</p>
568<p>
569If you want to be notified whenever the viewport bounds changes, you can register a "ViewportBoundsChanged" <a>DiagramEvent</a> listener.
570</p>
571
572<h2 id="ScrollMargin">Scroll margin</h2>
573<p>
574 <a>Diagram.scrollMargin</a> allows the user to scroll into empty space at the edges of the viewport,
575 when the document bounds (including its <a>Diagram.padding</a> margin) is greater than the viewport bounds.
576 This can be useful when users need extra space at the edges of a Diagram,
577 for instance to have an area to create new nodes with the <a>ClickCreatingTool</a>.
578</p>
579<p>
580 <a>Diagram.padding</a> is added as if part of the document bounds,
581 whereas <code>scrollMargin</code> makes sure you can scroll to empty space beyond the document bounds.
582 Because of this, <code>scrollMargin</code> does not create additional scrollable empty space if none
583 is needed to scroll the margin distance beyond, such as when the document bounds are very small in the viewport.
584</p>
585<p>
586 Below is a Diagram with <code>scrollMargin</code> set to <code>100</code>.
587 As you drag to the boundary, you will find the additional space created by the margin.
588</p>
589
590<pre class="lang-js" id="scrollmargin" style="display: none;"><code>
591 diagram.grid = $(go.Panel, "Grid",
592 $(go.Shape, "LineH", { stroke: "gray", strokeWidth: 0.5 }),
593 $(go.Shape, "LineH", { stroke: "darkslategray", strokeWidth: 1.5, interval: 10 }),
594 $(go.Shape, "LineV", { stroke: "gray", strokeWidth: 0.5 }),
595 $(go.Shape, "LineV", { stroke: "darkslategray", strokeWidth: 1.5, interval: 10 })
596 );
597 diagram.scrollMargin = 100;
598
599diagram.nodeTemplate =
600 $(go.Node, "Auto",
601 $(go.Shape, "RoundedRectangle",
602 new go.Binding("fill", "color")),
603 $(go.TextBlock,
604 { margin: 3 },
605 new go.Binding("text", "key"))
606 );
607
608var nodes = [];
609for (var i = 0; i < 99; i++) {
610 nodes.push({ key: "Alpha", color: "lightblue" });
611}
612diagram.model = new go.GraphLinksModel(nodes,[]);
613
614</code></pre>
615<script>goCode("scrollmargin", 400, 400)</script>
616
617
618<h2 id="ScrollingModes">Scrolling modes</h2>
619<p>
620 <a>Diagram.scrollMode</a> allows the user to either scroll to document bound borders with <a>Diagram,DocumentScroll</a> (the default),
621 or scroll endlessly with <a>Diagram,InfiniteScroll</a>.
622</p>
623<p>
624 <a>Diagram.positionComputation</a> and <a>Diagram.scaleComputation</a> allow you to determine what positions
625 and scales are acceptable to be scrolled to.
626 For instance, you could allow only integer position values, or only allow scaling to the values of 0.5, 1, or 2.
627</p>
628<p>
629 The <a href="../samples/scrollModes.html">Scroll Modes sample</a> displays all the code for the example below,
630 which lets you toggle these three properties.
631</p>
632
633<pre class="lang-js" id="scrollmodes" style="display: none;"><code>
634 diagram.minScale = 0.25;
635 diagram.grid = $(go.Panel, "Grid",
636 $(go.Shape, "LineH", { stroke: "gray", strokeWidth: 0.5 }),
637 $(go.Shape, "LineH", { stroke: "darkslategray", strokeWidth: 1.5, interval: 10 }),
638 $(go.Shape, "LineV", { stroke: "gray", strokeWidth: 0.5 }),
639 $(go.Shape, "LineV", { stroke: "darkslategray", strokeWidth: 1.5, interval: 10 })
640 );
641 diagram.toolManager.draggingTool.isGridSnapEnabled = true;
642 diagram.undoManager.isEnabled = true;
643
644
645 diagram.nodeTemplate =
646 $(go.Node, "Auto",
647 $(go.Shape, "RoundedRectangle",
648 new go.Binding("fill", "color")),
649 $(go.TextBlock,
650 { margin: 3 },
651 new go.Binding("text", "key"))
652 );
653
654 // create the model data that will be represented by Nodes and Links
655 diagram.model = new go.GraphLinksModel(
656 [
657 { key: "Alpha", color: "lightblue" },
658 { key: "Beta", color: "orange" },
659 { key: "Gamma", color: "lightgreen" },
660 { key: "Delta", color: "pink" }
661 ],
662 [
663 { from: "Alpha", to: "Beta" },
664 { from: "Alpha", to: "Gamma" },
665 { from: "Gamma", to: "Delta" },
666 { from: "Delta", to: "Alpha" }
667 ]);
668
669 // make accessible to the HTML buttons
670 myDiagram2 = diagram;
671</code></pre>
672<script>goCode("scrollmodes", 400, 400)</script>
673<p>
674 <label><input id="infscroll" type="checkbox" />Enable Infinite Scrolling, setting <a>Diagram.scrollMode</a></label>
675</p>
676<pre class="lang-js"><code>
677 myDiagram.scrollMode = checked ? go.Diagram.InfiniteScroll : go.Diagram.DocumentScroll;
678</code></pre>
679
680<p>
681 <label><input id="poscomp" type="checkbox" />Enable <a>Diagram.positionComputation</a> function</label>
682</p>
683<pre class="lang-js"><code>
684 function positionfunc(diagram, pos) {
685 var size = diagram.grid.gridCellSize;
686 return new go.Point(
687 Math.round(pos.x / size.width) * size.width,
688 Math.round(pos.y / size.height) * size.height);
689 }
690</code></pre>
691
692<p>
693 <label><input id="scalecomp" type="checkbox" />Enable <a>Diagram.scaleComputation</a> function</label>
694</p>
695<pre class="lang-js"><code>
696 function scalefunc(diagram, scale) {
697 var oldscale = diagram.scale;
698 if (scale > oldscale) {
699 return oldscale + 0.25;
700 } else if (scale < oldscale) {
701 return oldscale - 0.25;
702 }
703 return oldscale;
704 }
705</code></pre>
706<script type="text/javascript">
707 function positionfunc(diagram, pos) {
708 var size = diagram.grid.gridCellSize;
709 return new go.Point(
710 Math.round(pos.x / size.width) * size.width,
711 Math.round(pos.y / size.height) * size.height);
712 }
713
714 function scalefunc(diagram, scale) {
715 var oldscale = diagram.scale;
716 if (scale > oldscale) {
717 return oldscale + 0.25;
718 } else if (scale < oldscale) {
719 return oldscale - 0.25;
720 }
721 return oldscale;
722 }
723
724 var infscroll = document.getElementById('infscroll');
725 infscroll.addEventListener('change', function(e) {
726 myDiagram2.commit(function(d) { d.scrollMode = infscroll.checked ? go.Diagram.InfiniteScroll : go.Diagram.DocumentScroll; });
727 });
728
729 var poscomp = document.getElementById('poscomp');
730 poscomp.addEventListener('change', function(e) {
731 myDiagram2.commit(function(d) { d.positionComputation = poscomp.checked ? positionfunc : null; });
732 });
733
734 var scalecomp = document.getElementById('scalecomp');
735 scalecomp.addEventListener('change', function(e) {
736 myDiagram2.commit(function(d) { d.scaleComputation = scalecomp.checked ? scalefunc : null; });
737 });
738</script>
739
740<h2 id="PanelCoordinates">Panel coordinates</h2>
741<p>
742A <a>GraphObject</a> that is not a <a>Part</a> but is an element of a <a>Panel</a> has measurements
743that are in panel coordinates, not in document coordinates.
744That means that <a>GraphObject.position</a>, <a>GraphObject.actualBounds</a>, <a>GraphObject.maxSize</a>,
745<a>GraphObject.minSize</a>, <a>GraphObject.measuredBounds</a>, <a>GraphObject.margin</a>, and
746<a>RowColumnDefinition</a> properties apply to all elements of a panel using the same coordinate system.
747</p>
748<p>
749Some <a>GraphObject</a> properties use units that have values before they are transformed for use by
750the containing <a>Panel</a>'s coordinate system.
751In particular, <a>GraphObject.desiredSize</a> (which means <a>GraphObject.width</a> and <a>GraphObject.height</a>),
752<a>GraphObject.naturalBounds</a>, <a>Shape.geometry</a>, and <a>Shape.strokeWidth</a> are in "local" coordinates,
753before the object is scaled and rotated by the value of <a>GraphObject.scale</a> and <a>GraphObject.angle</a>.
754</p>
755<p>
756<a>GraphObject.actualBounds</a> will tell you the position and size of an element within its panel.
757If you want to get the document position of some object that is within a Node,
758call <a>GraphObject.getDocumentPoint</a>.
759</p>
760<p>
761For examples of the sizes of elements in a panel, see <a href="sizing.html">Sizing GraphObjects</a>.
762</p>
763
764<h3 id="NestedPanelCoordinates">Nested Panel coordinates</h3>
765<pre class="lang-js" id="nestedpanelcoords" style="display: none;"><code>
766 // read-only to avoid accidentally moving any Part in document coordinates
767 diagram.isReadOnly = true;
768 diagram.allowSelect = false;
769 diagram.initialPosition = new go.Point(-5, -5);
770 diagram.initialScale = 0.45;
771
772 // data objects for data tables.
773 function InfoBox(key,gro,loc) {
774 this.category = "info";
775 this.key = key;
776 this.location = go.Point.parse(loc);
777 this.gro = gro;
778 }
779
780 // alignment properties for TextBlocks in data tables.
781 function AlignmentObject(column,columnSpan) {
782 this.column = column;
783 this.columnSpan = columnSpan;
784 this.verticalAlignment = go.Spot.Center;
785 this.textAlign = "center";
786 this.alignment = go.Spot.Center;
787 this.height = 24;
788 }
789
790 // creates functions which have limited precision return values.
791 function prec(conv) { return function (g) { return conv(g).toPrecision(3) }}
792
793 // generates cells in data tables
794 function dataBlock(conv, alo1, alo2) {
795 return $(go.TextBlock, "", new go.Binding("text", "gro", prec(conv)), new AlignmentObject(alo1, alo2));
796 }
797
798 var nodeTemplates = new go.Map();
799
800 // Template for data tables
801 nodeTemplates.add("info",
802 $(go.Node, "Auto",
803 // Allows location to be set in data object
804 new go.Binding("location"), { padding: 0, scale: 2 },
805 $(go.Panel, "Table",
806 {name: "table",
807 defaultRowSeparatorStroke: "black", defaultColumnSeparatorStroke: "black",
808 defaultAlignment: go.Spot.Center, background: "white"
809 },
810 // sets a different look for the defining row.
811 $(go.RowColumnDefinition,
812 {row: 0,
813 background: "lightgray", separatorStrokeWidth: 0,
814 separatorPadding: 0, coversSeparators: true,
815 height: 24
816 }),
817 // sets a different look for the defining column.
818 $(go.RowColumnDefinition,
819 {column: 0,
820 coversSeparators: true, separatorStrokeWidth: 0,
821 separatorPadding: 0, background: "lightgray",
822 width: 45
823 }),
824 // necessary to keep weirdness involving the columnSpan of certain elements in the table
825 // from causing separators to go through elements.
826 $(go.RowColumnDefinition, {column: 1, width: 28}),
827 $(go.RowColumnDefinition, {column: 2, separatorStroke: "transparent", width: 28}),
828 $(go.RowColumnDefinition, {column: 3, width: 28}),
829 $(go.RowColumnDefinition, {column: 4, separatorStroke: "transparent", width: 28}),
830 // defining row
831 $(go.Panel, "TableRow", {row: 0},
832 $(go.TextBlock, "Container", new AlignmentObject(1,2)),
833 $(go.TextBlock, "Diagram", new AlignmentObject(3,2))),
834
835 // angle row
836 $(go.Panel, "TableRow", {row: 1},
837 $(go.TextBlock, "angle", {column: 0}),
838 // container angle
839 dataBlock(function (g) { return g.angle }, 1, 2),
840 // document angle
841 dataBlock(function (g) { return g.getDocumentAngle() }, 3, 2)),
842
843 // scale row
844 $(go.Panel, "TableRow", {row: 2},
845 $(go.TextBlock, "scale", {column: 0}),
846 // container scale
847 dataBlock(function (g) { return g.scale }, 1, 2),
848 // document scale
849 dataBlock(function (g) { return g.getDocumentScale() }, 3, 2)),
850
851 // position row
852 $(go.Panel, "TableRow", {row: 3},
853
854 $(go.TextBlock, "X Y", {column: 0}),
855
856 // container x and y values
857 dataBlock(function (g) { return g.actualBounds.x }, 1, 1),
858 dataBlock(function (g) { return g.actualBounds.y }, 2, 1),
859
860 // document x and y values
861 dataBlock(function (g) { return g.getDocumentBounds().x }, 3, 1),
862 dataBlock(function (g) { return g.getDocumentBounds().y }, 4, 1)),
863
864 // dimension row
865 $(go.Panel, "TableRow", {row: 4},
866 $(go.TextBlock, "size", {column: 0}),
867
868 // container width and height
869 dataBlock(function (g) { return g.actualBounds.width }, 1, 1),
870 dataBlock(function (g) { return g.actualBounds.height }, 2, 1),
871
872 // document width and height
873 dataBlock(function (g) { return g.getDocumentBounds().width }, 3, 1),
874 dataBlock(function (g) { return g.getDocumentBounds().width }, 4, 1)))));
875
876
877 // data object for labels on data tables
878 function WordBubble(key,width,loc,desc,color) {
879 this.key = key; this.category = "words"; this.width = width;
880 this.desc = desc; this.location = go.Point.parse(loc); this.color = color;
881 }
882
883 // template for wordbubble objects
884 nodeTemplates.add("words",
885 $(go.Node, "Auto",
886 new go.Binding("location"),
887 $(go.TextBlock, "",
888 new go.Binding("text", "desc"),
889 new go.Binding("stroke","color"),
890 new go.Binding("width"),
891 { textAlign: "left", font: "24pt sans-serif" })));
892
893 // creating the main node's template, adding the nested Panels to it, and adding it to the node template map.
894 let vertPanel = posPanel = spotPanel = vertLabel = topLabel = {};
895 var BigNode =
896 $(go.Node, "Auto",
897 {
898 location: new go.Point(300,0),
899 },
900 vertPanel =
901 $(go.Panel, "Vertical",
902 {portId: "vertPanel",
903 angle: 165, scale: 1.5,
904 background: "lightblue",
905 padding: 20
906 },
907 vertLabel =
908 $(go.TextBlock, "Vertical Panel", {font: "bold 12pt sans-serif"}),
909 posPanel =
910 $(go.Panel, "Position",
911 {portId: "posPanel",
912 angle: 120, scale: 0.8, padding: 50,
913 background: go.Brush.mix("brown", "lightyellow", 0.4)},
914 $(go.Panel, "Auto", { position: new go.Point(25,0), desiredSize: new go.Size(60,90)},
915 $(go.Shape, "Triangle", { fill: "transparent" }),
916 $(go.TextBlock, "This Side Up")),
917 $(go.TextBlock, "Position Panel", { position: new go.Point(0,100), font: "bold 12pt sans-serif" }),
918 ),
919 spotPanel =
920 $(go.Panel, "Spot",
921 {portId: "spotPanel",
922 angle: 30, scale: 1.5,
923 background: "lightgreen" },
924 $(go.Shape, "RoundedRectangle", {strokeWidth: 0, desiredSize: new go.Size(50,100), fill: "transparent"}),
925 $(go.TextBlock, "Spot Panel",
926 {
927 font: "bold 12pt sans-serif",
928 alignment: go.Spot.Center,
929 }
930 ),
931 $(go.TextBlock, "Top",
932 {
933 margin: 5,
934 font: "bold 12pt sans-serif",
935 alignment: go.Spot.Top,
936 }
937 ),
938 bottomLabel =
939 $(go.TextBlock, "Bottom",
940 {portId: "bottomLabel",
941 font: "bold 12pt sans-serif",
942 alignment: go.Spot.Bottom
943 }))));
944 nodeTemplates.add("", BigNode);
945
946 diagram.nodeTemplateMap = nodeTemplates;
947
948 diagram.linkTemplate =
949 $(go.Link,
950 new go.Binding("fromNode", "from", diagram.findNodeForKey),
951 new go.Binding("to"), new go.Binding("toPortId"),
952 $(go.Shape, {strokeWidth: 5}),
953 $(go.Shape, {scale: 3,toArrow: "Standard"}));
954
955 diagram.model = new go.GraphLinksModel(
956 [
957 {key: "bn"},
958 // creating infoboxes
959 new InfoBox(0,vertPanel,"-20 30"),
960 new InfoBox(1,posPanel,"20 470"),
961 new InfoBox(2,spotPanel,"900 500"),
962 new InfoBox(3,bottomLabel,"900 180"),
963
964 // creating wordbubbles
965 new WordBubble(4,250,"60 0","Vertical Panel","blue"),
966 new WordBubble(5,250,"60 440","Position Panel","red"),
967 new WordBubble(6,250,"980 470","Spot Panel","green"),
968 new WordBubble(7,275,"960 105","TextBlock aligned at Spot.Bottom","black")
969 ],
970 [
971 // linking each infobox to an item on the main node.
972 {from: 0, to: "bn", toPortId: "vertPanel"},
973 {from: 1, to: "bn", toPortId: "posPanel"},
974 {from: 2, to: "bn", toPortId: "spotPanel"},
975 {from: 3, to: "bn", toPortId: "bottomLabel"}
976 ]);
977</code></pre>
978<p>
979 The transformations of each element in a <a>Panel</a> are compounded by that panel's transformations.
980</p>
981<script>goCode("nestedpanelcoords", 600, 400)</script>
982<p>
983 The <a>TextBlock</a> that is "Bottom" has the default <a>GraphObject.angle</a> of zero, so that the text is drawn upright.
984 But that TextBlock is an element in the green "Spot" <a>Panel</a> whose <a>GraphObject.angle</a> to 30,
985 so it and its text should appear somewhat tilted.
986 However the blue "Vertical" Panel itself has an <a>GraphObject.angle</a> of 165.
987 Because each Panel has its own coordinate system and because transformations on nested elements are compounded,
988 the effective angle for the green Panel is 195 degrees, the sum of those individual angles (30 + 165), which is nearly upside down.
989</p>
990<p>
991 The <a>GraphObject.scale</a> property also affects how an object is sized in its container Panel.
992 The brown "Position" <a>Panel</a> has a scale of 0.8 relative to its container.
993 But because the "Vertical" Panel has a scale of 1.5, its effective scale is 1.2 overall,
994 the product of those individual scales (0.8 x 1.5).
995</p>
996 </div>
997 </div>
998
999 <div class="bg-nwoods-primary">
1000 <section class="max-w-screen-lg text-white container mx-auto py-2 px-12">
1001 <p id="version" class="leading-none mb-2 my-4">GoJS</p>
1002 </section>
1003 </div><footer class="bg-nwoods-primary text-white">
1004 <div class="container max-w-screen-lg mx-auto px-8">
1005 <div class="w-full py-6">
1006
1007 <div class="max-w-screen-lg xl:max-w-screen-xl mx-auto px-4 sm:px-6 md:px-8">
1008 <ul class="text-sm font-medium pb-14 sm:pb-20 grid grid-cols-1 sm:grid-cols-3 gap-y-10">
1009 <li class="list-none row-span-2">
1010 <h2 class="text-base font-semibold tracking-wide">GoJS</h2>
1011 <ul class="list-none space-y-4 md:space-y-1 px-0">
1012 <li>
1013 <a href="../samples/index.html">Samples</a>
1014 </li>
1015 <li>
1016 <a href="../learn/index.html">Learn</a>
1017 </li>
1018 <li>
1019 <a href="../intro/index.html">Intro</a>
1020 </li>
1021 <li>
1022 <a href="../api/index.html">API</a>
1023 </li>
1024 <li>
1025 <a href="../changelog.html">Changelog</a>
1026 </li>
1027 <li>
1028 <a href="https://github.com/NorthwoodsSoftware/GoJS">GitHub</a>
1029 </li>
1030 </ul>
1031 </li>
1032 <li class="list-none row-span-2">
1033 <h2 class="text-base font-semibold tracking-wide">Support</h2>
1034 <ul class="list-none space-y-4 md:space-y-1 px-0">
1035 <li>
1036 <a href="https://www.nwoods.com/contact.html"
1037 target="_blank" rel="noopener" onclick="getOutboundLink('https://www.nwoods.com/contact.html', 'contact');">Contact</a>
1038 </li>
1039 <li>
1040 <a href="https://forum.nwoods.com/c/gojs">Forum</a>
1041 </li>
1042 <li>
1043 <a href="https://www.nwoods.com/app/activate.aspx?sku=gojs">Activate</a>
1044 </li>
1045 <li>
1046 <a href="https://www.nwoods.com/sales/index.html"
1047 target="_blank" rel="noopener" onclick="getOutboundLink('https://www.nwoods.com/sales/index.html', 'buy');">Buy</a>
1048 </li>
1049 <li>
1050 <a href="https://www.youtube.com/channel/UC9We8EoX596-6XFjJDtZIDg">Videos</a>
1051 </li>
1052 </ul>
1053 </li>
1054 <li class="list-none row-span-2">
1055 <h2 class="text-base font-semibold tracking-wide">Company</h2>
1056 <ul class="list-none space-y-4 md:space-y-1 px-0">
1057 <li>
1058 <a href="https://www.nwoods.com">Northwoods</a>
1059 </li>
1060 <li>
1061 <a href="https://www.nwoods.com/about.html">About Us</a>
1062 </li>
1063 <li>
1064 <a href="https://www.nwoods.com/contact.html">Contact Us</a>
1065 </li>
1066 <li>
1067 <a href="https://twitter.com/northwoodsgo">Twitter</a>
1068 </li>
1069
1070 </ul>
1071 </li>
1072 </ul>
1073
1074
1075 <p class="text-sm text-gray-100 md:mb-6">
1076 Copyright 1998-2021 <a class="text-white" href="https://www.nwoods.com">Northwoods Software</a>
1077 </p>
1078 </div>
1079 </div>
1080</footer> </body>
1081
1082<script async src="https://www.googletagmanager.com/gtag/js?id=UA-1506307-5"></script>
1083<script>
1084 window.dataLayer = window.dataLayer || [];
1085 function gtag(){dataLayer.push(arguments);}
1086 gtag('js', new Date()); gtag('config', 'UA-1506307-5');
1087 var getOutboundLink = function(url, label) {
1088 gtag('event', 'click', {
1089 'event_category': 'outbound',
1090 'event_label': label,
1091 'transport_type': 'beacon'
1092 });
1093 }
1094
1095 // topnav
1096 var topButton = document.getElementById("topnavButton");
1097 var topnavList = document.getElementById("topnavList");
1098 topButton.addEventListener("click", function() {
1099 this.classList.toggle("active");
1100 topnavList.classList.toggle("hidden");
1101 document.getElementById("topnavOpen").classList.toggle("hidden");
1102 document.getElementById("topnavClosed").classList.toggle("hidden");
1103 });
1104</script>
1105 <script src="../assets/js/prism.js"></script>
1106 <script src="../release/go.js"></script>
1107 <script src="../assets/js/goDoc.js"></script>
1108 <script>
1109 document.addEventListener("DOMContentLoaded", function() {
1110 if (window.go) document.getElementById('version').textContent = "GoJS version " + go.version;
1111 if (window.goDoc) window.goDoc();
1112 var d = window.diagrams;
1113 for (var i = 0; i < d.length; i++) {
1114 var dargs = d[i];
1115 goCodeExecute(dargs[0], dargs[1], dargs[2], dargs[3], dargs[4]);
1116 }
1117 if (window.extra) window.extra();
1118 });
1119 </script>
1120</html>