4 CXGN::Sunshine::Browser - a class that implements a network browser for SGN loci.
10 Lukas Mueller <lam87@cornell.edu>
14 This class implements the following methods:
22 package CXGN
::Sunshine
::Browser
;
28 use CXGN
::Sunshine
::Node
;
29 use CXGN
::Phenome
::Locus
;
30 #use CXGN::Phenome::Locus2Locus;
33 use base qw
| CXGN
::DB
::Object
|;
38 =head2 include_on_page
40 Usage: CXGN::Sunshine::Browser->include_on_page("locus", $id)
41 Desc: includes the browser on the respective page
42 by including its javascript code
44 Args: the type of object, and an object id
45 Side Effects: prints the browser code to STDOUT
59 <table><tr><td height="450" width="450"><div id=\"network_browser\" >\[loading...\]</div></td><td width="250"><div id="relationships_legend">[Legend]</div><br /><div id="level_selector">[Levels]</div></td></tr></table>
61 <script language="javascript" type="text/javascript">
63 // document.write('HELLO FROM JAVASCRIPT');
65 var nb = new CXGN.Sunshine.NetworkBrowser();
70 nb.fetchRelationships();
71 //nb.setHiddenRelationshipTypes('');
76 //document.write("....ANOTHER HELLO FROM JS");
90 Usage: my $browser = CXGN::Sunshine::Browser->new($dbh)
92 Ret: a CXGN::Sunshine::Browser object
93 Args: a database handle
94 Side Effects: connects to the database
102 my $self = $class->SUPER::new($dbh);
104 # set a standard image size
106 $self->set_image_width(400);
107 $self->set_image_height(400);
108 $self->set_level_depth(1);
110 $self->set_hide_relationships( () ) ;
112 $self->fetch_relationships();
114 $self->set_image(GD::Image->new($self->get_image_width(), $self->get_image_height(), 1));
116 $self->get_image()->filledRectangle(0, 0, $self->get_image_width(), $self->get_image_height(), $self->get_image()->colorAllocate(255, 255, 255));
123 =head2 accessors get_graph, set_graph
125 Usage: $b->set_graph($graph)
126 Desc: The Graph object that this instance should
128 Property a L<Graph> object
136 return $self->{graph};
141 $self->{graph} = shift;
147 Usage: $b->build_graph($ref_node_name, $ref_node_type, $graph)
148 Desc: builds the graph $graph for node ref_node_name, and the
152 Side Effects: the graph $graph is modified and database access is possible
160 my $ref_node_name = $self->get_ref_node_name();
161 my $ref_node_type = $self->get_ref_node_type();
163 my $graph = Graph::Undirected->new();
165 # recursively get all the connection to the reference node.
167 $self->d("TYPE IS $ref_node_type. NAME IS $ref_node_name.\n");
168 if ($ref_node_type eq "test") {
169 # $self->d("Getting test data...\n");
170 $self->get_test_graph($graph);
172 elsif ($ref_node_type eq "locus") {
173 $self->d("GETTING LOCUS INFORMATION...\n");
174 $self->get_locus_graph($ref_node_name, $graph, $self->get_level_depth());
176 $self->set_graph($graph);
180 =head2 get_locus_graph
182 Usage: called by build_graph() if graph type is 'locus
'.
183 Desc: builds a graph in the Graph object (a Perl class).
184 Ret: nothing, but changes the datastructure in graph.
185 Args: a locus_id [int], a graph [Graph], and level [int]
186 the level will determine how 'deep
' the graph will be.
192 sub get_locus_graph {
194 my $locus_id = shift;
196 my $max_level = shift;
198 my $reference_locus = CXGN::Phenome::Locus->new($self->get_dbh(), $locus_id);
199 my $reference_node = CXGN::Sunshine::Node->new($locus_id);
201 $reference_node->set_name($reference_locus->get_locus_symbol());
202 $reference_node->set_unique_id($locus_id);
204 $self->add_node_list($reference_node);
207 my (@loci) = ($locus_id);
209 my %already_processed = ( );
211 # my %associations = ();
213 my %relationship_cache = ();
219 # foreach my $l (1..($level)) {
220 # $self->d("PROCESSING LEVEL $l...\n");
223 foreach my $r (@loci) {
225 #$self->d("...processing locus $r\n");
227 if (exists($already_processed{$r})) { # ||
228 #( $r == $reference_node && $level ==1)) {
229 $self->d("......this locus was already processed. Skipping.\n");
233 if (! exists($loci_cache{$r})) {
234 $loci_cache{$r} = CXGN::Phenome::Locus->new($self->get_dbh(), $r);
238 #######my @object_loci = $loci_cache{$r}->get_object_locus2locus_objects();
239 my @locus_groups= $loci_cache{$r}->get_locusgroups();
241 # print STDERR "HIDDEN RELTYPES: ".(join ", ", ($self->get_hide_relationships()));
245 # go through all loci and build a list with some meta information
250 foreach my $group (@locus_groups) {
251 if (!$self->relationship_hidden($group->get_relationship_id())) {
252 my @members=$group->get_locusgroup_members();
253 $all_groups{ $group->get_locusgroup_id() } = $group;
254 foreach my $member(@members) {
255 my $member_id = $member->get_column('locus_id
');
256 my $member_locus=CXGN::Phenome::Locus->new($self->get_dbh(), $member_id);
257 my @member_groups = $member_locus->get_locusgroups();
258 foreach my $mg (@member_groups) {
259 if (!defined $all_groups{ $mg->get_locusgroup_id() } ) {
260 $all_groups{ $mg->get_locusgroup_id } = $mg;
265 $self->d("Relationship ".($group->get_relationship_id())." currently hidden!\n");
268 foreach my $locusgroup_id( keys %all_groups ) {
269 my $group= $all_groups{$locusgroup_id};
270 if (!$self->relationship_hidden($group->get_relationship_id())) {
272 my @members=$group->get_locusgroup_members();
273 foreach my $member (@members) {
274 my $member_id = $member->get_column('locus_id
');
275 if ($member_id == $locus_id) { next() ; }
276 my @list = ($member->get_column('locus_id
'), $group->get_relationship_id());
277 $loci_list{ join "-", @list } = \@list;
282 foreach my $a (values %loci_list) {
285 if (!exists($loci_cache{$a->[0]})) {
286 $loci_cache{$a->[0]} = CXGN::Phenome::Locus->new($self->get_dbh(), $a->[0]);
290 if (!defined($loci_cache{$a->[0]}->get_locus_id())) {
291 # die "The locus (".($a->[0]).") is not defined... Skipping.\n";
295 my $node = CXGN::Sunshine::Node->new($loci_cache{$a->[0]}->get_locus_id());
297 $node->set_level($level);
298 $node->set_name($loci_cache{$a->[0]}->get_locus_symbol());
299 $self->add_node_list($node);
301 $node->set_unique_id($loci_cache{$a->[0]}->get_locus_id());
305 $graph->add_vertex($loci_cache{$a->[0]}->get_locus_id());
307 if (!exists($loci_cache{$r}) || !defined($loci_cache{$r}->get_locus_id())) {
308 die "Locus $r does not exist... \n";
312 # check if either of the nodes fall outside of the currently viewable levels
314 if ($level <= ($max_level) || $self->get_node($loci_cache{$a->[0]}->get_locus_id())->get_level() <=$max_level) {
315 $self->d("Generating the edge from ".($loci_cache{$a->[0]}->get_locus_symbol())." and ".($loci_cache{$r}->get_locus_symbol())."\n");
316 $graph->add_edge($loci_cache{$r}->get_locus_id(), $loci_cache{$a->[0]}->get_locus_id());
317 $graph->set_edge_attribute($loci_cache{$r}->get_locus_id(), $loci_cache{$a->[0]}->get_locus_id(), "relationship_type", $a->[1]);
320 $already_processed{$r}=1;
323 $continue = @loci != values(%loci_list);
326 @loci = map { $_->[0] } values(%loci_list) ;
330 =head2 fetch_relationships
332 Usage: my $json = $b->fetch_relationships()
333 Desc: retrieves all relationship ids from the database (cvterms)
334 and returns them as a json string.
343 sub fetch_relationships {
345 my $q = "SELECT distinct(cvterm_id), locusgroup.locusgroup_id FROM phenome.locusgroup JOIN cvterm on(locusgroup.relationship_id=cvterm_id)";
346 my $h = $self->get_dbh()->prepare($q);
349 while (my ($cvterm_id, $lg_id) = $h->fetchrow_array()) {
350 $self->{relationship_ids}->{$lg_id}= CXGN::Chado::Cvterm->new($self->get_dbh(), $cvterm_id);
355 =head2 get_relationship_menu_info
366 sub get_relationship_menu_info {
368 my $q = "SELECT DISTINCT(relationship_id), cvterm_id FROM phenome.locusgroup JOIN public.cvterm on (locusgroup.relationship_id=cvterm_id)";
369 my $h = $self->get_dbh()->prepare($q);
371 my @relationship_menu_info = ();
372 while (my ($relationship_id, $cvterm_id) = $h->fetchrow_array()) {
373 my $cvterm = CXGN::Chado::Cvterm->new($self->get_dbh(), $cvterm_id);
374 push @relationship_menu_info, {"id"=>$relationship_id, "name"=>$cvterm->get_cvterm_name(), "color"=>join(",",$self->get_relationshiptype_color($relationship_id))};
378 my $json = JSON->new();
379 my $jobj = $json->objToJson(\@relationship_menu_info);
383 =head2 relationship_hidden
385 Usage: my $hidden = $b->relationship_hidden($relationship_id)
386 Desc: returns true if the relationship with relationship_id
387 $relationship_id is hidden, false otherwise.
395 sub relationship_hidden {
398 foreach my $r ($self->get_hide_relationships()) {
407 =head2 accessors get_hide_relationships, set_hide_relationships
409 Usage: $b->set_hide_relationships($relationship_id1, ... )
410 Desc: the relationships with $relationship_id1 and other
411 listed relationships will not be shown on the browser.
413 Side Effects: the specified list of relationship types will be
414 hidden in the browser. The list of relationship types
415 will have the corresponding relationships unchecked.
420 sub get_hide_relationships {
422 return @{$self->{hide_relationships}};
425 sub set_hide_relationships {
427 @{$self->{hide_relationships}} = @_;
430 sub get_pathway_graph {
434 open (F, "data/lycocyc_dump.txt");
438 my ($pathway, $ec, $reaction, $unigene) = split /\t/;
449 my @connections = ();
451 my $center = CXGN::Sunshine::Node->new("0");
452 $self->add_node_list($center);
454 $connections[$i] = CXGN::Sunshine::Node->new($i);
456 $self->add_node_list($connections[$i]);
457 $graph->add_vertex($i);
459 $graph->add_edge($i, $center->get_unique_id());
460 if ($i>1) { $graph->add_edge($i, $i-1);}
464 $connections[$i] = CXGN::Sunshine::Node->new($i);
465 $self->add_node_list($connections[$i]);
466 $graph->add_vertex($i);
467 $graph->add_edge($i, $i-5);
471 # =head2 get_locus2locus_connections
482 # sub get_locus2locus_connections {
486 # my @connections = $node->get_associated_loci();
487 # $graph->add_vertex($node->get_locus_id());
488 # $self->add_node_list($node);
490 # my @more_connections = ();
491 # foreach my $c (@connections) {
492 # @more_connections = $self->get_locus2locus_connections($graph, $c);
494 # @connections = (@connections, @more_connections);
495 # return @connections;
512 if (exists($self->{nodes}->{$node->get_unique_id()})) {
513 #warn "Node named ".$node->get_unique_id()." already exists!";
516 $self->{nodes}->{$node->get_unique_id()}=$node;
533 return values(%{$self->{nodes}});
550 return $self->{nodes}->{$id};
554 =head2 get_level_depth, set_level_depth
565 sub get_level_depth {
567 return $self->{level_depth};
571 sub set_level_depth {
573 $self->{level_depth}=shift;
576 =head2 accessors get_reference_object, set_reference_object
586 sub get_reference_object {
588 return ($self->{reference_object_id}, $self->{reference_object_type});
591 sub set_reference_object {
595 $self->{reference_object_id} = $id;
596 $self->{reference_object_type} = $type;
600 =head2 accessors get_reference_node, set_reference_node
610 sub get_reference_node {
612 return $self->{reference_node};
615 sub set_reference_node {
619 # return an error if the node is not part of the graph
620 if (!exists($self->{nodes}->{$node->get_unique_id()})) {
623 $self->{reference_node} = $node;
650 Desc: lays out the graph
659 # get the smaller dimension
660 my $dimension = $self->get_image_height();
661 if ($self->get_image_width() < $self->get_image_height()) {
662 $dimension = $self->get_image_width();
665 # define the positions of all the other nodes.
666 my $radius = $dimension / $self->get_level_depth() / 2.5;
667 $self->set_radius($radius);
668 my @n = $self->get_graph()->neighbours($self->get_reference_node()->get_unique_id());
670 $self->d("Checking graph...\n");
672 $self->d("Retrieved neighbour $n\n");
673 if ($n eq $self->get_reference_node()->get_unique_id()) {
674 die "The reference node is among the linked nodes, you fool!\n";
678 $self->calculate_nodes_by_level();
680 # define the position of the reference node
682 $self->get_reference_node()->set_X(int($self->get_image_width()/2));
683 $self->get_reference_node()->set_Y(int($self->get_image_height()/2));
685 # deal with the nodes on the other levels
687 foreach my $level (0..$self->get_level_depth()) {
689 my @n = $self->get_nodes_by_level($level);
691 #print STDERR "Layout: level $level, laying out nodes ".(join " ", @n)."\n";
693 for (my $i=0; $i<@n; $i++) {
695 # calculate the angle as the circumference of the circle divided by n,
696 # and adding a little offset to each level so that we don't get
697 # exact alignments on the same grid (lines cross too much and make
698 # the whole picture less clear)
700 my $angle = (2 * $PI * $i + $level * $level * 0.1 * $PI )/ scalar(@n); #
701 my ($x, $y) = $self->deg2coords($radius * $level, $angle);
703 $self->get_node($n[$i])->set_X($x + int($self->get_image_width()/2));
704 $self->get_node($n[$i])->set_Y($y + int($self->get_image_height()/2));
705 #print STDERR "Node $i:\n";
706 #print STDERR "X: ".$self->get_node($n[$i])->get_X()."\n";
707 #print STDERR "Y: ".$self->get_node($n[$i])->get_Y()."\n";
713 =head2 calculate_nodes_by_level
715 Usage: $b->calculate_nodes_by_level()
716 Desc: calculates which nodes are on which level
717 the levels are defined as containing loci with
718 direct relationships to the previous level. Level 0
719 is the reference entity.
720 The nodes on each level calculated here can be
721 retrieved using get_nodes_by_level() (see below).
729 sub calculate_nodes_by_level
{
732 my $level = $self->get_level_depth();
734 my $ref = $self->get_reference_node()->get_unique_id();
736 my %previous_level_nodes = ($ref => 1);
738 my @level_nodes = (); # an array for each level, containing a hash for the elements
740 $level_nodes[0]->{$ref} =1;
742 foreach my $l (1..$level) {
743 #print STDERR "Generating the unique nodes for level $l...\n";
745 foreach my $n (keys (%{$level_nodes[$l-1]})) {
746 foreach my $neighbor ($self->get_graph()->neighbours($n)) {
747 #print STDERR "NEIGHBOR of $n is $neighbor\n";
748 $level_nodes[$l]->{$neighbor}=1;
752 # remove the nodes that occur in the previous levels
753 foreach my $k (keys %previous_level_nodes) {
754 if (exists($level_nodes[$l]->{$k})) {
755 delete($level_nodes[$l]->{$k});
759 # # add this level to the previous level nodes
760 # # to exclude relationships in the next iteration
761 foreach my $k (keys(%{$level_nodes[$l]})) {
762 $previous_level_nodes{$k}=1;
766 $self->{nodes_by_level
} = \
@level_nodes;
767 %{$self->{all_nodes
}} = %previous_level_nodes;
769 # reorder the current level of the graph such that nodes with
770 # connections to nodes of the previous level are close the those
774 # foreach my $l (0..$level-1) {
775 # #foreach my $x (1..2) {
777 # for(my $m=0; $m<@{$nodes->[$l]}; $m++) {
778 # for (my $n=0; $n<@{$nodes->[$l]}; $n++) {
779 # if ($self->get_graph()->has_edge($nodes->[$l]->[$m],
780 # $nodes->[$l+1]->[$n]
782 # print STDERR "Swapping nodes $nodes->[$l]->[$m] and $nodes->[$l]->[$n]...\n";
783 # ($nodes->[$l]->[$n], $nodes->[$l]->[$m]) =
784 # ($nodes->[$l]->[$m], $nodes->[$l]->[$n]);
788 # @{$self->{nodes_by_level}->[$l]}=@{$nodes->[$l]};
796 =head2 get_nodes_by_level
798 Usage: my @nodes = $b->get_nodes_by_level($level)
799 Desc: returns the nodes that belong to level $level.
800 Ret: a list of CXGN::Sunshine::Node objects
807 sub get_nodes_by_level
{
810 if (!exists($self->{nodes_by_level
}->[$level])) {
811 $self->{nodes_by_level
}->[$level] = {};
813 return keys %{$self->{nodes_by_level
}->[$level]};
816 =head2 accessors get_previously_clicked_nodes, set_previously_clicked_nodes
818 Usage: [not yet implemented]
826 sub get_previously_clicked_nodes
{
828 return @
{$self->{previously_clicked_nodes
}};
831 sub set_previously_clicked_nodes
{
833 @
{$self->{previously_clicked_nodes
}} = @_;
836 =head2 accessors get_hilited_nodes, set_hilited_nodes
838 Usage: [ not yet implemented]
846 sub get_hilited_nodes
{
848 return $self->{hilited_nodes
};
851 sub set_hilited_nodes
{
853 $self->{hilited_nodes
} = shift;
856 =head2 function get_relationshiptype_color()
858 Usage: ($r, $g, $b) = $b->get_relationshiptype_color($rel)
859 Desc: returns the color in red, green and blue components
860 used for the relationshiptype given by $rel.
861 Ret: three values defining a color
862 Args: the relationshiptype_id [int]
866 sub get_relationshiptype_color
{
868 my $relationship_id = shift;
871 my %color = ( 39638 => [ 255, 0, 0 ],
872 39639 => [ 0, 255, 0 ],
873 39640 => [ 0, 0, 255 ],
874 39642 => [ 255, 255, 0 ],
875 39649 => [ 0, 255, 255 ],
876 39650 => [ 255, 0, 255 ],
878 if (!defined($color{$relationship_id})) { return (0, 0, 0); }
879 return @
{$color{$relationship_id}};
884 =head2 function render()
898 my $reference_node = $self->get_reference_node();
899 $reference_node->render($self->get_image());
901 my $gray = $self->get_image()->colorAllocate(200,200,200);
902 my $black = $self->get_image()->colorAllocate(0, 0, 0);
903 my $blue = $self->get_image()->colorAllocate(0, 0, 255);
905 my @include_nodes = ();
906 foreach my $level (0..$self->get_level_depth()) {
907 my @nodes = $self->get_nodes_by_level($level);
910 my @edges = $self->get_graph()->edges();
911 foreach my $c (@edges) {
913 my ($start, $end) = ($c->[0], $c->[1]);
915 my $start_node = $self->get_node($start);
916 my $end_node = $self->get_node($end);
918 if (!$start_node || !$end_node) {
919 die "$start and /or $end don't exist!\n";
922 my $relationship_id = $self->get_graph()->get_edge_attribute($start_node->get_unique_id(), $end_node->get_unique_id(), "relationship_type");
923 #print STDERR "RELATIONSHIP_ID = $relationship_id\n";
927 my $color = $self->get_image()->colorAllocate($self->get_relationshiptype_color($relationship_id ));
928 $self->get_image()->setAntiAliased($color);
929 $self->get_image()->line($start_node->get_X(),
930 $start_node->get_Y(),
937 foreach my $level (0..$self->get_level_depth()) {
938 #print STDERR "Rendering level $level...\n";
941 $self->get_image->arc($self->get_reference_node()->get_X(),
942 $self->get_reference_node()->get_Y(),
943 2 * $level * $self->get_radius(),
944 2 * $level * $self->get_radius(),
951 foreach my $n ($self->get_nodes_by_level($level)) {
952 my $node = $self->get_node($n);
953 my $unique_id = $node->get_unique_id();
954 my $ref_node_type = $self->get_ref_node_type();
955 my $show_levels = $self->get_level_depth();
956 my $hidden_relationships = join ", ", $self->get_hide_relationships();
957 if (!$hidden_relationships) { $hidden_relationships=0; }
958 $node->set_url("javascript:nb.getImage($unique_id, '$ref_node_type' , $show_levels, $hidden_relationships)");
959 #print STDERR " [$level]: node node\n";
960 $node->render($self->get_image());
970 my $map_name = shift;
972 my $map = qq { <map name
="$map_name" > };
973 foreach my $n ($self->get_node_list()) {
974 $map .= $n->get_image_map() ."\n";
976 $map .= qq { </map>\n };
984 return $self->get_image()->png();
989 my $filename = shift;
992 my $png = $self->get_image->png();
994 open (my $F, ">$filename") || die "Can't open $filename";
999 =head2 accessors get_radius, set_radius
1011 return $self->{radius
};
1016 $self->{radius
} = shift;
1019 =head2 accessors get_image_width, set_image_width
1029 sub get_image_width
{
1031 return $self->{image_width
};
1034 sub set_image_width
{
1036 $self->{image_width
} = shift;
1039 =head2 accessors get_image_height, set_image_height
1049 sub get_image_height
{
1051 return $self->{image_heigth
};
1054 sub set_image_height
{
1056 $self->{image_heigth
} = shift;
1060 =head2 accessors get_image, set_image
1072 return $self->{image
};
1077 $self->{image
} = shift;
1094 my $degrees = shift;
1096 my $x = sin($degrees)* $radius;
1097 my $y = cos($degrees)* $radius;
1103 =head2 accessors get_ref_node_name, set_ref_node_name
1113 sub get_ref_node_name
{
1115 return $self->{ref_node_name
};
1118 sub set_ref_node_name
{
1120 $self->{ref_node_name
} = shift;
1123 =head2 accessors get_ref_node_type, set_ref_node_type
1133 sub get_ref_node_type
{
1135 return $self->{ref_node_type
};
1138 sub set_ref_node_type
{
1140 $self->{ref_node_type
} = shift;