File Coverage

blib/lib/Venn/Chart.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package Venn::Chart;
2            
3 1     1   24063 use warnings;
  1         3  
  1         31  
4 1     1   6 use strict;
  1         3  
  1         59  
5 1     1   5 use Carp;
  1         6  
  1         84  
6            
7             #==================================================================
8             # $Author : Djibril Ousmanou $
9             # $Copyright : 2011 $
10             # $Update : 01/01/2011 00:00:00 $
11             # $AIM : Create a Venn diagram image $
12             #==================================================================
13            
14 1     1   470 use GD;
  0            
  0            
15             use GD::Graph::hbars;
16             use GD::Graph::colour;
17             use GD::Text::Align;
18             use List::Compare;
19            
20             use vars qw($VERSION);
21             $VERSION = '1.02';
22            
23             my %DEFAULT = (
24             Hlegend => 70,
25             Htitle => 30,
26             space => 10,
27             colors => [ [ 189, 66, 238, 0 ], [ 255, 133, 0, 0 ], [ 0, 107, 44, 0 ] ],
28             );
29             my $WIDTH = 500;
30             my $HEIGHT = 500;
31             my $MIN_LEGEND = 2;
32             my $MAX_LEGEND = 3;
33             my $MIN_PLOT = $MIN_LEGEND;
34             my $MAX_PLOT = $MAX_LEGEND;
35             my $MIN_LIST_REGION = 3;
36             my $MAX_LIST_REGION = 7;
37             my $CUBE_SIZE = 10;
38             my @RGB_WHITE = ( 255, 255, 255 );
39             my @RGB_BLACK = ( 0, 0, 0 );
40             my @RGBA = ( 0, 0, 0, 0 );
41             my @DEGREES = ( 0, 360 );
42             my $SLASH = q{/};
43            
44             sub new {
45             my ( $self, $width, $height ) = @_;
46            
47             $self = ref($self) || $self;
48             my $this = {};
49             bless $this, $self;
50            
51             $this->{_width} = $width || $WIDTH;
52             $this->{_height} = $height || $HEIGHT;
53             $this->{_dim}{Ht} = 0;
54             $this->{_dim}{HLeg} = 0;
55             $this->{_dim}{space} = $DEFAULT{space};
56             $this->{_colors} = $DEFAULT{colors};
57             $this->{_circles}{number} = 0;
58             $this->{_legends}{number} = 0;
59            
60             return $this;
61             }
62            
63             sub set {
64             my ( $this, %param ) = @_;
65            
66             carp("set method deprecated, please use set_options method\n");
67             $this->set_options(%param);
68            
69             return 1;
70             }
71            
72             sub set_options {
73             my ( $this, %param ) = @_;
74             $this->{_colors} = $param{'-colors'} || $DEFAULT{colors};
75            
76             if ( exists $param{'-title'} ) {
77             $this->{_title} = $param{'-title'};
78             $this->{_dim}{Ht} = $DEFAULT{Htitle};
79             }
80             return 1;
81             }
82            
83             sub set_legends {
84             my ( $this, @legends ) = @_;
85             $this->{_legends}{number} = scalar @legends;
86            
87             if ( $this->{_legends}{number} < $MIN_LEGEND or $this->{_legends}{number} > $MAX_LEGEND ) {
88             carp("You must set $MIN_LEGEND or $MAX_LEGEND legends");
89             return;
90             }
91            
92             $this->{_legend} = \@legends;
93             $this->{_dim}{HLeg} = $DEFAULT{Hlegend};
94            
95             return 1;
96             }
97            
98             sub _legend {
99             my ( $this, $image ) = @_;
100            
101             # Coords
102             my $cubex1 = $DEFAULT{space};
103             my $cubey1 = $this->{_dim}{Ht} + $this->{_dim}{Hc} + $CUBE_SIZE;
104             my $cubex2 = $cubex1 + $CUBE_SIZE;
105             my $cubey2 = $cubey1 + $CUBE_SIZE;
106             my $xtext = $cubex2 + $CUBE_SIZE;
107             my $ytext = $cubey1;
108            
109             for ( 0 .. 2 ) {
110             my $idcolor = $_ + 1;
111             last if ( !( $this->{_legend}->[$_] and $this->{_conf_color}{"color$idcolor"} ) );
112             $image->filledRectangle( $cubex1, $cubey1, $cubex2, $cubey2, $this->{_conf_color}{"color$idcolor"} );
113             $image->string( gdMediumBoldFont, $xtext, $ytext, $this->{_legend}->[$_], $this->{_conf_color}{black} );
114             $cubey1 = $cubey2 + $CUBE_SIZE;
115             $cubey2 = $cubey1 + $CUBE_SIZE;
116             $ytext = $cubey1;
117             }
118            
119             return 1;
120             }
121            
122             sub plot {
123             my ( $this, @data ) = @_;
124             $this->{_circles}{number} = scalar @data;
125             if ( $this->{_circles}{number} < $MIN_PLOT or $this->{_circles}{number} > $MAX_PLOT ) {
126             croak("You must plot $MIN_PLOT or $MAX_PLOT lists");
127             }
128            
129             $this->{_dim}{R} = ( $this->{_width} - ( $MIN_PLOT * $this->{_dim}{space} ) ) / $MAX_PLOT;
130             $this->{_dim}{D} = $this->{_dim}{R} * $MIN_PLOT;
131            
132             # Check Height dimension and recalcul space
133             my $diff
134             = ( $this->{_dim}{Ht} + $this->{_dim}{D} + $this->{_dim}{R} + $this->{_dim}{HLeg} - $this->{_height} );
135             if ( $diff > 0 ) {
136             $this->{_dim}{space} += ( $diff / $MIN_PLOT );
137             $this->{_dim}{R} = ( $this->{_width} - ( $MIN_PLOT * $this->{_dim}{space} ) ) / $MAX_PLOT;
138             $this->{_dim}{D} = $this->{_dim}{R} * $MIN_PLOT;
139             }
140            
141             my $image = GD::Image->new( $this->{_width}, $this->{_height} );
142            
143             $this->{_conf_color}{white} = $image->colorAllocate(@RGB_WHITE);
144             $this->{_conf_color}{black} = $image->colorAllocate(@RGB_BLACK);
145            
146             # make the background transparent and interlaced
147             $image->transparent( $this->{_conf_color}{white} );
148             $image->interlaced('true');
149            
150             # display circle
151             if ( $this->{_title} ) { $this->_title($image); }
152             $this->_circle( $image, @data );
153             if ( $this->{_legend} ) { $this->_legend($image); }
154            
155             $this->{_gd}{plot} = $image;
156            
157             return $image;
158             }
159            
160             sub _title {
161             my ( $this, $image ) = @_;
162            
163             if ( not defined $image ) { return; }
164            
165             $this->{_coords}{xtitle} = $this->{_dim}{space};
166             $this->{_coords}{ytitle} = $this->{_dim}{Ht} / 2;
167            
168             my $align = GD::Text::Align->new(
169             $image,
170             valign => 'center',
171             halign => 'center',
172             colour => $this->{_conf_color}{black},
173             );
174            
175             $align->set_font(gdMediumBoldFont);
176             $align->set_text( $this->{_title} );
177             $align->draw( $this->{_width} / 2, $this->{_coords}{ytitle}, 0 );
178            
179             return 1;
180             }
181            
182             sub _circle {
183             my ( $this, $image, $ref_data1, $ref_data2, $ref_data3 ) = @_;
184            
185             if ( not defined $image ) { return; }
186            
187             # Venn with 2 circles
188             # Coords
189             $this->{_coords}{xc1} = $this->{_dim}{space} + $this->{_dim}{R};
190             $this->{_coords}{yc1} = $this->{_dim}{R} + $this->{_dim}{Ht};
191            
192             $this->{_coords}{xc2} = $this->{_coords}{xc1} + $this->{_dim}{R};
193             $this->{_coords}{yc2} = $this->{_coords}{yc1};
194            
195             # display circles
196             $image->arc(
197             $this->{_coords}{xc1},
198             $this->{_coords}{yc1},
199             $this->{_dim}{D},
200             $this->{_dim}{D},
201             @DEGREES, $this->{_conf_color}{black}
202             );
203             $image->arc(
204             $this->{_coords}{xc2},
205             $this->{_coords}{yc2},
206             $this->{_dim}{D},
207             $this->{_dim}{D},
208             @DEGREES, $this->{_conf_color}{black}
209             );
210            
211             # text circle
212             my $lcm = List::Compare->new( { lists => [ $ref_data1, $ref_data2, $ref_data3 ], } );
213             my @list1 = $lcm->get_unique(0);
214             my $data1 = scalar @list1;
215             my @list2 = $lcm->get_unique(1);
216             my $data2 = scalar @list2;
217             my @list3 = $lcm->get_unique(2);
218             my $data3 = scalar @list3;
219             my @list123 = $lcm->get_intersection;
220             my $data123 = scalar @list123;
221            
222             my $lc = List::Compare->new( $ref_data1, $ref_data2 );
223             my @list12 = $lc->get_intersection;
224             my $lc12 = List::Compare->new( \@list12, \@list123 );
225             @list12 = $lc12->get_unique;
226             my $data12 = scalar @list12;
227            
228             $lc = List::Compare->new( $ref_data1, $ref_data3 );
229             my @list13 = $lc->get_intersection;
230             my $lc13 = List::Compare->new( \@list13, \@list123 );
231             @list13 = $lc13->get_unique;
232             my $data13 = scalar @list13;
233            
234             $lc = List::Compare->new( $ref_data2, $ref_data3 );
235             my @list23 = $lc->get_intersection;
236             my $lc23 = List::Compare->new( \@list23, \@list123 );
237             @list23 = $lc23->get_unique;
238             my $data23 = scalar @list23;
239            
240             # for get_regions
241             $this->{_regions} = [ $data1, $data2, $data12 ];
242             $this->{_listregions} = [ \@list1, \@list2, \@list12 ];
243            
244             $this->{_coords}{xt1} = $this->{_dim}{space} + ( $this->{_dim}{R} / $MAX_PLOT );
245             $this->{_coords}{yt1} = $this->{_coords}{yc1};
246            
247             $this->{_coords}{xt2} = $this->{_dim}{space} + $this->{_dim}{D} + ( $this->{_dim}{R} / $MAX_PLOT );
248             $this->{_coords}{yt2} = $this->{_coords}{yc1};
249            
250             $this->{_coords}{xt12} = $this->{_coords}{xc1} + ( $this->{_dim}{R} / $MIN_PLOT );
251             $this->{_coords}{yt12} = $this->{_coords}{yc1} - ( $this->{_dim}{R} / $MIN_PLOT );
252            
253             if ( $this->{_colors}->[0] and $this->{_colors}->[1] ) {
254             $this->{_conf_color}{color1} = $image->colorAllocateAlpha( @{ $this->{_colors}->[0] } );
255             $this->{_conf_color}{color2} = $image->colorAllocateAlpha( @{ $this->{_colors}->[1] } );
256             my $ref_color12 = $this->_moy_color( $this->{_colors}->[0], $this->{_colors}->[1] );
257             $this->{_conf_color}{color12} = $image->colorAllocateAlpha( @{$ref_color12} );
258            
259             $image->fill( $this->{_coords}{xt1}, $this->{_coords}{yt1}, $this->{_conf_color}{color1} );
260             $image->fill( $this->{_coords}{xt2}, $this->{_coords}{yt2}, $this->{_conf_color}{color2} );
261             $image->fill( $this->{_coords}{xt12}, $this->{_coords}{yt12}, $this->{_conf_color}{color12} );
262            
263             $this->{_colors_regions} = [ $this->{_colors}->[0], $this->{_colors}->[1], $ref_color12 ];
264             }
265             $image->string( gdMediumBoldFont,
266             $this->{_coords}{xt1},
267             $this->{_coords}{yt1},
268             $data1, $this->{_conf_color}{black}
269             );
270             $image->string( gdMediumBoldFont,
271             $this->{_coords}{xt2},
272             $this->{_coords}{yt2},
273             $data2, $this->{_conf_color}{black}
274             );
275             $image->string( gdMediumBoldFont,
276             $this->{_coords}{xt12},
277             $this->{_coords}{yt12},
278             $data12, $this->{_conf_color}{black}
279             );
280             $this->{_dim}{Hc} = $this->{_dim}{D};
281            
282             # Venn with 3 circles
283             if ( defined $ref_data3 ) {
284             $this->{_coords}{xc3} = $this->{_coords}{xc1} + ( $this->{_dim}{R} / $MIN_PLOT );
285             $this->{_coords}{yc3} = $this->{_coords}{yc1} + $this->{_dim}{R};
286            
287             $image->arc(
288             $this->{_coords}{xc3},
289             $this->{_coords}{yc3},
290             $this->{_dim}{D},
291             $this->{_dim}{D},
292             @DEGREES, $this->{_conf_color}{black}
293             );
294            
295             $this->{_coords}{xt3} = $this->{_coords}{xc3};
296             $this->{_coords}{yt3} = $this->{_coords}{yc3} + ( $this->{_dim}{R} / $MIN_PLOT );
297            
298             $this->{_coords}{xt13} = $this->{_coords}{xc1} - ( $this->{_dim}{D} / ( $MAX_PLOT * 2 ) );
299             $this->{_coords}{yt13} = $this->{_coords}{yc3} - ( $this->{_dim}{R} / $MIN_PLOT );
300            
301             $this->{_coords}{xt23} = $this->{_coords}{xc2};
302             $this->{_coords}{yt23} = $this->{_coords}{yc3} - ( $this->{_dim}{R} / $MAX_PLOT );
303            
304             $this->{_coords}{xt123} = $this->{_coords}{xt3};
305             $this->{_coords}{yt123} = $this->{_coords}{yc3} - 2 * ( $this->{_dim}{R} / $MAX_PLOT );
306            
307             if ( $this->{_colors}->[2] ) {
308             $this->{_conf_color}{color3} = $image->colorAllocateAlpha( @{ $this->{_colors}->[2] } );
309             my $ref_color13 = $this->_moy_color( $this->{_colors}->[0], $this->{_colors}->[2] );
310             my $ref_color23 = $this->_moy_color( $this->{_colors}->[1], $this->{_colors}->[2] );
311             my $ref_color123
312             = $this->_moy_color( $this->{_colors}->[0], $this->{_colors}->[1], $this->{_colors}->[2] );
313             $this->{_conf_color}{color13} = $image->colorAllocateAlpha( @{$ref_color13} );
314             $this->{_conf_color}{color23} = $image->colorAllocateAlpha( @{$ref_color23} );
315             $this->{_conf_color}{color123} = $image->colorAllocateAlpha( @{$ref_color123} );
316            
317             $image->fill( $this->{_coords}{xt3}, $this->{_coords}{yt3}, $this->{_conf_color}{color3} );
318             $image->fill( $this->{_coords}{xt13}, $this->{_coords}{yt13}, $this->{_conf_color}{color13} );
319             $image->fill( $this->{_coords}{xt23}, $this->{_coords}{yt23}, $this->{_conf_color}{color23} );
320             $image->fill( $this->{_coords}{xt123}, $this->{_coords}{yt123}, $this->{_conf_color}{color123} );
321             push @{ $this->{_colors_regions} }, $this->{_colors}->[2], $ref_color13, $ref_color23, $ref_color123;
322             }
323            
324             $image->string( gdMediumBoldFont,
325             $this->{_coords}{xt3},
326             $this->{_coords}{yt3},
327             $data3, $this->{_conf_color}{black}
328             );
329             $image->string( gdMediumBoldFont,
330             $this->{_coords}{xt13},
331             $this->{_coords}{yt13},
332             $data13, $this->{_conf_color}{black}
333             );
334             $image->string( gdMediumBoldFont,
335             $this->{_coords}{xt23},
336             $this->{_coords}{yt23},
337             $data23, $this->{_conf_color}{black}
338             );
339             $image->string( gdMediumBoldFont,
340             $this->{_coords}{xt123},
341             $this->{_coords}{yt123},
342             $data123, $this->{_conf_color}{black}
343             );
344            
345             $this->{_dim}{Hc} = $this->{_dim}{D} + $this->{_dim}{R};
346             push @{ $this->{_regions} }, $data3, $data13, $data23, $data123;
347             push @{ $this->{_listregions} }, \@list3, \@list13, \@list23, \@list123;
348             }
349            
350             return 1;
351             }
352            
353             sub get_list_regions {
354             my $this = shift;
355            
356             if ( $this->{_listregions} ) { return @{ $this->{_listregions} }; }
357             return;
358             }
359            
360             sub get_regions {
361             my $this = shift;
362            
363             if ( $this->{_regions} ) { return @{ $this->{_regions} }; }
364             return;
365             }
366            
367             sub get_colors_regions {
368             my $this = shift;
369            
370             if ( @{ $this->{_regions} } == $MIN_LIST_REGION or @{ $this->{_regions} } == $MAX_LIST_REGION ) {
371             return @{ $this->{_colors_regions} };
372            
373             }
374             else {
375             croak('No data to plot');
376             }
377             return;
378             }
379            
380             sub _moy_color {
381             my ( $this, @couleurs ) = @_;
382             my ( $R, $G, $B, $A ) = @RGBA;
383             foreach my $ref_couleur (@couleurs) {
384             my ( $R2, $G2, $B2, $A2 ) = @{$ref_couleur};
385             $R += $R2;
386             $G += $G2;
387             $B += $B2;
388             $A += $A2;
389             }
390             my $total = scalar @couleurs;
391            
392             my @moy_couleur = ( int( $R / $total ), int( $G / $total ), int( $B / $total ), int( $A / $total ) );
393             return \@moy_couleur;
394             }
395            
396             sub plot_histogram {
397             my $this = shift;
398            
399             # Get data regions
400             my @regions = $this->get_regions();
401             my ( @data, @names );
402             if ( scalar @regions == $MIN_LIST_REGION ) {
403             @data = (
404             [ 'Region 1', 'Region 2', 'Region 1/2', ],
405             [ $regions[0], undef, undef, ],
406             [ undef, $regions[1], undef, ],
407             [ undef, undef, $regions[2], ],
408             [ undef, undef, undef, ],
409             [ undef, undef, undef, ],
410             [ undef, undef, undef, ],
411             [ undef, undef, undef, ],
412             );
413             }
414             elsif ( scalar @regions == $MAX_LIST_REGION ) {
415             @data = (
416             [ 'Region 1', 'Region 2', 'Region 1/2', 'Region 3', 'Region 1/3', 'Region 2/3', 'Region 1/2/3' ],
417             [ $regions[0], undef, undef, undef, undef, undef, undef, ],
418             [ undef, $regions[1], undef, undef, undef, undef, undef, ],
419             [ undef, undef, $regions[2], undef, undef, undef, undef, ],
420             [ undef, undef, undef, $regions[3], undef, undef, undef, ],
421             [ undef, undef, undef, undef, $regions[4], undef, undef, ],
422             [ undef, undef, undef, undef, undef, $regions[5], undef, ],
423             [ undef, undef, undef, undef, undef, undef, $regions[6], ],
424             );
425            
426             }
427             else {
428             croak('No data to plot an histogram');
429             }
430            
431             my $graph = GD::Graph::bars->new( $this->{_width}, $this->{_height} );
432            
433             if ( $this->{_circles}{number} == $MIN_LEGEND and $this->{_legends}{number} == $MIN_LEGEND ) {
434             @names = (
435             $this->{_legend}->[0],
436             $this->{_legend}->[1],
437             $this->{_legend}->[0] . $SLASH . $this->{_legend}->[1],
438             );
439             $graph->set_legend(@names);
440             }
441             elsif ( $this->{_circles}{number} == $MAX_LEGEND and $this->{_legends}{number} == $MAX_LEGEND ) {
442             @names = (
443             $this->{_legend}->[0],
444             $this->{_legend}->[1],
445             $this->{_legend}->[0] . $SLASH . $this->{_legend}->[1],
446             $this->{_legend}->[2],
447             $this->{_legend}->[0] . $SLASH . $this->{_legend}->[2],
448             $this->{_legend}->[1] . $SLASH . $this->{_legend}->[2],
449             $this->{_legend}->[0] . $SLASH . $this->{_legend}->[1] . $SLASH . $this->{_legend}->[2],
450             );
451             $graph->set_legend(@names);
452             }
453             elsif ( $this->{_circles}{number} > 0
454             and $this->{_legends}{number} > 0
455             and $this->{_circles}{number} != $this->{_legends}{number} )
456             {
457             carp("You have to set $this->{_circles}{number} legends if you want to see a legend");
458             }
459            
460             $graph->set(
461             cumulate => 'true',
462             box_axis => 0,
463             x_ticks => 0,
464             x_plot_values => 0,
465             ) or carp $graph->error;
466            
467             my @color_regions = map { GD::Graph::colour::rgb2hex( @{$_}[ 0 .. 2 ] ) } $this->get_colors_regions();
468             $graph->set( dclrs => \@color_regions );
469             my $gd = $graph->plot( \@data ) or croak $graph->error;
470            
471             return $gd;
472             }
473            
474             1; # End of Venn::Chart
475            
476             __END__