UNPKG

create-gojs-kit

Version:

A CLI for downloading GoJS samples, extensions, and docs

688 lines (611 loc) 26.9 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="Tutorial for GraphObject manipulation with GoJS" /> <meta itemprop="description" content="Tutorial for GraphObject manipulation with GoJS" /> <meta property="og:description" content="Tutorial for GraphObject manipulation with GoJS" /> <meta name="twitter:description" content="Tutorial for GraphObject manipulation with GoJS" /> <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="Learn GraphObject Manipulation" /> <meta property="og:title" content="Learn GraphObject Manipulation" /> <meta name="twitter:title" content="Learn GraphObject Manipulation" /> <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/learn/graphObject.html" /> <meta property="twitter:url" content="https://gojs.net/latest/learn/graphObject.html" /> <meta name="twitter:card" content="summary_large_image" /> <meta property="og:type" content="website" /> <meta property="twitter:domain" content="gojs.net" /> <title> Learn GraphObject Manipulation | GoJS </title> <link rel="stylesheet" href="../assets/css/prism.css"/> <script src="https://cdn.jsdelivr.net/npm/gojs@3.1.0"></script> </head> <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="../learn/index.html">Get Started</a> <a href="../learn/graphObject.html">Manipulating GraphObjects</a> <a href="../learn/interactivity.html">Interacting with Diagrams</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>GraphObject Manipulation</h1> <h2 id="ProgrammaticallyInteractiveWithNodes">Programmatically interacting with Nodes</h2> <p> This guide will show you some basic ways of programmatically interacting with <b>GoJS</b> nodes and links and model data. Throughout this page, we will use the following diagram setup as our starting point: </p> <pre class="lang-js"><code> const myDiagram = new go.Diagram("myDiagramDiv", { "undoManager.isEnabled": true }); // define a simple Node template myDiagram.nodeTemplate = new go.Node("Auto") .add( new go.Shape("Rectangle", { strokeWidth: 0 }) // zero strokeWidth means don't draw any outline // the Shape.fill comes from the Node.data.color property .bind("fill", "color"), new go.TextBlock({ margin: 6, font: "18px sans-serif" }) // leave some space around larger-than-normal text // the TextBlock.text comes from the Node.data.key property .bind("text") ); myDiagram.model = new go.GraphLinksModel( [ { key: 1, text: "Alpha", color: "lightblue" }, { key: 2, text: "Beta", color: "orange" }, { key: 3, text: "Gamma", color: "lightgreen" }, { key: 4, text: "Delta", color: "pink" } ]); </code></pre> <p>The code produces this Diagram:</p> <!-- LIVE --> <div id="myDiagramDiv1" class="diagramStyling" style="width: 700px; height: 150px"></div> <script> function setupDiagram(divname) { const myDiagram = new go.Diagram(divname, { 'undoManager.isEnabled': true }); // define a simple Node template myDiagram.nodeTemplate = new go.Node('Auto') .add( new go.Shape('Rectangle', { strokeWidth: 0, name: 'SHAPE' }) // zero strokeWidth means don't draw any outline // the Shape.fill comes from the Node.data.color property .bind('fill', 'color'), new go.TextBlock({ margin: 6, font: '18px sans-serif' }) // leave some space around larger-than-normal text // the TextBlock.text comes from the Node.data.key property .bind('text') ); myDiagram.model = new go.GraphLinksModel([ { key: 1, text: 'Alpha', color: 'lightblue' }, { key: 2, text: 'Beta', color: 'orange' }, { key: 3, text: 'Gamma', color: 'lightgreen' }, { key: 4, text: 'Delta', color: 'pink' }, ]); return myDiagram; } setupDiagram('myDiagramDiv1'); </script> <h2 id="FindingSingleNodesDiagramFindNodeForKey">Finding single nodes: Diagram.findNodeForKey</h2> <p> You can use <code>Diagram.findNodeForKey(key)</code> to get a reference to a Node in JavaScript. Key values in <b>GoJS</b> can be either strings or numbers. You can then use the Node reference to inspect and manipulate the Node or its data. </p> <pre class="lang-js"><code> // Note the first node data object has key: 1 const node = myDiagram.findNodeForKey(1); // Selects the node programmatically, // rather than the user clicking interactively: myDiagram.select(node); // Outputs a JavaScript object in the developer console window. // The format of what is output will differ per browser, but is essentially the object: // { key: 1, text: "Alpha", color: "lightblue" } console.log(node.data); </code></pre> <!-- LIVE --> <div id="myDiagramDiv2" class="diagramStyling" style="width: 700px; height: 150px"></div> <script> const myDiagram2 = setupDiagram('myDiagramDiv2'); const node2 = myDiagram2.findNodeForKey(1); myDiagram2.select(node2); console.log(node2.data); </script> <p> However <code>findNodeForKey</code> may return <code>null</code> if no node data object uses that key value. It only looks at the model data to find a node data that uses the given key value, from which it finds the corresponding Node in the Diagram. It does not look at the text values of any TextBlocks that are within the Nodes, so it can work even if no text is shown at all. And it does not look at any programmatically added Nodes that were not created from model data. </p> <p> Once you have a <code>Node</code>, you can get its key either via the <code>Node.key</code> property or by looking at its data: <code>someNode.data.key</code>, just as you can look at any of the data properties. </p> <h2 id="CollectionsOfNodesAndLinks">Collections of Nodes and Links</h2> <p> Diagrams have several properties and methods that return GoJS iterators describing collections of Parts. Both Nodes and Links are kinds of Parts. <code>Diagram.nodes</code> and <code>Diagram.links</code> return iterators of all Nodes and Links in the Diagram, respectively. <code>Diagram.selection</code> returns an iterator of selected Parts (both selected Nodes and selected Links). </p> <p> There are also more specific methods for common operations, such as <code>Diagram.findTreeRoots()</code> which returns an iterator of all top-level Nodes that have no parent nodes. </p> <p> This next example uses <code>Diagram.nodes</code> and shows how to iterate over the collection. </p> <pre class="lang-js"><code> // Calling Diagram.commit executes the given function between startTransaction and commitTransaction // calls. That automatically updates the display and allows the effects to be undone. myDiagram.commit(d => { // d === myDiagram // iterate over all nodes in Diagram d.nodes.each(node => { if (node.data.text === "Beta") return; // skip Beta, just for contrast node.scale = 0.4; // shrink each other node }); }, "decrease scale"); </code></pre> <p>As a result we have very scaled-down nodes, except for Beta:</p> <!-- LIVE --> <div id="myDiagramDiv3" class="diagramStyling" style="width: 700px; height: 150px"></div> <script> const myDiagram3 = setupDiagram('myDiagramDiv3'); myDiagram3.commit(d => { // d === myDiagram // iterate over all nodes in Diagram d.nodes.each(node => { if (node.data.text === 'Beta') return; //skip Beta, just to contrast node.scale = 0.4; // shrink each node }); }, 'decrease scale'); </script> <h2 id="NamedGraphObjectsAndPanelFindObject">Named GraphObjects and Panel.findObject</h2> <p> Often we want to manipulate a property that belongs to one of the Node's elements, perhaps an element arbitrarily deep in the template. In our example Diagram, each Node has one Shape, and if we want to change the color of this Shape directly we would need a reference to it. To make it possible to find, we can give that Shape a name: </p> <pre class="lang-js"><code> myDiagram.nodeTemplate = new go.Node("Auto") .add( new go.Shape("Rectangle", { strokeWidth: 0, name: "SHAPE" // added the name property }) .bind("fill", "color"), new go.TextBlock({ margin: 6, font: "18px sans-serif" }), .bind("text") ); </code></pre> <p> Names allow us to easily find GraphObjects inside of Panels (all Nodes are also Panels) using <code>Panel.findObject</code>, which will search the visual tree of a Panel starting at that panel. So when we have a reference to a Node, we can call <code>someNode.findObject("SomeName")</code> to search through the node for a GraphObject with that name. It will return a reference to the named GraphObject if it is found, or <code>null</code> otherwise. </p> <p>Using this, we could make an HTML button that changes the fill of the Shape inside of a selected Node:</p> <pre class="lang-js"><code> const selectionButton = document.getElementById("selectionButton"); selectionButton.addEventListener("click", () => { myDiagram.commit(d => { d.selection.each(node => { const shape = node.findObject("SHAPE"); // If there was a GraphObject in the node named SHAPE, then set its fill to red: if (shape !== null) { shape.fill = "red"; } }); }, "change color"); }); </code></pre> <!-- LIVE --> <div id="myDiagramDiv4" class="diagramStyling" style="width: 700px; height: 150px"></div> <button id="selectionButton">Select some Nodes, then click here to change their Shape.fill</button> <script> const myDiagram4 = setupDiagram('myDiagramDiv4'); const selectionButton = document.getElementById('selectionButton'); selectionButton.addEventListener('click', () => { myDiagram4.commit(d => { d.selection.each(node => { const shape = node.findObject('SHAPE'); // If there was a GraphObject in the node named SHAPE, then set its fill to red: if (shape !== null) { shape.fill = 'red'; } }); }, 'change color'); }); </script> <h2 id="ChangingProperteisAndUpdatingModelUsingDataBindings">Changing Properties and Updating the Model using Data Bindings</h2> <p> Looking again at our Node template, we have the <code>Shape.fill</code> property data-bound to the "color" property of our Node data: </p> <pre class="lang-js"><code> myDiagram.nodeTemplate = new go.Node("Auto") .add( new go.Shape("Rectangle", { strokeWidth: 0, name: "SHAPE" }) .bind("fill", "color"), // note this data binding new go.TextBlock({ margin: 6, font: "18px sans-serif" }) .bind("text") ); </code></pre> <p> Changing the Shape's <code>fill</code> property inside our node will not, as the Node template currently stands, update the model data. </p> <pre class="lang-js"><code> const node = myDiagram.findNodeForKey(1); const shape = node.findObject("SHAPE"); shape.fill = "red"; // outputs "lightblue" - the model has not changed! console.log(node.data.color); </code></pre> <p> This is undesirable in many cases. When we want the change to persist after saving and loading, we will want the model data updated too. </p> <p> However, in some situations this lack of persistence might be a good thing. For instance if we want the color change for only cosmetic purposes, such as changing the color of a button when hovering over it with the mouse, we would not want to modify the model data that might be saved. </p> <p> For now, suppose that we do want to update the model. The preferred way to do this is to modify the data in the model and depend on the data binding to automatically update the Shape. However, we cannot modify the data directly by just setting the JavaScript property. </p> <pre class="lang-js" style="border: 6px solid red"><code> const node = myDiagram.findNodeForKey(1); // DO NOT DO THIS! // This would update the data, but GoJS would not be notified // that this arbitrary JavaScript object has been modified, // and the associated Node will not be updated appropriately, // nor will undo or redo work. node.data.color = "red"; </code></pre> <p> Instead we should set the data property using the method <code>Model.set(data, propertyName, propertyValue)</code>. As always, changes should always be performed within a transaction. There should only be a single transaction for all changes that need to be made at the same time, from the user's point of view. A convenient way to perform a transaction is to call <code>Model.commit</code>. </p> <pre class="lang-js" style="border: 6px solid lime"><code> const node = myDiagram.findNodeForKey(1); // Model.commit executes the given function within a transaction myDiagram.model.commit(m => { // m == the Model // This is the safe way to change model data. // GoJS will be notified that the data has changed // so that it can update the node in the Diagram // and record the change in the UndoManager. m.set(node.data, "color", "red"); }, "changed color"); // outputs "red" - the model has changed! console.log(node.data.color); // and the user will see the red node </code></pre> <!-- LIVE --> <div id="myDiagramDiv5" class="diagramStyling" style="width: 700px; height: 150px"></div> <script> const myDiagram5 = setupDiagram('myDiagramDiv5'); const node5 = myDiagram5.findNodeForKey(1); // Model.commit executes the given function within a transaction myDiagram5.model.commit(m => { // m == the Model // This is the safe way to change model data. // GoJS will be notified that the data has changed // so that it can update the node in the Diagram // and record the change in the UndoManager. m.set(node5.data, 'color', 'red'); }, 'changed color'); </script> <p> Note that there is no longer any need to name the Shape "SHAPE", because there is no longer any need to call <code>findObject</code> to look for the particular Shape. Data binding will automatically update properties, so we do not have to do that ourselves. </p> <pre class="lang-js"><code> myDiagram.nodeTemplate = new go.Node("Auto") .add( new go.Shape("Rectangle", { strokeWidth: 0 }) // removed the name property .bind("fill", "color"), new go.TextBlock({ margin: 6, font: "18px sans-serif" }) .bind("text") ); </code></pre> <h2 id="SubjectMentionedAndFurtherReading">Subjects Mentioned and Further Reading</h2> <ul> <li> Data Binding — See the <a href="../intro/dataBinding.html">intro page on Data Binding</a> for lots more detail. </li> <li> Transactions — See the <a href="../intro/transactions.html">intro page on Transactions</a>. </li> <li> <code>console.log</code> — A powerful debugging aid that is part of most browser's developer tools. See the <a href="https://developers.google.com/chrome-developer-tools/docs/console">Chrome guide</a> or the <a href="https://developer.mozilla.org/en-US/docs/Tools/Browser_Console">Firefox guide</a> for their respective developer consoles. See more suggestions for debugging <b>GoJS</b> diagrams at the <a href="../intro/debugging.html">intro page on Debugging</a>. </li> </ul> <p> You may want to read more tutorials, such as the <a href="index.html">Learn GoJS</a> tutorial and the <a href="interactivity.html">Interactivity</a> tutorial. You can also watch tutorials on <a href="https://www.youtube.com/channel/UC9We8EoX596-6XFjJDtZIDg">YouTube</a>. </p> <p> If you are ready for a comprehensive overview of <b>GoJS</b>, have a look at the <a href="../intro/index.html">technical introduction</a>. If you want to explore by example, have a look at <a href="../samples/index.html">the samples</a> to get a feel for what's possible with <b>GoJS</b>. </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="../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(); } ); </script> </html>