10 my $graphviz = $g->
build();
11 $graphviz->as_png(
'location.png');
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
22 Copyright [1999-2015] Wellcome Trust Sanger Institute and the EMBL-European Bioinformatics Institute
23 Copyright [2016-2024] EMBL-European Bioinformatics Institute
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
28 http://www.apache.org/licenses/LICENSE-2.0
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.
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
40 The rest of the documentation details each of the object methods.
41 Internal methods are usually preceded with a _
46 package Bio::EnsEMBL::Hive::Utils::Graph;
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;
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
76 my $pipeline = shift @_;
78 my $self = bless({}, ref($class) || $class);
83 $self->config($config);
84 $self->context( [
'Graph' ] );
92 Arg [1] : The
GraphViz instance created by this module
102 if(! exists $self->{
'_graph'}) {
105 'name' =>
'AnalysisWorkflow',
106 'concentrate' =>
'true',
107 'pad' => $self->config_get(
'Pad') || 0,
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;
114 return $self->{
'_graph'};
120 Arg [1] : The HivePipeline instance
121 Returntype : HivePipeline
129 $self->{
'_pipeline'} = shift @_;
132 return $self->{
'_pipeline'};
136 sub _grouped_dataflow_rules {
137 my ($self, $analysis) = @_;
139 my $gdr = $self->{
'_gdr'} ||= {};
141 return $gdr->{$analysis} ||= $analysis->get_grouped_dataflow_rules;
145 sub _analysis_node_name {
146 my ($self, $analysis) = @_;
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;
152 return $analysis_node_name;
156 sub _table_node_name {
157 my ($self, $naked_table) = @_;
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;
165 sub _accu_sink_node_name {
166 my ($funnel_dfr) = @_;
168 return 'sink_'.(UNIVERSAL::isa($funnel_dfr,
'Bio::EnsEMBL::Hive::DataflowRule') ? _midpoint_name($funnel_dfr) : ($funnel_dfr ||
''));
175 return ( UNIVERSAL::isa($df_rule,
'Bio::EnsEMBL::Hive::DataflowRule') ?
'cl_'._midpoint_name($df_rule) : ($df_rule ||
'cl_noname') );
179 our %_midpoint_ref_to_temp_id;
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)};
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;
190 return 'dfr_'.$dfr_id.
'_mp';
192 throw(
"Wrong argument to _midpoint_name");
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.
209 my $main_pipeline = $self->pipeline;
211 $self->{
'_foreign_analyses'} = {};
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 );
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 );
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 );
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 );
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
'
244 my %cluster_2_nodes = ();
245 $self->graph->cluster_2_nodes( \%cluster_2_nodes );
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
';
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
')];
259 if($self->config_get('DisplaySemaphoreBoxes
') ) {
260 foreach my $analysis ( $main_pipeline->collection_of('Analysis
')->list, values %{ $self->{'_foreign_analyses
'} } ) {
262 push @{$cluster_2_nodes{ _cluster_name( $analysis->{'_funnel_dfr
'} ) } }, $self->_analysis_node_name( $analysis );
264 foreach my $group ( @{ $self->_grouped_dataflow_rules($analysis) } ) {
266 my ($df_rule, $fan_dfrs, $df_targets) = @$group;
268 my $choice = (scalar(@$df_targets)!=1) || defined($df_targets->[0]->on_condition);
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 );
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
287 push @{$cluster_2_nodes{ _cluster_name( $df_rule->{'_funnel_dfr
'} ) }}, _cluster_name( $df_rule );
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"
295 foreach my $df_target (@$df_targets) {
296 my $target_object = $df_target->to_analysis;
299 push @{$cluster_2_nodes{ _cluster_name( $target_object->{'_funnel_dfr
'} ) } }, $self->_table_node_name( $target_object );
303 push @{$cluster_2_nodes{ _cluster_name( $analysis->{'_funnel_dfr
'} ) } }, _accu_sink_node_name( $analysis->{'_funnel_dfr
'} );
309 $self->graph->nested_bgcolour( [$self->config_get('Box
', 'Semaphore
', 'ColourScheme
'), $self->config_get('Box
', 'Semaphore
', 'ColourOffset
')] );
312 return $self->graph();
316 sub _propagate_allocation {
317 my ($self, $source_object, $curr_allocation ) = @_;
319 $curr_allocation ||= $source_object->hive_pipeline->hive_pipeline_name;
321 if(!exists $source_object->{'_funnel_dfr
'} ) { # only allocate on the first-come basis:
322 $source_object->{'_funnel_dfr
'} = $curr_allocation;
326 foreach my $group ( @{ $self->_grouped_dataflow_rules($source_object) } ) {
328 my ($df_rule, $fan_dfrs, $df_targets) = @$group;
330 $df_rule->{'_funnel_dfr
'} = $curr_allocation;
332 foreach my $df_target (@$df_targets) {
333 my $target_object = $df_target->to_analysis;
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 : '' );
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;
344 foreach my $df_target (@{ $fan_dfr->get_my_targets }) {
345 my $fan_target_object = $df_target->to_analysis;
347 $self->_propagate_allocation( $fan_target_object, ($source_object->hive_pipeline == $fan_target_object->hive_pipeline) ? $df_rule :
'' );
352 } #
if source_object isa Analysis
357 sub _add_analysis_node {
358 my ($self, $analysis) = @_;
360 my $this_analysis_node_name = $self->_analysis_node_name( $analysis );
362 return $this_analysis_node_name
if($self->{
'_created_analysis'}{ $analysis }++); # making sure every Analysis node gets created no more than once
364 my $analysis_stats = $analysis->
stats();
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;
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>';
387 $bar_chart .=
'<td>='.$total_job_count.
'</td>';
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>};
403 if( my $job_limit = $self->config_get(
'DisplayJobs') ) {
404 my $display_job_length = $self->config_get(
'DisplayJobLength');
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 );
411 my @jobs = @{ $analysis->jobs_collection };
414 if(scalar(@jobs)>$job_limit) {
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';
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>};
429 $analysis_label .= qq{<tr><td colspan=
"$colspan">[ + }.($total_job_count-$job_limit).qq{ more jobs ]</td></tr>};
432 $analysis_label .=
'</table>>';
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,
442 $self->_add_control_rules( $analysis->control_rules_collection );
443 $self->_add_dataflow_rules( $analysis );
445 return $this_analysis_node_name;
449 sub _add_accu_sink_node {
450 my ($self, $funnel_dfr) = @_;
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');
458 my $accu_sink_node_name = _accu_sink_node_name( $funnel_dfr );
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,
469 return $accu_sink_node_name;
473 sub _add_control_rules {
474 my ($self, $ctrl_rules) = @_;
476 my $control_colour = $self->config_get(
'Edge',
'Control',
'Colour');
477 my $graph = $self->graph();
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;
484 my $ctrled_is_local = $ctrled_analysis->is_local_to( $self->pipeline );
485 my $condition_is_local = $condition_analysis->is_local_to( $self->pipeline );
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;
491 next unless( $ctrled_is_local or $condition_is_local or $self->{'_foreign_analyses
'}{ $condition_analysis->relative_display_name($self->pipeline) } );
493 my $from_node_name = $self->_analysis_node_name( $condition_analysis );
494 my $to_node_name = $self->_analysis_node_name( $ctrled_analysis );
496 $graph->add_edge( $from_node_name => $to_node_name,
497 color => $control_colour,
504 sub _last_part_arrow {
505 my ($self, $from_analysis, $source_node_name, $label_prefix, $df_target, $extras) = @_;
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
');
512 my $target_object = $df_target->to_analysis;
513 my $target_node_name =
517 : die "Unknown node type";
521 my $from_is_local = $from_analysis->is_local_to( $self->pipeline );
522 my $target_is_local = $target_object->is_local_to( $self->pipeline );
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;
528 return unless( $from_is_local or $target_is_local or $self->{
'_foreign_analyses'}{ $target_object->relative_display_name($self->pipeline) } );
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 ?
'' :
'') );
537 $graph->add_edge( $source_node_name => $target_node_name,
539 UNIVERSAL::isa($target_object,
'Bio::EnsEMBL::Hive::Accumulator')
541 color => $accu_colour,
542 fontcolor => $accu_colour,
546 label => $label_prefix.
"\n=> ".$target_object->relative_display_name( $self->pipeline ),
548 color => $dataflow_colour,
549 fontcolor => $dataflow_colour,
550 label => $label_prefix.
"\n".$multistring_template,
552 fontname => $df_edge_fontname,
558 my ($self, $df_rule, $df_targets) = @_;
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');
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 );
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>};
577 my $targets_grouped_by_condition = $df_rule->get_my_targets_grouped_by_condition( $df_targets );
579 foreach my $i (0..scalar(@$targets_grouped_by_condition)-1) {
581 my $condition = $targets_grouped_by_condition->[$i]->[0];
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
588 $tablabel .= qq{<tr><td port=
"cond_$i">}.((defined $condition) ?
"WHEN $condition" : $choice ?
'ELSE' :
'').
"</td></tr>";
590 $tablabel .=
'</table>>';
592 $graph->add_node( $midpoint_name, # midpoint itself
594 shape => $switch_shape,
595 style => $switch_style,
596 fillcolor => $switch_colour,
597 fontname => $switch_font,
598 fontcolor => $switch_fontcolour,
607 $graph->add_edge( $from_node_name => $midpoint_name, # first half of the two-part arrow
609 fontcolor =>
'black',
610 fontname => $df_edge_fontname,
611 label =>
'#'.$df_rule->branch_code,
614 arrowhead =>
'normal',
620 foreach my $i (0..scalar(@$targets_grouped_by_condition)-1) {
622 my $target_group = $targets_grouped_by_condition->[$i]->[1];
624 foreach my $df_target (@$target_group) {
625 $self->_last_part_arrow($from_analysis, $midpoint_name,
'', $df_target, $choice ? [ tailport =>
"cond_$i" ] : [ tailport =>
's' ]);
629 return $midpoint_name;
633 sub _add_dataflow_rules {
634 my ($self, $from_analysis) = @_;
636 my $graph = $self->graph();
637 my $semablock_colour = $self->config_get(
'Edge',
'Semablock',
'Colour');
639 foreach my $group ( @{ $self->_grouped_dataflow_rules($from_analysis) } ) {
641 my ($df_rule, $fan_dfrs, $df_targets) = @$group;
643 if(@$fan_dfrs) { # semaphored funnel
case => all rules have an Analysis target and have two parts:
645 my $funnel_midpoint_name = $self->_twopart_arrow( $df_rule, $df_targets );
647 foreach my $fan_dfr (@$fan_dfrs) {
648 my $fan_midpoint_name = $self->_twopart_arrow( $fan_dfr );
650 # add a semaphore inter-rule blocking arc:
651 $graph->add_edge( $fan_midpoint_name => $funnel_midpoint_name,
652 color => $semablock_colour,
661 my $choice = (scalar(@$df_targets)!=1) || defined($df_targets->[0]->on_condition);
664 $self->_twopart_arrow( $df_rule, $df_targets );
666 my $from_node_name = $self->_analysis_node_name( $from_analysis );
667 my $df_target = $df_targets->[0];
669 $self->_last_part_arrow($from_analysis, $from_node_name,
'#'.$df_rule->branch_code, $df_target, []);
673 } # /
foreach my $group
677 sub _add_table_node {
678 my ($self, $naked_table) = @_;
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');
687 my $hive_pipeline = $self->pipeline;
688 my $this_table_node_name = $self->_table_node_name( $naked_table );
690 my (@column_names, $columns, $table_data, $data_limit, $hit_limit);
692 if( $data_limit = $self->config_get(
'DisplayData') and my $naked_table_adaptor = $naked_table->adaptor ) {
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) );
698 if(scalar(@$table_data)>$data_limit) {
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>';
706 if( $data_limit and $columns) {
707 my $display_column_length = $self->config_get(
'DisplayColumnLength');
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>';
715 $table_label .= qq{<tr><td colspan=
"$columns">[ more data ]</td></tr>};
718 $table_label .=
'</table>>';
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,
729 return $this_table_node_name;