9 An extension of GraphViz that employs a collection of hacks
10 to use some functionality of dot that is not available through GraphViz.
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
17 =head1 EXTERNAL DEPENDENCIES
23 See the NOTICE file distributed with this work for additional information
24 regarding copyright ownership.
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
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.
37 Please subscribe to the
Hive mailing list: http:
51 Title : new (constructor)
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).
62 my $ratio_value =
delete $all_params{
'ratio'} ||
'compress';
63 my $injection = join(
'',
map {
'"; ' . $_ .
' = "' . $all_params{$_} } sort keys %all_params);
65 return $class->SUPER::new(
'ratio' => $ratio_value.$injection );
72 $self->{_cluster_2_nodes} = shift @_;
74 return $self->{_cluster_2_nodes} ||= {};
78 sub cluster_2_attributes {
81 $self->{_cluster_2_attributes} = shift @_;
83 return $self->{_cluster_2_attributes} ||= {};
90 $self->{_nested_bgcolour} = shift @_;
92 return $self->{_nested_bgcolour};
96 sub dot_input_filename {
99 $self->{_dot_input_filename} = shift @_;
101 return $self->{_dot_input_filename};
105 sub display_subgraph {
106 my ($self, $cluster_name, $depth) = @_;
110 my ($box_colour_pair, $auto_colour) = $cluster_attributes->{
'fill_colour_pair' } || ($self->nested_bgcolour, 1);
111 my $cluster_label = $cluster_attributes->{
'cluster_label'} ||
'';
113 my $prefix =
"\t" x $depth;
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
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
121 my ($colour_scheme, $colour_offset) = @$box_colour_pair;
122 my $adjusted_colour = $auto_colour ? $colour_offset+$depth : $colour_offset;
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);
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
133 } # otherwise just draw a black frame around the subgraph
135 foreach my $node_name ( sort @{ $self->cluster_2_nodes->{ $cluster_name } || [] } ) {
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 );
140 $text .= $prefix . "\t${node_name};\n";
143 $text .= $prefix . "}\n";
149 sub top_level_cluster_names {
152 my %top_level_candidate_set = map { ($_ => 1) } keys %{ $self->cluster_2_nodes };
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};
163 # what remains are the top-level clusters:
164 return [ sort keys %top_level_candidate_set ];
171 my $text = $self->SUPER::_as_debug;
175 foreach my $top_level_cluster_name (@{ $self->top_level_cluster_names }) {
176 $text .= $self->display_subgraph( $top_level_cluster_name, 0);
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:
185 $text=~s/\bcomment="new_shape:(\w+)",\s(.*shape=)"record"/$2"$1"/mg;
187 if(my $dot_input_filename = $self->dot_input_filename) {
188 if (ref($dot_input_filename)) {
189 print $dot_input_filename $text;
191 open(my $dot_input, ">", $dot_input_filename) or die "cannot open > $dot_input_filename : $!";
192 print $dot_input $text;
203 my $node_name = shift @_;
206 my $desired_shape = delete $param_hash{'shape
'}; # smuggle in the desired shape as a comment, to be substituted later by _as_debug() method
208 return $self->SUPER::add_node($node_name, %param_hash, $desired_shape ? (shape => 'record
', comment => qq{new_shape:$desired_shape}) : () );
212 sub protect_string_for_display { # NB: $self is only needed for calling, and isn't used in any other way
214 my ($self, $string, $length_limit, $drop_framing_curlies) = @_;
216 if($drop_framing_curlies) {
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;
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{<}{<}g;
232 $string=~s{>}{>}g;