rename TreeOfTexts to less annoying stemmaweb
[scpubgit/stemmatology.git] / stemmaweb / root / js / svginteraction.js
1 function getRelativePath( action ) {
2     path_elements = window.location.pathname.split('/'); 
3     if( path_elements[1].length > 0 ) {
4         return window.location.pathname.split('/')[1] + '/' + action;
5     } else {
6         return action;
7     }
8 }
9
10 function svgLoaded() {
11   $('ellipse').attr( {stroke:'black', fill:'#fff'} );
12   ncpath = getRelativePath( 'node_click' );
13   var jqjson = $.getJSON( ncpath, 'node_id=null', function(data) {
14     $.each( data, function(item, node_id_and_state) {
15       if( node_id_and_state[1] == 1 ) {
16         node_ellipse = $('.node').children('title').filter( function(index) {
17           return $(this).text() == node_id_and_state[0];
18         }).siblings('ellipse');
19         node_ellipse.attr( {stroke:'green', fill:'#b3f36d'} );
20         $('#constructedtext').append( node_ellipse.siblings('text').text() + ' ' );
21       } else {
22         if( node_id_and_state[1] == null ) {
23           $('#constructedtext').append( ' … ' );
24         }
25       }
26     });
27     add_node_objs();
28   });
29 }
30
31 function add_node_objs() {
32   $('ellipse[fill="#fff"]').each( function() {
33       $(this).data( 'node_obj', new node_obj( $(this) ) );
34     }
35   );
36 }
37
38 function get_node_obj( node_id ) {
39   return $('.node').children('title').filter( function(index) {
40     return $(this).text() == node_id;
41   }).siblings('ellipse').data( 'node_obj' );
42 }
43
44 function get_edge( edge_id ) {
45   return $('.edge').filter( function(index) {
46     return $(this).children( 'title' ).text() == $('<div/>').html(edge_id).text() ;
47   });
48 }
49
50 function node_obj(ellipse) {
51   this.ellipse = ellipse;
52   var self = this;
53   
54   this.x = 0;
55   this.y = 0;
56   this.dx = 0;
57   this.dy = 0;
58   this.node_elements = node_elements_for(self.ellipse);
59   this.sub_nodes = [];
60   this.super_node = null;
61
62   this.dblclick_listener = function(evt) {
63     node_id = self.ellipse.siblings('title').text();
64     ncpath = getRelativePath( 'node_click' );
65     var jqjson = $.getJSON( ncpath, 'node_id=' + node_id, function(data) {
66       $('#constructedtext').empty();
67       $.each( data, function(item, node_id_and_state) {
68         node = get_node_obj( node_id_and_state[0] );
69         // 1 -> turn the associated SVG node on, put in the associate word in the text box.
70         // 0 -> turn SVG node off.
71         // null -> turn node off, put in ellipsis in text box at the corresponding place.
72         if( node_id_and_state[1] == 1 ) {
73 //TODO: create test suite en refactor this in to more OO! (node and node_ellipse are 'conflated')
74           node_ellipse = $('.node').children('title').filter( function(index) {
75             return $(this).text() == node_id_and_state[0];
76           }).siblings('ellipse');
77           $('#constructedtext').append( node_ellipse.siblings('text').text() + '&#32;' );
78           if( node ) { node.set_draggable( false ) }
79         } else {
80           if( node ) { node.set_draggable( true ) };
81           if( node_id_and_state[1] == null ) {
82             $('#constructedtext').append( ' &hellip; ' );
83           }
84         }
85       });
86     });
87   }
88
89   this.set_draggable = function( draggable ) {
90     if( draggable ) {
91       self.ellipse.attr( {stroke:'black', fill:'#fff'} );
92       self.ellipse.mousedown( this.mousedown_listener );
93       self.ellipse.hover( this.enter_node, this.leave_node );  
94     } else {
95       self.ellipse.unbind('mouseenter').unbind('mouseleave').unbind('mousedown');
96       self.ellipse.attr( {stroke:'green', fill:'#b3f36d'} );
97     }
98   }
99
100   this.mousedown_listener = function(evt) {
101     evt.stopPropagation();
102     self.x = evt.clientX;
103     self.y = evt.clientY;
104     $('body').mousemove( self.mousemove_listener );
105     $('body').mouseup( self.mouseup_listener );
106     self.ellipse.unbind('mouseenter').unbind('mouseleave')
107     self.ellipse.attr( 'fill', '#ff66ff' );
108   }
109
110   this.mousemove_listener = function(evt) {
111     self.dx = evt.clientX - self.x;
112     self.dy = evt.clientY - self.y;
113     self.move_elements();
114   }
115
116   this.mouseup_listener = function(evt) {    
117     if( $('ellipse[fill="#ffccff"]').size() > 0 ) {
118       $('#source_node_id').val( self.ellipse.siblings('title').text() );
119       $('#target_node_id').val( $('ellipse[fill="#ffccff"]').siblings("title").text() );
120       $( '#dialog-form' ).dialog( 'open' );
121     };
122     $('body').unbind('mousemove');
123     $('body').unbind('mouseup');
124     self.ellipse.attr( 'fill', '#fff' );
125     self.ellipse.hover( self.enter_node, self.leave_node );
126     if( self.super_node ) {
127       self.eclipse();
128     } else {
129       self.reset_elements();
130     }
131   }
132
133   this.cpos = function() {
134     return { x: self.ellipse.attr('cx'), y: self.ellipse.attr('cy') };
135   }
136
137   this.get_g = function() {
138     return self.ellipse.parent('g');
139   }
140
141   this.stack_behind = function( collapse_info ) {
142     self.super_node = get_node_obj( collapse_info.target );
143     self.super_node.sub_nodes.push( self );
144     self.eclipse();
145     if( collapse_info.edges ) {
146       $.each( collapse_info.edges, function( source_edge_id, target_info ) {
147         get_edge(source_edge_id).attr( 'display', 'none' );
148         target_edge = get_edge(target_info.target);
149         // Unfortunately, the simple solution doesn't work...
150         // target_edge.children( 'text' ).replaceWith( '<text x="2270" y="-59.400001525878906"><tspan text-anchor="middle">A, B</tspan><tspan fill="red">, C</tspan></text>' );
151         // ..so we take the long and winding road...
152         var svg = $('#svgbasics').children('svg').svg().svg('get');
153         textx = target_edge.children( 'text' )[0].x.baseVal.getItem(0).value
154         texty = target_edge.children( 'text' )[0].y.baseVal.getItem(0).value
155         current_label = target_edge.children( 'text' ).text(); 
156         target_edge.children( 'text' ).remove();
157         texts = svg.createText();
158         texts.span(current_label, {'text-anchor': 'middle'}).span(target_info.label, {fill: 'red'});
159         svg.text(target_edge, textx, texty, texts);
160       }); 
161     }
162   }
163
164   this.eclipse = function() {
165     self.dx = new Number( self.super_node.cpos().x ) - new Number( self.cpos().x ) + ( 10 * (self.super_node.sub_nodes.indexOf(self) + 1) );
166     self.dy = new Number( self.super_node.cpos().y ) - new Number( self.cpos().y ) + ( 5 * (self.super_node.sub_nodes.indexOf(self) + 1) );
167     self.move_elements();
168     eclipse_index = self.super_node.sub_nodes.indexOf(self) - 1;
169     if( eclipse_index > -1 ) {
170       self.get_g().insertBefore( self.super_node.sub_nodes[eclipse_index].get_g() );
171     } else {
172       self.get_g().insertBefore( self.super_node.get_g() );
173     }
174   }
175
176   this.enter_node = function(evt) {
177     self.ellipse.attr( 'fill', '#ffccff' );
178   }
179
180   this.leave_node = function(evt) {
181     self.ellipse.attr( 'fill', '#fff' );
182   }
183
184   this.move_elements = function() {
185     $.each( self.node_elements, function(index, value) {
186       value.move(self.dx,self.dy);
187     });
188   }
189
190   this.reset_elements = function() {
191     $.each( self.node_elements, function(index, value) {
192       value.reset();
193     });
194   }
195
196   this.ellipse.dblclick( this.dblclick_listener );
197   self.set_draggable( true );
198 }
199
200 function svgshape( shape_element ) {
201   this.shape = shape_element;
202   this.move = function(dx,dy) {
203     this.shape.attr( "transform", "translate(" + dx + " " + dy + ")" );
204   }
205   this.reset = function() {
206     this.shape.attr( "transform", "translate( 0, 0 )" );
207   }
208 }
209
210 function svgpath( path_element ) {
211   this.path = path_element;
212   this.x = this.path.x;
213   this.y = this.path.y;
214   this.move = function(dx,dy) {
215     this.path.x = this.x + dx;
216     this.path.y = this.y + dy;
217   }
218   this.reset = function() {
219     this.path.x = this.x;
220     this.path.y = this.y;
221   }
222 }
223
224 function node_elements_for( ellipse ) {
225   node_elements = get_edge_elements_for( ellipse );
226   node_elements.push( new svgshape( ellipse.siblings('text') ) );
227   node_elements.push( new svgshape( ellipse ) );
228   return node_elements;
229 }
230
231 function get_edge_elements_for( ellipse ) {
232   edge_elements = new Array();
233   node_id = ellipse.siblings('title').text();
234   edge_in_pattern = new RegExp( node_id + '$' );
235   edge_out_pattern = new RegExp( '^' + node_id );
236   $.each( $('.edge').children('title'), function(index) {
237     title = $(this).text();
238     if( edge_in_pattern.test(title) ) {
239       edge_elements.push( new svgshape( $(this).siblings('polygon') ) );
240       path_segments = $(this).siblings('path')[0].pathSegList;
241       edge_elements.push( new svgpath( path_segments.getItem(path_segments.numberOfItems - 1) ) );
242     }
243     if( edge_out_pattern.test(title) ) {
244       path_segments = $(this).siblings('path')[0].pathSegList;
245       edge_elements.push( new svgpath( path_segments.getItem(0) ) );
246     }
247   });
248   return edge_elements;
249
250
251 $(document).ready(function () {
252   $('#graph').ajaxError(function() {
253     console.log( 'Oops.. something went wrong with trying to save this change. Please try again...' );
254   });
255   $('#graph').mousedown(function (event) {
256     $(this)
257       .data('down', true)
258       .data('x', event.clientX)
259       .data('scrollLeft', this.scrollLeft);
260       return false;
261   }).mouseup(function (event) {
262     $(this).data('down', false);
263   }).mousemove(function (event) {
264     if ($(this).data('down') == true ) {
265       this.scrollLeft = $(this).data('scrollLeft') + $(this).data('x') - event.clientX;
266     }
267   }).mousewheel(function (event, delta) {
268       this.scrollLeft -= (delta * 30);
269   }).css({
270     'overflow' : 'hidden',
271     'cursor' : '-moz-grab'
272   });
273   $( "#dialog-form" ).dialog({
274     autoOpen: false,
275     height: 150,
276     width: 250,
277     modal: true,
278     buttons: {
279       "Ok": function() {
280         form_values = $('#collapse_node_form').serialize()
281         ncpath = getRelativePath( 'node_collapse' );
282         var jqjson = $.getJSON( ncpath, form_values, function(data) {
283           $.each( data, function(item, collapse_info) { 
284             get_node_obj( item ).stack_behind( collapse_info );
285           });
286         });
287         $( this ).dialog( "close" );
288       },
289       Cancel: function() {
290         $( this ).dialog( "close" );
291       }
292     },
293     close: function() {
294       $('#reason').val( "" ).removeClass( "ui-state-error" );
295     }
296   });
297 });
298
299
300 $(window).mouseout(function (event) {
301   if ($('#graph').data('down')) {
302     try {
303       if (event.originalTarget.nodeName == 'BODY' || event.originalTarget.nodeName == 'HTML') {
304         $('#graph').data('down', false);
305       }                
306     } catch (e) {}
307   }
308 });
309
310