1#==========================================================================
2#              Copyright (c) 1995-1998 Martien Verbruggen
3#--------------------------------------------------------------------------
4#
5#   Name:
6#       GD::Graph::bars.pm
7#
8# $Id: bars.pm,v 1.26 2007/04/26 03:16:09 ben Exp $
9#
10#==========================================================================
11
12package GD::Graph::bars;
13
14($GD::Graph::bars::VERSION) = '$Revision: 1.26 $' =~ /\s([\d.]+)/;
15
16use strict;
17
18use GD::Graph::axestype;
19use GD::Graph::utils qw(:all);
20use GD::Graph::colour qw(:colours);
21
22@GD::Graph::bars::ISA = qw(GD::Graph::axestype);
23
24use constant PI => 4 * atan2(1,1);
25
26sub initialise
27{
28    my $self = shift;
29    $self->SUPER::initialise();
30    $self->set(correct_width => 1);
31}
32
33sub draw_data
34{
35    my $self = shift;
36
37    $self->SUPER::draw_data() or return;
38
39    unless ($self->{no_axes})
40    {
41        # redraw the 'zero' axis
42        if ($self->{rotate_chart})
43        {
44            $self->{graph}->line(
45                $self->{zeropoint}, $self->{top},
46                $self->{zeropoint}, $self->{bottom},
47                $self->{fgci} );
48        }
49        else
50        {
51            $self->{graph}->line(
52                $self->{left}, $self->{zeropoint},
53                $self->{right}, $self->{zeropoint},
54                $self->{fgci} );
55        }
56    }
57
58    return $self;
59}
60
61sub _top_values
62{
63    my $self = shift;
64    my @topvalues;
65
66    if ($self->{cumulate})
67    {
68        my $data = $self->{_data};
69        for my $i (0 .. $data->num_points - 1)
70        {
71            push @topvalues, $data->get_y_cumulative($data->num_sets, $i);
72        }
73    }
74
75    return \@topvalues;
76}
77
78#
79# Draw the shadow
80#
81sub _draw_shadow
82{
83    my $self = shift;
84    my ($ds, $i, $value, $topvalues, $l, $t, $r, $b) = @_;
85    my $bsd = $self->{shadow_depth} or return;
86    my $bsci = $self->set_clr(_rgb($self->{shadowclr}));
87
88    if ($self->{cumulate})
89    {
90        return if $ds > 1;
91        $value = $topvalues->[$i];
92        if ($self->{rotate_chart})
93        {
94            $r = ($self->val_to_pixel($i + 1, $value, $ds))[0];
95        }
96        else
97        {
98            $t = ($self->val_to_pixel($i + 1, $value, $ds))[1];
99        }
100    }
101
102    # XXX Clean this up
103    if ($value >= 0)
104    {
105        if ($self->{rotate_chart})
106        {
107            $self->{graph}->filledRectangle(
108                $l, $t + $bsd, $r - $bsd, $b + $bsd, $bsci);
109        }
110        else
111        {
112            $self->{graph}->filledRectangle(
113                $l + $bsd, $t + $bsd, $r + $bsd, $b, $bsci);
114        }
115    }
116    else
117    {
118        if ($self->{rotate_chart})
119        {
120            $self->{graph}->filledRectangle(
121                $l + $bsd, $t, $r + $bsd, $b, $bsci);
122        }
123        else
124        {
125            $self->{graph}->filledRectangle(
126                $l + $bsd, $b, $r + $bsd, $t + $bsd, $bsci);
127        }
128    }
129}
130
131sub draw_data_set_h
132{
133    my $self = shift;
134    my $ds = shift;
135
136    my $bar_s = $self->{bar_spacing}/2;
137
138    # Pick a data colour
139    my $dsci = $self->set_clr($self->pick_data_clr($ds));
140    # contrib "Bremford, Mike" <mike.bremford@gs.com>
141    my $brci = $self->set_clr($self->pick_border_clr($ds));
142
143    my @values = $self->{_data}->y_values($ds) or
144        return $self->_set_error("Impossible illegal data set: $ds",
145            $self->{_data}->error);
146
147    my $topvalues = $self->_top_values;
148    #
149    # Draw all shadows.
150    for my $i (0 .. $#values)
151    {
152        my $value = $values[$i];
153        next unless defined $value;
154
155        my $l = $self->_get_bottom($ds, $i);
156        my ($r, $xp) = $self->val_to_pixel($i + 1, $value, $ds);
157
158        # calculate top and bottom of bar
159        my ($t, $b);
160        my $window = $self->{x_step} - $self->{bargroup_spacing};
161
162        if (ref $self eq 'GD::Graph::mixed' || $self->{overwrite})
163        {
164            $t = $xp - $window/2 + $bar_s + 1;
165            $b = $xp + $window/2 - $bar_s;
166        }
167        else
168        {
169            $t = $xp
170                - $window/2
171                + ($ds - 1) * $window/$self->{_data}->num_sets
172                + $bar_s + 1; # GRANTM thinks this +1 should be conditional on bargroup_spacing being absent
173            $b = $xp
174                - $window/2
175                + $ds * $window/$self->{_data}->num_sets
176                - $bar_s;
177        }
178
179        $self->_draw_shadow($ds, $i, $value, $topvalues, $l, $t, $r, $b);
180    }
181
182    for my $i (0 .. $#values)
183    {
184        my $value = $values[$i];
185        next unless defined $value;
186
187        my $l = $self->_get_bottom($ds, $i);
188        $value = $self->{_data}->get_y_cumulative($ds, $i)
189            if ($self->{cumulate});
190
191        # CONTRIB Jeremy Wadsack
192        #
193        # cycle_clrs option sets the color based on the point,
194        # not the dataset.
195        $dsci = $self->set_clr($self->pick_data_clr($i + 1))
196            if $self->{cycle_clrs};
197        $brci = $self->set_clr($self->pick_data_clr($i + 1))
198            if $self->{cycle_clrs} > 1;
199
200        # get coordinates of right and center of bar
201        my ($r, $xp) = $self->val_to_pixel($i + 1, $value, $ds);
202
203        # calculate top and bottom of bar
204        my ($t, $b);
205        my $window = $self->{x_step} - $self->{bargroup_spacing};
206
207        if (ref $self eq 'GD::Graph::mixed' || $self->{overwrite})
208        {
209            $t = $xp - $window/2 + $bar_s + 1;
210            $b = $xp + $window/2 - $bar_s;
211        }
212        else
213        {
214            $t = $xp
215                - $window/2
216                + ($ds - 1) * $window/$self->{_data}->num_sets
217                + $bar_s + 1; # GRANTM thinks this +1 should be conditional on bargroup_spacing being absent
218            $b = $xp
219                - $window/2
220                + $ds * $window/$self->{_data}->num_sets
221                - $bar_s;
222        }
223
224        # draw the bar
225        if ($value < 0) { ($r,$l) = ($l,$r) }
226
227        $self->{graph}->filledRectangle($l, $t, $r, $b, $dsci)
228            if defined $dsci;
229        $self->{graph}->rectangle($l, $t, $r, $b, $brci)
230               if defined $brci && $b - $t > $self->{accent_treshold};
231
232        $self->{_hotspots}->[$ds]->[$i] = ['rect', $l, $t, $r, $b];
233    }
234
235    return $ds;
236}
237
238sub draw_data_set_v
239{
240    my $self = shift;
241    my $ds = shift;
242
243    my $bar_s = $self->{bar_spacing}/2;
244
245    # Pick a data colour
246    my $dsci = $self->set_clr($self->pick_data_clr($ds));
247    # contrib "Bremford, Mike" <mike.bremford@gs.com>
248    my $brci = $self->set_clr($self->pick_border_clr($ds));
249
250    my @values = $self->{_data}->y_values($ds) or
251        return $self->_set_error("Impossible illegal data set: $ds",
252            $self->{_data}->error);
253
254    my $topvalues = $self->_top_values;
255
256    my ($bar_sets,$ds_adj) = ( $self->{_data}->num_sets , $ds );
257    if ( $self->isa( 'GD::Graph::mixed' ) ) {
258        my @types =  $self->types;
259        $bar_sets =  grep { $_  eq 'bars' } @types;
260        $ds_adj   =  grep { $_  eq 'bars' } @types[0..$ds-1];
261    }
262
263    # Draw all shadows.
264    for my $i (0 .. $#values)
265    {
266        my $value = $values[$i];
267        next unless defined $value;
268
269        my $bottom = $self->_get_bottom($ds, $i);
270        my ($xp, $t) = $self->val_to_pixel($i + 1, $value, $ds);
271        my ($l, $r);
272        my $window = $self->{x_step} - $self->{bargroup_spacing};
273
274        if ($self->{overwrite})
275        {
276            $l = $xp - $window/2 + $bar_s + 1;
277            $r = $xp + $window/2 - $bar_s;
278        }
279        else
280        {
281            $l = $xp
282                - $window/2
283                + ($ds_adj - 1) * $window/$bar_sets
284                + $bar_s + 1; # GRANTM thinks this +1 should be conditional on bargroup_spacing being absent
285            $r = $xp
286                - $window/2
287                + $ds_adj * $window/$bar_sets
288                - $bar_s;
289        }
290
291        $self->_draw_shadow($ds, $i, $value, $topvalues, $l, $t, $r, $bottom);
292    }
293
294    # Then all bars.
295    for my $i (0 .. $#values)
296    {
297        my $value = $values[$i];
298        next unless defined $value;
299
300        my $bottom = $self->_get_bottom($ds, $i);
301        $value = $self->{_data}->get_y_cumulative($ds, $i)
302            if ($self->{cumulate});
303
304        # CONTRIB Jeremy Wadsack
305        #
306        # cycle_clrs option sets the color based on the point,
307        # not the dataset.
308        $dsci = $self->set_clr($self->pick_data_clr($i + 1))
309            if $self->{cycle_clrs};
310        $brci = $self->set_clr($self->pick_data_clr($i + 1))
311            if $self->{cycle_clrs} > 1;
312
313        # get coordinates of top and center of bar
314        my ($xp, $t) = $self->val_to_pixel($i + 1, $value, $ds);
315
316        # calculate left and right of bar
317        my ($l, $r);
318        my $window = $self->{x_step} - $self->{bargroup_spacing};
319
320        if ($self->{overwrite})
321        {
322            $l = $xp - $window/2 + $bar_s + 1;
323            $r = $xp + $window/2 - $bar_s;
324        }
325        else
326        {
327            $l = $xp
328                - $window/2
329                + ($ds_adj - 1) * $window/$bar_sets
330                + $bar_s + 1; # GRANTM thinks this +1 should be conditional on bargroup_spacing being absent
331            $r = $xp
332                - $window/2
333                + $ds_adj * $window/$bar_sets
334                - $bar_s;
335        }
336
337        # draw the bar
338
339        if ($value < 0) { ($bottom,$t) = ($t,$bottom) }
340        $self->{graph}->filledRectangle($l, $t, $r, $bottom, $dsci)
341            if defined $dsci;
342        $self->{graph}->rectangle($l, $t, $r, $bottom, $brci)
343            if defined $brci && $r - $l > $self->{accent_treshold};
344        $self->{_hotspots}->[$ds]->[$i] = ['rect', $l, $t, $r, $bottom]
345    }
346
347    return $ds;
348}
349
350sub draw_data_set
351{
352    $_[0]->{rotate_chart} ? goto &draw_data_set_h : goto &draw_data_set_v;
353}
354
355sub draw_values
356{
357    my $self = shift;
358
359    return $self unless $self->{show_values};
360    my $has_args = @_;
361
362    my $text_angle = $self->{values_vertical} ? PI/2 : 0;
363    my @numPoints = $self->{_data}->num_points();
364    my @datasets = $has_args ? @_ : 1 .. $self->{_data}->num_sets;
365
366    my ($l, $r, $b, $t) = ($self->{left}, $self->{right}, $self->{bottom}, $self->{top});
367
368    for my $dsn ( @datasets )
369    {   # CONTRIB Romeo Juncu
370        my @values = ();
371        if (!$self->get("cumulate")) {
372          @values = $self->{_data}->y_values($dsn) or
373            return $self->_set_error("Impossible illegal data set: $dsn",
374                $self->{_data}->error);
375        } else {
376          my $nPoints = $numPoints[$dsn] || 0;
377          my $vec = $has_args ? \@datasets : undef;
378          @values = map { $self->{_data}->get_y_cumulative($dsn, $_, $vec) }
379            (0..$nPoints - 1) ;
380        }
381        my @display = $self->{show_values}->y_values($dsn) or next;
382
383        for (my $i = 0; $i < @values; $i++)
384        {
385            next unless defined $display[$i];
386
387            my $value = $display[$i];
388            if (defined $self->{values_format})
389            {
390                $value = ref $self->{values_format} eq 'CODE' ?
391                    &{$self->{values_format}}($value) :
392                    sprintf($self->{values_format}, $value);
393            }
394
395            my ($xp, $yp);
396            if (defined($self->{x_min_value}) && defined($self->{x_max_value}))
397            {
398                ($xp, $yp) = $self->val_to_pixel(
399                    $self->{_data}->get_x($i), $values[$i], $dsn);
400            }
401            else
402            {
403                ($xp, $yp) = $self->val_to_pixel($i+1, $values[$i], $dsn);
404            }
405            if ($self->{rotate_chart})
406            {
407                $xp += $self->{values_space};
408                unless ($self->{overwrite})
409                {
410                    $yp -= $self->{x_step}/2 - ($dsn - 0.5)
411                        * $self->{x_step}/@datasets;
412                }
413            }
414            else
415            {
416                $yp -= $self->{values_space};
417                unless ($self->{overwrite})
418                {
419                    $xp -= $self->{x_step}/2 - ($dsn - 0.5)
420                        * $self->{x_step}/@datasets;
421                }
422            }
423
424            $self->{gdta_values}->set_text($value);
425            if ( $self->{'hide_overlapping_values'} ) {
426                my @bbox = $self->{gdta_values}->bounding_box($xp, $yp, $text_angle);
427                next if grep $_ < $l || $_ > $r, @bbox[0, 2];
428                next if grep $_ < $t || $_ > $b, @bbox[1, 5];
429            }
430            $self->{gdta_values}->draw($xp, $yp, $text_angle);
431        }
432    }
433
434    return $self
435}
436
437"Just another true value";
438