create-gojs-kit
Version:
A CLI for downloading GoJS samples, extensions, and docs
791 lines (735 loc) • 29.9 kB
HTML
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, viewport-fit=cover"/>
<link rel="preconnect" href="https://rsms.me/">
<link rel="stylesheet" href="../assets/css/style.css">
<!-- Copyright 1998-2025 by Northwoods Software Corporation. -->
<meta itemprop="name" content="Link Connection Points on Nodes" />
<meta property="og:title" content="Link Connection Points on Nodes" />
<meta name="twitter:title" content="Link Connection Points on Nodes" />
<meta property="og:image" content="https://gojs.net/latest/assets/images/fp/defaultCard.png" />
<meta itemprop="image" content="https://gojs.net/latest/assets/images/fp/defaultCard.png" />
<meta name="twitter:image" content="https://gojs.net/latest/assets/images/fp/defaultCard.png" />
<meta property="og:url" content="https://gojs.net/latest/intro/connectionPoints.html" />
<meta property="twitter:url" content="https://gojs.net/latest/intro/connectionPoints.html" />
<meta name="twitter:card" content="summary_large_image" />
<meta property="og:type" content="website" />
<meta property="twitter:domain" content="gojs.net" />
<title>
Link Connection Points on Nodes | GoJS
</title>
<link rel="stylesheet" href="../assets/css/prism.css"/>
</head>
<script>
window.diagrams = [];
window.goCode = function (pre, w, h, parentid, animation) {
window
.diagrams
.push([pre, w, h, parentid, animation]);
}
</script>
<body>
<nav id="navTop" class=" w-full h-[var(--topnav-h)] z-30 bg-white border-b border-b-gray-200">
<div class="max-w-screen-xl mx-auto flex flex-wrap items-start justify-between px-4">
<a class="text-white bg-nwoods-primary font-bold !leading-[calc(var(--topnav-h)_-_1px)] my-0 px-2 text-4xl lg:text-5xl logo"
href="../">
GoJS
</a>
<div class="relative">
<button id="topnavButton" class="h-[calc(var(--topnav-h)_-_1px)] px-2 m-0 text-gray-900 bg-inherit shadow-none md:hidden hover:!bg-inherit hover:!text-nwoods-accent hover:!shadow-none" aria-label="Navigation">
<svg class="h-7 w-7 block" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<div id="topnavList" class="hidden md:block">
<div class="absolute right-0 z-30 flex flex-col items-end rounded border border-gray-200 p-4 pl-12 shadow bg-white text-gray-900 font-semibold
md:flex-row md:space-x-4 md:items-start md:border-0 md:p-0 md:shadow-none md:bg-inherit">
<a href="../learn/">Learn</a>
<a href="../samples/">Samples</a>
<a href="../intro/">Intro</a>
<a href="../api/">API</a>
<a href="../download.html">Download</a>
<a href="https://forum.nwoods.com/c/gojs/11" target="_blank" rel="noopener">Forum</a>
<a id="tc" href="https://nwoods.com/contact.html"
target="_blank" rel="noopener" onclick="getOutboundLink('https://nwoods.com/contact.html', 'contact');">Contact</a>
<a id="tb" href="https://nwoods.com/sales/index.html"
target="_blank" rel="noopener" onclick="getOutboundLink('https://nwoods.com/sales/index.html', 'buy');">Buy</a>
</div>
</div>
</div>
</div>
</nav>
<script>
window.addEventListener("DOMContentLoaded", function () {
// topnav
var topButton = document.getElementById("topnavButton");
var topnavList = document.getElementById("topnavList");
if (topButton && topnavList) {
topButton.addEventListener("click", function (e) {
topnavList
.classList
.toggle("hidden");
e.stopPropagation();
});
document.addEventListener("click", function (e) {
// if the clicked element isn't the list, close the list
if (!topnavList.classList.contains("hidden") && !e.target.closest("#topnavList")) {
topButton.click();
}
});
// set active <a> element
var url = window
.location
.href
.toLowerCase();
var aTags = topnavList.getElementsByTagName('a');
for (var i = 0; i < aTags.length; i++) {
var lowerhref = aTags[i]
.href
.toLowerCase();
if (lowerhref.endsWith('.html'))
lowerhref = lowerhref.slice(0, -5);
if (url.startsWith(lowerhref)) {
aTags[i]
.classList
.add('active');
break;
}
}
}
});
</script>
<div class="sticky top-0 left-0 z-10 px-2 w-full bg-white border-b border-b-gray-200 md:hidden">
<button id="sidenavButton" class="flex p-2 text-gray-900 bg-inherit shadow-none items-center text-sm font-semibold hover:!bg-inherit hover:!text-nwoods-accent hover:!shadow-none" aria-label="Navigation">
<svg class="h-7 w-7 block mr-2" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<span>Menu</span>
</button>
</div>
<script>
window.addEventListener("DOMContentLoaded", function () {
// sidenav
var sideButton = document.getElementById("sidenavButton");
var sidenav = document.getElementById("sidenav");
if (sideButton && sidenav) {
sideButton.addEventListener("click", function (e) {
sidenav
.classList
.toggle("hidden");
e.stopPropagation();
});
document.addEventListener("click", function (e) {
// if the clicked element isn't the list, close the list
if (!sidenav.classList.contains("hidden") && !e.target.closest("#sidenavList")) {
sideButton.click();
}
});
}
});
</script>
<div class="flex flex-row md:min-h-screen w-full max-w-screen-xl mx-auto">
<aside id="sidenav"
class="hidden fixed top-0 left-0 z-10 w-full bg-black/10 min-h-screen max-h-screen overflow-x-hidden overflow-y-auto shrink-0
md:block md:sticky md:w-52 md:min-h-0 md:bg-inherit md:border-r md:border-r-gray-200 md:overscroll-auto">
<nav id="sidenavList" class="flex flex-col bg-white w-52 min-h-screen pl-2 pt-4 pb-24 md:w-full md:min-h-0">
<a href="index.html">Basics</a>
<a href="buildingObjects.html">Building Parts</a>
<a href="usingModels.html">Using Models</a>
<a href="dataBinding.html">Data Binding</a>
<a href="react.html">GoJS with React</a>
<a href="svelte.html">GoJS with Svelte</a>
<a href="angular.html">GoJS with Angular</a>
<a href="textBlocks.html">TextBlocks</a>
<a href="shapes.html">Shapes</a>
<a href="pictures.html">Pictures</a>
<a href="panels.html">Panels</a>
<a href="tablePanels.html">Table Panels</a>
<a href="brush.html">Brushes</a>
<a href="sizing.html">Sizing Objects</a>
<a href="itemArrays.html">Item Arrays</a>
<a href="changedEvents.html">Changed Events</a>
<a href="transactions.html">Transactions</a>
<a href="viewport.html">Coordinates</a>
<a href="initialView.html">Initial View</a>
<a href="collections.html">Collections</a>
<a href="links.html">Links</a>
<a href="linkLabels.html">Link Labels</a>
<a href="connectionPoints.html">Link Points</a>
<a href="ports.html">Ports</a>
<a href="nodes.html">Nodes</a>
<a href="typings.html">Typings</a>
<a href="debugging.html">Debugging</a>
<a href="layouts.html">Layouts</a>
<a href="routers.html">Routers</a>
<a href="trees.html">Trees</a>
<a href="subtrees.html">SubTrees</a>
<a href="groups.html">Groups</a>
<a href="subgraphs.html">SubGraphs</a>
<a href="sizedGroups.html">Sized Groups</a>
<a href="selection.html">Selection</a>
<a href="highlighting.html">Highlighting</a>
<a href="theming.html">Theming</a>
<a href="tooltips.html">ToolTips</a>
<a href="contextMenus.html">Context Menus</a>
<a href="events.html">Diagram Events</a>
<a href="tools.html">Tools</a>
<a href="commands.html">Commands</a>
<a href="accessibility.html">Accessibility</a>
<a href="buttons.html">Buttons</a>
<a href="permissions.html">Permissions</a>
<a href="validation.html">Validation</a>
<a href="animation.html">Animation</a>
<a href="HTMLInteraction.html">HTML Interaction</a>
<a href="layers.html">Layers & Z-ordering</a>
<a href="palette.html">Palette</a>
<a href="overview.html">Overview</a>
<a href="replacingDeleting.html">Replacing and Deleting</a>
<a href="templateMaps.html">Template Maps</a>
<a href="legends.html">Legends and Titles</a>
<a href="extensions.html">Extensions</a>
<a href="geometry.html">Geometry Strings</a>
<a href="grids.html">Grid Patterns</a>
<a href="graduatedPanels.html">Graduated Panels</a>
<a href="SVGContext.html">Rendering to SVG</a>
<a href="makingSVG.html">Snapshot to SVG</a>
<a href="makingImages.html">Diagram Images</a>
<a href="printing.html">Printing</a>
<a href="serverSideImages.html">Server-side Images</a>
<a href="nodeScript.html">GoJS in Node.js</a>
<a href="testing.html">Testing</a>
<a href="performance.html">Performance</a>
<a href="platforms.html">Platforms</a>
<a href="deployment.html">Deployment</a>
</nav>
</aside>
<script>
var navList = document.getElementById('sidenavList');
if (navList !== null) {
var url = window.location.href;
var lindex = url.lastIndexOf('/');
url = url
.slice(lindex + 1)
.toLowerCase();
var aTags = navList.getElementsByTagName('a');
var currentindex = -1;
for (var i = 0; i < aTags.length; i++) {
var lowerhref = aTags[i]
.href
.toLowerCase();
if (lowerhref.indexOf('/' + url) !== -1) {
currentindex = i;
aTags[i]
.classList
.add('active');
break;
}
}
}
</script>
<div class="px-4 pb-16 w-full overflow-hidden prose">
<h1>Link Connection Points on Nodes</h1>
<p>
There is flexibility in controlling exactly how and where a link connects with a node.
In the previous examples both ends of each link have always ended at the edge of the node.
But you can specify the <a>Spot</a> on a node at which a link terminates.
</p>
<h2 id="NonRectangularNodes">Non-rectangular Nodes</h2>
<p>
When a <a>Node</a> does not have a rectangular shape, by default links will end where the line toward the
center of the node intersects with the edge of the node.
</p>
<p>
Here is a demonstration of that -- drag one of the nodes around and watch how the link always connects to
the nearest intersection or to the center of the node.
This example includes arrowheads at both ends of the link, to make it clear that the link route really
ends right at the edge of the node rather than the end being hidden underneath the node.
</p>
<pre class="lang-js" id="nonRectangular"><code>
diagram.nodeTemplate =
new go.Node("Auto", {
width: 90, height: 90,
selectionAdorned: false
})
.bind("location", "loc", go.Point.parse)
.add(
new go.Shape("FivePointedStar", { fill: "lightgray" }),
new go.TextBlock()
.bind("text")
);
diagram.linkTemplate =
new go.Link()
.add(
new go.Shape(),
// the "from" end arrowhead
new go.Shape({ fromArrow: "Chevron" }),
// the "to" end arrowhead
new go.Shape({ toArrow: "StretchedDiamond", fill: "red" })
);
const nodeDataArray = [
{ key: 1, text: "Alpha", loc: "0 0" },
{ key: 2, text: "Beta", loc: "100 50" }
];
const linkDataArray = [
{ from: 1, to: 2 }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</code></pre>
<script>
goCode('nonRectangular', 600, 250);
</script>
<h2 id="ToSpotAndFromSpot">ToSpot and FromSpot</h2>
<p>
You can easily require links to end at a particular point within the bounds of the node, rather than at the nearest edge intersection. Set the
<a>GraphObject.toSpot</a> to a <a>Spot</a> value other than <a>Spot.None</a> to cause links coming into the node to end at that spot within the node, with a
direction that is appropriate for the side that the spot is at. Similarly, set the <a>GraphObject.fromSpot</a> for the ends of links coming out of the node.
</p>
<p>
The following examples all display the same graph but use different templates to demonstrate how links can connect to nodes. They all call this common
function to define some nodes and links.
</p>
<pre class="lang-js" id="makeGraph"><code>
function makeGraph(diagram) {
diagram.layout =
new go.LayeredDigraphLayout({ // this will be discussed in a later section
columnSpacing: 5,
setsPortSpots: false
});
const nodeDataArray = [
{ key: "Alpha" }, { key: "Beta" }, { key: "Gamma" }, { key: "Delta" },
{ key: "Epsilon" }, { key: "Zeta" }, { key: "Eta" }, { key: "Theta" }
];
const linkDataArray = [
{ from: "Beta", to: "Alpha" },
{ from: "Gamma", to: "Alpha" },
{ from: "Delta", to: "Alpha" },
{ from: "Alpha", to: "Epsilon" },
{ from: "Alpha", to: "Zeta" },
{ from: "Alpha", to: "Eta" },
{ from: "Alpha", to: "Theta" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
}
</code></pre>
<script>
eval(document.getElementById('makeGraph').textContent);
</script>
<p>
Let us specify that links coming into a node connect at the middle of the left side, and that links going out of a node connect at the middle of the right
side. Such a convention is appropriate for diagrams that have a general sense of direction to them, such as the following one which goes from left to right.
</p>
<pre class="lang-js" id="leftright"><code>
diagram.nodeTemplate =
new go.Node("Auto", {
fromSpot: go.Spot.Right, // coming out from middle-right
toSpot: go.Spot.Left // going into middle-left
})
.add(
new go.Shape("Rectangle", { fill: "lightgray" }),
new go.TextBlock({ margin: 5})
.bind("text", "key")
);
diagram.linkTemplate =
new go.Link()
.add(
new go.Shape(),
new go.Shape({ toArrow: "Standard" })
);
makeGraph(diagram);
</code></pre>
<script>
goCode('leftright', 600, 150);
</script>
<p>
You can also specify that the links go into a node not at a single spot but spread out along one side. Instead of <a>Spot.Right</a> use <a>Spot.RightSide</a>,
and similarly for the left side.
</p>
<pre class="lang-js" id="leftrightSides"><code>
diagram.nodeTemplate =
new go.Node("Auto", {
fromSpot: go.Spot.RightSide, // coming out from right side
toSpot: go.Spot.LeftSide // going into left side
})
.add(
new go.Shape("Rectangle", { fill: "lightgray" }),
new go.TextBlock({ margin: 5})
.bind("text", "key")
);
diagram.linkTemplate =
new go.Link()
.add(
new go.Shape(),
new go.Shape({ toArrow: "Standard" })
);
makeGraph(diagram);
</code></pre>
<script>
goCode('leftrightSides', 600, 150);
</script>
<p>Of course this only looks good when the nodes are basically rectangular.</p>
<p>You can use a different kind of <a>Link.routing</a>:</p>
<pre class="lang-js" id="leftrightSidesOrthogonal"><code>
diagram.nodeTemplate =
new go.Node("Auto", {
fromSpot: go.Spot.RightSide, // coming out from right side
toSpot: go.Spot.LeftSide // going into left side
})
.add(
new go.Shape("Rectangle", { fill: "lightgray" }),
new go.TextBlock({ margin: 5})
.bind("text", "key")
);
diagram.linkTemplate =
new go.Link({
routing: go.Routing.Orthogonal, // Orthogonal routing
corner: 10 // with rounded corners
})
.add(
new go.Shape(),
new go.Shape({ toArrow: "Standard" })
);
makeGraph(diagram);
</code></pre>
<script>
goCode('leftrightSidesOrthogonal', 600, 150);
</script>
<p>Or you can use a different kind of <a>Link.curve</a>:</p>
<pre class="lang-js" id="leftrightSidesBezier"><code>
diagram.nodeTemplate =
new go.Node("Auto", {
fromSpot: go.Spot.RightSide, // coming out from right side
toSpot: go.Spot.LeftSide // going into left side
})
.add(
new go.Shape("Rectangle", { fill: "lightgray" }),
new go.TextBlock({ margin: 5})
.bind("text", "key")
);
diagram.linkTemplate =
new go.Link({
curve: go.Curve.Bezier, // Bezier curve
toEndSegmentLength: 30 // the path extends farther out from the "to" end
})
.add(
new go.Shape(),
new go.Shape({ toArrow: "Standard" })
);
makeGraph(diagram);
</code></pre>
<script>
goCode('leftrightSidesBezier', 600, 150);
</script>
<p>But you need to be careful to specify sensible spots for how the graph is arranged.</p>
<pre class="lang-js" id="leftrightSidesBad"><code>
diagram.nodeTemplate =
new go.Node("Auto", {
fromSpot: go.Spot.TopSide, // coming out from top side -- BAD!
toSpot: go.Spot.RightSide // going into right side -- BAD!
})
.add(
new go.Shape("Rectangle", { fill: "lightgray" }),
new go.TextBlock({ margin: 5})
.bind("text", "key")
);
diagram.linkTemplate =
new go.Link()
.add(
new go.Shape(),
new go.Shape({ toArrow: "Standard" })
);
makeGraph(diagram);
diagram.add( // this is just a comment
new go.Part({ location: new go.Point(300, 50) })
.add(
new go.TextBlock("Bad Spots", { font: "16pt bold", stroke: "red" })
)
);
</code></pre>
<script>
goCode('leftrightSidesBad', 600, 150);
</script>
<h3 id="UndirectedSpots">Undirected Spots</h3>
<p>
When no spot is specified for the <a>GraphObject.fromSpot</a> or <a>GraphObject.toSpot</a>, the route computation will compute the furthest point on the route
of the link from the center of the port to the other port that is an intersection of an edge of the port. This was demonstrated above in
<a href="#NonRectangularNodes">Non-rectangular Nodes</a> and is again demonstrated here.
</p>
<pre class="lang-js" id="noSpotFocus0"><code>
diagram.nodeTemplate =
new go.Node("Vertical")
.bind("location", "loc", go.Point.parse)
.add(
new go.Shape("YinYang", { fill: "white", portId: "" })
.bind("fill", "color"),
new go.TextBlock({ margin: 8 })
.bindTwoWay("text")
);
diagram.model = new go.GraphLinksModel(
[
{ key: 1, text: "Alpha", color: "lightblue", loc: "0 50" },
{ key: 2, text: "Beta", color: "orange", loc: "150 0" },
{ key: 3, text: "Gamma", color: "lightgreen", loc: "300 50" }
],
[
{ from: 1, to: 2 },
{ from: 2, to: 3 }
]);
</code></pre>
<script>
goCode('noSpotFocus0', 600, 250);
</script>
<p>
However it is possible to specify a focus point that is different from the center of the port. Use a <a>Spot</a> value that has <a>Spot.x</a> and
<a>Spot.y</a> equal to 0.5 but with <a>Spot.offsetX</a> and <a>Spot.offsetY</a>
values that specify where you want links to focus towards, relative to the center of the port.
</p>
<pre class="lang-js" id="noSpotFocus1"><code>
diagram.nodeTemplate =
new go.Node("Vertical")
.bind("location", "loc", go.Point.parse)
.add(
new go.Shape("YinYang", {
fill: "white", portId: "",
fromSpot: new go.Spot(0.5, 0.5, 0, -25),
toSpot: new go.Spot(0.5, 0.5, 0, 25)
})
.bind("fill", "color"),
new go.TextBlock({ margin: 8 })
.bind("text")
);
diagram.model = new go.GraphLinksModel(
[
{ key: 1, text: "Alpha", color: "lightblue", loc: "0 50" },
{ key: 2, text: "Beta", color: "orange", loc: "150 0" },
{ key: 3, text: "Gamma", color: "lightgreen", loc: "300 50" }
],
[
{ from: 1, to: 2 },
{ from: 2, to: 3 }
]);
</code></pre>
<script>
goCode('noSpotFocus1', 600, 250);
</script>
<p>
In this example, links always appear to be coming from the hole near the top of the "YinYang" figure towards the dot near the bottom of the figure. Try moving
the nodes to see this behavior. Note that the <a>Spot.x</a> and <a>Spot.y</a> values are both 0.5, with fixed offsets from the center of the port.
</p>
<p>
It is also possible to have links go directly to particular spots within a port. Use regular <a>Spot</a> values, but set the Link's end segment length to
zero, <a>Link.fromEndSegmentLength</a> or <a>Link.toEndSegmentLength</a>.
</p>
<pre class="lang-js" id="noSpotFocus2"><code>
diagram.nodeTemplate =
new go.Node("Vertical")
.bind("location", "loc", go.Point.parse)
.add(
new go.Shape("YinYang", {
fill: "white", portId: "",
fromSpot: new go.Spot(0.5, 0.25), toSpot: new go.Spot(0.5, 0.75),
fromEndSegmentLength: 0, toEndSegmentLength: 0
})
.bind("fill", "color"),
new go.TextBlock({ margin: 8 })
.bind("text")
);
diagram.model = new go.GraphLinksModel(
[
{ key: 1, text: "Alpha", color: "lightblue", loc: "0 50" },
{ key: 2, text: "Beta", color: "orange", loc: "150 0" },
{ key: 3, text: "Gamma", color: "lightgreen", loc: "300 50" }
],
[
{ from: 1, to: 2 },
{ from: 2, to: 3 }
]);
</code></pre>
<script>
goCode('noSpotFocus2', 600, 250);
</script>
<p>
Again, links always appear to be coming from the hole near the top of the "YinYang" figure towards the dot near the bottom of the figure, but now they go all
the way rather than stop at the edge. Note that the <a>Spot.x</a> and <a>Spot.y</a> values are <i>not</i> both 0.5, and that the Link end segment lengths are
zero.
</p>
<h2 id="SpotsForIndividualLinks">Spots for Individual Links</h2>
<p>
Setting the <a>GraphObject.fromSpot</a> and <a>GraphObject.toSpot</a> properties specifies
the default link connection point for all links connected with the node.
What if you want some links to go to the middle-top spot but some other links to go to the
middle-left spot of the same node?
You can achieve this by setting the <a>Link.fromSpot</a> and <a>Link.toSpot</a> properties,
which take precedence over the correspondingly named properties of what the link connects with.
</p>
<pre class="lang-js" id="customSpots"><code>
diagram.nodeTemplate =
new go.Node("Auto")
.add(
new go.Shape("Rectangle", { fill: "lightgray" }),
new go.TextBlock({ margin: 5})
.bind("text", "key")
);
diagram.linkTemplate =
new go.Link()
// get the link spots from the link data
.bind("fromSpot", "fromSpot", go.Spot.parse)
.bind("toSpot", "toSpot", go.Spot.parse)
.add(
new go.Shape(),
new go.Shape({ toArrow: "Standard" })
);
const nodeDataArray = [
{ key: "Alpha" }, { key: "Beta" }, { key: "Gamma" }, { key: "Delta" }
];
const linkDataArray = [
{ from: "Alpha", to: "Beta", fromSpot: "TopRight", toSpot: "Left" },
{ from: "Alpha", to: "Gamma", fromSpot: "Left", toSpot: "Left" },
{ from: "Alpha", to: "Delta", fromSpot: "None", toSpot: "Top" }
];
diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
</code></pre>
<script>
goCode('customSpots', 600, 150);
</script>
<h3 id="SomeLayoutsSetLinkSpots">Some Layouts set Link Spots</h3>
<p>
Some of the predefined <a>Layout</a>s automatically set <a>Link.fromSpot</a> and <a>Link.toSpot</a> when the nature of the layout implies a natural direction.
So, for example, a <a>TreeLayout</a> with a <a>TreeLayout.angle</a> <code>== 90</code> will set each Link's fromSpot to be <a>Spot.Bottom</a> and each Link's
toSpot to be <a>Spot.Top</a>.
</p>
<p>
You can disable the setting of Link spots for TreeLayout by setting <a>TreeLayout.setsPortSpot</a> and/or <a>TreeLayout.setsChildPortSpot</a> to false. For
LayeredDigraphLayout, set <a>LayeredDigraphLayout.setsPortSpots</a> to false. For ForceDirectedLayout, set <a>ForceDirectedLayout.setsPortSpots</a> to false,
although this is rarely needed.
</p>
</div>
</div>
<footer class="bg-white text-gray-900 border-t border-t-gray-200">
<div class="w-full max-w-screen-lg mx-auto px-4 py-6">
<p id="version" class="text-xs text-gray-900 m-0"></p>
<div class="text-sm px-0 mb-4 grid grid-cols-2 sm:grid-cols-3 gap-y-10">
<div>
<h2 class="text-base font-semibold text-nwoods-primary">GoJS</h2>
<ul class="list-none space-y-4 md:space-y-1 px-0">
<li>
<a href="../samples/index.html">Samples</a>
</li>
<li>
<a href="../learn/index.html">Learn</a>
</li>
<li>
<a href="../intro/index.html">Intro</a>
</li>
<li>
<a href="../api/index.html">API</a>
</li>
<li>
<a href="../changelog.html">Changelog</a>
</li>
<li>
<a href="https://github.com/NorthwoodsSoftware/GoJS" target="_blank" rel="noopener">GitHub</a>
</li>
</ul>
</div>
<div>
<h2 class="text-base font-semibold text-nwoods-primary">Support</h2>
<ul class="list-none space-y-4 md:space-y-1 px-0">
<li>
<a href="https://nwoods.com/contact.html"
target="_blank" rel="noopener" onclick="getOutboundLink('https://nwoods.com/contact.html', 'contact');">Contact</a>
</li>
<li>
<a href="https://forum.nwoods.com/c/gojs" target="_blank" rel="noopener">Forum</a>
</li>
<li>
<a href="https://nwoods.com/app/activate.aspx?sku=gojs" target="_blank" rel="noopener">Activate</a>
</li>
<li>
<a href="https://nwoods.com/sales/index.html"
target="_blank" rel="noopener" onclick="getOutboundLink('https://nwoods.com/sales/index.html', 'buy');">Buy</a>
</li>
<li>
<a href="https://nwoods.com/register.html" target="_blank" rel="noopener">Register</a>
</li>
</ul>
</div>
<div>
<h2 class="text-base font-semibold text-nwoods-primary">Company</h2>
<ul class="list-none space-y-4 md:space-y-1 px-0">
<li>
<a target="_blank" href="https://nwoods.com" target="_blank" rel="noopener">Northwoods</a>
</li>
<li>
<a target="_blank" href="https://nwoods.com/about.html" target="_blank" rel="noopener">About Us</a>
</li>
<li>
<a target="_blank" href="https://nwoods.com/contact.html" target="_blank" rel="noopener">Contact Us</a>
</li>
<li>
<a target="_blank" href="https://nwoods.com/consulting.html" target="_blank" rel="noopener">Consulting</a>
</li>
<li>
<a target="_blank" href="https://twitter.com/northwoodsgo" target="_blank" rel="noopener">Twitter</a>
</li>
</ul>
</div>
</div>
<p class="text-sm text-gray-900 md:mb-6">
Copyright 1998-2025 <a href="https://nwoods.com">Northwoods Software</a>
</p>
</div>
</footer>
</body>
<script async src="https://www.googletagmanager.com/gtag/js?id=G-S5QK8VSK84"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-S5QK8VSK84');
var getOutboundLink = function (url, label) {
gtag('event', 'click', {
'event_category': 'outbound',
'event_label': label,
'transport_type': 'beacon'
});
}
const params = new URL(document.location).searchParams
let a = params.get('a');
if (a) localStorage.setItem('a', a);
a = localStorage.getItem('a');
if (a) {
const links = [...document.body.getElementsByTagName("a")].filter((l) => l.href.includes('nwoods.com'));
for (const l of links) {
const url = new URL(l.href);
url.searchParams.set('a', a);
l.href = url;
}
}
</script>
<script src="../assets/js/prism.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gojs@3.1.0"></script>
<script src="../extensions/Figures.js"></script>
<script src="../assets/js/goDoc.js"></script>
<script>
document.addEventListener("DOMContentLoaded", function () {
if (window.go)
document
.getElementById('version')
.textContent = "GoJS version " + go.version;
if (window.goDoc)
window.goDoc();
var d = window.diagrams;
for (var i = 0; i < d.length; i++) {
var dargs = d[i];
goCodeExecute(dargs[0], dargs[1], dargs[2], dargs[3], dargs[4]);
}
if (window.extra)
window.extra();
}
);
</script>
</html>