create-gojs-kit
Version:
A CLI for downloading GoJS samples, extensions, and docs
440 lines (398 loc) • 19.3 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"/>
<meta name="description" content="TypeScript: SwimLaneLayout, laying out the whole graph while assigning nodes to stay in lanes/groups." />
<meta itemprop="description" content="TypeScript: SwimLaneLayout, laying out the whole graph while assigning nodes to stay in lanes/groups." />
<meta property="og:description" content="TypeScript: SwimLaneLayout, laying out the whole graph while assigning nodes to stay in lanes/groups." />
<meta name="twitter:description" content="TypeScript: SwimLaneLayout, laying out the whole graph while assigning nodes to stay in lanes/groups." />
<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="Swim Lane Layout Keeps Nodes in their Lanes" />
<meta property="og:title" content="Swim Lane Layout Keeps Nodes in their Lanes" />
<meta name="twitter:title" content="Swim Lane Layout Keeps Nodes in their Lanes" />
<meta property="og:image" content="https://gojs.net/latest/assets/images/screenshots/swimlanelayout.png" />
<meta itemprop="image" content="https://gojs.net/latest/assets/images/screenshots/swimlanelayout.png" />
<meta name="twitter:image" content="https://gojs.net/latest/assets/images/screenshots/swimlanelayout.png" />
<meta property="og:url" content="https://gojs.net/latest/samples/SwimLaneLayout.html" />
<meta property="twitter:url" content="https://gojs.net/latest/samples/SwimLaneLayout.html" />
<meta name="twitter:card" content="summary_large_image" />
<meta property="og:type" content="website" />
<meta property="twitter:domain" content="gojs.net" />
<title>
Swim Lane Layout Keeps Nodes in their Lanes | GoJS Diagramming Library
</title>
</head>
<body>
<!-- This top nav is not part of the sample code -->
<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="flex flex-col prose">
<div class="w-full max-w-screen-xl mx-auto">
<!-- * * * * * * * * * * * * * -->
<!-- Start of GoJS sample code -->
<script src="https://cdn.jsdelivr.net/npm/gojs@3.1.0"></script>
<div id="allSampleContent" class="p-4 w-full">
<script src="../extensions/SwimLaneLayout.js"></script>
<script id="code">
var DIRECTION = 90; // used to customize the layout and the templates, only upon first initialization
function init() {
myDiagram = new go.Diagram('myDiagramDiv', {
// automatically scale the diagram to fit the viewport's size
initialAutoScale: go.AutoScale.Uniform,
// disable user copying of parts
allowCopy: false,
// position all of the nodes and route all of the links
layout: new SwimLaneLayout({
laneProperty: 'group', // needs to know how to assign vertexes/nodes into lanes/groups
direction: DIRECTION, // Group template also depends on DIRECTION
setsPortSpots: false,
layerSpacing: 20,
columnSpacing: 5,
commitLayers: function (layerRects, offset) {
// method override requires function, not =>
if (layerRects.length === 0) return;
var horiz = this.direction === 0 || this.direction === 180;
var forwards = this.direction === 0 || this.direction === 90;
var rect = layerRects[forwards ? layerRects.length - 1 : 0];
var totallength = horiz ? rect.right : rect.bottom;
if (horiz) {
offset.y -= this.columnSpacing*3/2;
} else {
offset.x -= this.columnSpacing*3/2;
}
for (var i = 0; i < this.laneNames.length; i++) {
var lane = this.laneNames[i];
// assume lane names do not conflict with node names
var group = this.diagram.findNodeForKey(lane);
if (group === null) {
this.diagram.model.addNodeData({ key: lane, isGroup: true });
group = this.diagram.findNodeForKey(lane);
}
if (horiz) {
group.location = new go.Point(-this.layerSpacing / 2, this.lanePositions.get(lane) * this.columnSpacing + offset.y);
} else {
group.location = new go.Point(this.lanePositions.get(lane) * this.columnSpacing + offset.x, -this.layerSpacing / 2);
}
var ph = group.findObject('PLACEHOLDER'); // won't be a go.Placeholder, but just a regular Shape
if (ph === null) ph = group;
if (horiz) {
ph.desiredSize = new go.Size(totallength, this.laneBreadths.get(lane) * this.columnSpacing);
} else {
ph.desiredSize = new go.Size(this.laneBreadths.get(lane) * this.columnSpacing, totallength);
}
}
}
})
});
// replace the default Node template in the nodeTemplateMap
myDiagram.nodeTemplate =
new go.Node('Vertical', { // the whole node panel
// when the DIRECTION is vertical, use the whole Node as the port
fromSpot: go.Spot.TopBottomSides,
toSpot: go.Spot.TopBottomSides
})
.add(
new go.TextBlock() // the text label
.bind('text', 'key'),
new go.Picture({ // the icon showing the logo
// You should set the desiredSize (or width and height)
// whenever you know what size the Picture should be.
desiredSize: new go.Size(50, 50),
// when the DIRECTION is horizontal, use this icon as the port
portId: DIRECTION === 0 || DIRECTION === 180 ? '' : null,
fromSpot: go.Spot.LeftRightSides,
toSpot: go.Spot.LeftRightSides
})
.bind('source', 'key', convertKeyImage)
);
function convertKeyImage(key) {
if (!key) key = 'NE';
return 'https://nwoods.com/go/beatpaths/' + key + '_logo-50x50.png';
}
// replace the default Link template in the linkTemplateMap
myDiagram.linkTemplate =
new go.Link({
curve: go.Curve.Bezier, fromEndSegmentLength: 50, toEndSegmentLength: 50,
})
.add(
new go.Shape({ strokeWidth: 1.5 }), // the link shape
);
myDiagram.groupTemplate = // assumes SwimLaneLayout.direction === 0
new go.Group(DIRECTION === 0 || DIRECTION === 180 ? 'Horizontal' : 'Vertical', {
layerName: 'Background', // always behind all regular nodes and links
movable: false, // user cannot move or copy any lanes
copyable: false,
locationObjectName: 'PLACEHOLDER', // this object will be sized and located by SwimLaneLayout
layout: null, // no lane lays out its member nodes
avoidable: false // don't affect any AvoidsNodes link routes
})
.add(
new go.TextBlock({
font: 'bold 12pt sans-serif',
angle: DIRECTION === 0 || DIRECTION === 180 ? 270 : 0
})
.bind('text', 'key'),
new go.Panel('Auto')
.add(
new go.Shape({
fill: 'transparent',
stroke: 'orange'
}),
new go.Shape({
name: 'PLACEHOLDER',
fill: null,
stroke: null,
strokeWidth: 0
})
),
new go.TextBlock({
font: 'bold 12pt sans-serif',
angle: DIRECTION === 0 || DIRECTION === 180 ? 90 : 0
})
.bind('text', 'key')
);
partitionBy('d');
}
// the array of node data describing each team, each division, and each conference
var nodeDataArray = [
{ key: 'AFC', isGroup: true },
{ key: 'NFC', isGroup: true },
{ key: 'AFCE', isGroup: true },
{ key: 'AFCN', isGroup: true },
{ key: 'AFCS', isGroup: true },
{ key: 'AFCW', isGroup: true },
{ key: 'NFCE', isGroup: true },
{ key: 'NFCN', isGroup: true },
{ key: 'NFCS', isGroup: true },
{ key: 'NFCW', isGroup: true },
{ key: 'NE', conf: 'AFC', div: 'AFCE' },
{ key: 'PIT', conf: 'AFC', div: 'AFCN' },
{ key: 'DAL', conf: 'NFC', div: 'NFCE' },
{ key: 'CLE', conf: 'AFC', div: 'AFCN' },
{ key: 'NYG', conf: 'NFC', div: 'NFCE' },
{ key: 'GB', conf: 'NFC', div: 'NFCN' },
{ key: 'SEA', conf: 'NFC', div: 'NFCW' },
{ key: 'IND', conf: 'AFC', div: 'AFCS' },
{ key: 'MIN', conf: 'NFC', div: 'NFCN' },
{ key: 'PHI', conf: 'NFC', div: 'NFCE' },
{ key: 'DET', conf: 'NFC', div: 'NFCN' },
{ key: 'JAC', conf: 'AFC', div: 'AFCS' },
{ key: 'SD', conf: 'AFC', div: 'AFCW' },
{ key: 'CHI', conf: 'NFC', div: 'NFCN' },
{ key: 'TB', conf: 'NFC', div: 'NFCS' },
{ key: 'KC', conf: 'AFC', div: 'AFCW' },
{ key: 'DEN', conf: 'AFC', div: 'AFCW' },
{ key: 'TEN', conf: 'AFC', div: 'AFCS' },
{ key: 'BUF', conf: 'AFC', div: 'AFCE' },
{ key: 'OAK', conf: 'AFC', div: 'AFCW' },
{ key: 'HOU', conf: 'AFC', div: 'AFCS' },
{ key: 'ATL', conf: 'NFC', div: 'NFCS' },
{ key: 'WAS', conf: 'NFC', div: 'NFCE' },
{ key: 'CIN', conf: 'AFC', div: 'AFCN' },
{ key: 'NYJ', conf: 'AFC', div: 'AFCE' },
{ key: 'CAR', conf: 'NFC', div: 'NFCS' },
{ key: 'NO', conf: 'NFC', div: 'NFCS' },
{ key: 'BAL', conf: 'AFC', div: 'AFCN' },
{ key: 'MIA', conf: 'AFC', div: 'AFCE' },
{ key: 'ARI', conf: 'NFC', div: 'NFCW' },
{ key: 'STL', conf: 'NFC', div: 'NFCW' },
{ key: 'SF', conf: 'NFC', div: 'NFCW' }
];
// the array of link data objects: the relationships between the nodes
var linkDataArray = [
{ from: 'NE', to: 'CLE' },
{ from: 'NE', to: 'DAL' },
{ from: 'NE', to: 'IND' },
{ from: 'PIT', to: 'CLE' },
{ from: 'DAL', to: 'NYG' },
{ from: 'DAL', to: 'GB' },
{ from: 'CLE', to: 'SEA' },
{ from: 'NYG', to: 'DET' },
{ from: 'GB', to: 'MIN' },
{ from: 'GB', to: 'PHI' },
{ from: 'SEA', to: 'PHI' },
{ from: 'SEA', to: 'CIN' },
{ from: 'IND', to: 'TB' },
{ from: 'IND', to: 'JAC' },
{ from: 'MIN', to: 'SD' },
{ from: 'PHI', to: 'NYJ' },
{ from: 'DET', to: 'CHI' },
{ from: 'DET', to: 'DEN' },
{ from: 'JAC', to: 'DEN' },
{ from: 'SD', to: 'DEN' },
{ from: 'CHI', to: 'OAK' },
{ from: 'TB', to: 'TEN' },
{ from: 'DEN', to: 'TEN' },
{ from: 'DEN', to: 'KC' },
{ from: 'DEN', to: 'BUF' },
{ from: 'TEN', to: 'OAK' },
{ from: 'TEN', to: 'ATL' },
{ from: 'TEN', to: 'HOU' },
{ from: 'BUF', to: 'WAS' },
{ from: 'OAK', to: 'MIA' },
{ from: 'HOU', to: 'MIA' },
{ from: 'HOU', to: 'CAR' },
{ from: 'WAS', to: 'NYJ' },
{ from: 'WAS', to: 'ARI' },
{ from: 'CIN', to: 'BAL' },
{ from: 'NYJ', to: 'MIA' },
{ from: 'CAR', to: 'ARI' },
{ from: 'CAR', to: 'STL' },
{ from: 'CAR', to: 'SF' },
{ from: 'NO', to: 'SF' },
{ from: 'BAL', to: 'STL' },
{ from: 'BAL', to: 'SF' }
];
function partitionBy(a) {
// create the model and assign it to the Diagram
var model = new go.GraphLinksModel();
// depending on how we are partitioning the graph, each node belongs either
// to a conference group or to a division group
model.nodeGroupKey = a === 'c' ? 'conf' : 'div';
model.nodeDataArray = nodeDataArray;
model.linkDataArray = linkDataArray;
// each node's lane information is the same as the group information
myDiagram.layout.laneProperty = model.nodeGroupKey;
// optionally, specify the order of known lane names, without setting laneComparer
myDiagram.layout.laneNames = a === 'c' ? ['AFC', 'NFC'] : ['AFCE', 'AFCN', 'AFCS', 'AFCW', 'NFCE', 'NFCN', 'NFCS', 'NFCW'];
myDiagram.model = model;
}
window.addEventListener('DOMContentLoaded', init);
</script>
<div id="sample">
<p><b>Beat Paths</b>: The 2007 NFL Season, divided by conference or by division</p>
<div id="myDiagramDiv" style="border: solid 1px gray; margin: 10px; height: 700px"></div>
<input type="radio" name="A" onclick="partitionBy('c')" id="conferenceButton" />
<label for="conferenceButton">Conferences</label><br />
<input type="radio" name="A" onclick="partitionBy('d')" id="divisionButton" checked />
<label for="divisionButton">Divisions</label><br />
<p>
This sample uses the <code>SwimLaneLayout</code> from
<a href="../extensions/SwimLaneLayout.js">SwimLaneLayout.js</a>
to place <a>Node</a>s into swim lanes based on 2007 NFL season data. To do
this it overwites <a>Layout.commitLayers</a>.
</p>
</div>
</div>
<!-- * * * * * * * * * * * * * -->
<!-- End of GoJS sample code -->
</div>
<div id="allTagDescriptions" class="p-4 w-full max-w-screen-xl mx-auto">
<hr/>
<h3 class="text-xl">GoJS Features in this sample</h3>
<!-- blacklist tags that do not correspond to a specific GoJS feature -->
<h4>Custom Layouts</h4>
<p>
GoJS allows for the creation of custom layouts to meet specific needs.
</p>
<p>
There are also many layouts that are extensions -- not predefined in the <code>go.js</code> or <code>go-debug.js</code> library,
but available as source code in one of the three extension directories, with some documentation and corresponding samples.
More information can be found in the <a href="../intro/layouts.html#CustomLayouts">GoJS Intro</a>.
</p>
<p>
<a href="../samples/index.html#customlayout">Related samples</a>
</p>
<hr>
<!-- blacklist tags that do not correspond to a specific GoJS feature -->
<h4>GoJS Extensions</h4>
<p>
<b>GoJS</b> can be extended in a variety of ways.
The most common way to change the standard behavior is to set properties on the <a href="../api/symbols/GraphObject.html" target="api">GraphObject</a>, <a href="../api/symbols/Diagram.html" target="api">Diagram</a>, <a href="../api/symbols/CommandHandler.html" target="api">CommandHandler</a>, <a href="../api/symbols/Tool.html" target="api">Tool</a>, or <a href="../api/symbols/Layout.html" target="api">Layout</a>.
But when the desired property does not exist, you might need to override methods of CommandHandler, Tool, Layout, Link, or Node.
Methods that you can override are documented in the API reference.
Various features of GoJS can be overriden, either by replacing a method on an instance (a feature of JavaScript) or by defining a subclass.
You should not modify the prototypes of any of the <b>GoJS</b> classes.
</p>
<p>
In addition to our samples, <b>GoJS</b> provides an <strong><a href="../samples/#extensions">extensions gallery</a></strong>,
showcasing the creation of custom tools and layouts.
Those classes and samples are written in TypeScript, available at <code>../extensionsJSM/</code>,
as ECMAScript/JavaScript modules -- these use the <code>../release/go-module.js</code> library.
We recommend that you copy the files that you need into your project, so that you can adjust how they refer to the GoJS library
that you choose and so that you can include them into your own building and packaging procedures.
</p>
<p>
More information can be found in the <a href="../intro/extensions.html">GoJS Intro</a>.
</p>
<p>
<a href="../samples/index.html#extensions">Related samples</a>
</p>
<hr>
</div>
</div>
</body>
<!-- This script is part of the gojs.net website, and is not needed to run the sample -->
<script src="../assets/js/goSamples.js"></script>
</html>