UNPKG

create-gojs-kit

Version:

A CLI for downloading GoJS samples, extensions, and docs

1,023 lines (926 loc) 39.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="Tutorial for interactivity with GoJS" /> <meta itemprop="description" content="Tutorial for interactivity with GoJS" /> <meta property="og:description" content="Tutorial for interactivity with GoJS" /> <meta name="twitter:description" content="Tutorial for interactivity 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 Interactivity" /> <meta property="og:title" content="Learn Interactivity" /> <meta name="twitter:title" content="Learn Interactivity" /> <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/interactivity.html" /> <meta property="twitter:url" content="https://gojs.net/latest/learn/interactivity.html" /> <meta name="twitter:card" content="summary_large_image" /> <meta property="og:type" content="website" /> <meta property="twitter:domain" content="gojs.net" /> <title> Learn Interactivity | 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>Interacting with Diagrams</h1> <h2 id="BuiltinGoJSInteractivity">Built-in GoJS Interactivity</h2> <p> <b>GoJS</b> implements a number of common editing operations such as manipulating parts -- moving, adding, copying, cutting, and deleting Nodes and Links. These editing abilities are accessible via mouse or touch and keyboard by default, and can also be invoked programmatically in JavaScript. </p> <p> The following Diagram defines a node template and has four nodes in its model: </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.text property .bind("text") ); myDiagram.model = new go.GraphLinksModel( [ // there will be a Node for each data object in this nodeDataArray { 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> <!-- LIVE --> <div id="myDiagramDiv" class="diagramStyling" style="width:700px; height:150px"></div> <script> 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 }) // 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.text property .bind("text") ); myDiagram.model = new go.GraphLinksModel( [ // there will be a Node for each data object in this nodeDataArray { key: 1, text: "Alpha", color: "lightblue" }, { key: 2, text: "Beta", color: "orange" }, { key: 3, text: "Gamma", color: "lightgreen" }, { key: 4, text: "Delta", color: "pink" } ]); </script> <p> Out of the box, several interactions are available: </p> <ul> <li> Click a node to select it. Press and drag on a node to move it around. Or press and drag in the diagram background to pan the entire Diagram. </li> <li> Common keyboard shortcuts such as <code>Ctrl-C</code>, <code>Ctrl-V</code>, <code>Ctrl-X</code> will copy, paste, and cut diagram parts, respectively. (Use the Command modifier on a Mac.) Note that copies will copy the location of the Node as well, unless a layout or the paste command moves the new node. </li> <li> Press-hold-and-drag in the background of the diagram allows you to create a selection box for selecting multiple nodes. Ctrl-clicking on nodes allows you to toggle their selection. Shift-clicking on nodes makes sure they are selected. </li> <li> Since the Diagram's undoManager was enabled, <code>Ctrl-Z</code> and <code>Ctrl-Y</code> (or <code>Ctrl-Shift-Z</code>) will undo and redo operations. Panning (scrolling), zooming, and selection are not considered undoable operations. </li> </ul> <p> By setting a few properties you can expose more interaction to a user, including manipulating groups: </p> <pre class="lang-js"><code> const myDiagram = new go.Diagram("myDiagramDiv", { // allow double-click in background to create a new node "clickCreatingTool.archetypeNodeData": { text: "Node", color: "lightgray" }, // allow Ctrl-G to group selected nodes "commandHandler.archetypeGroupData": { text: "Group", isGroup: true }, // have mouse wheel events zoom in and out instead of scroll up and down "toolManager.mouseWheelBehavior": go.WheelMode.Zoom, // enable undo &amp; redo "undoManager.isEnabled": true }); myDiagram.nodeTemplate = new go.Node("Auto") .add( new go.Shape("Rectangle", { strokeWidth: 0 }) .bind("fill", "color"), new go.TextBlock({ margin: 6, font: "18px sans-serif", editable: true }) .bindTwoWay("text") ); myDiagram.groupTemplate = new go.Group("Vertical", { // allow Ctrl-Shift-G to ungroup a selected Group. ungroupable: true }) .add( new go.TextBlock({ font: "bold 12pt sans-serif", editable: true }) .bindTwoWay("text"), new go.Panel("Auto") .add( new go.Shape({ fill: "transparent" }), new go.Placeholder({ padding: 10 }) ) ); myDiagram.model = new go.GraphLinksModel( [ { key: 1, text: "Alpha", color: "lightblue" }, { key: 2, text: "Beta", color: "orange" }, { key: 3, text: "Gamma", color: "lightgreen", group: 5 }, { key: 4, text: "Delta", color: "pink", group: 5 }, { key: 5, text: "Group1", isGroup: true } ]); </code></pre> <!-- LIVE --> <div id="myDiagramDiv2" class="diagramStyling" style="width:700px; height:250px"></div> <script> const myDiagram2 = new go.Diagram("myDiagramDiv2", { // allow double-click in background to create a new node "clickCreatingTool.archetypeNodeData": { text: "Node", color: "lightgray" }, // allow Ctrl-G to group selected nodes "commandHandler.archetypeGroupData": { text: "Group", isGroup: true }, // have mouse wheel events zoom in and out instead of scroll up and down "toolManager.mouseWheelBehavior": go.WheelMode.Zoom, "undoManager.isEnabled": true // enable undo & redo }); myDiagram2.nodeTemplate = new go.Node("Auto") .add( new go.Shape("Rectangle", { strokeWidth: 0 }) .bind("fill", "color"), new go.TextBlock({ margin: 6, font: "18px sans-serif", editable: true }) .bindTwoWay("text") ); myDiagram2.groupTemplate = new go.Group("Vertical", { // allow Ctrl-Shift-G to ungroup a selected Group. ungroupable: true }) .add( new go.TextBlock({ font: "bold 12pt sans-serif", editable: true }) .bindTwoWay("text"), new go.Panel("Auto") .add( new go.Shape({ fill: "transparent" }), new go.Placeholder({ padding: 10 }) ) ); myDiagram2.model = new go.GraphLinksModel( [ { key: 1, text: "Alpha", color: "lightblue" }, { key: 2, text: "Beta", color: "orange" }, { key: 3, text: "Gamma", color: "lightgreen", group: 5 }, { key: 4, text: "Delta", color: "pink", group: 5 }, { key: 5, text: "Group1", isGroup: true } ]); </script> <p> <ul> <li> <code>ClickCreatingTool.archetypeNodeData</code> allows a double-click in the background to create a new node with a copy of the specified data. </li> <li> <code>CommandHandler.archetypeGroupData</code> allows <code>Ctrl-G</code> to group a selection of nodes, with the group getting a copy of the specified data in the model. </li> <li> <code>Group.ungroupable</code> allows <code>Ctrl-Shift-G</code> to ungroup a selected group. </li> <li> <code>ToolManager.mouseWheelBehavior</code> allows the mouse wheel to zoom instead of scroll by default. You can toggle this property by clicking on the mouse-wheel. On touch devices, pinch-zooming is enabled by default. </li> <li> <code>TextBlock.editable</code> in the <code>TextBlock</code> definition allows the text to be edited in place. Select a node and then click on the text, or press F2, to begin text editing. Click anywhere else on the diagram or press <code>Tab</code> to finish editing text. </li> </ul> </p> <p> These interactions (and more!) are all present in the <a href="../samples/basic.html">basic.html sample</a>. Be sure to also see the <a href="../intro/commands.html">Intro page on <b>GoJS</b> commands</a>. </p> <p> You can disable portions of Diagram interactivity with several properties, depending on what you want to allow users to do. See the <a href="../intro/permissions.html">Intro page on <b>GoJS</b> permissions</a> for more explanation. </p> <h2 id="FocusKeyboardControlTools">Focus and Keyboard Control of Tools</h2> <p> You have always been able to manipulate diagrams using the mouse without using the keyboard, especially when making use of the default context menu, which has always been needed on touch devices. As of version 3.1 if you have a keyboard you no longer need to use the mouse to manipulate almost any diagram. This keyboard control mode, when enabled, is available as long as the diagram has keyboard focus. You can always <code>Tab</code> out of the diagram. </p> <p> Because the GraphObjects within a Diagram are not HTML elements, there is a separate GoJS focus object that is a GraphObject. The arrow keys, <code>Enter</code>, and <code>Escape</code> can change which GraphObject has GoJS focus. </p> <p> Additionally there is a virtual pointer acting as if it were the mouse, visible when the <code>Shift</code> key is held down. Control the virtual pointer using <code>Shift</code>-arrow keys, <code>Shift</code> Numpad0-9 keys, <code>Shift-Enter</code>, and a few other keys modified by <code>Shift</code>. </p> <p> Version 3.1 also includes built-in support for screen readers. However, we recommend that you customize the behavior to provide the best possible experience for your users. </p> <h3>Summary of Focus Navigation and the Virtual Pointer</h3> (On a Mac, use the Command modifier insead of the Control modifier.) <ul> <li> Type <code>Ctrl-Alt-Enter</code> to enable "keyboard control mode", if it hasn't already been turned on programmatically. That key chord will toggle the settings on and off. </li> <li> GoJS focus is shown by a double-colored dashed box around the GraphObject. </li> <li> Use the arrow keys to move focus around to other Nodes or other kinds of Parts. Use <code>Ctrl</code> arrow keys to follow a Link connected with a focused Node. </li> <li> Type <code>Space</code> to select a Part (a Node or a Link), <code>Shift-Space</code> to add a Part to the selection, and <code>Ctrl-Space</code> to toggle selection of a Part. </li> <li> Using <code>Enter</code> on a focused Node, if there are any GraphObjects within the Node that can be focused, will change focus to be on one such object, and arrow keys will keep focus within the Node. </li> <li> When focus is inside a Node, <code>Enter</code> will activate the focused object: click a button, start editing a TextBlock, or show a tooltip. <code>Space</code> will only show a tooltip, if one is available. </li> <li> When focus is inside a Node, <code>Escape</code> will change focus to be on the Node again. </li> <li> When focus is on a Group, <code>Enter</code> will first focus on focusable GraphObjects, if any, just as for regular Nodes, and then <code>Escape</code> will focus on the Group's member Nodes, if any, and finally <code>Escape</code> will focus on the Group again. </li> <li> Use the <code>ContextMenu</code> key to show any context menu for the object with GoJS focus. </li> <li> All other keyboard commands work normally, such as <code>Delete</code> of the selection, or <code>Ctrl-Z</code> to undo. </li> <li> Hold down a <code>Shift</code> key to show a magenta cross-hair virtual pointer that acts like the mouse. You can let go of the <code>Shift</code> key at any time without losing any state. </li> <li> <code>Shift</code> arrow keys move the virtual pointer around. With the <code>Ctrl-Shift</code> modifiers they only move a pixel at a time. You can also use <code>Shift-Numpad1</code> through <code>Shift-Numpad9</code> (except <code>Shift-Numpad5</code>) to move the virtual pointer around. Unlike the arrow keys, you will be able to move the virtual pointer on the diagonals. </li> <li> <code>Shift-Enter</code> or <code>Shift-Numpad5</code> toggles the mouse button down or up. </li> <li> <code>Tab</code> works normally to change keyboard focus to the next HTML element after the Diagram. If the diagram is showing scrollbars, <code>Tab</code> will focus on the HTML element holding the scrollbars, allowing for the arrow keys to scroll normally. </li> <li> Note that when using the Windows Narrator screen reader you will need to stop Scan Mode via the <code>CapsLock-Space</code> command. </li> </ul> <p> See the <a href="../intro/accessibility.html">Intro page on Accessibility</a> for demos and more explanation. </p> <h2 id="ToolTips">Tool Tips</h2> <p> <b>GoJS</b> provides a mechanism for you to define tooltips for any object or for the Diagram itself. In the example below, two tooltips are defined, one on the Node template and one on the Diagram. </p> <pre class="lang-js"><code> const myDiagram = new go.Diagram("myDiagramDiv", { toolTip: // define a tooltip for the whole Diagram, // shown when the mouse hovers over the background go.GraphObject.build("ToolTip") .add( new go.TextBlock("Diagram info") .bindObject("text", "", ad => `there are ${ad.diagram.nodes.count} nodes`) ), "undoManager.isEnabled": true }); myDiagram.nodeTemplate = new go.Node("Auto", { toolTip: // define a tooltip for a Node go.GraphObject.build("ToolTip") .add( new go.TextBlock("Node info") .bind("text", "color", c => `This Node is ${c}`) ) }) .add( new go.Shape("Rectangle", { strokeWidth: 0 }) .bind("fill", "color"), new go.TextBlock({ margin: 6, font: "18px sans-serif" }) .bind("text") ); myDiagram.model = new go.GraphLinksModel( [ // there will be a Node for each data object in this nodeDataArray { 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> <!-- LIVE --> <div id="myDiagramDiv3t" class="diagramStyling" style="width:700px; height:150px"></div> <script> const myDiagram3t = new go.Diagram("myDiagramDiv3t", { toolTip: // define a tooltip for the whole Diagram, // shown when the mouse hovers over the background go.GraphObject.build("ToolTip") .add( new go.TextBlock("Diagram info") .bindObject("text", "", ad => `there are ${ad.diagram.nodes.count} nodes`) ), "undoManager.isEnabled": true }); myDiagram3t.nodeTemplate = new go.Node("Auto", { toolTip: // define a tooltip for a Node go.GraphObject.build("ToolTip") .add( new go.TextBlock("Node info") .bind("text", "color", c => `This Node is ${c}`) ) }) .add( new go.Shape("Rectangle", { strokeWidth: 0 }) .bind("fill", "color"), new go.TextBlock({ margin: 6, font: "18px sans-serif" }) .bind("text") ); myDiagram3t.model = new go.GraphLinksModel( [ // there will be a Node for each data object in this nodeDataArray { key: 1, text: "Alpha", color: "lightblue" }, { key: 2, text: "Beta", color: "orange" }, { key: 3, text: "Gamma", color: "lightgreen" }, { key: 4, text: "Delta", color: "pink" } ]); </script> <p> Hover over a node to show the node's tooltip for five seconds. Hover anywhere in the viewport not over a node to see the tooltip for the diagram. </p> <p> The <a href="../samples/basic.html">basic.html sample</a> contains more complex tooltip examples. See the <a href="../intro/tooltips.html">Intro page on <b>GoJS</b> tooltips</a> for more discussion. </p> <h2 id="ContextMenus">Context Menus</h2> <p> <b>GoJS</b> provides a mechanism for you to define context menus for any object or for the Diagram itself. In the example below, two context menus are defined, one on the Node template (with one button) and one on the Diagram (with two buttons). </p> <pre class="lang-js"><code> const myDiagram = new go.Diagram("myDiagramDiv", { "undoManager.isEnabled": true }); // this function is called when the context menu button is clicked function nodeClicked(e, obj) { alert('node ' + obj.part.data.key + ', "' + obj.part.data.text + '" was clicked'); } // defines a context menu to be referenced in the node template const nodeContextMenu = go.GraphObject.build("ContextMenu") .add( go.GraphObject.build("ContextMenuButton", { click: nodeClicked }) .add(new go.TextBlock("Click me!")), // more ContextMenuButtons would go here ); myDiagram.nodeTemplate = new go.Node("Auto", { contextMenu: nodeContextMenu }) .add( new go.Shape("Rectangle", { strokeWidth: 0 }) .bind("fill", "color"), new go.TextBlock({ margin: 6, font: "18px sans-serif" }) .bind("text") ); // this function alerts the current number of nodes in the Diagram function countNodes(e, obj) { alert('there are ' + e.diagram.nodes.count + ' nodes'); } // this function creates a new node and inserts it at the last event's point function addNode(e, obj) { const data = { text: "Node", color: "white" }; // do not need to set "key" property -- addNodeData will assign it automatically e.diagram.model.addNodeData(data); const node = e.diagram.findPartForData(data); node.location = e.diagram.lastInput.documentPoint; } myDiagram.contextMenu = go.GraphObject.build("ContextMenu") .add( go.GraphObject.build("ContextMenuButton", { click: countNodes }) .add(new go.TextBlock("Count Nodes")), go.GraphObject.build("ContextMenuButton", { click: addNode }) .add(new go.TextBlock("Add Node")), // more ContextMenuButtons would go here ); myDiagram.model = new go.GraphLinksModel( [ { key: 1, text: "Alpha", color: "lightblue" }, { key: 2, text: "Beta", color: "orange" } ]); </code></pre> <!-- LIVE --> <div id="myDiagramDiv3" class="diagramStyling" style="width:700px; height:150px"></div> <script> const myDiagram3 = new go.Diagram("myDiagramDiv3", { "undoManager.isEnabled": true }); // this function is called when the context menu button is clicked function nodeClicked(e, obj) { alert('node ' + obj.part.data.key + ', "' + obj.part.data.text + '" was clicked'); } // defines a context menu to be referenced in the node template const nodeContextMenu = go.GraphObject.build("ContextMenu") .add( go.GraphObject.build("ContextMenuButton", { click: nodeClicked }) .add(new go.TextBlock("Click me!")), // more ContextMenuButtons would go here ); myDiagram3.nodeTemplate = new go.Node("Auto", { contextMenu: nodeContextMenu }) .add( new go.Shape("Rectangle", { strokeWidth: 0 }) .bind("fill", "color"), new go.TextBlock({ margin: 6, font: "18px sans-serif" }) .bind("text") ); // this function alerts the current number of nodes in the Diagram function countNodes(e, obj) { alert('there are ' + e.diagram.nodes.count + ' nodes'); } // this function creates a new node and inserts it at the last event's point function addNode(e, obj) { const data = { text: "Node", color: "white" }; // do not need to set "key" property -- addNodeData will assign it automatically e.diagram.model.addNodeData(data); const node = e.diagram.findPartForData(data); node.location = e.diagram.lastInput.documentPoint; } myDiagram3.contextMenu = go.GraphObject.build("ContextMenu") .add( go.GraphObject.build("ContextMenuButton", { click: countNodes }) .add(new go.TextBlock("Count Nodes")), go.GraphObject.build("ContextMenuButton", { click: addNode }) .add(new go.TextBlock("Add Node")), // more ContextMenuButtons would go here ); myDiagram3.model = new go.GraphLinksModel( [ { key: 1, text: "Alpha", color: "lightblue" }, { key: 2, text: "Beta", color: "orange" } ]); </script> <p> If you right-click (or long-tap on a touch device) on a Node or the Diagram, you will see the <b>GoJS</b> context menu appear with the defined options. </p> <p> The <a href="../samples/basic.html">basic.html sample</a> contains more complex context menu examples. See the <a href="../intro/contextmenus.html">Intro page on <b>GoJS</b> context menus</a> for more discussion. </p> <h2 id="LinkInteractions">Link interactions</h2> <p> <b>GoJS</b> lets users draw new Links by dragging out from a port on a Node to another port on a Node. Users can reconnect existing links by selecting one and dragging one of its handles. To enable these behaviors, some properties need to be set: </p> <pre class="lang-js"><code> const myDiagram = new go.Diagram("myDiagramDiv", { "undoManager.isEnabled": true }); myDiagram.nodeTemplate = new go.Node("Auto") .add( new go.Shape("Rectangle", { strokeWidth: 0, // declare this Shape to be the port element for the Node portId: "", cursor: "pointer", // set various port-related properties here on this port element fromLinkable: true, fromLinkableSelfNode: true, fromLinkableDuplicates: true, toLinkable: true, toLinkableSelfNode: true, toLinkableDuplicates: true }) .bind("fill", "color"), new go.TextBlock({ margin: 6, font: "18px sans-serif" }) .bind("text") ); myDiagram.linkTemplate = new go.Link({ // allow the user to reconnnect existing links: relinkableFrom: true, relinkableTo: true, // draw the link path shorter than normal, // so that it does not interfere with the appearance of the arrowhead toShortLength: 2 }) .add( new go.Shape({ strokeWidth: 2 }), new go.Shape({ toArrow: "Standard", strokeWidth: 0 }) ); 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" } ], [ { from: 1, to: 2 }, { from: 1, to: 4 } ]); </code></pre> <!-- LIVE --> <div id="myDiagramDiv4" class="diagramStyling" style="width:700px; height:150px"></div> <script> const myDiagram4 = new go.Diagram("myDiagramDiv4", { "undoManager.isEnabled": true }); myDiagram4.nodeTemplate = new go.Node("Auto") .add( new go.Shape("Rectangle", { strokeWidth: 0, // declare this Shape to be the port element for the Node portId: "", cursor: "pointer", // set various port-related properties here on this port element fromLinkable: true, fromLinkableSelfNode: true, fromLinkableDuplicates: true, toLinkable: true, toLinkableSelfNode: true, toLinkableDuplicates: true }) .bind("fill", "color"), new go.TextBlock({ margin: 6, font: "18px sans-serif" }) .bind("text") ); myDiagram4.linkTemplate = new go.Link({ // allow the user to reconnnect existing links: relinkableFrom: true, relinkableTo: true, // draw the link path shorter than normal, // so that it does not interfere with the appearance of the arrowhead toShortLength: 2 }) .add( new go.Shape({ strokeWidth: 2 }), new go.Shape({ toArrow: "Standard", strokeWidth: 0 }) ); myDiagram4.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" } ], [ { from: 1, to: 2 }, { from: 1, to: 4 } ]); </script> <p> In the above example: </p> <ul> <li> The Node's <code>Shape</code> has its <code>portId</code> set to make it the port instead of the entire Node. Then several <code>...Linkable...</code> properties are set, allowing each node to link to itself and to others. Depending on the functionality you want to provide to your users, you may not want to set all of those properties on your ports in your node templates. </li> <li> To draw a new link to another node, drag from the port (the edge of the Shape that is not behind the TextBlock) to any node, including itself. </li> <li> In the link template, <code>relinkable...</code> properties are set, so that when the link is selected, it shows handles that can be dragged in order to reconnect the link with a different node. </li> </ul> <p> <b>GoJS</b> allows linking and re-linking to abide by custom criteria that controls whether a link connection would be valid. You can read about this in the <a href="../intro/validation.html">Intro page on Validation</a>. </p> <p> <b>GoJS</b> links have many interesting properties that are covered in depth in the <a href="../intro/links.html">Intro page on Links</a> and on the following pages. </p> <p> You may want to read more tutorials, such as the <a href="index.html">Learn GoJS</a> tutorial and the <a href="graphObject.html">GraphObject Manipulation</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>