@@ -56,30 +56,18 @@ const TreeLayout = (function() {
5656 return data ;
5757 } ;
5858
59- // tree layout function
60- const tree = d3 . tree ( )
61- . nodeSize ( [ 30 , 80 ] )
62- . separation ( ( a , b ) => {
63- let separation = a . parent == b . parent ? 1 : 2 ;
64- function reduce ( acc , val ) {
65- if ( val . expanded ) { return acc + val . size ; }
66- return acc + 1 ;
67- }
68- separation += Math . max ( b . data . incoming . reduce ( reduce , 0 ) , a . data . incoming . reduce ( reduce , 0 ) ) / 2 ;
69- return separation ;
70- } ) ;
71-
7259 class TreeLayout {
7360 constructor ( el ) {
7461 // container element
7562 this . svg = d3 . select ( el ) ;
76- this . g = this . svg . append ( 'g' ) ;
63+ this . draggable = this . svg . append ( 'g' ) ;
64+ this . g = this . draggable . append ( 'g' ) ;
7765
7866 // add zoom/pan events
7967 this . svg . call ( d3 . zoom ( )
8068 . scaleExtent ( [ 1 / 2 , 4 ] )
8169 . on ( "zoom" , ( ) => {
82- this . g . attr ( 'transform' , d3 . event . transform ) ;
70+ this . draggable . attr ( 'transform' , d3 . event . transform ) ;
8371 } ) )
8472 . on ( "dblclick.zoom" , null ) ;
8573
@@ -88,6 +76,8 @@ const TreeLayout = (function() {
8876 this . maxDepth = 20 ; // default value for max dist from root
8977 }
9078 resize ( ) {
79+ // let bounds = this.svg.node().getBoundingClientRect();
80+ // this.g.attr('transform', `translate(${bounds.width / 2}, 30)`);// ${bounds.height / 2})`);
9181 }
9282 clear ( ) {
9383 this . word = null ;
@@ -98,27 +88,191 @@ const TreeLayout = (function() {
9888 * construct a set of hierarchies from an array of
9989 * Word or Link "root" nodes
10090 */
101- graph ( word ) {
102- this . word = word ;
91+ graph ( selected ) {
92+ this . resize ( ) ;
10393
10494 maxDepth = this . maxDepth ;
105- let sum = 0 ;
106- this . incoming = [ ] ;
107- this . data = ( function ( ) {
108- const seed = addNode ( word , 0 , null ) ;
10995
110- const root = d3 . hierarchy ( seed ) ;
96+ let data = [ ] ;
97+
98+ function addNode ( node , source = null , depth = 0 ) {
99+ let data = {
100+ node,
101+ depth,
102+ children : [ ] ,
103+ siblings : [ ]
104+ } ;
105+
106+ if ( depth < maxDepth ) {
107+ let links = node . links . filter ( l => l . top ) ;
108+ let args = [ ] ;
109+ let corefs = links . filter ( x => ! x . trigger )
110+ . map ( coref => {
111+ return {
112+ type : coref . reltype ,
113+ args : coref . arguments . filter ( x => x . anchor !== node && x . anchor !== source )
114+ . map ( x => addNode ( x . anchor , node , depth ) )
115+ } ;
116+ } ) ;
111117
112- const data = {
113- root,
114- tree : tree ( root ) ,
115- offset : sum
116- } ;
118+ if ( node instanceof Word ) {
119+ args = links . filter ( x => x . trigger === node ) ;
120+ }
121+ else if ( node instanceof Link ) {
122+ args = node . arguments . map ( x => x . anchor ) ;
123+ }
117124
118- return data ;
125+ data . children = args . map ( arg => addNode ( arg , data , depth + 1 ) ) ;
126+ data . siblings = corefs ;
127+ }
128+
129+ return data ;
130+ }
131+
132+ let hierarchy = addNode ( selected ) ;
133+
134+ let [ nodes , links ] = ( function ( ) {
135+ let nodes = [ ] ;
136+ let links = [ ] ;
137+
138+ function flatten ( node ) {
139+ nodes . push ( node ) ;
140+ node . siblings . forEach ( sibling => {
141+ sibling . args . forEach ( arg => {
142+ flatten ( arg ) ;
143+ links . push ( {
144+ type : 'sibling' ,
145+ label : sibling . type ,
146+ source : node ,
147+ target : arg
148+ } ) ;
149+ } ) ;
150+ } ) ;
151+ node . children . forEach ( child => {
152+ flatten ( child ) ;
153+ links . push ( {
154+ type : 'child' ,
155+ source : node ,
156+ target : child
157+ } )
158+ } ) ;
159+ }
160+ flatten ( hierarchy ) ;
161+
162+ return [ nodes , links ] ;
119163 } ) ( ) ;
120164
121- this . updateGraph ( ) ;
165+
166+ let maxWidth = 0 ;
167+ let layers = [ ] ;
168+ nodes . forEach ( node => {
169+ layers [ node . depth ] = layers [ node . depth ] || [ ] ;
170+ layers [ node . depth ] . push ( node ) ;
171+ } ) ;
172+
173+ function shiftSubtree ( node , dx , root ) {
174+ node . offset += dx ;
175+ if ( node . offset > maxWidth ) { maxWidth = node . offset ; }
176+ if ( ! root ) {
177+ node . siblings . forEach ( node => shiftSubtree ( node , dx ) )
178+ }
179+ node . children . forEach ( node => shiftSubtree ( node , dx ) ) ;
180+ }
181+ for ( let i = layers . length - 1 ; i >= 0 ; -- i ) {
182+ layers [ i ] . forEach ( ( node , j ) => {
183+ // 1st pass: assign an initial offset according to children
184+ if ( node . children . length > 0 ) {
185+ let leftChild = node . children [ 0 ] ;
186+ let rightChild = node . children [ node . children . length - 1 ] ;
187+ node . offset = ( leftChild . offset + rightChild . offset ) / 2 ;
188+ }
189+ else if ( j > 0 ) {
190+ node . offset = layers [ i ] [ j - 1 ] . offset ;
191+ }
192+ else {
193+ node . offset = 0 ;
194+ }
195+ } ) ;
196+
197+ // 2nd pass: check that subtree doesn't collide with left tree
198+ function computeWidth ( word , svg ) {
199+ let text = svg . append ( 'text' ) . text ( word ) ;
200+ let length = text . node ( ) . getComputedTextLength ( ) ;
201+ text . remove ( ) ;
202+ return length ;
203+ }
204+
205+ layers [ i ] . forEach ( ( node , j ) => {
206+ node . width = computeWidth ( node . node . val , this . svg ) ;
207+ if ( j > 0 ) {
208+ const prev = layers [ i ] [ j - 1 ] ;
209+ const separation = prev . siblings . some ( sibling => sibling . args . indexOf ( node ) > - 1 ) ? 50 : 20 ; // TODO: make more universal
210+
211+ let dx = prev . offset + prev . width / 2 + node . width / 2 - node . offset + separation ;
212+ if ( dx > 0 ) {
213+ // shift subtree and right-ward trees by dx
214+ for ( let k = j ; k < layers [ i ] . length ; ++ k ) {
215+ shiftSubtree ( layers [ i ] [ k ] , dx , true ) ;
216+ }
217+ }
218+ }
219+ if ( node . offset > maxWidth ) { maxWidth = node . offset ; }
220+ } ) ;
221+ } // end for
222+
223+
224+ let nodeSVG = this . g . selectAll ( '.node' )
225+ . data ( nodes , d => d . node ) ;
226+
227+ let edgeSVG = this . g . selectAll ( '.edge' )
228+ . data ( links , d => d . parent ) ;
229+
230+ //layout constants
231+ const rh = 50 ; // row height
232+ nodeSVG . exit ( ) . remove ( ) ;
233+ nodeSVG . enter ( ) . append ( 'text' )
234+ . attr ( 'class' , 'node' )
235+ . attr ( 'text-anchor' , 'middle' )
236+ . attr ( 'transform' , d => 'translate(' + [ d . offset , d . depth * rh ] + ')' )
237+ . merge ( nodeSVG )
238+ . text ( d => d . node . val )
239+ . transition ( )
240+ . attr ( 'transform' , d => 'translate(' + [ d . offset , d . depth * rh ] + ')' ) ;
241+
242+ // resize
243+ let bounds = this . svg . node ( ) . getBoundingClientRect ( ) ;
244+ this . g . attr ( 'transform' , 'translate(' + [ bounds . width / 2 - maxWidth / 2 , bounds . height / 2 - layers . length * rh / 2 ] + ')' ) ;
245+
246+ edgeSVG . exit ( ) . remove ( ) ;
247+ edgeSVG . enter ( ) . append ( 'path' )
248+ . attr ( 'class' , 'edge' )
249+ . attr ( 'stroke' , 'grey' )
250+ . attr ( 'stroke-width' , '1px' )
251+ . attr ( 'fill' , 'none' )
252+ . merge ( edgeSVG )
253+ . attr ( 'd' , d => {
254+ if ( d . type === 'sibling' ) {
255+ let x1 , x2 ;
256+ if ( d . target . offset > d . source . offset ) {
257+ x1 = d . source . offset + d . source . width / 2 ;
258+ x2 = d . target . offset - d . target . width / 2 ;
259+ }
260+ else {
261+ x1 = d . target . offset + d . target . width / 2 ;
262+ x2 = d . source . offset - d . source . width / 2 ;
263+ }
264+ return 'M' + [ x1 - 10 , d . source . depth * rh + 5 ]
265+ + 'v7h' + ( x2 - x1 + 20 ) + 'v-7' ;
266+ }
267+ else if ( d . type === 'child' ) {
268+ return 'M' + [ d . source . offset , d . source . depth * rh + 5 ]
269+ + 'C' + [
270+ d . source . offset , d . source . depth * rh + 25 ,
271+ d . target . offset , d . target . depth * rh - 40 ,
272+ d . target . offset , d . target . depth * rh - 15
273+ ] ;
274+ }
275+ } )
122276 }
123277
124278 updateIncoming ( data , index ) {
@@ -144,43 +298,6 @@ const TreeLayout = (function() {
144298 node . y += dy ;
145299 } ) ;
146300
147- /* console.log('----- range',d3.extent(this.data[index].root.descendants(), d => d.x));
148- console.log('graft range',d3.extent(root.descendants(), d => d.x));
149- console.log(data.anchor.x, dx);
150- */
151- // -------- in progress
152- // test case : Pos_reg --> graft "outside"
153- // test case : Promotes --> graft "inside"
154- // test case : Ubiquitination --> graft left
155- // test case : Phosphorylation --> two
156- /* let graftLeftOfRoot = data.anchor.x < this.data[index].root.x;
157- console.log(root.descendants());
158- // rearrange old tree to not interfere with graft
159- let range = d3.extent(root.leaves().concat(data.anchor), d => d.x);
160- console.log(range);
161- console.log(this.data[index].root.descendants().map(d => d.x));
162- let children = data.anchor.descendants();
163- let offset = Number.MIN_SAFE_INTEGER;
164- this.data[index].root.descendants().forEach(node => {
165- // not a shared branch
166- if (children.indexOf(node) < 0) {
167- if (node.x <= range[1] && node.x >= range[0]) {
168- offset = Math.max(offset, node.x);
169- }
170- }
171- });
172- offset = data.anchor.x - offset;
173- console.log(offset);
174- this.data[index].root.descendants().forEach(node => {
175- if (children.indexOf(node) < 0) {
176- if (node.x <= range[1] && node.x >= range[0]) {
177- node.x -= offset;
178- }
179- }
180- })
181- */
182- // ------ end testing
183-
184301 this . data . push ( {
185302 index,
186303 root,
0 commit comments