ensembl-hive  2.6
Graph.pm
Go to the documentation of this file.
1 =pod
2 
3 =head1 NAME
4 
6 
7 =head1 SYNOPSIS
8 
9  my $g = Bio::EnsEMBL::Hive::Utils::Graph->new( $hive_pipeline );
10  my $graphviz = $g->build();
11  $graphviz->as_png('location.png');
12 
13 =head1 DESCRIPTION
14 
15  This is a module for converting a hive database's flow of analyses, control
16  rules and dataflows into the GraphViz model language. This information can
17  then be converted to an image or to the dot language for further manipulation
18  in GraphViz.
19 
20 =head1 LICENSE
21 
22  Copyright [1999-2015] Wellcome Trust Sanger Institute and the EMBL-European Bioinformatics Institute
23  Copyright [2016-2024] EMBL-European Bioinformatics Institute
24 
25  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
26  You may obtain a copy of the License at
27 
28  http://www.apache.org/licenses/LICENSE-2.0
29 
30  Unless required by applicable law or agreed to in writing, software distributed under the License
31  is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
32  See the License for the specific language governing permissions and limitations under the License.
33 
34 =head1 CONTACT
35 
36  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
37 
38 =head1 APPENDIX
39 
40  The rest of the documentation details each of the object methods.
41  Internal methods are usually preceded with a _
42 
43 =cut
44 
45 
46 package Bio::EnsEMBL::Hive::Utils::Graph;
47 
48 use strict;
49 use warnings;
50 
51 use Bio::EnsEMBL::Hive::Analysis;
52 use Bio::EnsEMBL::Hive::Utils qw(destringify throw);
53 use Bio::EnsEMBL::Hive::Utils::GraphViz;
54 use Bio::EnsEMBL::Hive::Utils::Config;
55 use Bio::EnsEMBL::Hive::TheApiary;
56 
58 
59 
60 =head2 new()
61 
62  Arg [1] : Bio::EnsEMBL::Hive::HivePipeline $pipeline;
63  The adaptor to get information from
64  Arg [2] : (optional) string $config_file_name;
65  A JSON file name to initialize the Config object with.
66  If one is not given then we don't pass anything into Config's constructor,
67  which results in loading configuration from Config's standard locations.
68  Returntype : Graph object
69  Exceptions : If the parameters are not as required
70  Status : Beta
71 
72 =cut
73 
74 sub new {
75  my $class = shift @_;
76  my $pipeline = shift @_;
77 
78  my $self = bless({}, ref($class) || $class);
79 
80  $self->pipeline( $pipeline );
81 
82  my $config = Bio::EnsEMBL::Hive::Utils::Config->new( @_ );
83  $self->config($config);
84  $self->context( [ 'Graph' ] );
85 
86  return $self;
87 }
88 
89 
90 =head2 graph()
91 
92  Arg [1] : The GraphViz instance created by this module
93  Returntype : GraphViz
94  Exceptions : None
95  Status : Beta
96 
97 =cut
98 
99 sub graph {
100  my ($self) = @_;
101 
102  if(! exists $self->{'_graph'}) {
103 
104  $self->{'_graph'} = Bio::EnsEMBL::Hive::Utils::GraphViz->new(
105  'name' => 'AnalysisWorkflow',
106  'concentrate' => 'true',
107  'pad' => $self->config_get('Pad') || 0,
108  );
109 
110  # Defined on its own because it should not be in the dot output but
111  # Bio::EnsEMBL::Hive::Utils::GraphViz->new passes all its parameters to dot
112  $self->{'_graph'}->{'SORT'} = 1;
113  }
114  return $self->{'_graph'};
115 }
116 
117 
118 =head2 pipeline()
119 
120  Arg [1] : The HivePipeline instance
121  Returntype : HivePipeline
122 
123 =cut
124 
125 sub pipeline {
126  my $self = shift @_;
127 
128  if(@_) {
129  $self->{'_pipeline'} = shift @_;
130  }
131 
132  return $self->{'_pipeline'};
133 }
134 
135 
136 sub _grouped_dataflow_rules {
137  my ($self, $analysis) = @_;
138 
139  my $gdr = $self->{'_gdr'} ||= {};
140 
141  return $gdr->{$analysis} ||= $analysis->get_grouped_dataflow_rules;
142 }
143 
144 
145 sub _analysis_node_name {
146  my ($self, $analysis) = @_;
147 
148  my $analysis_node_name = 'analysis_' . $analysis->relative_display_name( $self->pipeline );
149  if($analysis_node_name=~s/\W/__/g) {
150  $analysis_node_name = 'foreign_' . $analysis_node_name;
151  }
152  return $analysis_node_name;
153 }
154 
155 
156 sub _table_node_name {
157  my ($self, $naked_table) = @_;
158 
159  my $table_node_name = 'table_' . $naked_table->relative_display_name( $self->pipeline );
160  $table_node_name=~s/\W/__/g;
161  return $table_node_name;
162 }
163 
164 
165 sub _accu_sink_node_name {
166  my ($funnel_dfr) = @_;
167 
168  return 'sink_'.(UNIVERSAL::isa($funnel_dfr, 'Bio::EnsEMBL::Hive::DataflowRule') ? _midpoint_name($funnel_dfr) : ($funnel_dfr || ''));
169 }
170 
171 
172 sub _cluster_name {
173  my ($df_rule) = @_;
174 
175  return ( UNIVERSAL::isa($df_rule, 'Bio::EnsEMBL::Hive::DataflowRule') ? 'cl_'._midpoint_name($df_rule) : ($df_rule || 'cl_noname') );
176 }
177 
178 
179 our %_midpoint_ref_to_temp_id;
180 
181 sub _midpoint_name {
182  my ($df_rule) = @_;
183 
184  if (UNIVERSAL::isa($df_rule, 'Bio::EnsEMBL::Hive::DataflowRule')) {
185  my $dfr_id = $df_rule->dbID || $_midpoint_ref_to_temp_id{scalar($df_rule)};
186  unless ($dfr_id) {
187  $dfr_id = 'p'.(scalar(keys %_midpoint_ref_to_temp_id) + 1); # a unique id of a df_rule when dbIDs are not available;
188  $_midpoint_ref_to_temp_id{scalar($df_rule)} = $dfr_id;
189  }
190  return 'dfr_'.$dfr_id.'_mp';
191  } else {
192  throw("Wrong argument to _midpoint_name");
193  }
194 }
195 
196 
197 =head2 build()
198 
199  Returntype : The GraphViz object built & populated
200  Exceptions : Raised if there are issues with accessing the database
201  Description : Builds the graph object and returns it.
202  Status : Beta
203 
204 =cut
205 
206 sub build {
207  my ($self) = @_;
208 
209  my $main_pipeline = $self->pipeline;
210 
211  $self->{'_foreign_analyses'} = {};
212 
213  foreach my $source_analysis ( @{ $main_pipeline->get_source_analyses } ) {
214  # run the recursion in each component that has a non-cyclic start:
215  $self->_propagate_allocation( $source_analysis );
216  }
217  foreach my $cyclic_analysis ( $main_pipeline->collection_of( 'Analysis' )->list ) {
218  next if(defined $cyclic_analysis->{'_funnel_dfr'});
219  $self->_propagate_allocation( $cyclic_analysis );
220  }
221 
222  foreach my $source_analysis ( @{ $main_pipeline->get_source_analyses } ) {
223  # run the recursion in each component that has a non-cyclic start:
224  $self->_add_analysis_node( $source_analysis );
225  }
226  foreach my $cyclic_analysis ( $main_pipeline->collection_of( 'Analysis' )->list ) {
227  next if($self->{'_created_analysis'}{ $cyclic_analysis });
228  $self->_add_analysis_node( $cyclic_analysis );
229  }
230 
231  if($self->config_get('DisplayStretched') ) { # put each analysis before its' funnel midpoint
232  foreach my $analysis ( $main_pipeline->collection_of('Analysis')->list ) {
233  if(ref($analysis->{'_funnel_dfr'})) { # this should only affect analyses that have a funnel
234  my $from = $self->_analysis_node_name( $analysis );
235  my $to = _midpoint_name( $analysis->{'_funnel_dfr'} );
236  $self->graph->add_edge( $from => $to,
237  style => 'invis', # toggle visibility by changing 'invis' to 'dashed'
238  color => 'black',
239  );
240  }
241  }
242  }
243 
244  my %cluster_2_nodes = ();
245  $self->graph->cluster_2_nodes( \%cluster_2_nodes );
246 
247  if( $self->config_get('DisplayDetails') ) {
248  foreach my $pipeline ( Bio::EnsEMBL::Hive::TheApiary->pipelines_collection->list ) {
249  my $pipeline_cluster_name = _cluster_name( $pipeline->hive_pipeline_name );
250  $self->graph->cluster_2_attributes->{ $pipeline_cluster_name }{ 'cluster_label' } = $pipeline_cluster_name;
251  $self->graph->cluster_2_attributes->{ $pipeline_cluster_name }{ 'style' } = 'bold,filled';
252 
253  $self->graph->cluster_2_attributes->{ $pipeline_cluster_name }{ 'fill_colour_pair' } = ($pipeline == $main_pipeline)
254  ? [$self->config_get('Box', 'MainPipeline', 'ColourScheme'), $self->config_get('Box', 'MainPipeline', 'ColourOffset')]
255  : [$self->config_get('Box', 'OtherPipeline', 'ColourScheme'), $self->config_get('Box', 'OtherPipeline', 'ColourOffset')];
256  }
257  }
258 
259  if($self->config_get('DisplaySemaphoreBoxes') ) {
260  foreach my $analysis ( $main_pipeline->collection_of('Analysis')->list, values %{ $self->{'_foreign_analyses'} } ) {
261 
262  push @{$cluster_2_nodes{ _cluster_name( $analysis->{'_funnel_dfr'} ) } }, $self->_analysis_node_name( $analysis );
263 
264  foreach my $group ( @{ $self->_grouped_dataflow_rules($analysis) } ) {
265 
266  my ($df_rule, $fan_dfrs, $df_targets) = @$group;
267 
268  my $choice = (scalar(@$df_targets)!=1) || defined($df_targets->[0]->on_condition);
269 
270  if(@$fan_dfrs or $choice) {
271  # top-level funnels define clusters (top-level "boxes"):
272  push @{$cluster_2_nodes{ _cluster_name( $df_rule->{'_funnel_dfr'} ) }}, _midpoint_name( $df_rule );
273 
274  my $box_needed = 0;
275  foreach my $fan_dfr (@$fan_dfrs) {
276  my $fan_targets = $fan_dfr->get_my_targets; # FIXME: ->get_my_targets() may be too computationally-expensive to run so may times?
277  foreach my $fan_target (@$fan_targets) {
278  my $target_object = $fan_target->to_analysis;
279  if( $target_object->{'_funnel_dfr'} eq $df_rule
280 # or $target_object->hive_pipeline ne $fan_dfr->hive_pipeline # crossing the pipeline boundary
281  ) {
282  $box_needed = 1;
283  }
284  }
285  }
286  if( $box_needed ) {
287  push @{$cluster_2_nodes{ _cluster_name( $df_rule->{'_funnel_dfr'} ) }}, _cluster_name( $df_rule );
288  }
289 
290  foreach my $fan_dfr (@$fan_dfrs) {
291  push @{$cluster_2_nodes{ _cluster_name( $fan_dfr->{'_funnel_dfr'} ) } }, _midpoint_name( $fan_dfr ); # midpoints of rules that have a funnel live inside "boxes"
292  }
293  }
294 
295  foreach my $df_target (@$df_targets) {
296  my $target_object = $df_target->to_analysis;
297  if( UNIVERSAL::isa($target_object, 'Bio::EnsEMBL::Hive::NakedTable') ) { # put the table into the same "box" as the dataflow source:
298 
299  push @{$cluster_2_nodes{ _cluster_name( $target_object->{'_funnel_dfr'} ) } }, $self->_table_node_name( $target_object );
300 
301  } elsif( UNIVERSAL::isa($target_object, 'Bio::EnsEMBL::Hive::Accumulator') ) { # put the accu sink into the same "box" as the dataflow source:
302 
303  push @{$cluster_2_nodes{ _cluster_name( $analysis->{'_funnel_dfr'} ) } }, _accu_sink_node_name( $analysis->{'_funnel_dfr'} );
304  }
305  }
306  } # /foreach group
307  }
308 
309  $self->graph->nested_bgcolour( [$self->config_get('Box', 'Semaphore', 'ColourScheme'), $self->config_get('Box', 'Semaphore', 'ColourOffset')] );
310  }
311 
312  return $self->graph();
313 }
314 
315 
316 sub _propagate_allocation {
317  my ($self, $source_object, $curr_allocation ) = @_;
318 
319  $curr_allocation ||= $source_object->hive_pipeline->hive_pipeline_name;
320 
321  if(!exists $source_object->{'_funnel_dfr'} ) { # only allocate on the first-come basis:
322  $source_object->{'_funnel_dfr'} = $curr_allocation;
323 
324  if(UNIVERSAL::isa($source_object, 'Bio::EnsEMBL::Hive::Analysis')) {
325 
326  foreach my $group ( @{ $self->_grouped_dataflow_rules($source_object) } ) {
327 
328  my ($df_rule, $fan_dfrs, $df_targets) = @$group;
329 
330  $df_rule->{'_funnel_dfr'} = $curr_allocation;
331 
332  foreach my $df_target (@$df_targets) {
333  my $target_object = $df_target->to_analysis;
334 
335  # In case we have crossed pipeline borders, let the next call decide its own allocation by resetting it.
336  $self->_propagate_allocation( $target_object, ($source_object->hive_pipeline == $target_object->hive_pipeline) ? $curr_allocation : '' );
337  }
338 
339  # all fan members point to the funnel.
340  # Request midpoint's allocation since we definitely have a funnel to link to.
341  foreach my $fan_dfr (@$fan_dfrs) {
342  $fan_dfr->{'_funnel_dfr'} = $curr_allocation;
343 
344  foreach my $df_target (@{ $fan_dfr->get_my_targets }) {
345  my $fan_target_object = $df_target->to_analysis;
346 
347  $self->_propagate_allocation( $fan_target_object, ($source_object->hive_pipeline == $fan_target_object->hive_pipeline) ? $df_rule : '' );
348  }
349  }
350 
351  } # /foreach group
352  } # if source_object isa Analysis
353  }
354 }
355 
356 
357 sub _add_analysis_node {
358  my ($self, $analysis) = @_;
359 
360  my $this_analysis_node_name = $self->_analysis_node_name( $analysis );
361 
362  return $this_analysis_node_name if($self->{'_created_analysis'}{ $analysis }++); # making sure every Analysis node gets created no more than once
363 
364  my $analysis_stats = $analysis->stats();
365 
366  my ($breakout_label, $total_job_count, $count_hash) = $analysis_stats->job_count_breakout();
367  my $analysis_status = $analysis_stats->status;
368  my $analysis_status_colour = $self->config_get('Node', 'AnalysisStatus', $analysis_status, 'Colour');
369  my $analysis_shape = $self->config_get('Node', 'AnalysisStatus', 'Shape');
370  my $analysis_style = $analysis->can_be_empty() ? 'dashed, filled' : 'filled' ;
371  my $node_fontname = $self->config_get('Node', 'AnalysisStatus', $analysis_status, 'Font');
372  my $display_stats = $self->config_get('DisplayStats');
373  my $display_dbIDs = $self->config_get('DisplayDBIDs');
374  my $hive_pipeline = $self->pipeline;
375 
376  my $colspan = 0;
377  my $bar_chart = '';
378 
379  if( $display_stats eq 'barchart' ) {
380  foreach my $count_method (qw(SEMAPHORED READY INPROGRESS DONE FAILED)) {
381  if(my $count=$count_hash->{lc($count_method).'_job_count'}) {
382  $bar_chart .= '<td bgcolor="'.$self->config_get('Node', 'JobStatus', $count_method, 'Colour').'" width="'.int(100*$count/$total_job_count).'%">'.$count.lc(substr($count_method,0,1)).'</td>';
383  ++$colspan;
384  }
385  }
386  if($colspan != 1) {
387  $bar_chart .= '<td>='.$total_job_count.'</td>';
388  ++$colspan;
389  }
390  }
391 
392  $colspan ||= 1;
393  my $analysis_label = '<<table border="0" cellborder="0" cellspacing="0" cellpadding="1"><tr><td colspan="'.$colspan.'">'.$analysis->relative_display_name( $hive_pipeline ).($display_dbIDs ? ' ('.($analysis->dbID || 'unstored').')' : '').'</td></tr>';
394  if( $display_stats ) {
395  $analysis_label .= qq{<tr><td colspan="$colspan"> </td></tr>};
396  if( $display_stats eq 'barchart') {
397  $analysis_label .= qq{<tr>$bar_chart</tr>};
398  } elsif( $display_stats eq 'text') {
399  $analysis_label .= qq{<tr><td colspan="$colspan">$breakout_label</td></tr>};
400  }
401  }
402 
403  if( my $job_limit = $self->config_get('DisplayJobs') ) {
404  my $display_job_length = $self->config_get('DisplayJobLength');
405 
406  if(my $job_adaptor = $analysis->adaptor && $analysis->adaptor->db->get_AnalysisJobAdaptor) {
407  my @jobs = sort {$a->dbID <=> $b->dbID} @{ $job_adaptor->fetch_some_by_analysis_id_limit( $analysis->dbID, $job_limit+1 )};
408  $analysis->jobs_collection( \@jobs );
409  }
410 
411  my @jobs = @{ $analysis->jobs_collection };
412 
413  my $hit_limit;
414  if(scalar(@jobs)>$job_limit) {
415  pop @jobs;
416  $hit_limit = 1;
417  }
418 
419  $analysis_label .= '<tr><td colspan="'.$colspan.'"> </td></tr>';
420  foreach my $job (@jobs) {
421  my $input_id = $self->graph->protect_string_for_display( $job->input_id, $display_job_length, 1 );
422  my $status = $job->status;
423  my $job_id = $job->dbID || 'unstored';
424 
425  $analysis_label .= qq{<tr><td align="left" colspan="$colspan" bgcolor="}.$self->config_get('Node', 'JobStatus', $status, 'Colour').qq{">$job_id [$status]: $input_id</td></tr>};
426  }
427 
428  if($hit_limit) {
429  $analysis_label .= qq{<tr><td colspan="$colspan">[ + }.($total_job_count-$job_limit).qq{ more jobs ]</td></tr>};
430  }
431  }
432  $analysis_label .= '</table>>';
433 
434  $self->graph->add_node( $this_analysis_node_name,
435  shape => $analysis_shape,
436  style => $analysis_style,
437  fillcolor => $analysis_status_colour,
438  fontname => $node_fontname,
439  label => $analysis_label,
440  );
441 
442  $self->_add_control_rules( $analysis->control_rules_collection );
443  $self->_add_dataflow_rules( $analysis );
444 
445  return $this_analysis_node_name;
446 }
447 
448 
449 sub _add_accu_sink_node {
450  my ($self, $funnel_dfr) = @_;
451 
452  my $accusink_shape = $self->config_get('Node', 'AccuSink', 'Shape');
453  my $accusink_style = $self->config_get('Node', 'AccuSink', 'Style');
454  my $accusink_colour = $self->config_get('Node', 'AccuSink', 'Colour');
455  my $accusink_font = $self->config_get('Node', 'AccuSink', 'Font');
456  my $accusink_fontcolour = $self->config_get('Node', 'AccuSink', 'FontColour');
457 
458  my $accu_sink_node_name = _accu_sink_node_name( $funnel_dfr );
459 
460  $self->graph->add_node( $accu_sink_node_name,
461  style => $accusink_style,
462  shape => $accusink_shape,
463  fillcolor => $accusink_colour,
464  fontname => $accusink_font,
465  fontcolor => $accusink_fontcolour,
466  label => 'Accu',
467  );
468 
469  return $accu_sink_node_name;
470 }
471 
472 
473 sub _add_control_rules {
474  my ($self, $ctrl_rules) = @_;
475 
476  my $control_colour = $self->config_get('Edge', 'Control', 'Colour');
477  my $graph = $self->graph();
478 
479  #The control rules are always from and to an analysis so no need to search for odd cases here
480  foreach my $c_rule ( @$ctrl_rules ) {
481  my $condition_analysis = $c_rule->condition_analysis;
482  my $ctrled_analysis = $c_rule->ctrled_analysis;
483 
484  my $ctrled_is_local = $ctrled_analysis->is_local_to( $self->pipeline );
485  my $condition_is_local = $condition_analysis->is_local_to( $self->pipeline );
486 
487  if($ctrled_is_local and !$condition_is_local) { # register a new "near neighbour" node if it's reachable by following one rule "out":
488  $self->{'_foreign_analyses'}{ $condition_analysis->relative_display_name($self->pipeline) } = $condition_analysis;
489  }
490 
491  next unless( $ctrled_is_local or $condition_is_local or $self->{'_foreign_analyses'}{ $condition_analysis->relative_display_name($self->pipeline) } );
492 
493  my $from_node_name = $self->_analysis_node_name( $condition_analysis );
494  my $to_node_name = $self->_analysis_node_name( $ctrled_analysis );
495 
496  $graph->add_edge( $from_node_name => $to_node_name,
497  color => $control_colour,
498  arrowhead => 'tee',
499  );
500  }
501 }
502 
503 
504 sub _last_part_arrow {
505  my ($self, $from_analysis, $source_node_name, $label_prefix, $df_target, $extras) = @_;
506 
507  my $graph = $self->graph();
508  my $dataflow_colour = $self->config_get('Edge', 'Data', 'Colour');
509  my $accu_colour = $self->config_get('Edge', 'Accu', 'Colour');
510  my $df_edge_fontname = $self->config_get('Edge', 'Data', 'Font');
511 
512  my $target_object = $df_target->to_analysis;
513  my $target_node_name =
514  UNIVERSAL::isa($target_object, 'Bio::EnsEMBL::Hive::Analysis') ? $self->_add_analysis_node( $target_object )
515  : UNIVERSAL::isa($target_object, 'Bio::EnsEMBL::Hive::NakedTable') ? $self->_add_table_node( $target_object )
516  : UNIVERSAL::isa($target_object, 'Bio::EnsEMBL::Hive::Accumulator') ? $self->_add_accu_sink_node( $from_analysis->{'_funnel_dfr'} )
517  : die "Unknown node type";
518 
519  if(UNIVERSAL::isa($target_object, 'Bio::EnsEMBL::Hive::Analysis')) { # skip some *really* foreign dataflow rules:
520 
521  my $from_is_local = $from_analysis->is_local_to( $self->pipeline );
522  my $target_is_local = $target_object->is_local_to( $self->pipeline );
523 
524  if($from_is_local and !$target_is_local) { # register a new "near neighbour" node if it's reachable by following one rule "out":
525  $self->{'_foreign_analyses'}{ $target_object->relative_display_name($self->pipeline) } = $target_object;
526  }
527 
528  return unless( $from_is_local or $target_is_local or $self->{'_foreign_analyses'}{ $target_object->relative_display_name($self->pipeline) } );
529  }
530 
531  my $input_id_template = $self->config_get('DisplayInputIDTemplate') ? $df_target->input_id_template : undef;
532  my $extend_param_stack = $df_target->extend_param_stack;
533  my $multistring_template= ($extend_param_stack ? "INPUT_PLUS " : '')
534  .($input_id_template ? '{'.join(",\n", sort keys( %{destringify($input_id_template)} )).'}'
535  : ($extend_param_stack ? '' : '') );
536 
537  $graph->add_edge( $source_node_name => $target_node_name,
538  @$extras,
539  UNIVERSAL::isa($target_object, 'Bio::EnsEMBL::Hive::Accumulator')
540  ? (
541  color => $accu_colour,
542  fontcolor => $accu_colour,
543  style => 'dashed',
544  dir => 'both',
545  arrowtail => 'crow',
546  label => $label_prefix."\n=> ".$target_object->relative_display_name( $self->pipeline ),
547  ) : (
548  color => $dataflow_colour,
549  fontcolor => $dataflow_colour,
550  label => $label_prefix."\n".$multistring_template,
551  ),
552  fontname => $df_edge_fontname,
553  );
554 }
555 
556 
557 sub _twopart_arrow {
558  my ($self, $df_rule, $df_targets) = @_;
559 
560  my $graph = $self->graph();
561  my $df_edge_fontname = $self->config_get('Edge', 'Data', 'Font');
562  my $switch_shape = $self->config_get('Node', 'Switch', 'Shape');
563  my $switch_style = $self->config_get('Node', 'Switch', 'Style');
564  my $switch_colour = $self->config_get('Node', 'Switch', 'Colour');
565  my $switch_font = $self->config_get('Node', 'Switch', 'Font');
566  my $switch_fontcolour = $self->config_get('Node', 'Switch', 'FontColour');
567  my $display_cond_length = $self->config_get('DisplayConditionLength');
568 
569  my $from_analysis = $df_rule->from_analysis;
570  my $from_node_name = $self->_analysis_node_name( $from_analysis );
571  my $midpoint_name = _midpoint_name( $df_rule );
572 
573  $df_targets ||= $df_rule->get_my_targets;
574  my $choice = (scalar(@$df_targets)!=1) || defined($df_targets->[0]->on_condition);
575  my $tablabel = qq{<<table border="0" cellborder="0" cellspacing="0" cellpadding="1">i<tr><td></td></tr>};
576 
577  my $targets_grouped_by_condition = $df_rule->get_my_targets_grouped_by_condition( $df_targets );
578 
579  foreach my $i (0..scalar(@$targets_grouped_by_condition)-1) {
580 
581  my $condition = $targets_grouped_by_condition->[$i]->[0];
582 
583  if(defined($condition)) {
584  $condition = $display_cond_length
585  ? $graph->protect_string_for_display( $condition, $display_cond_length ) # trim and protect it
586  : 'condition_'.$i; # override it completely with a numbered label
587  }
588  $tablabel .= qq{<tr><td port="cond_$i">}.((defined $condition) ? "WHEN $condition" : $choice ? 'ELSE' : '')."</td></tr>";
589  }
590  $tablabel .= '</table>>';
591 
592  $graph->add_node( $midpoint_name, # midpoint itself
593  $choice ? (
594  shape => $switch_shape,
595  style => $switch_style,
596  fillcolor => $switch_colour,
597  fontname => $switch_font,
598  fontcolor => $switch_fontcolour,
599  label => $tablabel,
600  ) : (
601  shape => 'point',
602  fixedsize => 1,
603  width => 0.01,
604  height => 0.01,
605  ),
606  );
607  $graph->add_edge( $from_node_name => $midpoint_name, # first half of the two-part arrow
608  color => 'black',
609  fontcolor => 'black',
610  fontname => $df_edge_fontname,
611  label => '#'.$df_rule->branch_code,
612  headport => 'n',
613  $choice ? (
614  arrowhead => 'normal',
615  ) : (
616  arrowhead => 'none',
617  ),
618  );
619 
620  foreach my $i (0..scalar(@$targets_grouped_by_condition)-1) {
621 
622  my $target_group = $targets_grouped_by_condition->[$i]->[1];
623 
624  foreach my $df_target (@$target_group) {
625  $self->_last_part_arrow($from_analysis, $midpoint_name, '', $df_target, $choice ? [ tailport => "cond_$i" ] : [ tailport => 's' ]);
626  }
627  }
628 
629  return $midpoint_name;
630 }
631 
632 
633 sub _add_dataflow_rules {
634  my ($self, $from_analysis) = @_;
635 
636  my $graph = $self->graph();
637  my $semablock_colour = $self->config_get('Edge', 'Semablock', 'Colour');
638 
639  foreach my $group ( @{ $self->_grouped_dataflow_rules($from_analysis) } ) {
640 
641  my ($df_rule, $fan_dfrs, $df_targets) = @$group;
642 
643  if(@$fan_dfrs) { # semaphored funnel case => all rules have an Analysis target and have two parts:
644 
645  my $funnel_midpoint_name = $self->_twopart_arrow( $df_rule, $df_targets );
646 
647  foreach my $fan_dfr (@$fan_dfrs) {
648  my $fan_midpoint_name = $self->_twopart_arrow( $fan_dfr );
649 
650  # add a semaphore inter-rule blocking arc:
651  $graph->add_edge( $fan_midpoint_name => $funnel_midpoint_name,
652  color => $semablock_colour,
653  style => 'dashed',
654  dir => 'both',
655  arrowhead => 'tee',
656  arrowtail => 'crow',
657  );
658  }
659 
660  } else {
661  my $choice = (scalar(@$df_targets)!=1) || defined($df_targets->[0]->on_condition);
662 
663  if($choice) {
664  $self->_twopart_arrow( $df_rule, $df_targets );
665  } else {
666  my $from_node_name = $self->_analysis_node_name( $from_analysis );
667  my $df_target = $df_targets->[0];
668 
669  $self->_last_part_arrow($from_analysis, $from_node_name, '#'.$df_rule->branch_code, $df_target, []);
670  }
671  }
672 
673  } # /foreach my $group
674 }
675 
676 
677 sub _add_table_node {
678  my ($self, $naked_table) = @_;
679 
680  my $table_shape = $self->config_get('Node', 'Table', 'Shape');
681  my $table_style = $self->config_get('Node', 'Table', 'Style');
682  my $table_colour = $self->config_get('Node', 'Table', 'Colour');
683  my $table_header_colour = $self->config_get('Node', 'Table', 'HeaderColour');
684  my $table_fontcolour = $self->config_get('Node', 'Table', 'FontColour');
685  my $table_fontname = $self->config_get('Node', 'Table', 'Font');
686 
687  my $hive_pipeline = $self->pipeline;
688  my $this_table_node_name = $self->_table_node_name( $naked_table );
689 
690  my (@column_names, $columns, $table_data, $data_limit, $hit_limit);
691 
692  if( $data_limit = $self->config_get('DisplayData') and my $naked_table_adaptor = $naked_table->adaptor ) {
693 
694  @column_names = sort keys %{$naked_table_adaptor->column_set};
695  $columns = scalar(@column_names);
696  $table_data = $naked_table_adaptor->fetch_all( 'LIMIT '.($data_limit+1) );
697 
698  if(scalar(@$table_data)>$data_limit) {
699  pop @$table_data;
700  $hit_limit = 1;
701  }
702  }
703 
704  my $table_label = '<<table border="0" cellborder="0" cellspacing="0" cellpadding="1"><tr><td colspan="'.($columns||1).'">'. $naked_table->relative_display_name( $hive_pipeline ) .'</td></tr>';
705 
706  if( $data_limit and $columns) {
707  my $display_column_length = $self->config_get('DisplayColumnLength');
708 
709  $table_label .= '<tr><td colspan="'.$columns.'"> </td></tr>';
710  $table_label .= '<tr>'.join('', map { qq{<td bgcolor="$table_header_colour" border="1">$_</td>} } @column_names).'</tr>';
711  foreach my $row (@$table_data) {
712  $table_label .= '<tr>'.join('', map { '<td>'.$self->graph->protect_string_for_display($_, $display_column_length).'</td>' } @{$row}{@column_names}).'</tr>';
713  }
714  if($hit_limit) {
715  $table_label .= qq{<tr><td colspan="$columns">[ more data ]</td></tr>};
716  }
717  }
718  $table_label .= '</table>>';
719 
720  $self->graph()->add_node( $this_table_node_name,
721  shape => $table_shape,
722  style => $table_style,
723  fillcolor => $table_colour,
724  fontname => $table_fontname,
725  fontcolor => $table_fontcolour,
726  label => $table_label,
727  );
728 
729  return $this_table_node_name;
730 }
731 
732 1;
Bio::EnsEMBL::Hive::NakedTable
Definition: NakedTable.pm:10
map
public map()
Bio::EnsEMBL::Hive::Analysis::stats
public Bio::EnsEMBL::Hive::AnalysisStats stats()
Bio::EnsEMBL::Hive::Utils::Config::new
public new()
Bio::EnsEMBL::Hive::Utils::GraphViz::new
public new()
Bio::EnsEMBL::Hive::Utils::Config
Definition: Config.pm:12
Bio::EnsEMBL::Hive::Utils::Graph::new
public Graph new()
Bio::EnsEMBL::Hive::Utils::Graph
Definition: Graph.pm:26
Bio::EnsEMBL::Hive::AnalysisStats::job_count_breakout
public job_count_breakout()
Bio::EnsEMBL::Hive::Configurable
Definition: Configurable.pm:12
Bio::EnsEMBL::Hive::Utils::GraphViz
Definition: GraphViz.pm:16
Bio::EnsEMBL::Hive::Accumulator
Definition: Accumulator.pm:11
Bio::EnsEMBL::Hive::Utils::Graph::pipeline
public HivePipeline pipeline()
Bio::EnsEMBL::Hive::Utils::Graph::build
public The build()
Bio::EnsEMBL::Hive::Analysis
Definition: Analysis.pm:18
Bio::EnsEMBL::Hive::Cacheable::relative_display_name
public relative_display_name()