CollateX format for GraphML output changed; parser update
[scpubgit/stemmaweb.git] / root / js / relationship.js
1 function getTextPath() {
2     var currpath = window.location.pathname
3     if( currpath.lastIndexOf('/') == currpath.length - 1 ) { 
4         currpath = currpath.slice( 0, currpath.length - 1) 
5     };
6     var path_elements = currpath.split('/');
7     var textid = path_elements.pop();
8     var basepath = path_elements.join( '/' );
9     var path_parts = [ basepath, textid ];
10     return path_parts;
11 }
12
13 function getRelativePath() {
14         var path_parts = getTextPath();
15         return path_parts[0];
16 }
17
18 function getRelationshipURL() {
19         var path_parts = getTextPath();
20         return path_parts[0] + '/' + path_parts[1] + '/relationships';
21 }
22
23 function svgEnlargementLoaded() {
24         //Give some visual evidence that we are working
25         $('#loading_overlay').show();
26     //Set viewbox widht and height to widht and height of $('#svgenlargement svg').
27     //This is essential to make sure zooming and panning works properly.
28     $('#svgenlargement ellipse').attr( {stroke:'green', fill:'#b3f36d'} );
29     var graph_svg = $('#svgenlargement svg');
30     var svg_g = $('#svgenlargement svg g')[0];
31     svg_root = graph_svg.svg().svg('get').root();
32     svg_root.viewBox.baseVal.width = graph_svg.attr( 'width' );
33     svg_root.viewBox.baseVal.height = graph_svg.attr( 'height' );
34     //Now set scale and translate so svg height is about 150px and vertically centered in viewbox.
35     //This is just to create a nice starting enlargement.
36     var initial_svg_height = 250;
37     var scale = initial_svg_height/graph_svg.attr( 'height' );
38     var additional_translate = (graph_svg.attr( 'height' ) - initial_svg_height)/(2*scale);
39     var transform = svg_g.getAttribute('transform');
40     var translate = parseFloat( transform.match( /translate\([^\)]*\)/ )[0].split('(')[1].split(' ')[1].split(')')[0] );
41     translate += additional_translate;
42     var transform = 'rotate(0) scale(' + scale + ') translate(4 ' + translate + ')';
43     svg_g.setAttribute('transform', transform);
44     //used to calculate min and max zoom level:
45     start_element_height = $("#svgenlargement .node title:contains('#START#')").siblings('ellipse')[0].getBBox().height;
46     add_relations();
47     // $('#loading_overlay').hide();
48 }
49
50 function add_relations() {
51         var basepath = getRelativePath();
52         var textrelpath = getRelationshipURL();
53     $.getJSON( basepath + '/definitions', function(data) {
54         var rel_types = data.types.sort();
55         $.getJSON( textrelpath,
56         function(data) {
57             $.each(data, function( index, rel_info ) {
58                 var type_index = $.inArray(rel_info.type, rel_types);
59                 if( type_index != -1 ) {
60                     var relation = relation_manager.create( rel_info.source, rel_info.target, type_index );
61                     relation.data( 'type', rel_info.type );
62                     relation.data( 'scope', rel_info.scope );
63                     relation.data( 'note', rel_info.note );
64                     var node_obj = get_node_obj(rel_info.source);
65                     node_obj.set_draggable( false );
66                     node_obj.ellipse.data( 'node_obj', null );
67                     node_obj = get_node_obj(rel_info.target);
68                     node_obj.set_draggable( false );
69                     node_obj.ellipse.data( 'node_obj', null );
70                 }
71             })
72         });
73     });
74 }
75
76 function get_ellipse( node_id ) {
77   return $('#svgenlargement .node').children('title').filter( function(index) {
78     return $(this).text() == node_id;
79   }).siblings('ellipse');
80 }
81
82 function get_node_obj( node_id ) {
83     var node_ellipse = get_ellipse( node_id );
84     if( node_ellipse.data( 'node_obj' ) == null ) {
85         node_ellipse.data( 'node_obj', new node_obj(node_ellipse) );
86     };
87     return node_ellipse.data( 'node_obj' );
88 }
89
90 function get_edge( edge_id ) {
91   return $('#svgenlargement .edge').filter( function(index) {
92     return $(this).children( 'title' ).text() == $('<div/>').html(edge_id).text() ;
93   });
94 }
95
96 function node_obj(ellipse) {
97   this.ellipse = ellipse;
98   var self = this;
99   
100   this.x = 0;
101   this.y = 0;
102   this.dx = 0;
103   this.dy = 0;
104   this.node_elements = node_elements_for(self.ellipse);
105
106   this.get_id = function() {
107     return self.ellipse.siblings('title').text()
108   }
109   
110   this.set_draggable = function( draggable ) {
111     if( draggable ) {
112       self.ellipse.attr( {stroke:'black', fill:'#fff'} );
113       self.ellipse.mousedown( this.mousedown_listener );
114       self.ellipse.hover( this.enter_node, this.leave_node );  
115     } else {
116       self.ellipse.unbind('mouseenter').unbind('mouseleave').unbind('mousedown');
117       self.ellipse.attr( {stroke:'green', fill:'#b3f36d'} );
118     }
119   }
120
121   this.mousedown_listener = function(evt) {
122     evt.stopPropagation();
123     self.x = evt.clientX;
124     self.y = evt.clientY;
125     $('body').mousemove( self.mousemove_listener );
126     $('body').mouseup( self.mouseup_listener );
127     self.ellipse.unbind('mouseenter').unbind('mouseleave')
128     self.ellipse.attr( 'fill', '#ff66ff' );
129     first_node_g_element = $("#svgenlargement g .node" ).filter( ":first" );
130     if( first_node_g_element.attr('id') !== self.get_g().attr('id') ) { self.get_g().insertBefore( first_node_g_element ) };
131   }
132
133   this.mousemove_listener = function(evt) {
134     self.dx = (evt.clientX - self.x) / mouse_scale;
135     self.dy = (evt.clientY - self.y) / mouse_scale;
136     self.move_elements();
137   }
138
139   this.mouseup_listener = function(evt) {    
140     if( $('ellipse[fill="#ffccff"]').size() > 0 ) {
141         var source_node_id = self.ellipse.siblings('title').text();
142         var target_node_id = $('ellipse[fill="#ffccff"]').siblings("title").text();
143         $('#source_node_id').val( source_node_id );
144         $('#target_node_id').val( target_node_id );
145         $('#dialog-form').dialog( 'open' );
146     };
147     $('body').unbind('mousemove');
148     $('body').unbind('mouseup');
149     self.ellipse.attr( 'fill', '#fff' );
150     self.ellipse.hover( self.enter_node, self.leave_node );
151     self.reset_elements();
152   }
153
154   this.cpos = function() {
155     return { x: self.ellipse.attr('cx'), y: self.ellipse.attr('cy') };
156   }
157
158   this.get_g = function() {
159     return self.ellipse.parent('g');
160   }
161
162   this.enter_node = function(evt) {
163     self.ellipse.attr( 'fill', '#ffccff' );
164   }
165
166   this.leave_node = function(evt) {
167     self.ellipse.attr( 'fill', '#fff' );
168   }
169
170   this.greyout_edges = function() {
171       $.each( self.node_elements, function(index, value) {
172         value.grey_out('.edge');
173       });
174   }
175
176   this.ungreyout_edges = function() {
177       $.each( self.node_elements, function(index, value) {
178         value.un_grey_out('.edge');
179       });
180   }
181
182   this.move_elements = function() {
183     $.each( self.node_elements, function(index, value) {
184       value.move(self.dx,self.dy);
185     });
186   }
187
188   this.reset_elements = function() {
189     $.each( self.node_elements, function(index, value) {
190       value.reset();
191     });
192   }
193
194   this.update_elements = function() {
195       self.node_elements = node_elements_for(self.ellipse);
196   }
197
198   self.set_draggable( true );
199 }
200
201 function svgshape( shape_element ) {
202   this.shape = shape_element;
203   this.move = function(dx,dy) {
204     this.shape.attr( "transform", "translate(" + dx + " " + dy + ")" );
205   }
206   this.reset = function() {
207     this.shape.attr( "transform", "translate( 0, 0 )" );
208   }
209   this.grey_out = function(filter) {
210       if( this.shape.parent(filter).size() != 0 ) {
211           this.shape.attr({'stroke':'#e5e5e5', 'fill':'#e5e5e5'});
212       }
213   }
214   this.un_grey_out = function(filter) {
215       if( this.shape.parent(filter).size() != 0 ) {
216         this.shape.attr({'stroke':'#000000', 'fill':'#000000'});
217       }
218   }
219 }
220
221 function svgpath( path_element, svg_element ) {
222   this.svg_element = svg_element;
223   this.path = path_element;
224   this.x = this.path.x;
225   this.y = this.path.y;
226   this.move = function(dx,dy) {
227     this.path.x = this.x + dx;
228     this.path.y = this.y + dy;
229   }
230   this.reset = function() {
231     this.path.x = this.x;
232     this.path.y = this.y;
233   }
234   this.grey_out = function(filter) {
235       if( this.svg_element.parent(filter).size() != 0 ) {
236           this.svg_element.attr('stroke', '#e5e5e5');
237           this.svg_element.siblings('text').attr('fill', '#e5e5e5');
238       }
239   }
240   this.un_grey_out = function(filter) {
241       if( this.svg_element.parent(filter).size() != 0 ) {
242           this.svg_element.attr('stroke', '#000000');
243           this.svg_element.siblings('text').attr('fill', '#000000');
244       }
245   }
246 }
247
248 function node_elements_for( ellipse ) {
249   node_elements = get_edge_elements_for( ellipse );
250   node_elements.push( new svgshape( ellipse.siblings('text') ) );
251   node_elements.push( new svgshape( ellipse ) );
252   return node_elements;
253 }
254
255 function get_edge_elements_for( ellipse ) {
256   edge_elements = new Array();
257   node_id = ellipse.siblings('title').text();
258   edge_in_pattern = new RegExp( node_id + '$' );
259   edge_out_pattern = new RegExp( '^' + node_id );
260   $.each( $('#svgenlargement .edge,#svgenlargement .relation').children('title'), function(index) {
261     title = $(this).text();
262     if( edge_in_pattern.test(title) ) {
263         polygon = $(this).siblings('polygon');
264         if( polygon.size() > 0 ) {
265             edge_elements.push( new svgshape( polygon ) );
266         }
267         path_segments = $(this).siblings('path')[0].pathSegList;
268         edge_elements.push( new svgpath( path_segments.getItem(path_segments.numberOfItems - 1), $(this).siblings('path') ) );
269     }
270     if( edge_out_pattern.test(title) ) {
271       path_segments = $(this).siblings('path')[0].pathSegList;
272       edge_elements.push( new svgpath( path_segments.getItem(0), $(this).siblings('path') ) );
273     }
274   });
275   return edge_elements;
276
277
278 function relation_factory() {
279     var self = this;
280     this.color_memo = null;
281     //TODO: colors hard coded for now
282     this.temp_color = '#FFA14F';
283     this.relation_colors = [ "#5CCCCC", "#67E667", "#F9FE72", "#6B90D4", "#FF7673", "#E467B3", "#AA67D5", "#8370D8", "#FFC173" ];
284
285     this.create_temporary = function( source_node_id, target_node_id ) {
286         var relation = $('#svgenlargement .relation').filter( function(index) {
287             var relation_id = $(this).children('title').text();
288             if( ( relation_id == ( source_node_id + '->' + target_node_id ) ) || ( relation_id == ( target_node_id + '->' + source_node_id ) ) ) {
289                 return true;
290             } 
291         } );
292         if( relation.size() == 0 ) { 
293             draw_relation( source_node_id, target_node_id, self.temp_color );
294         } else {
295             self.color_memo = relation.children('path').attr( 'stroke' );
296             relation.children('path').attr( 'stroke', self.temp_color );
297         }
298     }
299     this.remove_temporary = function() {
300         var path_element = $('#svgenlargement .relation').children('path[stroke="' + self.temp_color + '"]');
301         if( self.color_memo != null ) {
302             path_element.attr( 'stroke', self.color_memo );
303             self.color_memo = null;
304         } else {
305             var temporary = path_element.parent('g').remove();
306             temporary.empty();
307             temporary = null; 
308         }
309     }
310     this.create = function( source_node_id, target_node_id, color_index ) {
311         //TODO: Protect from (color_)index out of bound..
312         var relation_color = self.relation_colors[ color_index ];
313         var relation = draw_relation( source_node_id, target_node_id, relation_color );
314         get_node_obj( source_node_id ).update_elements();
315         get_node_obj( target_node_id ).update_elements();
316         return relation;
317     }
318     this.toggle_active = function( relation_id ) {
319         var relation = $("#svgenlargement .relation:has(title:contains('" + relation_id + "'))");
320         var relation_path = relation.children('path');
321         if( !relation.data( 'active' ) ) {
322             relation_path.css( {'cursor':'pointer'} );
323             relation_path.mouseenter( function(event) { 
324                 outerTimer = setTimeout( function() { 
325                     timer = setTimeout( function() { 
326                         var title = relation.children('title').text();
327                         var source_node_id = title.substring( 0, title.indexOf( "->" ) );
328                         var target_node_id = title.substring( (title.indexOf( "->" ) + 2) );
329                         $('#delete_source_node_id').val( source_node_id );
330                         $('#delete_target_node_id').val( target_node_id );
331                         self.showinfo(relation); 
332                     }, 500 ) 
333                 }, 1000 );
334             });
335             relation_path.mouseleave( function(event) {
336                 clearTimeout(outerTimer); 
337                 if( timer != null ) { clearTimeout(timer); } 
338             });
339             relation.data( 'active', true );
340         } else {
341             relation_path.unbind( 'mouseenter' );
342             relation_path.unbind( 'mouseleave' );
343             relation_path.css( {'cursor':'inherit'} );
344             relation.data( 'active', false );
345         }
346     }
347     this.showinfo = function(relation) {
348         var htmlstr = 'type: ' + relation.data( 'type' ) + '<br/>scope: ' + relation.data( 'scope' );
349         if( relation.data( 'note' ) ) {
350                 htmlstr = htmlstr + '<br/>note: ' + relation.data( 'note' );
351         }
352         $('#delete-form-text').html( htmlstr );
353         var points = relation.children('path').attr('d').slice(1).replace('C',' ').split(' ');
354         var xs = parseFloat( points[0].split(',')[0] );
355         var xe = parseFloat( points[1].split(',')[0] );
356         var ys = parseFloat( points[0].split(',')[1] );
357         var ye = parseFloat( points[3].split(',')[1] );
358         var p = svg_root.createSVGPoint();
359         p.x = xs + ((xe-xs)*1.1);
360         p.y = ye - ((ye-ys)/2);
361         var ctm = svg_root.children[0].getScreenCTM();
362         var nx = p.matrixTransform(ctm).x;
363         var ny = p.matrixTransform(ctm).y;
364         var dialog_aria = $ ("div[aria-labelledby='ui-dialog-title-delete-form']");
365         $('#delete-form').dialog( 'open' );
366         dialog_aria.offset({ left: nx, top: ny });
367     }
368     this.remove = function( relation_id ) {
369         var relation = $("#svgenlargement .relation:has(title:contains('" + relation_id + "'))");
370         relation.remove();
371     }
372 }
373
374 function draw_relation( source_id, target_id, relation_color ) {
375     var source_ellipse = get_ellipse( source_id );
376     var target_ellipse = get_ellipse( target_id );
377     var svg = $('#svgenlargement').children('svg').svg().svg('get');
378     var path = svg.createPath(); 
379     var sx = parseInt( source_ellipse.attr('cx') );
380     var rx = parseInt( source_ellipse.attr('rx') );
381     var sy = parseInt( source_ellipse.attr('cy') );
382     var ex = parseInt( target_ellipse.attr('cx') );
383     var ey = parseInt( target_ellipse.attr('cy') );
384     var relation = svg.group( $("#svgenlargement svg g"), {'class':'relation'} );
385     svg.title( relation, source_id + '->' + target_id );
386     svg.path( relation, path.move( sx, sy ).curveC( sx + (2*rx), sy, ex + (2*rx), ey, ex, ey ), {fill: 'none', stroke: relation_color, strokeWidth: 4});
387     var relation_element = $('#svgenlargement .relation').filter( ':last' );
388     relation_element.insertBefore( $('#svgenlargement g g').filter(':first') );
389     return relation_element;
390 }
391
392
393 $(document).ready(function () {
394     
395   timer = null;
396   relation_manager = new relation_factory();
397   $('#update_workspace_button').data('locked', false);
398   
399   $('#enlargement').mousedown(function (event) {
400     $(this)
401         .data('down', true)
402         .data('x', event.clientX)
403         .data('y', event.clientY)
404         .data('scrollLeft', this.scrollLeft)
405         stateTf = svg_root.children[0].getCTM().inverse();
406         var p = svg_root.createSVGPoint();
407         p.x = event.clientX;
408         p.y = event.clientY;
409         stateOrigin = p.matrixTransform(stateTf);
410         return false;
411   }).mouseup(function (event) {
412         $(this).data('down', false);
413   }).mousemove(function (event) {
414     if( timer != null ) { clearTimeout(timer); } 
415     if ( ($(this).data('down') == true) && ($('#update_workspace_button').data('locked') == false) ) {
416         var p = svg_root.createSVGPoint();
417         p.x = event.clientX;
418         p.y = event.clientY;
419         p = p.matrixTransform(stateTf);
420         var matrix = stateTf.inverse().translate(p.x - stateOrigin.x, p.y - stateOrigin.y);
421         var s = "matrix(" + matrix.a + "," + matrix.b + "," + matrix.c + "," + matrix.d + "," + matrix.e + "," + matrix.f + ")";
422         svg_root.children[0].setAttribute("transform", s);
423     }
424   }).mousewheel(function (event, delta) {
425     event.returnValue = false;
426     event.preventDefault();
427     if ( $('#update_workspace_button').data('locked') == false ) {
428         if( delta < -9 ) { delta = -9 }; 
429         var z = 1 + delta/10;
430         var g = svg_root.children[0];
431         if( (z<1 && (g.getScreenCTM().a * start_element_height) > 4.0) || (z>1 && (g.getScreenCTM().a * start_element_height) < 100) ) {
432             var root = svg_root;
433             var p = root.createSVGPoint();
434             p.x = event.clientX;
435             p.y = event.clientY;
436             p = p.matrixTransform(g.getCTM().inverse());
437             var k = root.createSVGMatrix().translate(p.x, p.y).scale(z).translate(-p.x, -p.y);
438             var matrix = g.getCTM().multiply(k);
439             var s = "matrix(" + matrix.a + "," + matrix.b + "," + matrix.c + "," + matrix.d + "," + matrix.e + "," + matrix.f + ")";
440             g.setAttribute("transform", s);
441         }
442     }
443   }).css({
444     'overflow' : 'hidden',
445     'cursor' : '-moz-grab'
446   });
447   
448
449   $( "#dialog-form" ).dialog({
450     autoOpen: false,
451     height: 270,
452     width: 290,
453     modal: true,
454     buttons: {
455       "Ok": function() {
456         $('#status').empty();
457         form_values = $('#collapse_node_form').serialize()
458         ncpath = getRelationshipURL();
459         $(':button :contains("Ok")').attr("disabled", true);
460         var jqjson = $.post( ncpath, form_values, function(data) {
461             $.each( data, function(item, source_target) { 
462                 var relation = relation_manager.create( source_target[0], source_target[1], $('#rel_type').attr('selectedIndex') );
463                 relation.data( 'type', $('#rel_type :selected').text()  );
464                 relation.data( 'scope', $('#scope :selected').text()  );
465                 relation.data( 'note', $('#note').val()  );
466                 relation_manager.toggle_active( relation.children('title').text() );
467             });
468             $( "#dialog-form" ).dialog( "close" );
469         }, 'json' );
470       },
471       Cancel: function() {
472           $( this ).dialog( "close" );
473       }
474     },
475     create: function(event, ui) { 
476         $(this).data( 'relation_drawn', false );
477         //TODO? Err handling?
478                 var basepath = getRelativePath();
479         var jqjson = $.getJSON( basepath + '/definitions', function(data) {
480             var types = data.types.sort();
481             $.each( types, function(index, value) {   
482                  $('#rel_type').append( $('<option>').attr( "value", value ).text(value) ); 
483                  $('#keymaplist').append( $('<li>').css( "border-color", relation_manager.relation_colors[index] ).text(value) ); 
484             });
485             var scopes = data.scopes;
486             $.each( scopes, function(index, value) {   
487                  $('#scope').append( $('<option>').attr( "value", value ).text(value) ); 
488             });
489         });        
490     },
491     open: function() {
492         relation_manager.create_temporary( $('#source_node_id').val(), $('#target_node_id').val() );
493         $(".ui-widget-overlay").css("background", "none");
494         $("#dialog_overlay").show();
495         $("#dialog_overlay").height( $("#enlargement_container").height() );
496         $("#dialog_overlay").width( $("#enlargement_container").width() );
497         $("#dialog_overlay").offset( $("#enlargement_container").offset() );
498     },
499     close: function() {
500         relation_manager.remove_temporary();
501         $( '#status' ).empty();
502         $("#dialog_overlay").hide();
503     }
504   }).ajaxError( function(event, jqXHR, ajaxSettings, thrownError) {
505       if( ( ajaxSettings.type == 'POST' ) && jqXHR.status == 403 ) {
506           var errobj = jQuery.parseJSON( jqXHR.responseText );
507           $('#status').append( '<p class="error">Error: ' + errobj.error + '</br>The relationship cannot be made.</p>' );
508       }
509   } );
510
511   $( "#delete-form" ).dialog({
512     autoOpen: false,
513     height: 135,
514     width: 160,
515     modal: false,
516     buttons: {
517         Cancel: function() {
518             $( this ).dialog( "close" );
519         },
520         Delete: function() {
521           form_values = $('#delete_relation_form').serialize()
522           ncpath = getRelationshipURL()
523           var jqjson = $.ajax({ url: ncpath, data: form_values, success: function(data) {
524               $.each( data, function(item, source_target) { 
525                   relation_manager.remove( source_target[0] + '->' + source_target[1] );
526               });
527               $( "#delete-form" ).dialog( "close" );
528           }, dataType: 'json', type: 'DELETE' });
529         }
530     },
531     create: function(event, ui) {
532         var buttonset = $(this).parent().find( '.ui-dialog-buttonset' ).css( 'width', '100%' );
533         buttonset.find( "button:contains('Cancel')" ).css( 'float', 'right' );
534         var dialog_aria = $("div[aria-labelledby='ui-dialog-title-delete-form']");  
535         dialog_aria.mouseenter( function() {
536             if( mouseWait != null ) { clearTimeout(mouseWait) };
537         })
538         dialog_aria.mouseleave( function() {
539             mouseWait = setTimeout( function() { $("#delete-form").dialog( "close" ) }, 2000 );
540         })
541     },
542     open: function() {
543         mouseWait = setTimeout( function() { $("#delete-form").dialog( "close" ) }, 2000 );
544     },
545     close: function() {
546     }
547   });
548
549   $('#update_workspace_button').click( function() {
550      var svg_enlargement = $('#svgenlargement').svg().svg('get').root();
551      mouse_scale = svg_root.children[0].getScreenCTM().a;
552      if( $(this).data('locked') == true ) {
553          $('#svgenlargement ellipse' ).each( function( index ) {
554              if( $(this).data( 'node_obj' ) != null ) {
555                  $(this).data( 'node_obj' ).ungreyout_edges();
556                  $(this).data( 'node_obj' ).set_draggable( false );
557                  var node_id = $(this).data( 'node_obj' ).get_id();
558                  toggle_relation_active( node_id );
559                  $(this).data( 'node_obj', null );
560              }
561          })
562          $(this).data('locked', false);
563          $(this).css('background-position', '0px 44px');
564      } else {
565          var left = $('#enlargement').offset().left;
566          var right = left + $('#enlargement').width();
567          var tf = svg_root.children[0].getScreenCTM().inverse(); 
568          var p = svg_root.createSVGPoint();
569          p.x=left;
570          p.y=100;
571          var cx_min = p.matrixTransform(tf).x;
572          p.x=right;
573          var cx_max = p.matrixTransform(tf).x;
574          $('#svgenlargement ellipse').each( function( index ) {
575              var cx = parseInt( $(this).attr('cx') );
576              if( cx > cx_min && cx < cx_max) { 
577                  if( $(this).data( 'node_obj' ) == null ) {
578                      $(this).data( 'node_obj', new node_obj( $(this) ) );
579                  } else {
580                      $(this).data( 'node_obj' ).set_draggable( true );
581                  }
582                  $(this).data( 'node_obj' ).greyout_edges();
583                  var node_id = $(this).data( 'node_obj' ).get_id();
584                  toggle_relation_active( node_id );
585              }
586          });
587          $(this).css('background-position', '0px 0px');
588          $(this).data('locked', true );
589      }
590   });
591   
592   $('.helptag').popupWindow({ 
593           height:500, 
594           width:800, 
595           top:50, 
596           left:50,
597           scrollbars:1 
598   }); 
599
600   
601   function toggle_relation_active( node_id ) {
602       $('#svgenlargement .relation').find( "title:contains('" + node_id +  "')" ).each( function(index) {
603           matchid = new RegExp( "^" + node_id );
604           if( $(this).text().match( matchid ) != null ) {
605               relation_manager.toggle_active( $(this).text() );
606           };
607       });
608   }
609
610 });
611
612