@@ -6,8 +6,21 @@ class Link {
66 this . links = [ ] ;
77 this . reltype = reltype ;
88
9- if ( this . trigger ) { this . trigger . links . push ( this ) ; }
10- this . arguments . forEach ( arg => arg . anchor . links . push ( this ) ) ;
9+ this . slot = 0 ;
10+
11+ if ( this . trigger ) {
12+ this . trigger . links . push ( this ) ;
13+ this . slot = this . trigger . slot ;
14+ }
15+ this . arguments . forEach ( arg => {
16+ arg . anchor . links . push ( this )
17+ if ( arg . anchor . slot > this . slot ) {
18+ this . slot = arg . anchor . slot ;
19+ }
20+ } ) ;
21+
22+ this . slot += 1 ;
23+ this . endpoints = this . getEndpoints ( ) ;
1124
1225 this . mainSVG = null ;
1326 this . svg = null ;
@@ -16,38 +29,50 @@ class Link {
1629 this . svgTexts = [ ] ;
1730 }
1831
19- init ( svg ) {
32+ init ( svg , words ) {
2033 this . mainSVG = svg ;
2134 this . svg = svg . group ( ) . addClass ( 'link' ) ;
2235
36+ this . recalculateSlots ( words ) ;
37+
2338 // init handles
2439 const s = 4 ;
40+
41+ // draw trigger
2542 if ( this . trigger ) {
2643 // draw a diamond at the location of the trigger
27- let x = this . trigger . cx ;
44+ let offset = this . trigger . links . indexOf ( this ) ;
45+ let x = this . trigger . cx + 8 * offset ;
2846 let y = this . trigger . absoluteY ;
47+
2948 let handle = this . svg . path ( `M${ s } ,0L0,${ s } L${ - s } ,0L0,${ - s } Z` )
3049 . x ( x - s )
31- . y ( y ) ;
32- this . handles . push ( { anchor : this . trigger , handle, x, y } ) ;
50+ . y ( y - s ) ;
51+ this . handles . push ( { anchor : this . trigger , handle, x, y, offset } ) ;
3352 }
53+
54+ // draw arguments
3455 this . arguments . forEach ( arg => {
3556 // draw a triangle at the location of the argument
36- let x = arg . anchor . cx ;
57+ let offset = arg . anchor . links . indexOf ( this ) ;
58+ let x = arg . anchor . cx + 8 * offset ;
3759 let y = arg . anchor . absoluteY ;
3860
3961 let handle = this . svg . path ( `M${ [ s , - s / 2 ] } L${ [ - s , - s / 2 ] } L0,${ s } ` )
4062 . x ( x - s )
41- . y ( y ) ;
42- this . handles . push ( { anchor : arg . anchor , handle, x, y } ) ;
63+ . y ( y - s ) ;
64+ this . handles . push ( { anchor : arg . anchor , handle, x, y, offset } ) ;
4365
66+ // draw svgText for each trigger-argument relation
4467 if ( this . trigger ) {
4568 let text = this . svg . text ( arg . type )
4669 . y ( - 7 )
4770 . addClass ( 'link-text' ) ;
4871 this . svgTexts . push ( text ) ;
4972 }
5073 } ) ;
74+
75+ // draw svgText for a non-trigger relation
5176 if ( this . reltype ) {
5277 let text = this . svg . text ( this . reltype )
5378 . y ( - 7 )
@@ -58,81 +83,159 @@ class Link {
5883 this . line = this . svg . path ( )
5984 . addClass ( 'polyline' ) ;
6085 this . draw ( ) ;
61-
62- //TODO: sort links and offset according to # of links the anchor has
6386 }
6487
6588 draw ( anchor ) {
6689 const s = 4 ;
90+ // redraw handles if word or link was moved
6791 this . handles . forEach ( h => {
6892 if ( anchor === h . anchor ) {
69- h . x = anchor . cx ;
93+ h . x = anchor . cx + 8 * h . offset ;
7094 h . y = anchor . absoluteY ;
7195 h . handle
7296 . x ( h . x - s )
73- . y ( h . y ) ;
97+ . y ( h . y - s ) ;
7498 }
75- } )
99+ } ) ;
100+
101+ // redraw line if it exists
76102 if ( this . line ) {
103+ let width = this . mainSVG . width ( ) ;
77104 let d = '' ;
105+
106+ // draw a polyline between the trigger and each of its arguments
78107 if ( this . trigger ) {
79- // draw a polyline between the trigger and each of its arguments
108+ let y = this . getY ( this . handles [ 1 ] ) ;
109+ let rowCrossed = false ;
110+
80111 for ( let i = 0 , il = this . arguments . length ; i < il ; ++ i ) {
81112 let leftOfTrigger = this . arguments [ i ] . anchor . idx < this . trigger . idx ;
82113 let dx = leftOfTrigger ? 5 : - 5 ;
83114 let textlen = leftOfTrigger ? this . svgTexts [ i ] . length ( ) : - this . svgTexts [ i ] . length ( ) ;
84115
116+ let handle1 = this . handles [ i + 1 ] ;
117+
85118 // draw a line from the prev arrow segment
86119 if ( i > 0 ) {
120+ // check if crossing over a row
121+ if ( rowCrossed ) {
122+ rowCrossed = false ;
123+ d += 'L' + [ width , y ] + 'M0,' ;
124+ y = this . getY ( handle1 ) ;
125+ d += y ;
126+ }
87127 if ( leftOfTrigger ) {
88- d += 'L' + [ this . handles [ i + 1 ] . x + dx , this . handles [ i + 1 ] . y - 10 ]
128+ d += 'L' + [ handle1 . x + dx , y ] ;
89129 }
90130 else {
91- d += 'L' + [ this . handles [ i + 1 ] . x + dx + textlen , this . handles [ i + 1 ] . y - 10 ]
131+ d += 'L' + [ handle1 . x + dx + textlen , y ] ;
92132 }
93133 }
94134 else if ( ! leftOfTrigger ) {
95135 // start drawing from the trigger
136+ y = this . getY ( this . handles [ 0 ] ) ;
96137 d += 'M' + [ this . handles [ 0 ] . x , this . handles [ 0 ] . y ]
97- + 'c' + [ 0 , - 10 , 0 , - 10 , - dx , - 10 ]
98- + 'L' + [ this . handles [ 1 ] . x + dx + textlen , this . handles [ 1 ] . y - 10 ] ;
138+ + 'C' + [ this . handles [ 0 ] . x , y , this . handles [ 0 ] . x , y , this . handles [ 0 ] . x - dx , y ] ;
139+
140+ // check if crossing over a row
141+ if ( this . handles [ 0 ] . anchor . row . idx < this . handles [ 1 ] . anchor . row . idx ) {
142+ d += 'L' + [ width , y ] + 'M0,' ;
143+ y = this . getY ( this . handles [ 1 ] ) ;
144+ d += y ;
145+ }
146+ d += 'L' + [ this . handles [ 1 ] . x + dx + textlen , y ] ;
99147 }
100148
149+ // draw the text svg
150+ this . svgTexts [ i ]
151+ . x ( handle1 . x + dx + textlen / 2 )
152+ . y ( y - 10 ) ;
153+
154+ let handlePrecedesTrigger = leftOfTrigger && ( i + 2 > il || this . arguments [ i + 1 ] . anchor . idx >= this . trigger . idx ) ;
155+
156+ // check if crossing over a row
157+ rowCrossed = ( handlePrecedesTrigger && this . handles [ 0 ] . anchor . row . idx != handle1 . anchor . row . idx ) || ( ! handlePrecedesTrigger && i + 1 < il && this . handles [ i + 2 ] . anchor . row . idx != handle1 . anchor . row . idx ) ;
158+
101159 // draw an arrow segment coming from each argument
102- d += 'M' + [ this . handles [ i + 1 ] . x , this . handles [ i + 1 ] . y ]
103- + 'c' + [ 0 , - 10 , 0 , - 10 , dx , - 10 ] ;
104- if ( leftOfTrigger ) {
105- d += 'm' + [ textlen , 0 ] ;
160+ if ( handlePrecedesTrigger && rowCrossed ) {
161+ // if row is crossed
162+ let tempY = this . getY ( handle1 ) ;
163+ y = this . getY ( this . handles [ 0 ] ) ;
164+
165+ d += 'M' + [ handle1 . x , handle1 . y ]
166+ + 'C' + [ handle1 . x , tempY , handle1 . x , tempY , handle1 . x + dx , tempY ]
167+ + 'm' + [ textlen , 0 ]
168+ + 'L' + [ width , tempY ]
169+ + 'M' + [ 0 , y ] ;
170+ rowCrossed = false ;
171+
172+ this . svgTexts [ i ] . y ( tempY - 10 ) ;
173+ }
174+ else {
175+ d += 'M' + [ handle1 . x , handle1 . y ]
176+ + 'C' + [ handle1 . x , y , handle1 . x , y , handle1 . x + dx , y ] ;
177+ if ( leftOfTrigger ) {
178+ d += 'm' + [ textlen , 0 ] ;
179+ }
106180 }
107181
108- if ( leftOfTrigger && ( i + 2 > il || this . arguments [ i + 1 ] . anchor . idx >= this . trigger . idx ) ) {
182+ if ( handlePrecedesTrigger ) {
109183 // draw trigger to the right of the arrow segment
110- d += 'L' + [ this . handles [ 0 ] . x - dx , this . handles [ 0 ] . y - 10 ]
111- + 'c' + [ dx , 0 , dx , 0 , dx , 10 ] ;
184+ d += 'L' + [ this . handles [ 0 ] . x - dx , y ]
185+ + 'c' + [ dx , 0 , dx , 0 , dx , this . handles [ 0 ] . y - y ] ;
112186 if ( i + 1 < il ) {
113- d += 'c' + [ 0 , - 10 , 0 , - 10 , dx , - 10 ] ;
187+ rowCrossed = this . handles [ i + 2 ] . anchor . row . idx != this . handles [ 0 ] . anchor . row . idx ;
188+ d += 'C' + [ this . handles [ 0 ] . x , y , this . handles [ 0 ] . x , y , this . handles [ 0 ] . x + dx , y ] ;
114189 }
115190 }
116-
117- // draw the text svg
118- this . svgTexts [ i ]
119- . x ( this . handles [ i + 1 ] . x + dx + textlen / 2 )
120- . y ( this . handles [ i + 1 ] . y - 10 - 7 ) ;
121191 }
122192 }
123193 else if ( this . reltype ) {
124- let avg = this . arguments . reduce ( ( acc , a ) => acc + a . anchor . cx , 0 ) / this . arguments . length ;
194+
195+ // draw lines between a non-trigger relationship
196+ let y = this . getY ( this . handles [ 0 ] ) ;
197+ let endHandle = this . handles [ this . handles . length - 1 ] ;
198+ let textlen = this . svgTexts [ 0 ] . length ( ) ;
199+ let avg ;
200+
201+ if ( this . handles [ 0 ] . anchor . row . idx === endHandle . anchor . row . idx ) {
202+ avg = this . arguments . reduce ( ( acc , a ) => acc + a . anchor . cx , 0 ) / this . arguments . length ;
203+ d = 'M' + [ this . handles [ 0 ] . x , this . handles [ 0 ] . y ]
204+ + 'C' + [ this . handles [ 0 ] . x , y , this . handles [ 0 ] . x , y , this . handles [ 0 ] . x + 5 , y ]
205+ + 'L' + [ avg - textlen / 2 , y ]
206+ + 'm' + [ textlen , 0 ]
207+ + 'L' + [ endHandle . x - 5 , y ]
208+ + 'C' + [ endHandle . x , y , endHandle . x , y , endHandle . x , endHandle . y ] ;
209+ }
210+ else {
211+ avg = ( this . handles [ 0 ] . x + width ) / 2 ;
212+ d = 'M' + [ this . handles [ 0 ] . x , this . handles [ 0 ] . y ]
213+ + 'C' + [ this . handles [ 0 ] . x , y , this . handles [ 0 ] . x , y , this . handles [ 0 ] . x + 5 , y ]
214+ + 'L' + [ avg - textlen / 2 , y ]
215+ + 'm' + [ textlen , 0 ]
216+ + 'L' + [ width , y ] ;
217+
218+ let tempY = this . getY ( endHandle ) ;
219+ d += 'M0,' + tempY
220+ + 'L' + [ endHandle . x - 5 , tempY ]
221+ + 'C' + [ endHandle . x , tempY , endHandle . x , tempY , endHandle . x , endHandle . y ] ;
222+ }
125223 this . svgTexts [ 0 ] . x ( avg )
126- . y ( this . arguments [ 0 ] . anchor . absoluteY - 10 - 7 ) ;
127- // d = 'M' + [this.arguments[0].anchor.cx, this.arguments[0].anchor.absoluteY]
128- // + 'L' + [this.arguments[1].anchor.cx, this.arguments[1].anchor.absoluteY];
224+ . y ( y - 10 ) ;
225+
129226 }
130227 this . line . plot ( d ) ;
131228 }
132229
133230 this . links . forEach ( l => l . draw ( this ) ) ;
134231 }
135232
233+ // helper function to calculate line-height in draw()
234+ getY ( handle ) {
235+ let r = handle . anchor . row ;
236+ return r . rh + r . ry - 45 - 15 * this . slot ;
237+ }
238+
136239 remove ( ) {
137240 this . svg . remove ( ) ;
138241
@@ -150,38 +253,91 @@ class Link {
150253 this . arguments . forEach ( arg => detachLink ( arg . anchor ) ) ;
151254 }
152255
153- hasAnchor ( a ) {
154- if ( this . trigger && a === this . trigger ) { return true ; }
155- return this . arguments . find ( arg => arg . anchor === a ) ;
156- }
256+ recalculateSlots ( words ) {
257+ // reorganize slots
258+ let self = this ;
259+ let ep = this . endpoints ;
260+ let wordArray = words . slice ( this . endpoints [ 0 ] . idx , this . endpoints [ 1 ] . idx + 1 ) ;
157261
158- get idx ( ) {
159- if ( this . trigger ) {
160- return this . trigger . idx ;
262+ // recursively increase the slots of l
263+ function incrementSlot ( l ) {
264+ l . slot += 1 ;
265+ l . links . forEach ( incrementSlot ) ;
161266 }
162- return this . arguments . reduce ( ( acc , arg ) => acc + arg . anchor . idx , 0 ) / this . arguments . length ;
267+
268+ // recursively check for collisions
269+ function checkCollision ( l ) {
270+ if ( l !== self && l . slot === self . slot ) {
271+ if ( l . endpoints [ 0 ] <= ep [ 0 ] && l . endpoints [ 1 ] >= ep [ 0 ] ) {
272+ incrementSlot ( l ) ;
273+ l . recalculateSlots ( words ) ;
274+ }
275+ else if ( l . endpoints [ 0 ] >= ep [ 0 ] && l . endpoints [ 0 ] <= ep [ 1 ] ) {
276+ // TODO: increase the slots of self
277+ incrementSlot ( self ) ;
278+ }
279+ l . links . forEach ( checkCollision ) ;
280+ }
281+ }
282+
283+ wordArray . forEach ( w => {
284+ // get relevant links
285+ w . links . forEach ( checkCollision ) ;
286+ } ) ;
287+
163288 }
164289
165- get cx ( ) {
290+ getEndpoints ( ) {
291+ let minWord = null ;
292+ let maxWord = null ;
293+
166294 if ( this . trigger ) {
167- // if (this.trigger.links.length > 1) {
168- // let offset = this.trigger.links.indexOf(this) / (this.trigger.links.length - 1) - 0.5;
169- // return this.trigger.cx + offset * 200;
170- // }
171- return this . trigger . cx ;
295+ minWord = maxWord = this . trigger ;
172296 }
173- if ( this . arguments . length > 0 ) {
174- return this . arguments . reduce ( ( acc , arg ) => acc + arg . anchor . cx , 0 ) / this . arguments . length ;
297+
298+ this . arguments . forEach ( arg => {
299+ if ( arg . anchor instanceof Link ) {
300+ let endpts = arg . anchor . getEndpoints ( ) ;
301+ if ( ! minWord || minWord . idx > endpts [ 0 ] . idx ) {
302+ minWord = endpts [ 0 ] ;
303+ }
304+ if ( ! maxWord || maxWord . idx < endpts [ 1 ] . idx ) {
305+ maxWord = endpts [ 1 ] ;
306+ }
307+ }
308+ else { // word or wordcluster
309+ if ( ! minWord || minWord . idx > arg . anchor . idx ) {
310+ minWord = arg . anchor ;
311+ }
312+ if ( ! maxWord || maxWord . idx < arg . anchor . idx ) {
313+ maxWord = arg . anchor ;
314+ }
315+ }
316+ } ) ;
317+ return [ minWord , maxWord ] ;
318+ }
319+
320+ get rootWord ( ) {
321+ if ( this . trigger ) { return this . trigger ; }
322+ if ( this . arguments [ 0 ] . anchor instanceof Word ) {
323+ return this . arguments [ 0 ] . anchor ;
175324 }
176- return 0 ;
325+ return this . arguments [ 0 ] . anchor . rootWord ;
177326 }
178- get absoluteY ( ) {
179- if ( this . trigger ) { return this . trigger . absoluteY - 15 ; }
180- if ( this . arguments [ 0 ] ) { return this . arguments [ 0 ] . absoluteY - 15 ; }
181- return 0 ;
327+
328+ get idx ( ) {
329+ return this . rootWord . idx ;
330+ }
331+
332+ get row ( ) {
333+ return this . rootWord . row ;
182334 }
183335
184- get val ( ) {
185- return this . trigger && this . trigger . val ;
336+ get cx ( ) {
337+ return this . rootWord . cx ;
338+ }
339+
340+ get absoluteY ( ) {
341+ return this . rootWord . row . rh + this . rootWord . row . ry - 45 - 15 * this . slot ;
186342 }
187343}
0 commit comments