File Coverage

blib/lib/Imager/TimelineDiagram.pm
Criterion Covered Total %
statement 25 96 26.0
branch 1 20 5.0
condition 0 8 0.0
subroutine 8 13 61.5
pod 3 4 75.0
total 37 141 26.2


line stmt bran cond sub pod time code
1             package Imager::TimelineDiagram;
2              
3 2     2   23475 use 5.00503;
  2         9  
  2         90  
4 2     2   11 use strict;
  2         4  
  2         89  
5 2     2   11 use vars qw($VERSION);
  2         9  
  2         344  
6 2     2   13715 use Imager;
  2         166546  
  2         18  
7 2     2   2089 use Imager::Fill;
  2         5708  
  2         62  
8 2     2   16 use Imager::Color;
  2         3  
  2         36  
9 2     2   11 use Carp;
  2         3  
  2         2401  
10              
11             $VERSION = '0.15';
12              
13             # create object
14             sub new {
15 1     1 0 2805 my ($class,@args) = @_;
16 1 50       6 if (scalar(@args)%2 != 0) {
17 1         398 carp("Invalid arguments. No in name/value pair format.");
18 1         5 return(undef);
19             }
20              
21 0           my %hashObject = (
22             imageHeight => 440,
23             imageWidth => 440,
24              
25             gridWidth => 401,
26             gridHeight => 401,
27             gridSpacing => 10,
28             gridXOffset => 20,
29             gridYOffset => 10,
30             gridColor => Imager::Color->new(200,200,200),
31              
32             dataColor => Imager::Color->new(255,100,100),
33             dataFormat => '%0.2f', # sprintf() format string
34             dataLabelSide => 'right',
35             showArrowheads => 1,
36              
37             labelColor => Imager::Color->new(0,0,0),
38             labelSize => 12,
39             labelFont => Imager::Font->new(file => 'ImUgly.ttf'),
40             );
41              
42 0           my %hash = @args;
43 0           for (keys %hash) {
44 0           $hashObject{$_} = $hash{$_};
45             }
46              
47 0 0         if (! defined($hashObject{'labelFont'})) {
48 0           carp("Failed to load labelFont specified.");
49 0           return(undef);
50             }
51              
52 0           $hashObject{_image} = Imager->new(xsize => $hashObject{'imageWidth'},
53             ysize => $hashObject{'imageHeight'},
54             channels => 4);
55              
56 0 0         if (! defined($hashObject{'_image'})) {
57 0           carp("Failed to create new Imager object : $!");
58 0           return(undef);
59             }
60              
61 0   0       my $self = bless(\%hashObject,$class||__PACKAGE__);
62             }
63              
64             # set list of milestones.
65             sub set_milestones {
66 0     0 1   my ($self,@milestones) = @_;
67 0           $self->{_legend} = [@milestones];
68             }
69              
70             # and AoA of :
71             # @array = (
72             # ['processFrom','processTo','time'],
73             # .
74             # .
75             # .
76             # )
77             # time being units from start of timeline
78             sub add_points {
79 0     0 1   my ($self,@aoa) = @_;
80 0           $self->{_data} = [@aoa];
81             }
82              
83             # write out to disk/stdout
84             # but first, this is where the magic happens
85             sub write {
86 0     0 1   my ($self,$file) = @_;
87 0           $self->_draw_grid();
88 0           $self->_draw_data();
89 0           $self->{'_image'}->write(file => $file);
90             }
91              
92              
93              
94             ######## internal functions #######
95              
96             # draw the grid and labels
97             sub _draw_grid {
98 0     0     my ($self) = @_;
99 0           my $image = $self->{_image};
100              
101 0           my @v_lines;
102 0           my @points = @{ $self->{_legend} };
  0            
103              
104             # for every $gridSpacing pixes across, draw a vertical line
105 0           for (my $i=$self->{'gridXOffset'}; $i <= $self->{'gridWidth'} ;$i += $self->{'gridSpacing'}) {
106 0           $image->line(color => $self->{'gridColor'}, x1 => $i, y1 => $self->{'gridYOffset'},
107             x2 => $i, y2 => $self->{'gridYOffset'}+$self->{'gridHeight'});
108 0           push(@v_lines,$i);
109             }
110              
111             # for every $gridSpacing pixes across, draw a horizontal line
112 0           for (my $i=$self->{'gridYOffset'}; $i < $self->{'gridYOffset'}+$self->{'gridHeight'} ;$i += $self->{'gridSpacing'}) {
113 0           $image->line(color => $self->{'gridColor'}, x1 => $self->{'gridXOffset'}, y1 => $i,
114             x2 => $self->{'gridWidth'}, y2 => $i);
115             }
116              
117             # Logic Time:
118             # There are scalar(@v_lines) rows in the grid.
119             # There are scalar(@points) connection point.
120 0           $self->{'px_per_point'} = int( scalar(@v_lines) / (scalar(@points)-1) ) * $self->{'gridSpacing'};
121 0           my $current_px = $self->{'gridXOffset'};
122 0           for (my $pn=0;$pn < scalar(@points);$pn++) {
123 0 0         if ($current_px > $v_lines[-1]) {
124 0           $current_px = $v_lines[-1];
125             }
126 0           $image->box(color => Imager::Color->new(0,0,0),
127             xmin => $current_px-1, ymin => $self->{'gridYOffset'},
128             xmax => $current_px+1, ymax => $self->{'gridHeight'}+$self->{'gridYOffset'},
129             filled => 1
130             );
131 0           my @bbox = $self->{'labelFont'}->bounding_box(string => $points[$pn]);
132 0           $image->string(font => $self->{'labelFont'},
133             text => $points[$pn],
134             x => $current_px-(($bbox[2]-$bbox[0])/2), # current line/2
135             y => $self->{'gridYOffset'}+$self->{'gridHeight'}+($bbox[3]), # grid + letter height
136             size => $self->{'labelSize'},
137             color => $self->{'labelColor'}
138             );
139 0           $self->{_label_to_x_offset}{$points[$pn]} = $current_px;
140 0           $current_px += $self->{'px_per_point'};
141             }
142              
143 0           $image->string(
144             font => $self->{'labelFont'},
145             size => $self->{'labelSize'},
146             color => $self->{'labelColor'},
147             text => sprintf($self->{dataFormat},0),
148             x => $self->{'gridWidth'},
149             y => $self->{'gridYOffset'},
150             );
151 0   0       $image->string(
152             font => $self->{'labelFont'},
153             size => $self->{'labelSize'},
154             color => $self->{'labelColor'},
155             text => sprintf($self->{dataFormat},($self->{'maxTime'} || $self->{_data}[-1][2])),
156             x => $self->{'gridWidth'},
157             y => $self->{'gridHeight'}+$self->{'gridYOffset'},
158             );
159             }
160              
161             sub _draw_data {
162 0     0     my ($self) = @_;
163 0 0         if (! $self->{'px_per_point'}) {
164 0           $self->_draw_grid();
165             }
166 0           my $image = $self->{'_image'};
167              
168             # ok, more logic :
169             # the grid is $self->{'gridHeight'} pixes high
170             # the highest scale needed is $self->{'maxTime'} || $self->{_data}[-1][2]
171             # there is no negative time, the scale begins at 0
172             # so ...
173             #
174             # gridHeight/maxTime pixels per second
175              
176 0   0       my $px_per_sec = ($self->{'gridHeight'}/($self->{'maxTime'} || $self->{_data}[-1][2]));
177 0           foreach my $aref (@{ $self->{_data} }) {
  0            
178 0           my $from = $aref->[0];
179 0           my $to = $aref->[1];
180 0           my $time = $aref->[2];
181            
182 0           my $fromX = $self->{_label_to_x_offset}{$from};
183 0           my $toX = $self->{_label_to_x_offset}{$to};
184 0           my $timeY = $px_per_sec * $time;
185            
186             #print "[$fromX,$timeY] -> [$toX,$timeY]\n";
187 0           $image->line(color => $self->{'dataColor'},
188             x1 => $fromX , y1 => $timeY,
189             x2 => $toX , y2 => $timeY,
190             );
191              
192 0           my $dlX;
193 0           my @bbox = $self->{'labelFont'}->bounding_box(string => sprintf($self->{'dataFormat'},$time));
194 0           my $dlY = $timeY;
195 0 0         if ($self->{'dataLabelSide'} eq 'left') {
196 0 0         $dlX = ( $fromX < $toX ? $fromX : $toX ) - 5 - ($bbox[2]-$bbox[0]);
197             } else {
198 0 0         $dlX = ( $fromX > $toX ? $fromX : $toX ) + 5;
199             }
200 0           $image->string(font => $self->{'labelFont'},
201             size => $self->{'labelSize'},
202             color => $self->{'labelColor'},
203             text => sprintf($self->{'dataFormat'},$time),
204             x => $dlX,
205             y => $dlY,
206             );
207              
208 0 0         if ($self->{'showArrowheads'}) {
209 0           my ($ahBkX,$ahBkY1,$ahBkY2);
210 0 0         if ($toX > $fromX) {
211 0           $ahBkX = $toX-3;
212             } else {
213 0           $ahBkX = $toX+3;
214             }
215 0           $ahBkY1 = $timeY-2;
216 0           $ahBkY2 = $timeY+2;
217             # ploygon's are anti-aliased ... and that core's my Imager :(
218             #$image->polygon(x => [$toX,$ahBkX,$ahBkX],y => [$timeY,$ahBkY1,$ahBkY2],color => $self->{'dataColor'});
219 0           $image->polyline(x => [$toX,$ahBkX,$ahBkX,$toX],y => [$timeY,$ahBkY1,$ahBkY2,$timeY],color => $self->{'dataColor'});
220             }
221             }
222             }
223              
224             1;
225             __END__