UNPKG

create-gojs-kit

Version:

A CLI for downloading GoJS samples, extensions, and docs

598 lines (547 loc) 28.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="Drag a link to reconnect it. Nodes have custom Adornments for selection, resizing, and rotating. The Palette includes links." /> <meta itemprop="description" content="Drag a link to reconnect it. Nodes have custom Adornments for selection, resizing, and rotating. The Palette includes links." /> <meta property="og:description" content="Drag a link to reconnect it. Nodes have custom Adornments for selection, resizing, and rotating. The Palette includes links." /> <meta name="twitter:description" content="Drag a link to reconnect it. Nodes have custom Adornments for selection, resizing, and rotating. The Palette includes links." /> <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="Allow Users to Drag Links and Reconnect Them on Drop" /> <meta property="og:title" content="Allow Users to Drag Links and Reconnect Them on Drop" /> <meta name="twitter:title" content="Allow Users to Drag Links and Reconnect Them on Drop" /> <meta property="og:image" content="https://gojs.net/latest/assets/images/screenshots/draggablelink.png" /> <meta itemprop="image" content="https://gojs.net/latest/assets/images/screenshots/draggablelink.png" /> <meta name="twitter:image" content="https://gojs.net/latest/assets/images/screenshots/draggablelink.png" /> <meta property="og:url" content="https://gojs.net/latest/samples/draggableLink.html" /> <meta property="twitter:url" content="https://gojs.net/latest/samples/draggableLink.html" /> <meta name="twitter:card" content="summary_large_image" /> <meta property="og:type" content="website" /> <meta property="twitter:domain" content="gojs.net" /> <title> Allow Users to Drag Links and Reconnect Them on Drop | GoJS Diagramming Library </title> </head> <body> <!-- This top nav is not part of the sample code --> <nav id="navTop" class=" w-full h-[var(--topnav-h)] z-30 bg-white border-b border-b-gray-200"> <div class="max-w-screen-xl mx-auto flex flex-wrap items-start justify-between px-4"> <a class="text-white bg-nwoods-primary font-bold !leading-[calc(var(--topnav-h)_-_1px)] my-0 px-2 text-4xl lg:text-5xl logo" href="../"> GoJS </a> <div class="relative"> <button id="topnavButton" class="h-[calc(var(--topnav-h)_-_1px)] px-2 m-0 text-gray-900 bg-inherit shadow-none md:hidden hover:!bg-inherit hover:!text-nwoods-accent hover:!shadow-none" aria-label="Navigation"> <svg class="h-7 w-7 block" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"> <path d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" stroke-linecap="round" stroke-linejoin="round"/> </svg> </button> <div id="topnavList" class="hidden md:block"> <div class="absolute right-0 z-30 flex flex-col items-end rounded border border-gray-200 p-4 pl-12 shadow bg-white text-gray-900 font-semibold md:flex-row md:space-x-4 md:items-start md:border-0 md:p-0 md:shadow-none md:bg-inherit"> <a href="../learn/">Learn</a> <a href="../samples/">Samples</a> <a href="../intro/">Intro</a> <a href="../api/">API</a> <a href="../download.html">Download</a> <a href="https://forum.nwoods.com/c/gojs/11" target="_blank" rel="noopener">Forum</a> <a id="tc" href="https://nwoods.com/contact.html" target="_blank" rel="noopener" onclick="getOutboundLink('https://nwoods.com/contact.html', 'contact');">Contact</a> <a id="tb" href="https://nwoods.com/sales/index.html" target="_blank" rel="noopener" onclick="getOutboundLink('https://nwoods.com/sales/index.html', 'buy');">Buy</a> </div> </div> </div> </div> </nav> <script> window.addEventListener("DOMContentLoaded", function () { // topnav var topButton = document.getElementById("topnavButton"); var topnavList = document.getElementById("topnavList"); if (topButton && topnavList) { topButton.addEventListener("click", function (e) { topnavList .classList .toggle("hidden"); e.stopPropagation(); }); document.addEventListener("click", function (e) { // if the clicked element isn't the list, close the list if (!topnavList.classList.contains("hidden") && !e.target.closest("#topnavList")) { topButton.click(); } }); // set active <a> element var url = window .location .href .toLowerCase(); var aTags = topnavList.getElementsByTagName('a'); for (var i = 0; i < aTags.length; i++) { var lowerhref = aTags[i] .href .toLowerCase(); if (lowerhref.endsWith('.html')) lowerhref = lowerhref.slice(0, -5); if (url.startsWith(lowerhref)) { aTags[i] .classList .add('active'); break; } } } }); </script> <div class="flex flex-col prose"> <div class="w-full max-w-screen-xl mx-auto"> <!-- * * * * * * * * * * * * * --> <!-- Start of GoJS sample code --> <script src="https://cdn.jsdelivr.net/npm/gojs@3.1.0"></script> <div id="allSampleContent" class="p-4 w-full"> <script src="../extensions/Figures.js"></script> <script id="code"> function init() { myDiagram = new go.Diagram('myDiagramDiv', { // must name or refer to the DIV HTML element grid: new go.Panel('Grid') .add( new go.Shape('LineH', { stroke: 'lightgray', strokeWidth: 0.5 }), new go.Shape('LineH', { stroke: 'gray', strokeWidth: 0.5, interval: 10 }), new go.Shape('LineV', { stroke: 'lightgray', strokeWidth: 0.5 }), new go.Shape('LineV', { stroke: 'gray', strokeWidth: 0.5, interval: 10 }) ), 'draggingTool.dragsLink': true, 'draggingTool.isGridSnapEnabled': true, 'linkingTool.isUnconnectedLinkValid': true, 'linkingTool.portGravity': 20, 'relinkingTool.isUnconnectedLinkValid': true, 'relinkingTool.portGravity': 20, 'relinkingTool.fromHandleArchetype': new go.Shape('Diamond', { segmentIndex: 0, cursor: 'pointer', desiredSize: new go.Size(8, 8), fill: 'tomato', stroke: 'darkred' }), 'relinkingTool.toHandleArchetype': new go.Shape('Diamond', { segmentIndex: -1, cursor: 'pointer', desiredSize: new go.Size(8, 8), fill: 'darkred', stroke: 'tomato' }), 'linkReshapingTool.handleArchetype': new go.Shape('Diamond', { desiredSize: new go.Size(7, 7), fill: 'lightblue', stroke: 'deepskyblue' }), 'rotatingTool.handleAngle': 270, 'rotatingTool.handleDistance': 30, 'rotatingTool.snapAngleMultiple': 15, 'rotatingTool.snapAngleEpsilon': 15, 'undoManager.isEnabled': true }); // when the document is modified, add a "*" to the title and enable the "Save" button myDiagram.addDiagramListener('Modified', e => { const button = document.getElementById('SaveButton'); if (button) button.disabled = !myDiagram.isModified; const idx = document.title.indexOf('*'); if (myDiagram.isModified) { if (idx < 0) document.title += '*'; } else { if (idx >= 0) document.title = document.title.slice(0, idx); } }); // Define a function for creating a "port" that is normally transparent. // The "name" is used as the GraphObject.portId, the "spot" is used to control how links connect // and where the port is positioned on the node, and the boolean "output" and "input" arguments // control whether the user can draw links from or to the port. function makePort(name, spot, output, input) { // the port is basically just a small transparent circle return new go.Shape('Circle', { fill: null, // not seen, by default; set to a translucent gray by showSmallPorts, defined below stroke: null, desiredSize: new go.Size(7, 7), alignment: spot, // align the port on the main Shape alignmentFocus: spot, // just inside the Shape portId: name, // declare this object to be a "port" fromSpot: spot, toSpot: spot, // declare where links may connect at this port fromLinkable: output, toLinkable: input, // declare whether the user may draw links to/from here cursor: 'pointer' // show a different cursor to indicate potential link point }); } var nodeSelectionAdornmentTemplate = new go.Adornment('Auto') .add( new go.Shape({ fill: null, stroke: 'deepskyblue', strokeWidth: 1.5, strokeDashArray: [4, 2] }), new go.Placeholder() ); var nodeResizeAdornmentTemplate = new go.Adornment('Spot', { locationSpot: go.Spot.Right }) .add( new go.Placeholder(), new go.Shape({ alignment: go.Spot.TopLeft, cursor: 'nw-resize', desiredSize: new go.Size(6, 6), fill: 'lightblue', stroke: 'deepskyblue' }), new go.Shape({ alignment: go.Spot.Top, cursor: 'n-resize', desiredSize: new go.Size(6, 6), fill: 'lightblue', stroke: 'deepskyblue' }), new go.Shape({ alignment: go.Spot.TopRight, cursor: 'ne-resize', desiredSize: new go.Size(6, 6), fill: 'lightblue', stroke: 'deepskyblue' }), new go.Shape({ alignment: go.Spot.Left, cursor: 'w-resize', desiredSize: new go.Size(6, 6), fill: 'lightblue', stroke: 'deepskyblue' }), new go.Shape({ alignment: go.Spot.Right, cursor: 'e-resize', desiredSize: new go.Size(6, 6), fill: 'lightblue', stroke: 'deepskyblue' }), new go.Shape({ alignment: go.Spot.BottomLeft, cursor: 'se-resize', desiredSize: new go.Size(6, 6), fill: 'lightblue', stroke: 'deepskyblue' }), new go.Shape({ alignment: go.Spot.Bottom, cursor: 's-resize', desiredSize: new go.Size(6, 6), fill: 'lightblue', stroke: 'deepskyblue' }), new go.Shape({ alignment: go.Spot.BottomRight, cursor: 'sw-resize', desiredSize: new go.Size(6, 6), fill: 'lightblue', stroke: 'deepskyblue' }) ); var nodeRotateAdornmentTemplate = new go.Adornment({ locationSpot: go.Spot.Center, locationObjectName: 'ELLIPSE' }) .add( new go.Shape('Ellipse', { name: 'ELLIPSE', cursor: 'pointer', desiredSize: new go.Size(7, 7), fill: 'lightblue', stroke: 'deepskyblue' }), new go.Shape({ geometryString: 'M3.5 7 L3.5 30', isGeometryPositioned: true, stroke: 'deepskyblue', strokeWidth: 1.5, strokeDashArray: [4, 2] }) ); myDiagram.nodeTemplate = new go.Node('Spot', { locationSpot: go.Spot.Center, selectable: true, selectionAdornmentTemplate: nodeSelectionAdornmentTemplate, resizable: true, resizeObjectName: 'PANEL', resizeAdornmentTemplate: nodeResizeAdornmentTemplate, rotatable: true, rotateAdornmentTemplate: nodeRotateAdornmentTemplate, // handle mouse enter/leave events to show/hide the ports mouseEnter: (e, node) => showSmallPorts(node, true), mouseLeave: (e, node) => showSmallPorts(node, false) }) .bindTwoWay('location', 'loc', go.Point.parse, go.Point.stringify) .bindTwoWay('angle') .add( // the main object is a Panel that surrounds a TextBlock with a Shape new go.Panel('Auto', { name: 'PANEL' }) .bindTwoWay('desiredSize', 'size', go.Size.parse, go.Size.stringify) .add( new go.Shape('Rectangle', { // default figure portId: '', // the default port: if no spot on link data, use closest side fromLinkable: true, toLinkable: true, cursor: 'pointer', fill: 'white', // default color strokeWidth: 2 }) .bind('figure') .bind('fill'), new go.TextBlock({ font: 'bold 10pt Helvetica, Arial, sans-serif', margin: 8, maxSize: new go.Size(160, NaN), wrap: go.Wrap.Fit, editable: true }) .bindTwoWay('text') ), // four small named ports, one on each side: makePort('T', go.Spot.Top, false, true), makePort('L', go.Spot.Left, true, true), makePort('R', go.Spot.Right, true, true), makePort('B', go.Spot.Bottom, true, false) ); function showSmallPorts(node, show) { node.ports.each(port => { if (port.portId !== '') { // don't change the default port, which is the big shape port.fill = show ? 'rgba(0,0,0,.3)' : null; } }); } var linkSelectionAdornmentTemplate = new go.Adornment('Link') .add( new go.Shape({ isPanelMain: true, // isPanelMain declares that this Shape shares the Link.geometry fill: null, stroke: 'deepskyblue', strokeWidth: 0 // use selection object's strokeWidth }) ); myDiagram.linkTemplate = new go.Link({ // the whole link panel selectable: true, selectionAdornmentTemplate: linkSelectionAdornmentTemplate, relinkableFrom: true, relinkableTo: true, reshapable: true, routing: go.Routing.AvoidsNodes, curve: go.Curve.JumpOver, corner: 5, toShortLength: 4 }) .bindTwoWay('points') .add( new go.Shape({ // the link path shape isPanelMain: true, strokeWidth: 2 }), new go.Shape({ // the arrowhead toArrow: 'Standard', stroke: null }), new go.Panel('Auto') .bindObject('visible', 'isSelected') .add( new go.Shape('RoundedRectangle', { // the link shape fill: '#F8F8F8', stroke: null }), new go.TextBlock({ textAlign: 'center', font: '10pt helvetica, arial, sans-serif', stroke: '#919191', margin: 2, minSize: new go.Size(10, NaN), editable: true }) .bindTwoWay('text') ) ); load(); // load an initial diagram from some JSON text // initialize the Palette that is on the left side of the page myPalette = new go.Palette('myPaletteDiv', { maxSelectionCount: 1, nodeTemplateMap: myDiagram.nodeTemplateMap, // share the templates used by myDiagram // simplify the link template, just in this Palette linkTemplate: new go.Link({ // because the GridLayout.alignment is Location and the nodes have locationSpot == Spot.Center, // to line up the Link in the same manner we have to pretend the Link has the same location spot locationSpot: go.Spot.Center, selectionAdornmentTemplate: new go.Adornment('Link', { locationSpot: go.Spot.Center }) .add( new go.Shape({ isPanelMain: true, fill: null, stroke: 'deepskyblue', strokeWidth: 0 }), new go.Shape({ // the arrowhead toArrow: 'Standard', stroke: null }) ), routing: go.Routing.AvoidsNodes, curve: go.Curve.JumpOver, corner: 5, toShortLength: 4 }) .bind('points') .add( new go.Shape({ // the link path shape isPanelMain: true, strokeWidth: 2 }), new go.Shape({ // the arrowhead toArrow: 'Standard', stroke: null }) ), model: new go.GraphLinksModel( [ // specify the contents of the Palette { text: 'Start', figure: 'Ellipse', size: '75 75', fill: '#00AD5F' }, { text: 'Step' }, { text: 'DB', figure: 'Database', fill: 'lightgray' }, { text: '???', figure: 'Diamond', fill: 'lightskyblue' }, { text: 'End', figure: 'Ellipse', size: '75 75', fill: '#CE0620' }, { text: 'Comment', figure: 'RoundedRectangle', fill: 'lightyellow' } ], [ // the Palette also has a disconnected Link, which the user can drag-and-drop { points: new go.List(/*go.Point*/).addAll([new go.Point(0, 0), new go.Point(30, 0), new go.Point(30, 40), new go.Point(60, 40)]) } ] ) }); } // Show the diagram's model in JSON format that the user may edit function save() { saveDiagramProperties(); // do this first, before writing to JSON document.getElementById('mySavedModel').value = myDiagram.model.toJson(); myDiagram.isModified = false; } function load() { myDiagram.model = go.Model.fromJson(document.getElementById('mySavedModel').value); loadDiagramProperties(); // do this after the Model.modelData has been brought into memory } function saveDiagramProperties() { myDiagram.model.modelData.position = go.Point.stringify(myDiagram.position); } function loadDiagramProperties(e) { // set Diagram.initialPosition, not Diagram.position, to handle initialization side-effects var pos = myDiagram.model.modelData.position; if (pos) myDiagram.initialPosition = go.Point.parse(pos); } window.addEventListener('DOMContentLoaded', init); </script> <div id="sample"> <div style="width: 100%; display: flex; justify-content: space-between"> <div id="myPaletteDiv" style="width: 105px; margin-right: 2px; background-color: whitesmoke; border: solid 1px black"></div> <div id="myDiagramDiv" style="flex-grow: 1; height: 620px; border: solid 1px black"></div> </div> <p> This sample demonstrates the ability for the user to drag around a Link as if it were a Node. When either end of the link passes over a valid port, the port is highlighted. </p> <p> The link-dragging functionality is enabled by setting some or all of the following properties: <a>DraggingTool.dragsLink</a>, <a>LinkingTool.isUnconnectedLinkValid</a>, and <a>RelinkingTool.isUnconnectedLinkValid</a>. </p> <p> Note that a Link is present in the <a>Palette</a> so that it too can be dragged out and onto the main Diagram. Because links are not automatically routed when either end is not connected with a Node, the route is provided explicitly when that Palette item is defined. </p> <p> This also demonstrates several custom Adornments: <a>Part.selectionAdornmentTemplate</a>, <a>Part.resizeAdornmentTemplate</a>, and <a>Part.rotateAdornmentTemplate</a>. </p> <p> Finally this sample demonstrates saving and restoring the <a>Diagram.position</a> as a property on the <a>Model.modelData</a> object that is automatically saved and restored when calling <a>Model.toJson</a> and <a>Model.fromJson</a>. </p> <div> <div> <button id="SaveButton" onclick="save()">Save</button> <button onclick="load()">Load</button> Diagram Model saved in JSON format: </div> <textarea id="mySavedModel" style="width: 100%; height: 300px"> { "class": "go.GraphLinksModel", "linkFromPortIdProperty": "fromPort", "linkToPortIdProperty": "toPort", "pointsDigits": 1, "nodeDataArray": [ ], "linkDataArray": [ ]} </textarea> </div> </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>Links</h4> <p> The <a href="../api/symbols/Link.html" target="api">Link</a> class is used to implement a visual relationship between nodes. Links are normally created by the presence of link data objects in the <a href="../api/symbols/GraphLinksModel.html#linkDataArray" target="api">GraphLinksModel.linkDataArray</a> or by a parent key reference as the value of the <a href="../api/symbols/TreeModel.html#nodeParentKeyProperty" target="api">TreeModel.nodeParentKeyProperty</a> of a node data object in a <a href="../api/symbols/TreeModel.html" target="api">TreeModel</a>. More information can be found in the <a href="../intro/links.html">GoJS Intro</a>. </p> <p> <a href="../samples/index.html#links">Related samples</a> </p> <hr> <!-- blacklist tags that do not correspond to a specific GoJS feature --> <h4>Tools</h4> <p> <a href="../api/symbols/Tool.html" target="api">Tool</a>s handle all input events, such as mouse and keyboard interactions, in a Diagram. There are many kinds of predefined Tool classes that implement all of the common operations that users do. </p> <p> For flexibility and simplicity, all input events are canonicalized as <a href="../api/symbols/InputEvent.html" target="api">InputEvent</a>s and redirected by the diagram to go to the <a href="../api/symbols/Diagram.html#currentTool" target="api">Diagram.currentTool</a>. By default the Diagram.currentTool is an instance of <a href="../api/symbols/ToolManager.html" target="api">ToolManager</a> held as the <a href="../api/symbols/Diagram.html#toolManager" target="api">Diagram.toolManager</a>. The ToolManager implements support for all mode-less tools. The ToolManager is responsible for finding another tool that is ready to run and then making it the new current tool. This causes the new tool to process all of the input events (mouse, keyboard, and touch) until the tool decides that it is finished, at which time the diagram's current tool reverts back to the <a href="../api/symbols/Diagram.html#defaultTool" target="api">Diagram.defaultTool</a>, which is normally the ToolManager, again. </p> <p> More information can be found in the <a href="../intro/tools.html">GoJS Intro</a>. </p> <p> <a href="../samples/index.html#tools">Related samples</a> </p> <hr> <!-- blacklist tags that do not correspond to a specific GoJS feature --> <h4>Palette</h4> <p> A <a href="../api/symbols/Palette.html" target="api">Palette</a> is a subclass of <a href="../api/symbols/Diagram.html" target="api">Diagram</a> that is used to display a number of <a href="../api/symbols/Part.html" target="api">Part</a>s that can be dragged into the diagram that is being modified by the user. The initialization of a <a href="../api/symbols/Palette.html" target="api">Palette</a> is just like the initialization of any <a href="../api/symbols/Diagram.html" target="api">Diagram</a>. Like Diagrams, you can have more than one Palette on the page at the same time. </p> <p> More information can be found in the <a href="../intro/palette.html">GoJS Intro</a>. </p> <p> <a href="../samples/index.html#palette">Related samples</a> </p> <hr> <!-- blacklist tags that do not correspond to a specific GoJS feature --> <h4>Geometry Path Strings</h4> <p> The <b>GoJS</b> <a href="../api/symbols/Geometry.html" target="api">Geometry</a> class controls the "shape" of a <a href="../api/symbols/Shape.html" target="api">Shape</a>, whereas the <a href="../api/symbols/Shape.html#fill" target="api">Shape.fill</a> and <a href="../api/symbols/Shape.html#stroke" target="api">Shape.stroke</a> and other shape properties control the colors and appearance of the shape. For common shape figures, there are predefined geometries that can be used by setting <a href="../api/symbols/Shape.html#figure" target="api">Shape.figure</a>. However one can also define custom geometries. </p> <p> One can construct any Geometry by allocating and initializing a <a href="../api/symbols/Geometry.html" target="api">Geometry</a> of at least one <a href="../api/symbols/PathFigure.html" target="api">PathFigure</a> holding some <a href="../api/symbols/PathSegment.html" target="api">PathSegment</a>s. But you may find that using the string representation of a Geometry is easier to write and save in a database. Use the static method <a href="../api/symbols/Geometry.html#parse" target="api">Geometry.parse</a> or the <a href="../api/symbols/Shape.html#geometryString" target="api">Shape.geometryString</a> property to transform a geometry path string into a <a href="../api/symbols/Geometry.html" target="api">Geometry</a> object. </p> <p> More information can be found in the <a href="../intro/geometry.html">GoJS Intro</a>. </p> <p> <a href="../samples/index.html#geometries">Related samples</a> </p> <hr> <!-- blacklist tags that do not correspond to a specific GoJS feature --> <h4>Grid Patterns</h4> <p> <b>GoJS</b> provides functionality to display a grid of lines drawn at regular intervals. Grid Panels can also force dragged parts to be aligned on grid points, and resize parts to be multiples of the grid cell size. </p> <p> Grids are implemented using a type of <a href="../api/symbols/Panel.html" target="api">Panel</a>, <a href="../api/symbols/PanelLayout.html#Grid" target="api">Panel.Grid</a>. Grid Panels, like most other types of Panels, can be used within <a href="../api/symbols/Node.html" target="api">Node</a>s or any other kind of <a href="../api/symbols/Part.html" target="api">Part</a>. However when they are used as the <a href="../api/symbols/Diagram.html#grid" target="api">Diagram.grid</a>, they are effectively infinite in extent. </p> <p> More information can be found in the <a href="../intro/grids.html">GoJS Intro</a>. </p> <p> <a href="../samples/index.html#grid">Related samples</a> </p> <hr> </div> </div> </body> <!-- This script is part of the gojs.net website, and is not needed to run the sample --> <script src="../assets/js/goSamples.js"></script> </html>