create-gojs-kit
Version:
A CLI for downloading GoJS samples, extensions, and docs
516 lines (452 loc) • 22.7 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="An example of virtualization where node bounds information is present in the node data, so no layout is required." />
<meta itemprop="description" content="An example of virtualization where node bounds information is present in the node data, so no layout is required." />
<meta property="og:description" content="An example of virtualization where node bounds information is present in the node data, so no layout is required." />
<meta name="twitter:description" content="An example of virtualization where node bounds information is present in the node data, so no layout is required." />
<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="Virtualized Diagram no Layout" />
<meta property="og:title" content="Virtualized Diagram no Layout" />
<meta name="twitter:title" content="Virtualized Diagram no Layout" />
<meta property="og:image" content="https://gojs.net/latest/assets/images/screenshots/virtualized.png" />
<meta itemprop="image" content="https://gojs.net/latest/assets/images/screenshots/virtualized.png" />
<meta name="twitter:image" content="https://gojs.net/latest/assets/images/screenshots/virtualized.png" />
<meta property="og:url" content="https://gojs.net/latest/samples/virtualized.html" />
<meta property="twitter:url" content="https://gojs.net/latest/samples/virtualized.html" />
<meta name="twitter:card" content="summary_large_image" />
<meta property="og:type" content="website" />
<meta property="twitter:domain" content="gojs.net" />
<title>
Virtualized Diagram no Layout | 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">
<style>
#mySpinner {
display: none;
position: absolute;
z-index: 100;
animation: spin 1s linear infinite;
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
</style>
<div id="sample">
<div id="myDiagramDiv" style="background-color: white; border: solid 1px black; width: 100%; height: 600px"></div>
<img id="mySpinner" src="images/spinner.png" />
<div id="description">
<p>This uses a <a>GraphLinksModel</a> but not any <a>Layout</a>. It demonstrates the virtualization of Links as well as simple Nodes.</p>
Node data in Model: <span id="myMessage1"></span>. Actual Nodes in Diagram: <span id="myMessage2"></span>.<br />
Link data in model: <span id="myMessage3"></span>. Actual Links in Diagram: <span id="myMessage4"></span>.
</div>
<script src="../extensions/Quadtree.js"></script>
<script id="code">
// The Diagram just shows what should be visible in the viewport.
// Its model does NOT include node data for the whole graph, but only that
// which might be visible in the viewport.
const myDiagram = new go.Diagram('myDiagramDiv', {
initialDocumentSpot: go.Spot.Center,
initialViewportSpot: go.Spot.Center,
// Assume there's no Layout -- all data.bounds are provided
layout: new go.Layout({ isInitial: false, isOngoing: false }), // never invalidates
// Define the template for Nodes, used by virtualization.
nodeTemplate:
new go.Node('Auto', {
isLayoutPositioned: false,
width: 70,
height: 70,
toolTip:
go.GraphObject.build("ToolTip")
.add(
new go.TextBlock({ margin: 3 })
.bind('text', '', d => `key: ${d.key}\nbounds: ${d.bounds}\ntext: ${d.text}\nprop1: ${d.prop1}\nprop2: ${d.prop2}`)
)
})
.bindTwoWay('position', 'bounds', b => b.position, (p, d) => new go.Rect(p.x, p.y, d.bounds.width, d.bounds.height))
.add(
new go.Shape('Rectangle')
.bind('fill', 'color'),
new go.TextBlock({ margin: 2 })
.bind('text', 'color')
),
// Define the template for Links
linkTemplate:
new go.Link({ isLayoutPositioned: false })
.add( // optimization
new go.Shape({ strokeWidth: 2 })
.bind('stroke', 'state', s => LinkColors[s]),
new go.Shape({ toArrow: 'OpenTriangle', strokeWidth: 2 })
.bind('stroke', 'state', s => LinkColors[s])
),
'animationManager.isEnabled': false
});
const LinkColors = ['black', 'green', 'blue', 'red'];
// This model includes all of the data
const myWholeModel = new go.GraphLinksModel(); // must match the model used by the Diagram, below
const myWholeQuadtree = new Quadtree();
// Do not set myDiagram.model = myWholeModel -- that would create a zillion Nodes and Links!
// In the future Diagram may have built-in support for virtualization.
// For now, we have to implement virtualization ourselves by having the Diagram's model
// be different than the "real" model.
myDiagram.model = // this only holds nodes that should be in the viewport
new go.GraphLinksModel(); // must match the model, above
// for now, we have to implement virtualization ourselves
myDiagram.isVirtualized = true;
myDiagram.addDiagramListener('ViewportBoundsChanged', onViewportChanged);
myDiagram.addModelChangedListener(onModelChanged);
myDiagram.model.makeUniqueKeyFunction = virtualUniqueKey; // ensure uniqueness in myWholeModel
myDiagram.commandHandler.selectAll = () => {}; // make Select All command a no-op
myDiagram.delayInitialization(() => spinDuring(myDiagram, 'mySpinner', load));
// implement a wait spinner in HTML with CSS animation
function spinDuring(diagram, spinner, compute) {
// where compute is a function of zero args
// show the animated spinner
if (typeof spinner === 'string') spinner = document.getElementById(spinner);
if (spinner) {
// position it in the middle of the viewport DIV
const x = Math.floor(diagram.div.offsetWidth / 2 - spinner.naturalWidth / 2);
const y = Math.floor(diagram.div.offsetHeight / 2 - spinner.naturalHeight / 2);
spinner.style.left = x + 'px';
spinner.style.top = y + 'px';
spinner.style.display = 'inline';
}
setTimeout(() => {
try {
compute(); // do the computation
} finally {
if (spinner) spinner.style.display = 'none';
}
}, 20);
}
function load() {
// create a lot of node data for the myWholeModel, and almost that many link data
const total = 123456;
const sqrt = Math.floor(Math.sqrt(total));
const data = [];
const ldata = [];
myWholeQuadtree.clear();
const temp = new go.Rect();
for (let i = 0; i < total; i++) {
const nd = {
key: i, // this node data's key
color: go.Brush.randomColor(), // the node's color
//!!!???@@@ this needs to be customized to account for your chosen Node template
bounds: new go.Rect((i % sqrt) * 140, Math.floor(i / sqrt) * 140, 70, 70),
text: 'Node ' + i.toString(),
prop1: Math.random() * 100,
prop2: Math.random() * 100
};
data.push(nd);
myWholeQuadtree.add(nd, nd.bounds);
if (i > 0 && i % sqrt > 0) {
// link sequential nodes
const ld = {
from: i - 1,
to: i,
state: Math.floor(Math.random() * 4)
};
ldata.push(ld);
}
if (i > sqrt) {
// link nodes vertically
const ld = {
from: i - sqrt,
to: i,
state: Math.floor(Math.random() * 4)
};
ldata.push(ld);
}
}
myWholeModel.nodeDataArray = data;
myWholeModel.linkDataArray = ldata;
// there is no virtualized layout to perform, but we still
// can't depend on regular bounds computation that depends on all Nodes existing
myDiagram.fixedBounds = computeDocumentBounds();
}
// The following functions implement virtualization of the Diagram
// Assume data.bounds is a Rect of the area occupied by the Node in document coordinates.
// It's not good enough to ensure keys are unique in the limited model that is myDiagram.model --
// need to ensure uniqueness in myWholeModel.
function virtualUniqueKey(model, data) {
myWholeModel.makeNodeDataKeyUnique(data);
return myWholeModel.getKeyForNodeData(data);
}
// The normal mechanism for determining the size of the document depends on all of the
// Nodes and Links existing, so we need to use a function that depends only on the model data.
function computeDocumentBounds() {
const b = new go.Rect();
const ndata = myWholeModel.nodeDataArray;
for (let i = 0; i < ndata.length; i++) {
const d = ndata[i];
if (!d.bounds) continue;
if (b.isEmpty()) b.set(d.bounds);
else b.unionRect(d.bounds);
}
return b;
}
// As the user scrolls or zooms, make sure the Parts (Nodes and Links) exist in the viewport.
function onViewportChanged(e) {
const diagram = e.diagram;
// make sure there are Nodes for each node data that is in the viewport
// and each Link that is connected to such a Node
const viewb = diagram.viewportBounds; // the new viewportBounds
const model = diagram.model;
const oldskips = diagram.skipsUndoManager;
diagram.skipsUndoManager = true;
//?? this does NOT remove Nodes or Links that are outside of the viewport
const b = new go.Rect();
const ndata = myWholeQuadtree.intersecting(viewb); // myWholeModel.nodeDataArray;
for (let i = 0; i < ndata.length; i++) {
const n = ndata[i];
if (model.containsNodeData(n)) continue;
if (!n.bounds) continue;
if (n.bounds.intersectsRect(viewb)) {
addNode(diagram, myWholeModel, n);
}
}
//?? not considering TreeModel
if (model instanceof go.GraphLinksModel) {
// this could be much more efficient if we kept track of links in a Quadtree;
// but then we would need to be able to update the Quadtree efficiently as nodes moved
const ldata = myWholeModel.linkDataArray;
for (let i = 0; i < ldata.length; i++) {
const l = ldata[i];
if (model.containsLinkData(l)) continue;
const fromkey = myWholeModel.getFromKeyForLinkData(l);
if (fromkey === undefined) continue;
const from = myWholeModel.findNodeDataForKey(fromkey);
if (from === null || !from.bounds) continue;
const tokey = myWholeModel.getToKeyForLinkData(l);
if (tokey === undefined) continue;
const to = myWholeModel.findNodeDataForKey(tokey);
if (to === null || !to.bounds) continue;
b.set(from.bounds);
b.unionRect(to.bounds);
if (b.intersectsRect(viewb)) {
// also make sure both connected nodes are present,
// so that link routing is authentic
addNode(diagram, myWholeModel, from);
addNode(diagram, myWholeModel, to);
model.addLinkData(l);
const link = diagram.findLinkForData(l);
if (link !== null) {
// do this now to avoid delayed routing outside of transaction
link.updateRoute();
}
}
}
}
diagram.skipsUndoManager = oldskips;
updateCounts(); // only for this sample
}
function addNode(diagram, wholeModel, data) {
const model = diagram.model;
if (model.containsNodeData(data)) return;
model.addNodeData(data);
const n = diagram.findNodeForData(data);
if (n !== null) n.ensureBounds();
}
function onModelChanged(e) {
// handle moves and insertions and removals
if (e.model.skipsUndoManager) return;
if (e.change === go.ChangeType.Property) {
if (e.propertyName === 'bounds') {
myWholeQuadtree.move(e.object, e.newValue.bounds.x, e.newValue.bounds.y);
}
} else if (e.change === go.ChangeType.Insert) {
if (e.propertyName === 'nodeDataArray') {
myWholeModel.addNodeData(e.newValue);
myWholeQuadtree.add(e.newValue, e.newValue.bounds);
} else if (e.propertyName === 'linkDataArray') {
myWholeModel.addLinkData(e.newValue);
}
} else if (e.change === go.ChangeType.Remove) {
if (e.propertyName === 'nodeDataArray') {
myWholeModel.removeNodeData(e.oldValue);
myWholeQuadtree.remove(e.oldValue);
} else if (e.propertyName === 'linkDataArray') {
myWholeModel.removeLinkData(e.oldValue);
}
}
}
// end of virtualized Diagram
// This function is only used in this sample to demonstrate the effects of the virtualization.
// In a real application you would delete this function and all calls to it.
function updateCounts() {
document.getElementById('myMessage1').textContent = myWholeModel.nodeDataArray.length;
document.getElementById('myMessage2').textContent = myDiagram.nodes.count;
document.getElementById('myMessage3').textContent = myWholeModel.linkDataArray.length;
document.getElementById('myMessage4').textContent = myDiagram.links.count;
}
</script>
</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>Collections</h4>
<p>
<b>GoJS</b> provides its own collection classes: <a href="../api/symbols/List.html" target="api">List</a>, <a href="../api/symbols/Set.html" target="api">Set</a>, and <a href="../api/symbols/Map.html" target="api">Map</a>.
You can iterate over a collection by using an <a href="../api/symbols/Iterator.html" target="api">Iterator</a>.
More information can be found in the <a href="../intro/collections.html">GoJS Intro</a>.
</p>
<p>
<a href="../samples/index.html#collections">Related samples</a>
</p>
<hr>
<!-- blacklist tags that do not correspond to a specific GoJS feature -->
<h4>Groups</h4>
<p>
The <a href="../api/symbols/Group.html" target="api">Group</a> class is used to treat a collection of <a href="../api/symbols/Node.html" target="api">Node</a>s and <a href="../api/symbols/Link.html" target="api">Link</a>s as if they were a single <a href="../api/symbols/Node.html" target="api">Node</a>.
Those nodes and links are members of the group; together they constitute a subgraph.
</p>
<p>
A subgraph is <em>not</em> another <a href="../api/symbols/Diagram.html" target="api">Diagram</a>, so there is no separate HTML Div element for the subgraph of a group.
All of the <a href="../api/symbols/Part.html" target="api">Part</a>s that are members of a <a href="../api/symbols/Group.html" target="api">Group</a> belong to the same Diagram as the Group.
There can be links between member nodes and nodes outside of the group as well as links between the group itself and other nodes.
There can even be links between member nodes and the containing group itself.
</p>
<p>
More information can be found in the <a href="../intro/groups.html">GoJS Intro</a>.
</p>
<p>
<a href="../samples/index.html#groups">Related samples</a>
</p>
<hr>
<!-- blacklist tags that do not correspond to a specific GoJS feature -->
<h4>ToolTips</h4>
<p>
A tooltip is an <a href="../api/symbols/Adornment.html" target="api">Adornment</a> that is shown when the mouse hovers over an object that has its <a href="../api/symbols/GraphObject.html#toolTip" target="api">GraphObject.toolTip</a> set.
The tooltip part is bound to the same data as the part itself.
</p>
<p>
It is typical to implement a tooltip as a "ToolTip" Panel holding a <a href="../api/symbols/TextBlock.html" target="api">TextBlock</a> or a Panel of TextBlocks and other objects.
Each "ToolTip" is just an "Auto" Panel <a href="../api/symbols/Adornment.html" target="api">Adornment</a> that is shadowed, and where the border is a rectangular <a href="../api/symbols/Shape.html" target="api">Shape</a> with a light gray fill.
However you can implement the tooltip as any arbitrarily complicated Adornment.
</p>
<p>
More information can be found in the <a href="../intro/tooltips.html">GoJS Intro</a>.
</p>
<p>
<a href="../samples/index.html#tooltips">Related samples</a>
</p>
<hr>
<!-- blacklist tags that do not correspond to a specific GoJS feature -->
<h4>Buttons</h4>
<p>
GoJS defines several <a href="../api/symbols/Panel.html" target="api">Panel</a>s for common uses.
These include "Button", "TreeExpanderButton", "SubGraphExpanderButton", "PanelExpanderButton", "ContextMenuButton", and "CheckBoxButton".
"ContextMenuButton"s are typically used inside of "ContextMenu" Panels;
"CheckBoxButton"s are used in the implementation of "CheckBox" Panels.
</p>
<p>
These predefined panels can be used as if they were <a href="../api/symbols/Panel.html" target="api">Panel</a>-derived classes in calls to <a href="../api/symbols/GraphObject.html#build" target="api">GraphObject.build</a>.
They are implemented as simple visual trees of <a href="../api/symbols/GraphObject.html" target="api">GraphObject</a>s in <a href="../api/symbols/Panel.html" target="api">Panel</a>s,
with pre-set properties and event handlers.
</p>
<p>
More information can be found in the <a href="../intro/buttons.html">GoJS Intro</a>.
</p>
<p>
<a href="../samples/index.html#buttons">Related samples</a>
</p>
<hr>
<!-- blacklist tags that do not correspond to a specific GoJS feature -->
</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>