ensembl-hive  2.7.0
GraphViz.pm
Go to the documentation of this file.
1 =pod
2 
3 =head1 NAME
4 
6 
7 =head1 DESCRIPTION
8 
9  An extension of GraphViz that employs a collection of hacks
10  to use some functionality of dot that is not available through GraphViz.
11 
12  There are at least 3 areas where we need it:
13  (1) passing in some parameters of the drawing (such as pad => ...)
14  (2) drawing clusters (boxes) around semaphore fans
15  (3) using the newest node types (such as Mrecord, tab and egg) with HTML-like labels
16 
17 =head1 EXTERNAL DEPENDENCIES
18 
19  GraphViz
20 
21 =head1 LICENSE
22 
23  See the NOTICE file distributed with this work for additional information
24  regarding copyright ownership.
25 
26  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
27  You may obtain a copy of the License at
28 
29  http://www.apache.org/licenses/LICENSE-2.0
30 
31  Unless required by applicable law or agreed to in writing, software distributed under the License
32  is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
33  See the License for the specific language governing permissions and limitations under the License.
34 
35 =head1 CONTACT
36 
37  Please subscribe to the Hive mailing list: http://listserver.ebi.ac.uk/mailman/listinfo/ehive-users to discuss Hive-related questions or to be notified of our updates
38 
39 =cut
40 
41 
42 package Bio::EnsEMBL::Hive::Utils::GraphViz;
43 
44 use strict;
45 use warnings;
46 use base ('GraphViz');
47 
48 
49 =head2 new
50 
51  Title : new (constructor)
52  Function: Instantiates a new Utils::GraphViz object
53  by injecting some variables unsupported by GraphViz (but understood by dot) directly into the dot output.
54  We rely on a particular quoting pattern used in dot's input format to fool GraphViz (which luckily doesn't escape quotes).
55 
56 =cut
57 
58 sub new {
59  my $class = shift @_;
60  my %all_params = @_;
61 
62  my $ratio_value = delete $all_params{'ratio'} || 'compress';
63  my $injection = join('', map { '"; ' . $_ . ' = "' . $all_params{$_} } sort keys %all_params);
64 
65  return $class->SUPER::new( 'ratio' => $ratio_value.$injection );
66 }
67 
68 
69 sub cluster_2_nodes {
70  my $self = shift @_;
71  if(@_) {
72  $self->{_cluster_2_nodes} = shift @_;
73  }
74  return $self->{_cluster_2_nodes} ||= {};
75 }
76 
77 
78 sub cluster_2_attributes {
79  my $self = shift @_;
80  if(@_) {
81  $self->{_cluster_2_attributes} = shift @_;
82  }
83  return $self->{_cluster_2_attributes} ||= {};
84 }
85 
86 
87 sub nested_bgcolour {
88  my $self = shift @_;
89  if(@_) {
90  $self->{_nested_bgcolour} = shift @_;
91  }
92  return $self->{_nested_bgcolour};
93 }
94 
95 
96 sub dot_input_filename {
97  my $self = shift @_;
98  if(@_) {
99  $self->{_dot_input_filename} = shift @_;
100  }
101  return $self->{_dot_input_filename};
102 }
103 
104 
105 sub display_subgraph {
106  my ($self, $cluster_name, $depth) = @_;
107 
108  my $cluster_attributes = $self->cluster_2_attributes->{$cluster_name};
109 
110  my ($box_colour_pair, $auto_colour) = $cluster_attributes->{ 'fill_colour_pair' } || ($self->nested_bgcolour, 1);
111  my $cluster_label = $cluster_attributes->{'cluster_label'} || '';
112 
113  my $prefix = "\t" x $depth;
114  my $text = '';
115  $text .= $prefix . "subgraph \"cluster_${cluster_name}\" {\n"; # NB: the "cluster_" prefix absolutely must be present.
116  $text .= $prefix . qq{\tlabel="$cluster_label";\n}; # In case some levels need the labels and some don't, need to override the parent level
117 
118  if($box_colour_pair && @$box_colour_pair) {
119  unshift(@$box_colour_pair, 'X11') if(scalar(@$box_colour_pair) == 1); # if it was just a simple colour, add the default palette
120 
121  my ($colour_scheme, $colour_offset) = @$box_colour_pair;
122  my $adjusted_colour = $auto_colour ? $colour_offset+$depth : $colour_offset;
123 
124  my @style_components = split(/,/, $cluster_attributes->{ 'style' } || 'noborder,filled');
125  my $noborder = grep /noborder/, @style_components;
126  my $cluster_style = join(',', grep !/noborder/, @style_components);
127 
128  $text .= $prefix . qq{\tstyle="$cluster_style";\n};
129  $text .= $prefix . qq{\tcolorscheme="$colour_scheme";\n};
130  $text .= $prefix . qq{\tfillcolor="$adjusted_colour";\n};
131  $text .= $prefix . qq{\tcolor="} . ( $noborder ? $adjusted_colour : '') .qq{";\n}; # NB: empty string is needed to reset back to default
132 
133  } # otherwise just draw a black frame around the subgraph
134 
135  foreach my $node_name ( sort @{ $self->cluster_2_nodes->{ $cluster_name } || [] } ) {
136 
137  if( exists $self->cluster_2_nodes->{ $node_name } or exists $self->cluster_2_attributes->{$node_name} ) {
138  $text .= $self->display_subgraph( $node_name, $depth+1 );
139  } else {
140  $text .= $prefix . "\t${node_name};\n";
141  }
142  }
143  $text .= $prefix . "}\n";
144 
145  return $text;
146 }
147 
148 
149 sub top_level_cluster_names {
150  my $self = shift @_;
151 
152  my %top_level_candidate_set = map { ($_ => 1) } keys %{ $self->cluster_2_nodes };
153 
154  # remove all keys that have been mentioned in the values (subclusters) :
155  foreach my $vector (values %{ $self->cluster_2_nodes }) {
156  foreach my $element (@$vector) {
157  if(exists $top_level_candidate_set{$element}) {
158  delete $top_level_candidate_set{$element};
159  }
160  }
161  }
162 
163  # what remains are the top-level clusters:
164  return [ sort keys %top_level_candidate_set ];
165 }
166 
167 
168 sub _as_debug {
169  my $self = shift @_;
170 
171  my $text = $self->SUPER::_as_debug;
172 
173  $text=~s/^}$//m;
174 
175  foreach my $top_level_cluster_name (@{ $self->top_level_cluster_names }) {
176  $text .= $self->display_subgraph( $top_level_cluster_name, 0);
177  }
178  $text .= "}\n";
179 
180  # GraphViz.pm thinks 'record' is the only shape that allows HTML-like labels,
181  # but newer versions of dot allow more freedom.
182  # Since we wanted to stick with the older GraphViz, we initially ask for shape="record",
183  # but put the desired shape into the comment and patch dot input after generation:
184  #
185  $text=~s/\bcomment="new_shape:(\w+)",\s(.*shape=)"record"/$2"$1"/mg;
186 
187  if(my $dot_input_filename = $self->dot_input_filename) {
188  if (ref($dot_input_filename)) {
189  print $dot_input_filename $text;
190  } else {
191  open(my $dot_input, ">", $dot_input_filename) or die "cannot open > $dot_input_filename : $!";
192  print $dot_input $text;
193  close $dot_input;
194  }
195  }
196 
197  return $text;
198 }
199 
200 
201 sub add_node {
202  my $self = shift @_;
203  my $node_name = shift @_;
204  my %param_hash = @_;
205 
206  my $desired_shape = delete $param_hash{'shape'}; # smuggle in the desired shape as a comment, to be substituted later by _as_debug() method
207 
208  return $self->SUPER::add_node($node_name, %param_hash, $desired_shape ? (shape => 'record', comment => qq{new_shape:$desired_shape}) : () );
209 }
210 
211 
212 sub protect_string_for_display { # NB: $self is only needed for calling, and isn't used in any other way
213 
214  my ($self, $string, $length_limit, $drop_framing_curlies) = @_;
215 
216  if($drop_framing_curlies) {
217  $string=~s/^\{//; # drop leading curly
218  $string=~s/\}$//; # drop trailing curly
219  }
220 
221  if(defined( $length_limit )) {
222  my $replacement_string = ' ...';
223  if (length($string) > $length_limit) {
224  # shorten down to $length_limit characters
225  $string = substr($string, 0, $length_limit-length($replacement_string)).$replacement_string;
226  }
227  }
228 
229  $string=~s{&}{&}g; # Since we are in HTML context now, ampersands should be escaped (first thing after trimming)
230  $string=~s{"}{"}g; # should fix a string display bug for pre-2.16 GraphViz'es
231  $string=~s{<}{&lt;}g;
232  $string=~s{>}{&gt;}g;
233 
234  return $string;
235 }
236 
237 1;
238 
EnsEMBL
Definition: Filter.pm:1
map
public map()
Bio::EnsEMBL::Hive::Utils::GraphViz::cluster_2_attributes
public cluster_2_attributes()
Bio::EnsEMBL::Hive::Version
Definition: Version.pm:19
Bio::EnsEMBL::Hive::Utils::GraphViz
Definition: GraphViz.pm:16
Bio::EnsEMBL::Hive
Definition: Hive.pm:38
Bio
Definition: AltAlleleGroup.pm:4
Bio::EnsEMBL::Hive::Utils
Definition: Utils.pm:35