1 | <!DOCTYPE html>
|
2 | <html lang="en">
|
3 | <head>
|
4 | <meta charset="utf-8"/>
|
5 | <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, viewport-fit=cover"/>
|
6 | <meta name="description" content="TypeScript: When reshaping an orthogonal link, make sure the points are moved onto a grid."/>
|
7 | <link rel="stylesheet" href="../assets/css/style.css"/>
|
8 |
|
9 | <title>Snap Link Reshaping</title>
|
10 | </head>
|
11 |
|
12 | <body>
|
13 |
|
14 | <nav id="navTop" class="w-full z-30 top-0 text-white bg-nwoods-primary">
|
15 | <div class="w-full container max-w-screen-lg mx-auto flex flex-wrap sm:flex-nowrap items-center justify-between mt-0 py-2">
|
16 | <div class="md:pl-4">
|
17 | <a class="text-white hover:text-white no-underline hover:no-underline
|
18 | font-bold text-2xl lg:text-4xl rounded-lg hover:bg-nwoods-secondary " href="../">
|
19 | <h1 class="mb-0 p-1 ">GoJS</h1>
|
20 | </a>
|
21 | </div>
|
22 | <button id="topnavButton" class="rounded-lg sm:hidden focus:outline-none focus:ring" aria-label="Navigation">
|
23 | <svg fill="currentColor" viewBox="0 0 20 20" class="w-6 h-6">
|
24 | <path id="topnavOpen" fill-rule="evenodd" d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM9 15a1 1 0 011-1h6a1 1 0 110 2h-6a1 1 0 01-1-1z" clip-rule="evenodd"></path>
|
25 | <path id="topnavClosed" class="hidden" fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
|
26 | </svg>
|
27 | </button>
|
28 | <div id="topnavList" class="hidden sm:block items-center w-auto mt-0 text-white p-0 z-20">
|
29 | <ul class="list-reset list-none font-semibold flex justify-end flex-wrap sm:flex-nowrap items-center px-0 pb-0">
|
30 | <li class="p-1 sm:p-0"><a class="topnav-link" href="../learn/">Learn</a></li>
|
31 | <li class="p-1 sm:p-0"><a class="topnav-link" href="../samples/">Samples</a></li>
|
32 | <li class="p-1 sm:p-0"><a class="topnav-link" href="../intro/">Intro</a></li>
|
33 | <li class="p-1 sm:p-0"><a class="topnav-link" href="../api/">API</a></li>
|
34 | <li class="p-1 sm:p-0"><a class="topnav-link" href="https://www.nwoods.com/products/register.html">Register</a></li>
|
35 | <li class="p-1 sm:p-0"><a class="topnav-link" href="../download.html">Download</a></li>
|
36 | <li class="p-1 sm:p-0"><a class="topnav-link" href="https://forum.nwoods.com/c/gojs/11">Forum</a></li>
|
37 | <li class="p-1 sm:p-0"><a class="topnav-link" href="https://www.nwoods.com/contact.html"
|
38 | target="_blank" rel="noopener" onclick="getOutboundLink('https://www.nwoods.com/contact.html', 'contact');">Contact</a></li>
|
39 | <li class="p-1 sm:p-0"><a class="topnav-link" href="https://www.nwoods.com/sales/index.html"
|
40 | target="_blank" rel="noopener" onclick="getOutboundLink('https://www.nwoods.com/sales/index.html', 'buy');">Buy</a></li>
|
41 | </ul>
|
42 | </div>
|
43 | </div>
|
44 | <hr class="border-b border-gray-600 opacity-50 my-0 py-0" />
|
45 | </nav>
|
46 | <div class="md:flex flex-col md:flex-row md:min-h-screen w-full max-w-screen-xl mx-auto">
|
47 | <div id="navSide" class="flex flex-col w-full md:w-48 text-gray-700 bg-white flex-shrink-0"></div>
|
48 |
|
49 |
|
50 |
|
51 |
|
52 | <div class="p-4 w-full">
|
53 | <div id="sample">
|
54 | <div style="width: 100%; display: flex; justify-content: space-between">
|
55 | <div id="myPaletteDiv" style="width: 105px; height: 620px; margin-right: 2px; background-color: whitesmoke; border: solid 1px black"></div>
|
56 | <div id="myDiagramDiv" style="flex-grow: 1; height: 620px; border: solid 1px black"></div>
|
57 | </div>
|
58 | <label><input type="checkbox" id="AvoidsNodesCheckBox" checked="checked"/>avoidsNodes</label>
|
59 | <p>
|
60 | This sample is a simplified version of the <a href="../samples/draggableLink.html">Draggable Link</a> sample.
|
61 | Links are not draggable, there are no custom <a>Adornment</a>s, nodes are not rotatable, and links
|
62 | do not have text labels.
|
63 | </p>
|
64 | <p>
|
65 | Its purpose is to demonstrate the <a href="SnapLinkReshapingTool.ts">SnapLinkReshapingTool</a>,
|
66 | an extension of <a>LinkReshapingTool</a> that snaps each dragged reshape handle of selected Links to
|
67 | the nearest grid point. If the <a>SnapLinkReshapingTool.avoidsNodes</a> option is true,
|
68 | as it is by default, then the reshaping is limited to points where the adjacent segments would not
|
69 | be crossing over any avoidable nodes.
|
70 | </p>
|
71 | <p>
|
72 | Note how the "LinkReshaped" DiagramEvent listener changes the <a>Link.routing</a> of the reshaped Link,
|
73 | so that it is no longer AvoidsNodes routing but simple Orthogonal routing.
|
74 | This combined with <a>Link.adjusting</a> being End permits the middle points of the link route to be
|
75 | retained even after the user moves or resizes nodes.
|
76 | Furthermore there is a TwoWay <a>Binding</a> on <a>Link.routing</a>, so that the model remembers
|
77 | whether the link route had ever been reshaped manually.
|
78 | </p>
|
79 | <button id="SaveButton">Save</button>
|
80 | <button id="LoadButton">Load</button>
|
81 | <textarea id="mySavedModel" style="width:100%;height:300px">
|
82 | { "class": "go.GraphLinksModel",
|
83 | "linkFromPortIdProperty": "fromPort",
|
84 | "linkToPortIdProperty": "toPort",
|
85 | "modelData": {"position":"0 0"},
|
86 | "nodeDataArray": [
|
87 | {"text":"DB", "figure":"Database", "fill":"lightgray", "key":-3, "loc":"184 176"},
|
88 | {"text":"DB", "figure":"Database", "fill":"lightgray", "key":-2, "loc":"248 248"},
|
89 | {"text":"DB", "figure":"Database", "fill":"lightgray", "key":-4, "loc":"424 192"},
|
90 | {"text":"DB", "figure":"Database", "fill":"lightgray", "key":-5, "loc":"320 152"},
|
91 | {"text":"DB", "figure":"Database", "fill":"lightgray", "key":-6, "loc":"424 320"},
|
92 | {"text":"DB", "figure":"Database", "fill":"lightgray", "key":-7, "loc":"352 256"},
|
93 | {"text":"DB", "figure":"Database", "fill":"lightgray", "key":-8, "loc":"176 296"},
|
94 | {"text":"DB", "figure":"Database", "fill":"lightgray", "key":-9, "loc":"288 344"},
|
95 | {"text":"Step", "key":-10, "loc":"96 240"},
|
96 | {"text":"Step", "key":-11, "loc":"536 280"}
|
97 | ],
|
98 | "linkDataArray": [
|
99 | {"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], "routing":"Orthogonal"}
|
100 | ]}
|
101 | </textarea>
|
102 | </div>
|
103 |
|
104 | <script type="module" id="code">
|
105 | import * as go from "../release/go-module.js";
|
106 | import './Figures.js';
|
107 | import { SnapLinkReshapingTool } from './SnapLinkReshapingTool.js';
|
108 |
|
109 | if (window.goSamples) goSamples();
|
110 | const $ = go.GraphObject.make;
|
111 |
|
112 | const myDiagram =
|
113 | $(go.Diagram, 'myDiagramDiv',
|
114 | {
|
115 |
|
116 | grid: $(go.Panel, 'Grid', { gridCellSize: new go.Size(8, 8) }, $(go.Shape, 'LineH', { stroke: 'lightgray', strokeWidth: 0.5 }), $(go.Shape, 'LineV', { stroke: 'lightgray', strokeWidth: 0.5 })),
|
117 | 'draggingTool.isGridSnapEnabled': true,
|
118 | linkReshapingTool: $(SnapLinkReshapingTool),
|
119 |
|
120 |
|
121 |
|
122 | 'LinkReshaped': (e) => { e.subject.routing = go.Link.Orthogonal; },
|
123 | 'animationManager.isEnabled': false,
|
124 | 'undoManager.isEnabled': true
|
125 | });
|
126 |
|
127 | myDiagram.addDiagramListener('Modified', (e) => {
|
128 | const button = document.getElementById('SaveButton');
|
129 | if (button)
|
130 | button.disabled = !myDiagram.isModified;
|
131 | const idx = document.title.indexOf('*');
|
132 | if (myDiagram.isModified) {
|
133 | if (idx < 0)
|
134 | document.title += '*';
|
135 | }
|
136 | else {
|
137 | if (idx >= 0)
|
138 | document.title = document.title.substr(0, idx);
|
139 | }
|
140 | });
|
141 |
|
142 |
|
143 |
|
144 |
|
145 | function makePort(name, spot, output, input) {
|
146 |
|
147 | return $(go.Shape, 'Circle', {
|
148 | fill: null,
|
149 | stroke: null,
|
150 | desiredSize: new go.Size(7, 7),
|
151 | alignment: spot,
|
152 | alignmentFocus: spot,
|
153 | portId: name,
|
154 | fromSpot: spot, toSpot: spot,
|
155 | fromLinkable: output, toLinkable: input,
|
156 | cursor: 'pointer'
|
157 | });
|
158 | }
|
159 | myDiagram.nodeTemplate =
|
160 | $(go.Node, 'Spot',
|
161 | { locationSpot: go.Spot.Center },
|
162 | new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify),
|
163 | { selectable: true },
|
164 | { resizable: true, resizeObjectName: 'PANEL' },
|
165 |
|
166 | $(go.Panel, 'Auto',
|
167 | { name: 'PANEL' },
|
168 | new go.Binding('desiredSize', 'size', go.Size.parse).makeTwoWay(go.Size.stringify),
|
169 | $(go.Shape, 'Rectangle',
|
170 | {
|
171 | portId: '',
|
172 | fromLinkable: true, toLinkable: true, cursor: 'pointer',
|
173 | fill: 'white'
|
174 | },
|
175 | new go.Binding('figure'),
|
176 | new go.Binding('fill')),
|
177 | $(go.TextBlock,
|
178 | {
|
179 | font: 'bold 11pt Helvetica, Arial, sans-serif',
|
180 | margin: 8,
|
181 | maxSize: new go.Size(160, NaN),
|
182 | wrap: go.TextBlock.WrapFit,
|
183 | editable: true
|
184 | },
|
185 | new go.Binding('text').makeTwoWay())
|
186 | ),
|
187 |
|
188 | makePort('T', go.Spot.Top, false, true),
|
189 | makePort('L', go.Spot.Left, true, true),
|
190 | makePort('R', go.Spot.Right, true, true),
|
191 | makePort('B', go.Spot.Bottom, true, false),
|
192 | {
|
193 | mouseEnter: (e, node) => {
|
194 | if (node instanceof go.Node)
|
195 | showSmallPorts(node, true);
|
196 | },
|
197 | mouseLeave: (e, node) => {
|
198 | if (node instanceof go.Node)
|
199 | showSmallPorts(node, false);
|
200 | }
|
201 | });
|
202 |
|
203 | function showSmallPorts(node, show) {
|
204 | node.ports.each((port) => {
|
205 | if (port.portId !== '') {
|
206 | port.fill = show ? 'rgba(0,0,0,.3)' : null;
|
207 | }
|
208 | });
|
209 | }
|
210 |
|
211 | myDiagram.linkTemplate =
|
212 | $(go.Link,
|
213 | { relinkableFrom: true, relinkableTo: true, reshapable: true, resegmentable: true },
|
214 | {
|
215 | routing: go.Link.AvoidsNodes,
|
216 | adjusting: go.Link.End,
|
217 | curve: go.Link.JumpOver,
|
218 | corner: 5,
|
219 | toShortLength: 4
|
220 | },
|
221 | new go.Binding('points').makeTwoWay(),
|
222 |
|
223 | new go.Binding('routing', 'routing', go.Binding.parseEnum(go.Link, go.Link.AvoidsNodes))
|
224 | .makeTwoWay(go.Binding.toString),
|
225 | $(go.Shape,
|
226 | { isPanelMain: true, strokeWidth: 2 }),
|
227 | $(go.Shape,
|
228 | { toArrow: 'Standard', stroke: null }));
|
229 |
|
230 | load();
|
231 |
|
232 | const link = myDiagram.links.first();
|
233 | if (link) link.isSelected = true;
|
234 |
|
235 |
|
236 | const myPalette =
|
237 | $(go.Palette, 'myPaletteDiv',
|
238 | {
|
239 | maxSelectionCount: 1,
|
240 | nodeTemplateMap: myDiagram.nodeTemplateMap,
|
241 | model: new go.GraphLinksModel([
|
242 | { text: 'Start', figure: 'Circle', fill: 'green' },
|
243 | { text: 'Step' },
|
244 | { text: 'DB', figure: 'Database', fill: 'lightgray' },
|
245 | { text: '???', figure: 'Diamond', fill: 'lightskyblue' },
|
246 | { text: 'End', figure: 'Circle', fill: 'red' },
|
247 | { text: 'Comment', figure: 'RoundedRectangle', fill: 'lightyellow' }
|
248 | ])
|
249 | });
|
250 |
|
251 |
|
252 | function save() {
|
253 | saveDiagramProperties();
|
254 | document.getElementById('mySavedModel').value = myDiagram.model.toJson();
|
255 | myDiagram.isModified = false;
|
256 | }
|
257 |
|
258 | function load() {
|
259 | myDiagram.model = go.Model.fromJson(document.getElementById('mySavedModel').value);
|
260 | loadDiagramProperties();
|
261 | }
|
262 |
|
263 | function saveDiagramProperties() {
|
264 | myDiagram.model.modelData.position = go.Point.stringify(myDiagram.position);
|
265 | }
|
266 |
|
267 |
|
268 | function loadDiagramProperties() {
|
269 |
|
270 | const pos = myDiagram.model.modelData.position;
|
271 | if (pos) myDiagram.initialPosition = go.Point.parse(pos);
|
272 | }
|
273 |
|
274 | document.getElementById("SaveButton").onclick = save;
|
275 | document.getElementById("LoadButton").onclick = load;
|
276 | document.getElementById("AvoidsNodesCheckBox").onclick = function(e) {
|
277 | myDiagram.toolManager.linkReshapingTool.avoidsNodes = e.target.checked;
|
278 | }
|
279 |
|
280 | window.myDiagram = myDiagram;
|
281 | </script>
|
282 | </div>
|
283 |
|
284 |
|
285 | </div>
|
286 | </body>
|
287 |
|
288 | <script src="../assets/js/goSamples.js"></script>
|
289 | </html>
|