From: Tara L Andrews Date: Thu, 12 Jan 2012 16:24:59 +0000 (+0100) Subject: start of relationship mapper X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=b90c84a0534d40fcddec1d2af1e981558e6b0ba4;p=scpubgit%2Fstemmatology.git start of relationship mapper --- diff --git a/TreeOfTexts/lib/TreeOfTexts.pm b/TreeOfTexts/lib/TreeOfTexts.pm index 20b70ae..e29f3a8 100644 --- a/TreeOfTexts/lib/TreeOfTexts.pm +++ b/TreeOfTexts/lib/TreeOfTexts.pm @@ -41,6 +41,9 @@ __PACKAGE__->config( # Disable deprecated behavior needed by old applications disable_component_resolution_regex_fallback => 1, default_view => 'TT', + 'View::JSON' => { + expose_stash => 'result', + }, ); # Start the application diff --git a/TreeOfTexts/lib/TreeOfTexts/Controller/Root.pm b/TreeOfTexts/lib/TreeOfTexts/Controller/Root.pm index 3141c0b..137d28a 100644 --- a/TreeOfTexts/lib/TreeOfTexts/Controller/Root.pm +++ b/TreeOfTexts/lib/TreeOfTexts/Controller/Root.pm @@ -61,8 +61,11 @@ sub relationships :Local { my( $self, $c ) = @_; my $m = $c->model('Directory'); my $tradition = $m->tradition( $c->request->params->{'textid'} ); - $c->stash->{alignment} = $tradition->collation->make_alignment_table( 'refs' ); - $c->stash->{template} = 'relationships.tt'; + my $table = $tradition->collation->make_alignment_table(); + my $witlist = shift @$table; + $c->stash->{witnesses} = $wits; + $c->stash->{alignment} = $table; + $c->stash->{template} = 'relate.tt'; } =head2 stexaminer @@ -88,6 +91,21 @@ sub stexaminer :Local { $c->stash->{conflict} = $t->{'conflict_count'}; } +=head2 alignment_table + +Return a JSON alignment table of a given text. + +=cut + +sub alignment_table :Local { + my( $self, $c ) = @_; + my $m = $c->model( 'Directory' ); + my $tradition = $m->tradition( $c->request->params->{'textid'} ); + my $table = $tradition->collation->make_alignment_table(); + $c->stash->{'result'} => $table; + $c->forward-( 'View::JSON' ); +} + =head1 OPENSOCIAL URLs =head2 view_table diff --git a/TreeOfTexts/lib/TreeOfTexts/View/JSON.pm b/TreeOfTexts/lib/TreeOfTexts/View/JSON.pm new file mode 100644 index 0000000..b08faa9 --- /dev/null +++ b/TreeOfTexts/lib/TreeOfTexts/View/JSON.pm @@ -0,0 +1,29 @@ +package TreeOfTexts::View::JSON; + +use strict; +use base 'Catalyst::View::JSON'; + +=head1 NAME + +TreeOfTexts::View::JSON - Catalyst JSON View + +=head1 SYNOPSIS + +See L + +=head1 DESCRIPTION + +Catalyst JSON View. + +=head1 AUTHOR + +Tara Andrews + +=head1 LICENSE + +This library is free software, you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut + +1; diff --git a/TreeOfTexts/root/js/relationship.js b/TreeOfTexts/root/js/relationship.js new file mode 100644 index 0000000..e69de29 diff --git a/TreeOfTexts/root/js/svginteraction.js b/TreeOfTexts/root/js/svginteraction.js new file mode 100644 index 0000000..8fa72e2 --- /dev/null +++ b/TreeOfTexts/root/js/svginteraction.js @@ -0,0 +1,310 @@ +function getRelativePath( action ) { + path_elements = window.location.pathname.split('/'); + if( path_elements[1].length > 0 ) { + return window.location.pathname.split('/')[1] + '/' + action; + } else { + return action; + } +} + +function svgLoaded() { + $('ellipse').attr( {stroke:'black', fill:'#fff'} ); + ncpath = getRelativePath( 'node_click' ); + var jqjson = $.getJSON( ncpath, 'node_id=null', function(data) { + $.each( data, function(item, node_id_and_state) { + if( node_id_and_state[1] == 1 ) { + node_ellipse = $('.node').children('title').filter( function(index) { + return $(this).text() == node_id_and_state[0]; + }).siblings('ellipse'); + node_ellipse.attr( {stroke:'green', fill:'#b3f36d'} ); + $('#constructedtext').append( node_ellipse.siblings('text').text() + ' ' ); + } else { + if( node_id_and_state[1] == null ) { + $('#constructedtext').append( ' … ' ); + } + } + }); + add_node_objs(); + }); +} + +function add_node_objs() { + $('ellipse[fill="#fff"]').each( function() { + $(this).data( 'node_obj', new node_obj( $(this) ) ); + } + ); +} + +function get_node_obj( node_id ) { + return $('.node').children('title').filter( function(index) { + return $(this).text() == node_id; + }).siblings('ellipse').data( 'node_obj' ); +} + +function get_edge( edge_id ) { + return $('.edge').filter( function(index) { + return $(this).children( 'title' ).text() == $('
').html(edge_id).text() ; + }); +} + +function node_obj(ellipse) { + this.ellipse = ellipse; + var self = this; + + this.x = 0; + this.y = 0; + this.dx = 0; + this.dy = 0; + this.node_elements = node_elements_for(self.ellipse); + this.sub_nodes = []; + this.super_node = null; + + this.dblclick_listener = function(evt) { + node_id = self.ellipse.siblings('title').text(); + ncpath = getRelativePath( 'node_click' ); + var jqjson = $.getJSON( ncpath, 'node_id=' + node_id, function(data) { + $('#constructedtext').empty(); + $.each( data, function(item, node_id_and_state) { + node = get_node_obj( node_id_and_state[0] ); + // 1 -> turn the associated SVG node on, put in the associate word in the text box. + // 0 -> turn SVG node off. + // null -> turn node off, put in ellipsis in text box at the corresponding place. + if( node_id_and_state[1] == 1 ) { +//TODO: create test suite en refactor this in to more OO! (node and node_ellipse are 'conflated') + node_ellipse = $('.node').children('title').filter( function(index) { + return $(this).text() == node_id_and_state[0]; + }).siblings('ellipse'); + $('#constructedtext').append( node_ellipse.siblings('text').text() + ' ' ); + if( node ) { node.set_draggable( false ) } + } else { + if( node ) { node.set_draggable( true ) }; + if( node_id_and_state[1] == null ) { + $('#constructedtext').append( ' … ' ); + } + } + }); + }); + } + + this.set_draggable = function( draggable ) { + if( draggable ) { + self.ellipse.attr( {stroke:'black', fill:'#fff'} ); + self.ellipse.mousedown( this.mousedown_listener ); + self.ellipse.hover( this.enter_node, this.leave_node ); + } else { + self.ellipse.unbind('mouseenter').unbind('mouseleave').unbind('mousedown'); + self.ellipse.attr( {stroke:'green', fill:'#b3f36d'} ); + } + } + + this.mousedown_listener = function(evt) { + evt.stopPropagation(); + self.x = evt.clientX; + self.y = evt.clientY; + $('body').mousemove( self.mousemove_listener ); + $('body').mouseup( self.mouseup_listener ); + self.ellipse.unbind('mouseenter').unbind('mouseleave') + self.ellipse.attr( 'fill', '#ff66ff' ); + } + + this.mousemove_listener = function(evt) { + self.dx = evt.clientX - self.x; + self.dy = evt.clientY - self.y; + self.move_elements(); + } + + this.mouseup_listener = function(evt) { + if( $('ellipse[fill="#ffccff"]').size() > 0 ) { + $('#source_node_id').val( self.ellipse.siblings('title').text() ); + $('#target_node_id').val( $('ellipse[fill="#ffccff"]').siblings("title").text() ); + $( '#dialog-form' ).dialog( 'open' ); + }; + $('body').unbind('mousemove'); + $('body').unbind('mouseup'); + self.ellipse.attr( 'fill', '#fff' ); + self.ellipse.hover( self.enter_node, self.leave_node ); + if( self.super_node ) { + self.eclipse(); + } else { + self.reset_elements(); + } + } + + this.cpos = function() { + return { x: self.ellipse.attr('cx'), y: self.ellipse.attr('cy') }; + } + + this.get_g = function() { + return self.ellipse.parent('g'); + } + + this.stack_behind = function( collapse_info ) { + self.super_node = get_node_obj( collapse_info.target ); + self.super_node.sub_nodes.push( self ); + self.eclipse(); + if( collapse_info.edges ) { + $.each( collapse_info.edges, function( source_edge_id, target_info ) { + get_edge(source_edge_id).attr( 'display', 'none' ); + target_edge = get_edge(target_info.target); + // Unfortunately, the simple solution doesn't work... + // target_edge.children( 'text' ).replaceWith( 'A, B, C' ); + // ..so we take the long and winding road... + var svg = $('#svgbasics').children('svg').svg().svg('get'); + textx = target_edge.children( 'text' )[0].x.baseVal.getItem(0).value + texty = target_edge.children( 'text' )[0].y.baseVal.getItem(0).value + current_label = target_edge.children( 'text' ).text(); + target_edge.children( 'text' ).remove(); + texts = svg.createText(); + texts.span(current_label, {'text-anchor': 'middle'}).span(target_info.label, {fill: 'red'}); + svg.text(target_edge, textx, texty, texts); + }); + } + } + + this.eclipse = function() { + self.dx = new Number( self.super_node.cpos().x ) - new Number( self.cpos().x ) + ( 10 * (self.super_node.sub_nodes.indexOf(self) + 1) ); + self.dy = new Number( self.super_node.cpos().y ) - new Number( self.cpos().y ) + ( 5 * (self.super_node.sub_nodes.indexOf(self) + 1) ); + self.move_elements(); + eclipse_index = self.super_node.sub_nodes.indexOf(self) - 1; + if( eclipse_index > -1 ) { + self.get_g().insertBefore( self.super_node.sub_nodes[eclipse_index].get_g() ); + } else { + self.get_g().insertBefore( self.super_node.get_g() ); + } + } + + this.enter_node = function(evt) { + self.ellipse.attr( 'fill', '#ffccff' ); + } + + this.leave_node = function(evt) { + self.ellipse.attr( 'fill', '#fff' ); + } + + this.move_elements = function() { + $.each( self.node_elements, function(index, value) { + value.move(self.dx,self.dy); + }); + } + + this.reset_elements = function() { + $.each( self.node_elements, function(index, value) { + value.reset(); + }); + } + + this.ellipse.dblclick( this.dblclick_listener ); + self.set_draggable( true ); +} + +function svgshape( shape_element ) { + this.shape = shape_element; + this.move = function(dx,dy) { + this.shape.attr( "transform", "translate(" + dx + " " + dy + ")" ); + } + this.reset = function() { + this.shape.attr( "transform", "translate( 0, 0 )" ); + } +} + +function svgpath( path_element ) { + this.path = path_element; + this.x = this.path.x; + this.y = this.path.y; + this.move = function(dx,dy) { + this.path.x = this.x + dx; + this.path.y = this.y + dy; + } + this.reset = function() { + this.path.x = this.x; + this.path.y = this.y; + } +} + +function node_elements_for( ellipse ) { + node_elements = get_edge_elements_for( ellipse ); + node_elements.push( new svgshape( ellipse.siblings('text') ) ); + node_elements.push( new svgshape( ellipse ) ); + return node_elements; +} + +function get_edge_elements_for( ellipse ) { + edge_elements = new Array(); + node_id = ellipse.siblings('title').text(); + edge_in_pattern = new RegExp( node_id + '$' ); + edge_out_pattern = new RegExp( '^' + node_id ); + $.each( $('.edge').children('title'), function(index) { + title = $(this).text(); + if( edge_in_pattern.test(title) ) { + edge_elements.push( new svgshape( $(this).siblings('polygon') ) ); + path_segments = $(this).siblings('path')[0].pathSegList; + edge_elements.push( new svgpath( path_segments.getItem(path_segments.numberOfItems - 1) ) ); + } + if( edge_out_pattern.test(title) ) { + path_segments = $(this).siblings('path')[0].pathSegList; + edge_elements.push( new svgpath( path_segments.getItem(0) ) ); + } + }); + return edge_elements; +} + +$(document).ready(function () { + $('#graph').ajaxError(function() { + console.log( 'Oops.. something went wrong with trying to save this change. Please try again...' ); + }); + $('#graph').mousedown(function (event) { + $(this) + .data('down', true) + .data('x', event.clientX) + .data('scrollLeft', this.scrollLeft); + return false; + }).mouseup(function (event) { + $(this).data('down', false); + }).mousemove(function (event) { + if ($(this).data('down') == true ) { + this.scrollLeft = $(this).data('scrollLeft') + $(this).data('x') - event.clientX; + } + }).mousewheel(function (event, delta) { + this.scrollLeft -= (delta * 30); + }).css({ + 'overflow' : 'hidden', + 'cursor' : '-moz-grab' + }); + $( "#dialog-form" ).dialog({ + autoOpen: false, + height: 150, + width: 250, + modal: true, + buttons: { + "Ok": function() { + form_values = $('#collapse_node_form').serialize() + ncpath = getRelativePath( 'node_collapse' ); + var jqjson = $.getJSON( ncpath, form_values, function(data) { + $.each( data, function(item, collapse_info) { + get_node_obj( item ).stack_behind( collapse_info ); + }); + }); + $( this ).dialog( "close" ); + }, + Cancel: function() { + $( this ).dialog( "close" ); + } + }, + close: function() { + $('#reason').val( "" ).removeClass( "ui-state-error" ); + } + }); +}); + + +$(window).mouseout(function (event) { + if ($('#graph').data('down')) { + try { + if (event.originalTarget.nodeName == 'BODY' || event.originalTarget.nodeName == 'HTML') { + $('#graph').data('down', false); + } + } catch (e) {} + } +}); + + diff --git a/TreeOfTexts/root/src/relate.tt b/TreeOfTexts/root/src/relate.tt new file mode 100644 index 0000000..4ef43c3 --- /dev/null +++ b/TreeOfTexts/root/src/relate.tt @@ -0,0 +1,22 @@ +[% BLOCK js %] + + + +[% END %] + +
+ + +[% FOREACH wit IN witnesses -%] + +[% END -%] + +[% FOREACH row IN alignment -%] + +[% FOREACH item in row -%] + +[% END -%] + +[% END -%] +
[% wit %]
[% item %]
+
\ No newline at end of file diff --git a/TreeOfTexts/t/view_JSON.t b/TreeOfTexts/t/view_JSON.t new file mode 100644 index 0000000..aa5a7ce --- /dev/null +++ b/TreeOfTexts/t/view_JSON.t @@ -0,0 +1,8 @@ +use strict; +use warnings; +use Test::More; + + +BEGIN { use_ok 'TreeOfTexts::View::JSON' } + +done_testing();