forked from NorthwoodsSoftware/GoJS
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathreact.html
More file actions
447 lines (414 loc) · 17 KB
/
Copy pathreact.html
File metadata and controls
447 lines (414 loc) · 17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GoJS and React -- Northwoods Software</title>
<!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
<script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<h1>Using GoJS with React</h1>
<p class="box" style="background-color: lightgoldenrodyellow;">
Examples of most of the topics discussed on this page can be found in the <a href="https://github.com/NorthwoodsSoftware/gojs-react-basic" target="_blank">gojs-react-basic</a> project,
which serves as a simple starter project.
</p>
<p>
If you are new to GoJS, it may be helpful to first visit the <a href="../learn/index.html" target="_blank">Getting Started Tutorial</a>.
</p>
<p>
The easiest way to get a component set up for a GoJS Diagram is to use the <a href="https://github.com/NorthwoodsSoftware/gojs-react" target="_blank">gojs-react</a> package,
which exports React Components for GoJS Diagrams, Palettes, and Overviews.
The <a href="https://github.com/NorthwoodsSoftware/gojs-react-basic" target="_blank">gojs-react-basic</a> project demonstrates how to use these components.
More information about the package, including the various props it takes, can be found on the <a href="https://github.com/NorthwoodsSoftware/gojs-react" target="_blank">Github</a> or
<a href="https://npmjs.com/gojs-react" target="_blank">NPM</a> pages. Our examples will be using a <a>GraphLinksModel</a>, but any model can be used.
</p>
<h2 id="quickstart">Quick start with an existing React application</h2>
<h4>Installation</h4>
<p>
Start by installing GoJS and gojs-react: <code>npm install gojs gojs-react</code>.
</p>
<h4>Diagram styling</h4>
<p>
Next, set up a CSS class for the GoJS diagram's div:
</p>
<pre class="lang-css">
/* App.css */
.diagram-component {
width: 400px;
height: 400px;
border: solid 1px black;
background-color: white;
}
</pre>
<h4>Rendering the component</h4>
<p>
Finally, add an initDiagram function and a model change handler function, and add the ReactDiagram component inside your render method.
Note that the UndoManager should always be enabled to allow for transactions to take place,
but the <a>UndoManager.maxHistoryLength</a> can be set to 0 to prevent undo and redo.
</p>
<pre class="lang-js">
// App.js
import React from 'react';
import * as go from 'gojs';
import { ReactDiagram } from 'gojs-react';
import './App.css'; // contains .diagram-component CSS
// ...
/**
* Diagram initialization method, which is passed to the ReactDiagram component.
* This method is responsible for making the diagram and initializing the model and any templates.
* The model's data should not be set here, as the ReactDiagram component handles that via the other props.
*/
function initDiagram() {
const $ = go.GraphObject.make;
const diagram =
$(go.Diagram,
{
'undoManager.isEnabled': true, // must be set to allow for model change listening
// 'undoManager.maxHistoryLength': 0, // uncomment disable undo/redo functionality
'clickCreatingTool.archetypeNodeData': { text: 'new node', color: 'lightblue' },
model: $(go.GraphLinksModel,
{
linkKeyProperty: 'key' // IMPORTANT! must be defined for merges and data sync when using GraphLinksModel
})
});
// define a simple Node template
diagram.nodeTemplate =
$(go.Node, 'Auto', // the Shape will go around the TextBlock
new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify),
$(go.Shape, 'RoundedRectangle',
{ name: 'SHAPE', fill: 'white', strokeWidth: 0 },
// Shape.fill is bound to Node.data.color
new go.Binding('fill', 'color')),
$(go.TextBlock,
{ margin: 8, editable: true }, // some room around the text
new go.Binding('text').makeTwoWay()
)
);
return diagram;
}
/**
* This function handles any changes to the GoJS model.
* It is here that you would make any updates to your React state, which is dicussed below.
*/
function handleModelChange(changes) {
alert('GoJS model changed!');
}
// render function...
function App() {
return (
<div>
...
<ReactDiagram
initDiagram={initDiagram}
divClassName='diagram-component'
nodeDataArray={[
{ key: 0, text: 'Alpha', color: 'lightblue', loc: '0 0' },
{ key: 1, text: 'Beta', color: 'orange', loc: '150 0' },
{ key: 2, text: 'Gamma', color: 'lightgreen', loc: '0 150' },
{ key: 3, text: 'Delta', color: 'pink', loc: '150 150' }
]}
linkDataArray={[
{ key: -1, from: 0, to: 1 },
{ key: -2, from: 0, to: 2 },
{ key: -3, from: 1, to: 1 },
{ key: -4, from: 2, to: 3 },
{ key: -5, from: 3, to: 0 }
]}
onModelChange={handleModelChange}
/>
...
</div>
);
}
</pre>
<p>
That's it! You should now have a GoJS diagram rendering within your React application.
Try editing the text of a node or deleting a node, and you'll see an alert on the page.
</p>
<h2 id="stateful">Usage in a stateful React app</h2>
<p>
Typically the data being passed to the ReactDiagram component will be used elsewhere in your app and will exist in React state.
For example, you may have some kind of inspector that can be used to modify node properties, and therefore the state should be lifted up and held by a parent component
of both the diagram and the inspector.
</p>
<p>
A basic setup can be seen in the <a href="https://github.com/NorthwoodsSoftware/gojs-react-basic" target="_blank">gojs-react-basic</a> project,
but we'll describe some of the methodology here.
</p>
<h4>Creating a wrapper component</h4>
<p>
When handling state, it is often useful to write a wrapper component around the gojs-react components to pass the necessary props along and keep GoJS initialization out of the main app.
There are a few things that should be set up in the wrapper component:
<ul>
<li>a set of props coming in from the parent component which holds state and handlers</li>
<li>a ref to the ReactDiagram component so getDiagram() can be used</li>
<li>componentDidMount and componentWillUnmount methods to add/remove app-specific diagram listeners</li>
<li>an initDiagram method to be passed to the ReactDiagram component</li>
</ul>
</p>
<p>
Below, we'll pass linkDataArray and modelData as props to the ReactDiagram, but note that they are not always needed in gojs-react components,
so your app may not need to include htem.
</p>
<pre class="lang-js">
import * as go from 'gojs';
import { ReactDiagram } from 'gojs-react';
import * as React from 'react';
// props passed in from a parent component holding state, some of which will be passed to ReactDiagram
interface WrapperProps {
nodeDataArray: Array<go.ObjectData>;
linkDataArray: Array<go.ObjectData>;
modelData: go.ObjectData;
skipsDiagramUpdate: boolean;
onDiagramEvent: (e: go.DiagramEvent) => void;
onModelChange: (e: go.IncrementalData) => void;
}
export class DiagramWrapper extends React.Component<WrapperProps, {}> {
/**
* Ref to keep a reference to the component, which provides access to the GoJS diagram via getDiagram().
*/
private diagramRef: React.RefObject<ReactDiagram>;
constructor(props: WrapperProps) {
super(props);
this.diagramRef = React.createRef();
}
/**
* Get the diagram reference and add any desired diagram listeners.
* Typically the same function will be used for each listener,
* with the function using a switch statement to handle the events.
* This is only necessary when you want to define additional app-specific diagram listeners.
*/
public componentDidMount() {
if (!this.diagramRef.current) return;
const diagram = this.diagramRef.current.getDiagram();
if (diagram instanceof go.Diagram) {
diagram.addDiagramListener('ChangedSelection', this.props.onDiagramEvent);
}
}
/**
* Get the diagram reference and remove listeners that were added during mounting.
* This is only necessary when you have defined additional app-specific diagram listeners.
*/
public componentWillUnmount() {
if (!this.diagramRef.current) return;
const diagram = this.diagramRef.current.getDiagram();
if (diagram instanceof go.Diagram) {
diagram.removeDiagramListener('ChangedSelection', this.props.onDiagramEvent);
}
}
/**
* Diagram initialization method, which is passed to the ReactDiagram component.
* This method is responsible for making the diagram and initializing the model, any templates,
* and maybe doing other initialization tasks like customizing tools.
* The model's data should not be set here, as the ReactDiagram component handles that via the other props.
*/
private initDiagram(): go.Diagram {
const $ = go.GraphObject.make;
const diagram =
$(go.Diagram,
{
'undoManager.isEnabled': true, // must be set to allow for model change listening
// 'undoManager.maxHistoryLength': 0, // uncomment disable undo/redo functionality
'clickCreatingTool.archetypeNodeData': { text: 'new node', color: 'lightblue' },
model: $(go.GraphLinksModel,
{
linkKeyProperty: 'key', // IMPORTANT! must be defined for merges and data sync when using GraphLinksModel
// positive keys for nodes
makeUniqueKeyFunction: (m: go.Model, data: any) => {
let k = data.key || 1;
while (m.findNodeDataForKey(k)) k++;
data.key = k;
return k;
},
// negative keys for links
makeUniqueLinkKeyFunction: (m: go.GraphLinksModel, data: any) => {
let k = data.key || -1;
while (m.findLinkDataForKey(k)) k--;
data.key = k;
return k;
}
})
});
// define a simple Node template
diagram.nodeTemplate =
$(go.Node, 'Auto', // the Shape will go around the TextBlock
new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify),
$(go.Shape, 'RoundedRectangle',
{
name: 'SHAPE', fill: 'white', strokeWidth: 0,
// set the port properties:
portId: '', fromLinkable: true, toLinkable: true, cursor: 'pointer'
},
// Shape.fill is bound to Node.data.color
new go.Binding('fill', 'color')),
$(go.TextBlock,
{ margin: 8, editable: true, font: '400 .875rem Roboto, sans-serif' }, // some room around the text
new go.Binding('text').makeTwoWay()
)
);
// relinking depends on modelData
diagram.linkTemplate =
$(go.Link,
new go.Binding('relinkableFrom', 'canRelink').ofModel(),
new go.Binding('relinkableTo', 'canRelink').ofModel(),
$(go.Shape),
$(go.Shape, { toArrow: 'Standard' })
);
return diagram;
}
public render() {
return (
<ReactDiagram
ref={this.diagramRef}
divClassName='diagram-component'
initDiagram={this.initDiagram}
nodeDataArray={this.props.nodeDataArray}
linkDataArray={this.props.linkDataArray}
modelData={this.props.modelData}
onModelChange={this.props.onModelChange}
skipsDiagramUpdate={this.props.skipsDiagramUpdate}
/>
);
}
}
</pre>
<h4>Using the wrapper component within the app</h4>
<p>
The application should set up a few things to be passed to the wrapper described above:
<ul>
<li>state containing a nodeDataArray, linkDataArray, modelData object, and skipsDiagramUpdate flag</li>
<li>a handleDiagramEvent method for any app-specific DiagramEvents, such as 'ChangedSelection'</li>
<li>a handleModelChange method for updating state based on updates from the GoJS model</li>
</ul>
</p>
<pre class="lang-js">
import * as go from 'gojs';
import * as React from 'react';
import { DiagramWrapper } from './components/Diagram';
interface AppState {
// ...
nodeDataArray: Array<go.ObjectData>;
linkDataArray: Array<go.ObjectData>;
modelData: go.ObjectData;
selectedKey: number | null;
skipsDiagramUpdate: boolean;
}
class App extends React.Component<{}, AppState> {
constructor(props: object) {
super(props);
this.state = {
// ...
nodeDataArray: [
{ key: 0, text: 'Alpha', color: 'lightblue', loc: '0 0' },
{ key: 1, text: 'Beta', color: 'orange', loc: '150 0' },
{ key: 2, text: 'Gamma', color: 'lightgreen', loc: '0 150' },
{ key: 3, text: 'Delta', color: 'pink', loc: '150 150' }
],
linkDataArray: [
{ key: -1, from: 0, to: 1 },
{ key: -2, from: 0, to: 2 },
{ key: -3, from: 1, to: 1 },
{ key: -4, from: 2, to: 3 },
{ key: -5, from: 3, to: 0 }
],
modelData: {
canRelink: true
},
selectedKey: null,
skipsDiagramUpdate: false
};
// bind handler methods
this.handleDiagramEvent = this.handleDiagramEvent.bind(this);
this.handleModelChange = this.handleModelChange.bind(this);
this.handleRelinkChange = this.handleRelinkChange.bind(this);
}
/**
* Handle any app-specific DiagramEvents, in this case just selection changes.
* On ChangedSelection, find the corresponding data and set the selectedKey state.
*
* This is not required, and is only needed when handling DiagramEvents from the GoJS diagram.
* @param e a GoJS DiagramEvent
*/
public handleDiagramEvent(e: go.DiagramEvent) {
const name = e.name;
switch (name) {
case 'ChangedSelection': {
const sel = e.subject.first();
if (sel) {
this.setState({ selectedKey: sel.key });
} else {
this.setState({ selectedKey: null });
}
break;
}
default: break;
}
}
/**
* Handle GoJS model changes, which output an object of data changes via Model.toIncrementalData.
* This method should iterates over those changes and update state to keep in sync with the GoJS model.
* This can be done via setState in React or another preferred state management method.
* @param obj a JSON-formatted string
*/
public handleModelChange(obj: go.IncrementalData) {
const insertedNodeKeys = obj.insertedNodeKeys;
const modifiedNodeData = obj.modifiedNodeData;
const removedNodeKeys = obj.removedNodeKeys;
const insertedLinkKeys = obj.insertedLinkKeys;
const modifiedLinkData = obj.modifiedLinkData;
const removedLinkKeys = obj.removedLinkKeys;
const modifiedModelData = obj.modelData;
console.log(obj);
// see gojs-react-basic for an example model change handler
// when setting state, be sure to set skipsDiagramUpdate: true since GoJS already has this update
}
/**
* Handle changes to the checkbox on whether to allow relinking.
* @param e a change event from the checkbox
*/
public handleRelinkChange(e: any) {
const target = e.target;
const value = target.checked;
this.setState({ modelData: { canRelink: value }, skipsDiagramUpdate: false });
}
public render() {
let selKey;
if (this.state.selectedKey !== null) {
selKey = <p>Selected key: {this.state.selectedKey}</p>;
}
return (
<div>
<DiagramWrapper
nodeDataArray={this.state.nodeDataArray}
linkDataArray={this.state.linkDataArray}
modelData={this.state.modelData}
skipsDiagramUpdate={this.state.skipsDiagramUpdate}
onDiagramEvent={this.handleDiagramEvent}
onModelChange={this.handleModelChange}
/>
<label>
Allow Relinking?
<input
type='checkbox'
id='relink'
checked={this.state.modelData.canRelink}
onChange={this.handleRelinkChange} />
</label>
{selKey}
</div>
);
}
}
</pre>
<p>
These are the basics for setting up GoJS within a React application. See <a href="https://github.com/NorthwoodsSoftware/gojs-react-basic" target="_blank">gojs-react-basic</a>
for a working example and the <a href="https://github.com/NorthwoodsSoftware/gojs-react" target="_blank">gojs-react</a> Github page for further explanation of various props
passed to the components.
</p>
</div>
</div>
</body>
</html>