UNPKG

create-gojs-kit

Version:

A CLI for downloading GoJS samples, extensions, and docs

469 lines (421 loc) 23.1 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="When reshaping an orthogonal link, make sure the points are moved onto a grid." /> <meta itemprop="description" content="When reshaping an orthogonal link, make sure the points are moved onto a grid." /> <meta property="og:description" content="When reshaping an orthogonal link, make sure the points are moved onto a grid." /> <meta name="twitter:description" content="When reshaping an orthogonal link, make sure the points are moved onto a grid." /> <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="Reshaping Links Snaps to Grid" /> <meta property="og:title" content="Reshaping Links Snaps to Grid" /> <meta name="twitter:title" content="Reshaping Links Snaps to Grid" /> <meta property="og:image" content="https://gojs.net/latest/assets/images/screenshots/snaplinkreshaping.png" /> <meta itemprop="image" content="https://gojs.net/latest/assets/images/screenshots/snaplinkreshaping.png" /> <meta name="twitter:image" content="https://gojs.net/latest/assets/images/screenshots/snaplinkreshaping.png" /> <meta property="og:url" content="https://gojs.net/latest/samples/SnapLinkReshaping.html" /> <meta property="twitter:url" content="https://gojs.net/latest/samples/SnapLinkReshaping.html" /> <meta name="twitter:card" content="summary_large_image" /> <meta property="og:type" content="website" /> <meta property="twitter:domain" content="gojs.net" /> <title> Reshaping Links Snaps to Grid | 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 src="../extensions/SnapLinkReshapingTool.js"></script> <script id="code"> function init() { myDiagram = new go.Diagram('myDiagramDiv', { // must name or refer to the DIV HTML element // supply a simple narrow grid that manually reshaped link routes will follow grid: new go.Panel('Grid', { gridCellSize: new go.Size(8, 8) }) .add( new go.Shape('LineH', { stroke: 'lightgray', strokeWidth: 0.5 }), new go.Shape('LineV', { stroke: 'lightgray', strokeWidth: 0.5 }) ), 'draggingTool.isGridSnapEnabled': true, linkReshapingTool: new SnapLinkReshapingTool(), // when the user reshapes a Link, change its Link.routing from AvoidsNodes to Orthogonal, // so that combined with Link.adjusting == End the link will retain its reshaped mid points // even after nodes are moved LinkReshaped: e => { e.subject.adjusting = go.LinkAdjusting.End; e.subject.routing = go.Routing.Orthogonal; }, 'animationManager.isEnabled': false, '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 square 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 }); } myDiagram.nodeTemplate = new go.Node('Spot', { locationSpot: go.Spot.Center, selectable: true, resizable: true, resizeObjectName: 'PANEL', // 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) .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 }) .bind('figure') .bind('fill'), new go.TextBlock({ font: 'bold 11pt 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; } }); } myDiagram.linkTemplate = new go.Link({ relinkableFrom: true, relinkableTo: true, reshapable: true, resegmentable: true, routing: go.Routing.AvoidsNodes, // but this is changed to go.Routing.Orthogonal when the Link is reshaped curve: go.Curve.JumpOver, corner: 5, toShortLength: 4 }) .bindTwoWay('points') // remember the Link.routing too .bindTwoWay('routing') .bindTwoWay('adjusting') .add( new go.Shape({ strokeWidth: 2 }), // the link path shape new go.Shape({ toArrow: 'Standard', stroke: null }) // the arrowhead ); load(); // load an initial diagram from some JSON text var link = myDiagram.links.first(); if (link) link.isSelected = true; // 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 model: new go.GraphLinksModel([ // specify the contents of the Palette { text: 'Start', figure: 'Circle', fill: 'green' }, { text: 'Step' }, { text: 'DB', figure: 'Database', fill: 'lightgray' }, { text: '???', figure: 'Diamond', fill: 'lightskyblue' }, { text: 'End', figure: 'Circle', fill: 'red' }, { text: 'Comment', figure: 'RoundedRectangle', fill: 'lightyellow' } ]) }); document.getElementById('AvoidsNodesCheckBox').onclick = e => { myDiagram.toolManager.linkReshapingTool.avoidsNodes = e.target.checked; }; } // 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(); } function saveDiagramProperties() { myDiagram.model.modelData.position = go.Point.stringify(myDiagram.position); } // Called by "InitialLayoutCompleted" DiagramEvent listener, NOT directly by load()! 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; height: 620px; 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> <label><input type="checkbox" id="AvoidsNodesCheckBox" checked="checked" />SnapLinkReshapingTool.avoidsNodes</label> <p> This sample is a simplified version of the <a href="../samples/draggableLink.html">Draggable Link</a> sample. Links are not draggable, there are no custom <a>Adornment</a>s, nodes are not rotatable, and links do not have text labels. </p> <p> Its purpose is to demonstrate the <a href="../extensions/SnapLinkReshapingTool.js">SnapLinkReshapingTool</a>, an extension of <a>LinkReshapingTool</a> that snaps each dragged reshape handle of selected Links to the nearest grid point. If the <a>SnapLinkReshapingTool.avoidsNodes</a> option is true, as it is by default, then the reshaping is limited to points where the adjacent segments would not be crossing over any avoidable nodes. </p> <p> Note how the "LinkReshaped" DiagramEvent listener changes the <a>Link.routing</a> of the reshaped Link, so that it is no longer AvoidsNodes routing but simple Orthogonal routing. This combined with <a>Link.adjusting</a> being End permits the middle points of the link route to be retained even after the user moves or resizes nodes. Furthermore there is a TwoWay <a>Binding</a> on <a>Link.routing</a>, so that the model remembers whether the link route had ever been reshaped manually. </p> <button id="SaveButton" onclick="save()">Save</button> <button onclick="load()">Load</button> <textarea id="mySavedModel" style="width: 100%; height: 300px"> { "class": "go.GraphLinksModel", "linkFromPortIdProperty": "fromPort", "linkToPortIdProperty": "toPort", "pointsDigits": 0, "modelData": {"position":"0 0"}, "nodeDataArray": [ {"text":"DB", "figure":"Database", "fill":"lightgray", "key":-3, "loc":"184 176"}, {"text":"DB", "figure":"Database", "fill":"lightgray", "key":-2, "loc":"248 248"}, {"text":"DB", "figure":"Database", "fill":"lightgray", "key":-4, "loc":"424 192"}, {"text":"DB", "figure":"Database", "fill":"lightgray", "key":-5, "loc":"320 152"}, {"text":"DB", "figure":"Database", "fill":"lightgray", "key":-6, "loc":"424 320"}, {"text":"DB", "figure":"Database", "fill":"lightgray", "key":-7, "loc":"352 256"}, {"text":"DB", "figure":"Database", "fill":"lightgray", "key":-8, "loc":"176 296"}, {"text":"DB", "figure":"Database", "fill":"lightgray", "key":-9, "loc":"288 344"}, {"text":"Step", "key":-10, "loc":"96 240"}, {"text":"Step", "key":-11, "loc":"536 280"} ], "linkDataArray": [ {"from":-10, "to":-11, "fromPort":"R", "toPort":"L", "points":[121,240,131,240,132,240,132,240,216,240,216,176,264,176,264,104,392,104,392,240,480,240,480,280,501,280,511,280]} ]} </textarea> </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>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>GoJS Extensions</h4> <p> <b>GoJS</b> can be extended in a variety of ways. The most common way to change the standard behavior is to set properties on the <a href="../api/symbols/GraphObject.html" target="api">GraphObject</a>, <a href="../api/symbols/Diagram.html" target="api">Diagram</a>, <a href="../api/symbols/CommandHandler.html" target="api">CommandHandler</a>, <a href="../api/symbols/Tool.html" target="api">Tool</a>, or <a href="../api/symbols/Layout.html" target="api">Layout</a>. But when the desired property does not exist, you might need to override methods of CommandHandler, Tool, Layout, Link, or Node. Methods that you can override are documented in the API reference. Various features of GoJS can be overriden, either by replacing a method on an instance (a feature of JavaScript) or by defining a subclass. You should not modify the prototypes of any of the <b>GoJS</b> classes. </p> <p> In addition to our samples, <b>GoJS</b> provides an <strong><a href="../samples/#extensions">extensions gallery</a></strong>, showcasing the creation of custom tools and layouts. Those classes and samples are written in TypeScript, available at <code>../extensionsJSM/</code>, as ECMAScript/JavaScript modules -- these use the <code>../release/go-module.js</code> library. We recommend that you copy the files that you need into your project, so that you can adjust how they refer to the GoJS library that you choose and so that you can include them into your own building and packaging procedures. </p> <p> More information can be found in the <a href="../intro/extensions.html">GoJS Intro</a>. </p> <p> <a href="../samples/index.html#extensions">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>