1+ /**
2+ * Sample app showcasing gojs-angular components
3+ * For use with gojs-angular version 2.x
4+ */
5+
16import { ChangeDetectorRef , Component , ViewChild , ViewEncapsulation } from '@angular/core' ;
27import * as go from 'gojs' ;
38import { DataSyncService , DiagramComponent , PaletteComponent } from 'gojs-angular' ;
4- import * as _ from 'lodash' ;
9+ import produce from "immer" ;
510
611@Component ( {
712 selector : 'app-root' ,
@@ -14,14 +19,57 @@ export class AppComponent {
1419 @ViewChild ( 'myDiagram' , { static : true } ) public myDiagramComponent : DiagramComponent ;
1520 @ViewChild ( 'myPalette' , { static : true } ) public myPaletteComponent : PaletteComponent ;
1621
22+ // Big object that holds app-level state data
23+ // As of gojs-angular 2.0, immutability is expected and required of state for ease of change detection.
24+ // Whenever updating state, immutability must be preserved. It is recommended to use immer for this, a small package that makes working with immutable data easy.
25+ public state = {
26+ // Diagram state props
27+ diagramNodeData : [
28+ { id : 'Alpha' , text : "Alpha" , color : 'lightblue' } ,
29+ { id : 'Beta' , text : "Beta" , color : 'orange' } ,
30+ { id : 'Gamma' , text : "Gamma" , color : 'lightgreen' } ,
31+ { id : 'Delta' , text : "Delta" , color : 'pink' }
32+ ] ,
33+ diagramLinkData : [
34+ { key : - 1 , from : 'Alpha' , to : 'Beta' , fromPort : 'r' , toPort : '1' } ,
35+ { key : - 2 , from : 'Alpha' , to : 'Gamma' , fromPort : 'b' , toPort : 't' } ,
36+ { key : - 3 , from : 'Beta' , to : 'Beta' } ,
37+ { key : - 4 , from : 'Gamma' , to : 'Delta' , fromPort : 'r' , toPort : 'l' } ,
38+ { key : - 5 , from : 'Delta' , to : 'Alpha' , fromPort : 't' , toPort : 'r' }
39+ ] ,
40+ diagramModelData : { prop : 'value' } ,
41+ skipsDiagramUpdate : false ,
42+ selectedNodeData : null , // used by InspectorComponent
43+
44+ // Palette state props
45+ paletteNodeData : [
46+ { key : 'PaletteNode1' , color : 'firebrick' } ,
47+ { key : 'PaletteNode2' , color : 'blueviolet' }
48+ ] ,
49+ paletteLinkData : [
50+ {
51+ 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 ) ] ) ,
52+ strokeWidth : 4 , dash : [ 6 , 3 ]
53+ } ,
54+ { points : new go . List ( /*go.Point*/ ) . addAll ( [ new go . Point ( 20 , 20 ) , new go . Point ( 60 , 20 ) , new go . Point ( 60 , 40 ) , new go . Point ( 60 , 50 ) ] ) }
55+ ] ,
56+ paletteModelData : { prop : 'val' } ,
57+ skipsPaletteUpdate : false
58+ } ;
59+
60+ public diagramDivClassName : string = 'myDiagramDiv' ;
61+ public paletteDivClassName = 'myPaletteDiv' ;
62+
1763 // initialize diagram / templates
1864 public initDiagram ( ) : go . Diagram {
1965
2066 const $ = go . GraphObject . make ;
2167 const dia = $ ( go . Diagram , {
2268 'undoManager.isEnabled' : true ,
69+ 'clickCreatingTool.archetypeNodeData' : { text : 'new node' , color : 'lightblue' } ,
2370 model : $ ( go . GraphLinksModel ,
2471 {
72+ nodeKeyProperty : 'id' ,
2573 linkToPortIdProperty : 'toPort' ,
2674 linkFromPortIdProperty : 'fromPort' ,
2775 linkKeyProperty : 'key' // IMPORTANT! must be defined for merges and data sync when using GraphLinksModel
@@ -58,10 +106,13 @@ export class AppComponent {
58106 } ,
59107 $ ( go . Panel , 'Auto' ,
60108 $ ( go . Shape , 'RoundedRectangle' , { stroke : null } ,
61- new go . Binding ( 'fill' , 'color' )
109+ new go . Binding ( 'fill' , 'color' , ( c , panel ) => {
110+
111+ return c ;
112+ } )
62113 ) ,
63- $ ( go . TextBlock , { margin : 8 } ,
64- new go . Binding ( 'text' ) )
114+ $ ( go . TextBlock , { margin : 8 , editable : true } ,
115+ new go . Binding ( 'text' ) . makeTwoWay ( ) )
65116 ) ,
66117 // Ports
67118 makePort ( 't' , go . Spot . TopCenter ) ,
@@ -73,37 +124,19 @@ export class AppComponent {
73124 return dia ;
74125 }
75126
76- public diagramNodeData : Array < go . ObjectData > = [
77- { key : 'Alpha' , text : "Node Alpha" , color : 'lightblue' } ,
78- { key : 'Beta' , text : "Node Beta" , color : 'orange' } ,
79- { key : 'Gamma' , text : "Node Gamma" , color : 'lightgreen' } ,
80- { key : 'Delta' , text : "Node Delta" , color : 'pink' }
81- ] ;
82- public diagramLinkData : Array < go . ObjectData > = [
83- { key : - 1 , from : 'Alpha' , to : 'Beta' , fromPort : 'r' , toPort : 'l' } ,
84- { key : - 2 , from : 'Alpha' , to : 'Gamma' , fromPort : 'b' , toPort : 't' } ,
85- { key : - 3 , from : 'Beta' , to : 'Beta' } ,
86- { key : - 4 , from : 'Gamma' , to : 'Delta' , fromPort : 'r' , toPort : 'l' } ,
87- { key : - 5 , from : 'Delta' , to : 'Alpha' , fromPort : 't' , toPort : 'r' }
88- ] ;
89- public diagramDivClassName : string = 'myDiagramDiv' ;
90- public diagramModelData = { prop : 'value' } ;
91- public skipsDiagramUpdate = false ;
92-
93- // When the diagram model changes, update app data to reflect those changes
127+ // When the diagram model changes, update app data to reflect those changes. Be sure to use immer's "produce" function to preserve immutability
94128 public diagramModelChange = function ( changes : go . IncrementalData ) {
95- // when setting state here, be sure to set skipsDiagramUpdate: true since GoJS already has this update
96- // (since this is a GoJS model changed listener event function)
97- // this way, we don't log an unneeded transaction in the Diagram's undoManager history
98- this . skipsDiagramUpdate = true ;
99-
100- this . diagramNodeData = DataSyncService . syncNodeData ( changes , this . diagramNodeData ) ;
101- this . diagramLinkData = DataSyncService . syncLinkData ( changes , this . diagramLinkData ) ;
102- this . diagramModelData = DataSyncService . syncModelData ( changes , this . diagramModelData ) ;
129+ const appComp = this ;
130+ this . state = produce ( this . state , draft => {
131+ // set skipsDiagramUpdate: true since GoJS already has this update
132+ // this way, we don't log an unneeded transaction in the Diagram's undoManager history
133+ draft . skipsDiagramUpdate = true ;
134+ draft . diagramNodeData = DataSyncService . syncNodeData ( changes , draft . diagramNodeData , appComp . observedDiagram . model ) ;
135+ draft . diagramLinkData = DataSyncService . syncLinkData ( changes , draft . diagramLinkData , appComp . observedDiagram . model ) ;
136+ draft . diagramModelData = DataSyncService . syncModelData ( changes , draft . diagramModelData ) ;
137+ } ) ;
103138 } ;
104139
105-
106-
107140 public initPalette ( ) : go . Palette {
108141 const $ = go . GraphObject . make ;
109142 const palette = $ ( go . Palette ) ;
@@ -118,35 +151,53 @@ export class AppComponent {
118151 new go . Binding ( 'fill' , 'color' )
119152 ) ,
120153 $ ( go . TextBlock , { margin : 8 } ,
121- new go . Binding ( 'text' ) )
154+ new go . Binding ( 'text' , 'key' ) )
122155 ) ;
123156
157+ palette . linkTemplate = $ ( go . Link ,
158+ { // because the GridLayout.alignment is Location and the nodes have locationSpot == Spot.Center,
159+ // to line up the Link in the same manner we have to pretend the Link has the same location spot
160+ locationSpot : go . Spot . Center ,
161+ selectionAdornmentTemplate :
162+ $ ( go . Adornment , 'Link' ,
163+ { locationSpot : go . Spot . Center } ,
164+ $ ( go . Shape ,
165+ { isPanelMain : true , fill : null , stroke : 'deepskyblue' , strokeWidth : 0 } , new go . Binding ( 'strokeDashArray' , 'dash' ) ) ,
166+ $ ( go . Shape , // the arrowhead
167+ { toArrow : 'Standard' , stroke : null } )
168+ )
169+ } ,
170+ {
171+ routing : go . Link . AvoidsNodes ,
172+ curve : go . Link . JumpOver ,
173+ corner : 5 ,
174+ toShortLength : 4
175+ } ,
176+ new go . Binding ( 'points' ) ,
177+ $ ( go . Shape , // the link path shape
178+ { isPanelMain : true } , new go . Binding ( 'strokeDashArray' , 'dash' ) ) ,
179+ $ ( go . Shape , // the arrowhead
180+ { toArrow : 'Standard' , stroke : null } )
181+ )
182+
124183 palette . model = $ ( go . GraphLinksModel ,
125184 {
126185 linkKeyProperty : 'key' // IMPORTANT! must be defined for merges and data sync when using GraphLinksModel
127186 } ) ;
128187
129188 return palette ;
130189 }
131- public paletteNodeData : Array < go . ObjectData > = [
132- { key : 'PaletteNode1' , text : "PaletteNode1" , color : 'red' } ,
133- { key : 'PaletteNode2' , text : "PaletteNode2" , color : 'yellow' }
134- ] ;
135- public paletteLinkData : Array < go . ObjectData > = [
136- { }
137- ] ;
138- public paletteModelData = { prop : 'val' } ;
139- public paletteDivClassName = 'myPaletteDiv' ;
140- public skipsPaletteUpdate = false ;
141- public paletteModelChange = function ( changes : go . IncrementalData ) {
142- // when setting state here, be sure to set skipsPaletteUpdate: true since GoJS already has this update
143- // (since this is a GoJS model changed listener event function)
144- // this way, we don't log an unneeded transaction in the Palette's undoManager history
145- this . skipsPaletteUpdate = true ;
146-
147- this . paletteNodeData = DataSyncService . syncNodeData ( changes , this . paletteNodeData ) ;
148- this . paletteLinkData = DataSyncService . syncLinkData ( changes , this . paletteLinkData ) ;
149- this . paletteModelData = DataSyncService . syncModelData ( changes , this . paletteModelData ) ;
190+
191+ // When the palette model changes, update app data to reflect those changes. Be sure to use immer's "produce" function to preserve immutability
192+ public paletteModelChange = function ( changes : go . IncrementalData ) {
193+ this . state = produce ( this . state , draft => {
194+ // set skipsPaletteUpdate: true since GoJS already has this update
195+ // this way, we don't log an unneeded transaction in the Palette's undoManager history
196+ draft . skipsPaletteUpdate = true ;
197+ draft . paletteNodeData = DataSyncService . syncNodeData ( changes , draft . paletteNodeData ) ;
198+ draft . paletteLinkData = DataSyncService . syncLinkData ( changes , draft . paletteLinkData ) ;
199+ draft . paletteModelData = DataSyncService . syncModelData ( changes , draft . paletteModelData ) ;
200+ } ) ;
150201 } ;
151202
152203 constructor ( private cdr : ChangeDetectorRef ) { }
@@ -161,10 +212,9 @@ export class AppComponent {
161212 public observedDiagram = null ;
162213
163214 // currently selected node; for inspector
164- public selectedNode : go . Node | null = null ;
215+ public selectedNodeData : go . ObjectData = null ;
165216
166217 public ngAfterViewInit ( ) {
167-
168218 if ( this . observedDiagram ) return ;
169219 this . observedDiagram = this . myDiagramComponent . diagram ;
170220 this . cdr . detectChanges ( ) ; // IMPORTANT: without this, Angular will throw ExpressionChangedAfterItHasBeenCheckedError (dev mode only)
@@ -173,40 +223,41 @@ export class AppComponent {
173223 // listener for inspector
174224 this . myDiagramComponent . diagram . addDiagramListener ( 'ChangedSelection' , function ( e ) {
175225 if ( e . diagram . selection . count === 0 ) {
176- appComp . selectedNode = null ;
226+ appComp . selectedNodeData = null ;
177227 }
178228 const node = e . diagram . selection . first ( ) ;
179- if ( node instanceof go . Node ) {
180- appComp . selectedNode = node ;
181- } else {
182- appComp . selectedNode = null ;
183- }
229+ appComp . state = produce ( appComp . state , draft => {
230+ if ( node instanceof go . Node ) {
231+ var idx = draft . diagramNodeData . findIndex ( nd => nd . id == node . data . id ) ;
232+ var nd = draft . diagramNodeData [ idx ] ;
233+ draft . selectedNodeData = nd ;
234+ } else {
235+ draft . selectedNodeData = null ;
236+ }
237+ } ) ;
184238 } ) ;
185-
186239 } // end ngAfterViewInit
187240
188241
189- public handleInspectorChange ( newNodeData ) {
190- const key = newNodeData . key ;
191- // find the entry in nodeDataArray with this key, replace it with newNodeData
192- let index = null ;
193- for ( let i = 0 ; i < this . diagramNodeData . length ; i ++ ) {
194- const entry = this . diagramNodeData [ i ] ;
195- if ( entry . key && entry . key === key ) {
196- index = i ;
197- }
198- }
242+ /**
243+ * Update a node's data based on some change to an inspector row's input
244+ * @param changedPropAndVal An object with 2 entries: "prop" (the node data prop changed), and "newVal" (the value the user entered in the inspector <input>)
245+ */
246+ public handleInspectorChange ( changedPropAndVal ) {
199247
200- if ( index >= 0 ) {
201- // here, we set skipsDiagramUpdate to false, since GoJS does not yet have this update
202- this . skipsDiagramUpdate = false ;
203- this . diagramNodeData [ index ] = _ . cloneDeep ( newNodeData ) ;
204- // this.diagramNodeData[index] = _.cloneDeep(newNodeData);
205- }
206-
207- // var nd = this.observedDiagram.model.findNodeDataForKey(newNodeData.key);
208- // console.log(nd);
248+ const path = changedPropAndVal . prop ;
249+ const value = changedPropAndVal . newVal ;
209250
251+ this . state = produce ( this . state , draft => {
252+ var data = draft . selectedNodeData ;
253+ data [ path ] = value ;
254+ const key = data . id ;
255+ const idx = draft . diagramNodeData . findIndex ( nd => nd . id == key ) ;
256+ if ( idx >= 0 ) {
257+ draft . diagramNodeData [ idx ] = data ;
258+ draft . skipsDiagramUpdate = false ; // we need to sync GoJS data with this new app state, so do not skips Diagram update
259+ }
260+ } ) ;
210261 }
211262
212263
0 commit comments