UNPKG

create-gojs-kit

Version:

A CLI for downloading GoJS samples, extensions, and docs

512 lines (446 loc) 20.2 kB
<!DOCTYPE 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 a very simple tree layout sets the bounds for each node data." /> <meta itemprop="description" content="An example of virtualization where a very simple tree layout sets the bounds for each node data." /> <meta property="og:description" content="An example of virtualization where a very simple tree layout sets the bounds for each node data." /> <meta name="twitter:description" content="An example of virtualization where a very simple tree layout sets the bounds for each node data." /> <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 Tree" /> <meta property="og:title" content="Virtualized Tree" /> <meta name="twitter:title" content="Virtualized Tree" /> <meta property="og:image" content="https://gojs.net/latest/assets/images/screenshots/virtualizedtree.png" /> <meta itemprop="image" content="https://gojs.net/latest/assets/images/screenshots/virtualizedtree.png" /> <meta name="twitter:image" content="https://gojs.net/latest/assets/images/screenshots/virtualizedtree.png" /> <meta property="og:url" content="https://gojs.net/latest/samples/virtualizedTree.html" /> <meta property="twitter:url" content="https://gojs.net/latest/samples/virtualizedTree.html" /> <meta name="twitter:card" content="summary_large_image" /> <meta property="og:type" content="website" /> <meta property="twitter:domain" content="gojs.net" /> <title> Virtualized Tree | 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 id="code"> // this controls whether the tree grows deeper towards the right or downwards: var HORIZONTAL = true; function init() { // 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. myDiagram = new go.Diagram('myDiagramDiv', { initialDocumentSpot: go.Spot.Center, initialViewportSpot: go.Spot.Center, // Do manual layout in the layoutTree function below, rather than automatic layout using a // TreeLayout which would require the Nodes and Links to exist first for an accurate layout. //layout: new go.TreeLayout( // { nodeSpacing: 4, compaction: go.TreeCompaction.None }), // Assume there's no Layout -- all data.bounds are calculated in layoutTree 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: 20, // in cooperation with the load function, below toolTip: go.GraphObject.build('ToolTip') .add( new go.TextBlock({ margin: 3 }) .bind('text', '', d => 'key: ' + d.key + '\nbounds: ' + d.bounds.toString()) ) }) // optimization .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, // optimization fromSpot: HORIZONTAL ? go.Spot.Right : go.Spot.Bottom, toSpot: HORIZONTAL ? go.Spot.Left : go.Spot.Top }) .add( new go.Shape() ), 'animationManager.isEnabled': false }); // This model includes all of the data myWholeModel = new go.TreeModel(); // must match the TreeModel used by the Diagram, below // 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.TreeModel(); // must match the model, above // for now, we have to implement virtualization ourselves myDiagram.isVirtualized = true; myDiagram.addDiagramListener('ViewportBoundsChanged', onViewportChanged); // This is a status message const textBlock = new go.TextBlock('loading...', { stroke: 'red', font: '20pt sans-serif' }); myLoading = new go.Part({ layerName: 'ViewportForeground', alignment: go.Spot.Center }) .add(textBlock); // // temporarily add the status indicator // const animate = state => { // textBlock.text = 'loading' + '.'.repeat(state); // setTimeout(() => { // animate(state++); // }, 50); // }; // animate(0); myDiagram.add(myLoading); // Allow the myLoading indicator to be shown now, // but allow objects added in load to also be considered part of the initial Diagram. // If you are not going to add temporary initial Parts, don't call delayInitialization. myDiagram.delayInitialization(load); } function load(diagram) { // create a lot of data for the myWholeModel var total = 123456; var treedata = []; for (var i = 0; i < total; i++) { var d = { key: i, // this node data's key color: go.Brush.randomColor(), // the node's color parent: i > 0 ? Math.floor((Math.random() * i) / 2) : undefined // the random parent's key }; //!!!???@@@ this needs to be customized to account for your chosen Node template d.bounds = new go.Rect(0, 0, 70, 20); treedata.push(d); } myWholeModel.nodeDataArray = treedata; // make it faster to get from a model parent data object to all of the children data improveNavigation(myWholeModel); // this sets the data.bounds on each node data // and Diagram.fixedBounds on the diagram, so the diagram knows how far it can scroll layoutTree(myWholeModel); // remove the status indicator diagram.remove(myLoading); } // this adds ._parent and ._children properties on each node data function improveNavigation(model) { // this must be a TreeModel var ndata = model.nodeDataArray; // create an Array of child data references for each parent data for (var i = 0; i < ndata.length; i++) { var child = ndata[i]; var parentkey = model.getParentKeyForNodeData(child); var parent = model.findNodeDataForKey(parentkey); if (parent) { child._parent = parent; var childarr = parent._children; if (childarr) { childarr.push(child); } else { parent._children = [child]; } } } } // The following functions implement virtualization of the Diagram // Assume data.bounds is a Rect of the area occupied by the Node in document coordinates. // 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() { var b = new go.Rect(); var ndata = myWholeModel.nodeDataArray; for (var i = 0; i < ndata.length; i++) { var d = ndata[i]; if (!d.bounds) continue; if (i === 0) { 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) { var diagram = e.diagram; // make sure there are Nodes for each node data that is in the viewport // or that is connected to such a Node var viewb = diagram.viewportBounds; // the new viewportBounds var model = diagram.model; var oldskips = diagram.skipsUndoManager; diagram.skipsUndoManager = true; var b = new go.Rect(); var ndata = myWholeModel.nodeDataArray; for (var i = 0; i < ndata.length; i++) { var n = ndata[i]; if (model.containsNodeData(n)) continue; if (!n.bounds) continue; if (n.bounds.intersectsRect(viewb)) { model.addNodeData(n); } // make sure links to all parent nodes appear var parentkey = myWholeModel.getParentKeyForNodeData(n); var parent = myWholeModel.findNodeDataForKey(parentkey); if (parent !== null) { if (n.bounds.intersectsRect(viewb)) { // N is inside viewport model.addNodeData(parent); // so that link to parent appears } else { // N is outside of viewport // see if there's a parent that is in the viewport, // or if the link might cross over the viewport b.set(n.bounds); b.unionRect(parent.bounds); if (b.intersectsRect(viewb)) { model.addNodeData(n); // add N so that link to parent appears } } } } diagram.skipsUndoManager = oldskips; if (myRemoveTimer === null) { // only remove offscreen nodes after a delay myRemoveTimer = setTimeout(() => removeOffscreen(diagram), 3000); } updateCounts(); // only for this sample } // occasionally remove Parts that are offscreen from the Diagram var myRemoveTimer = null; function removeOffscreen(diagram) { myRemoveTimer = null; var viewb = diagram.viewportBounds; var model = diagram.model; var remove = []; // collect for later removal var it = diagram.nodes; while (it.next()) { var n = it.value; var d = n.data; if (d === null) continue; if (!n.actualBounds.intersectsRect(viewb) && !n.isSelected) { // even if the node is out of the viewport, keep it if it is selected or // if any link connecting with the node is still in the viewport if (!n.linksConnected.any(l => l.actualBounds.intersectsRect(viewb))) { remove.push(d); } } } if (remove.length > 0) { var oldskips = diagram.skipsUndoManager; diagram.skipsUndoManager = true; model.removeNodeDataCollection(remove); diagram.skipsUndoManager = oldskips; } updateCounts(); // only for this sample } // end of virtualized Diagram // A very simple tree layout. // Basic tree layout parameters var nodeSpacing = 4; var layerSpacing = 50; // Layout the whole tree just using the model, not any Nodes or Links. function layoutTree(model) { var ndata = model.nodeDataArray; // layout each tree root if (HORIZONTAL) { var y = 0; for (var i = 0; i < ndata.length; i++) { var d = ndata[i]; // is this a root node? if (!d._parent) { y = walkTreeH(d, 0, y) + d.bounds.height + nodeSpacing; } } } else { // !HORIZONTAL var x = 0; for (var i = 0; i < ndata.length; i++) { var d = ndata[i]; // is this a root node? if (!d._parent) { x = walkTreeV(d, x, 0) + d.bounds.width + nodeSpacing; } } } // can't depend on regular bounds computation that depends on all Nodes existing myDiagram.fixedBounds = computeDocumentBounds(); } // Walk subtrees from each root node, positioning as we go. function walkTreeH(parent, oldx, oldy) { // HORIZONTAL var origy = oldy; var newy = oldy; var childarr = parent._children; if (childarr) { for (var i = 0; i < childarr.length; i++) { var child = childarr[i]; newy = walkTreeH(child, oldx + child.bounds.width + layerSpacing, oldy); oldy = newy + child.bounds.height + nodeSpacing; } } parent.bounds.x = oldx; parent.bounds.y = (origy + newy) / 2; return newy; } function walkTreeV(parent, oldx, oldy) { // !HORIZONTAL var origx = oldx; var newx = oldx; var childarr = parent._children; if (childarr) { for (var i = 0; i < childarr.length; i++) { var child = childarr[i]; newx = walkTreeV(child, oldx, oldy + child.bounds.height + layerSpacing); oldx = newx + child.bounds.width + nodeSpacing; } } parent.bounds.x = (origx + newx) / 2; parent.bounds.y = oldy; return newx; } // end of layoutTree functionality // 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('myMessage4').textContent = myDiagram.links.count; } window.addEventListener('DOMContentLoaded', init); </script> <div id="sample"> <div id="myDiagramDiv" style="background-color: white; border: solid 1px black; width: 100%; height: 600px"></div> <div id="description"> <p>This uses a <a>TreeModel</a> but not <a>TreeLayout</a>.</p> Node data in Model: <span id="myMessage1"></span>. Actual Nodes in Diagram: <span id="myMessage2"></span>. Actual Links in Diagram: <span id="myMessage4"></span>. </div> <br> <p> See also <a href="./virtualized.html">Virtualized</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>Tree Layout</h4> <p> This predefined layout is used for placing Nodes of a tree-structured graph in layers (rows or columns). For discussion and examples of the most commonly used properties of the <a href="../api/symbols/TreeLayout.html">TreeLayout</a>, see the <a href="../intro/trees.html">Trees</a> page in the Introduction. More information can be found in the <a href="../intro/layouts.html#TreeLayout">GoJS Intro</a>. </p> <p> <a href="../samples/index.html#treelayout">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 --> </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>