Patches from Patrick Michaud:
authorpmichaud <pmichaud@pobox.com>
Thu, 16 Apr 2009 23:36:09 +0000 (23:36 +0000)
committerTony Cook <tony@develop-help.com>
Thu, 16 Apr 2009 23:36:09 +0000 (23:36 +0000)
Ok, I've fixed the y-tic error - that was an embarrassing display of math
:(  I'm gonna work on the size issue now, and if I still have time, work
on using Chart::Math::Axis.

ok, i implemented autoscaling of the graph area, and removed a bunch of
cruft around graph size.

Ok, that auto-y-scaling is pretty nice.  Here's a diff that uses it, if
someone calls use_automatic_axis(), or if automatic_axis => 1 is in the
style.  Next I'm going to get some horizontal grid-lines going.

I added somewhat cheesy gridlines.  Since there aren't line styles, I just
manually dashed the line.  Right now it's hard coded to black - I felt odd
about letting the color be styled, when the fundamental style can't be
changed.

lib/Imager/Graph/Vertical.pm

index 1615fcf..ee6ccb8 100644 (file)
@@ -144,31 +144,46 @@ sub draw {
   my @image_box = ( 0, 0, $img->getwidth-1, $img->getheight-1 );
   $self->_set_image_box(\@image_box);
 
+  my @chart_box = ( 0, 0, $img->getwidth-1, $img->getheight-1 );
+  $self->_draw_legend(\@chart_box);
+  if ($style->{title}{text}) {
+    $self->_draw_title($img, \@chart_box)
+      or return;
+  }
+
   # Scale the graph box down to the widest graph that can cleanly hold the # of columns.
   $self->_get_data_range();
+  $self->_remove_tics_from_chart_box(\@chart_box);
   my $column_count = $self->_get_column_count();
 
   my $width = $self->_get_number('width');
   my $height = $self->_get_number('height');
-  my $size = $self->_get_number('size');
 
-  my $bottom = ($height - $size) / 2;
-  my $left   = ($width - $size) / 2;
+  my $graph_width = $chart_box[2] - $chart_box[0];
+  my $graph_height = $chart_box[3] - $chart_box[1];
 
-  my $col_width = int($size / $column_count) -1;
-  my $graph_width = $col_width * $column_count + 1;
+  my $col_width = int(($graph_width - 1) / $column_count) -1;
+  $graph_width = $col_width * $column_count + 1;
 
-  my @graph_box = ( $left, $bottom, $left + $graph_width - 1, $bottom + $size - 1 );
-  $self->_set_graph_box(\@graph_box);
+  my $tic_count = $self->_get_y_tics();
+  my $tic_distance = int(($graph_height-1) / ($tic_count - 1));
+  $graph_height = $tic_distance * ($tic_count - 1);
+
+  my $bottom = $chart_box[1];
+  my $left   = $chart_box[0];
 
-  $self->_draw_legend();
+  $self->{'_style'}{'graph_width'} = $graph_width;
+  $self->{'_style'}{'graph_height'} = $graph_height;
+
+  my @graph_box = ($left, $bottom, $left + $graph_width, $bottom + $graph_height);
+  $self->_set_graph_box(\@graph_box);
 
   $img->box(
             color   => $self->_get_color('outline.line'),
             xmin    => $left,
             xmax    => $left+$graph_width,
             ymin    => $bottom,
-            ymax    => $bottom+$size,
+            ymax    => $bottom+$graph_height,
             );
 
   $img->box(
@@ -176,7 +191,7 @@ sub draw {
             xmin    => $left + 1,
             xmax    => $left+$graph_width - 1,
             ymin    => $bottom + 1,
-            ymax    => $bottom+$size -1 ,
+            ymax    => $bottom+$graph_height-1 ,
             filled  => 1,
             );
 
@@ -186,7 +201,7 @@ sub draw {
 
   my $zero_position;
   if ($value_range) {
-    $zero_position =  $bottom + $size - (-1*$min_value / $value_range) * ($size -1);
+    $zero_position =  $bottom + $graph_height - (-1*$min_value / $value_range) * ($graph_height-1);
   }
 
   if ($min_value < 0) {
@@ -195,7 +210,7 @@ sub draw {
             xmin    => $left + 1,
             xmax    => $left+$graph_width- 1,
             ymin    => $zero_position,
-            ymax    => $bottom+$size -1,
+            ymax    => $bottom+$graph_height - 1,
             filled  => 1,
     );
     $img->line(
@@ -216,6 +231,14 @@ sub draw {
   if ($self->_get_data_series()->{'line'}) {
     $self->_draw_lines();
   }
+
+  if ($self->_get_y_tics()) {
+    $self->_draw_y_tics();
+  }
+  if ($self->_get_labels()) {
+    $self->_draw_x_tics();
+  }
+
   return $self->_get_image();
 }
 
@@ -247,9 +270,6 @@ sub _get_data_range {
   }
 
   my $range_padding = $self->_get_number('range_padding');
-  if (!defined $range_padding) {
-    $range_padding = 10;
-  }
   if (defined $config_min) {
     $min_value = $config_min;
   }
@@ -279,6 +299,22 @@ sub _get_data_range {
   }
   $column_count = $self->_max(0, $sc_cols, $l_cols);
 
+  if ($self->_get_number('automatic_axis')) {
+    # In case this was set via a style, and not by the api method
+    eval { require "Chart/Math/Axis.pm"; };
+    if ($@) {
+      die "Can't use automatic_axis - $@";
+    }
+
+    my $axis = Chart::Math::Axis->new();
+    $axis->add_data($min_value, $max_value);
+    $max_value = $axis->top;
+    $min_value = $axis->bottom;
+    my $ticks     = $axis->ticks;
+    # The +1 is there because we have the bottom tick as well
+    $self->set_y_tics($ticks+1);
+  }
+
   $self->_set_max_value($max_value);
   $self->_set_min_value($min_value);
   $self->_set_column_count($column_count);
@@ -397,6 +433,7 @@ sub _get_stacked_column_range {
 
 sub _draw_legend {
   my $self = shift;
+  my $chart_box = shift;
   my $style = $self->{'_style'};
 
   my @labels;
@@ -412,7 +449,7 @@ sub _draw_legend {
   }
 
   if ($style->{features}{legend} && (scalar @labels)) {
-    $self->SUPER::_draw_legend($self->_get_image(), \@labels, $self->_get_image_box())
+    $self->SUPER::_draw_legend($self->_get_image(), \@labels, $chart_box)
       or return;
   }
   return;
@@ -436,27 +473,23 @@ sub _draw_lines {
 
   my $width = $self->_get_number('width');
   my $height = $self->_get_number('height');
-  my $size = $self->_get_number('size');
 
-  my $bottom = ($height - $size) / 2;
-  my $left   = ($width - $size) / 2;
-
-  my $zero_position =  $bottom + $size - (-1*$min_value / $value_range) * ($size -1);
-
-  if ($self->_get_y_tics()) {
-    $self->_draw_y_tics();
-  }
-  if ($self->_get_labels()) {
-    $self->_draw_x_tics();
-  }
+  my $graph_width = $self->_get_number('graph_width');
+  my $graph_height = $self->_get_number('graph_height');
 
   my $line_series = $self->_get_data_series()->{'line'};
   my $series_counter = $self->_get_series_counter() || 0;
 
   my $has_columns = (defined $self->_get_data_series()->{'column'} || $self->_get_data_series->{'stacked_column'}) ? 1 : 0;
 
-  my $col_width = int($size / $column_count) -1;
-  my $graph_width = $col_width * $column_count + 1;
+  my $col_width = int($graph_width / $column_count) -1;
+
+  my $graph_box = $self->_get_graph_box();
+  my $left = $graph_box->[0] + 1;
+  my $bottom = $graph_box->[1];
+
+  my $zero_position =  $bottom + $graph_height - (-1*$min_value / $value_range) * ($graph_height - 1);
+
 
   my $line_aa = $self->_get_number("lineaa");
   foreach my $series (@$line_series) {
@@ -481,8 +514,8 @@ sub _draw_lines {
       $x1 += $has_columns * $interval / 2;
       $x2 += $has_columns * $interval / 2;
 
-      my $y1 = $bottom + ($value_range - $data[$i] + $min_value)/$value_range * $size;
-      my $y2 = $bottom + ($value_range - $data[$i + 1] + $min_value)/$value_range * $size;
+      my $y1 = $bottom + ($value_range - $data[$i] + $min_value)/$value_range * $graph_height;
+      my $y2 = $bottom + ($value_range - $data[$i + 1] + $min_value)/$value_range * $graph_height;
 
       push @marker_positions, [$x1, $y1];
       $img->line(x1 => $x1, y1 => $y1, x2 => $x2, y2 => $y2, aa => $line_aa, color => $color) || die $img->errstr;
@@ -491,7 +524,7 @@ sub _draw_lines {
     my $x2 = $left + ($data_size - 1) * $interval;
     $x2 += $has_columns * $interval / 2;
 
-    my $y2 = $bottom + ($value_range - $data[$data_size - 1] + $min_value)/$value_range * $size;
+    my $y2 = $bottom + ($value_range - $data[$data_size - 1] + $min_value)/$value_range * $graph_height;
 
     push @marker_positions, [$x2, $y2];
     foreach my $position (@marker_positions) {
@@ -588,21 +621,17 @@ sub _draw_columns {
 
   my $width = $self->_get_number('width');
   my $height = $self->_get_number('height');
-  my $size = $self->_get_number('size');
 
-  my $bottom = ($height - $size) / 2;
-  my $left   = ($width - $size) / 2 + 1;
+  my $graph_width = $self->_get_number('graph_width');
+  my $graph_height = $self->_get_number('graph_height');
 
-  my $zero_position =  int($bottom + $size - (-1*$min_value / $value_range) * ($size -1));
 
-  if ($self->_get_y_tics()) {
-    $self->_draw_y_tics();
-  }
-  if ($self->_get_labels()) {
-    $self->_draw_x_tics();
-  }
+  my $graph_box = $self->_get_graph_box();
+  my $left = $graph_box->[0] + 1;
+  my $bottom = $graph_box->[1];
+  my $zero_position =  int($bottom + $graph_height - (-1*$min_value / $value_range) * ($graph_height -1));
 
-  my $bar_width = int(($size)/ $column_count - 2);
+  my $bar_width = int($graph_width / $column_count - 1);
 
   my $outline_color;
   if ($style->{'features'}{'outline'}) {
@@ -630,7 +659,7 @@ sub _draw_columns {
       }
       my $x2 = $x1 + $bar_width;
 
-      my $y1 = int($bottom + ($value_range - $data[$i] + $min_value)/$value_range * $size);
+      my $y1 = int($bottom + ($value_range - $data[$i] + $min_value)/$value_range * $graph_height);
 
       my $color = $self->_data_color($series_counter);
 
@@ -670,16 +699,11 @@ sub _draw_stacked_columns {
   my $graph_box = $self->_get_graph_box();
   my $left = $graph_box->[0] + 1;
   my $bottom = $graph_box->[1];
-  my $size = $self->_get_number('size');
 
-  if ($self->_get_y_tics()) {
-    $self->_draw_y_tics();
-  }
-  if ($self->_get_labels()) {
-    $self->_draw_x_tics();
-  }
+  my $graph_width = $self->_get_number('graph_width');
+  my $graph_height = $self->_get_number('graph_height');
 
-  my $bar_width = int($size / $column_count -2);
+  my $bar_width = int($graph_width / $column_count -1);
   my $column_series = 0;
   if (my $column_series_data = $self->_get_data_series()->{'column'}) {
     $column_series = (scalar @$column_series_data);
@@ -691,7 +715,7 @@ sub _draw_stacked_columns {
     $outline_color = $self->_get_color('outline.line');
   }
 
-  my $zero_position =  $bottom + $size - (-1*$min_value / $value_range) * ($size -1);
+  my $zero_position =  $bottom + $graph_height - (-1*$min_value / $value_range) * ($graph_height -1);
   my $col_series = $self->_get_data_series()->{'stacked_column'};
   my $series_counter = $self->_get_series_counter() || 0;
   foreach my $series (@$col_series) {
@@ -700,10 +724,9 @@ sub _draw_stacked_columns {
     my $color = $self->_data_color($series_counter);
     for (my $i = 0; $i < $data_size; $i++) {
       my $x1 = int($left + $bar_width * ($column_series * $i)) + $column_series * $i;
-#      my $x1 = $left + $i * $size / ($data_size);
       my $x2 = $x1 + $bar_width;
 
-      my $y1 = $bottom + ($value_range - $data[$i] + $min_value)/$value_range * $size;
+      my $y1 = $bottom + ($value_range - $data[$i] + $min_value)/$value_range * $graph_height;
 
       if ($data[$i] > 0) {
         $img->box(xmin => $x1, xmax => $x2, ymin => $y1, ymax => $zero_position-1, color => $color, filled => 1);
@@ -745,6 +768,31 @@ sub _add_data_series {
 
 =over
 
+=item show_horizontal_gridlines()
+
+Shows horizontal gridlines at the y-tics.
+
+=cut
+
+sub show_horizontal_gridlines {
+    $_[0]->{'custom_style'}->{'horizontal_gridlines'} = 1;
+}
+
+=item use_automatic_axis()
+
+Automatically scale the Y axis, based on L<Chart::Math::Axis>
+
+=cut
+
+sub use_automatic_axis {
+  eval { require "Chart/Math/Axis.pm"; };
+  if ($@) {
+    die "use_automatic_axis - $@\nCalled from ".join(' ', caller)."\n";
+  }
+  $_[0]->{'custom_style'}->{'automatic_axis'} = 1;
+}
+
+
 =item set_y_tics($count)
 
 Set the number of Y tics to use.  Their value and position will be determined by the data range.
@@ -759,6 +807,82 @@ sub _get_y_tics {
   return $_[0]->{'y_tics'};
 }
 
+sub _remove_tics_from_chart_box {
+  my $self = shift;
+  my $chart_box = shift;
+
+  # XXX - bad default
+  my $tic_width = $self->_get_y_tic_width() || 10;
+  my @y_tic_box = ($chart_box->[0], $chart_box->[1], $chart_box->[0] + $tic_width, $chart_box->[3]);
+
+  # XXX - bad default
+  my $tic_height = $self->_get_x_tic_height() || 10;
+  my @x_tic_box = ($chart_box->[0], $chart_box->[3] - $tic_height, $chart_box->[2], $chart_box->[3]);
+
+  $self->_remove_box($chart_box, \@y_tic_box);
+  $self->_remove_box($chart_box, \@x_tic_box);
+}
+
+sub _get_y_tic_width{
+  my $self = shift;
+  my $min = $self->_get_min_value();
+  my $max = $self->_get_max_value();
+  my $tic_count = $self->_get_y_tics();
+
+  my $img = $self->_get_image();
+  my $graph_box = $self->_get_graph_box();
+  my $image_box = $self->_get_image_box();
+
+  my $interval = ($max - $min) / ($tic_count - 1);
+
+  my %text_info = $self->_text_style('legend')
+    or return;
+
+  my $max_width = 0;
+  for my $count (0 .. $tic_count - 1) {
+    my $value = sprintf("%.2f", ($count*$interval)+$min);
+
+    my @box = $self->_text_bbox($value, 'legend');
+    my $width = $box[2] - $box[0];
+
+    # For the tic width
+    $width += 10;
+    if ($width > $max_width) {
+      $max_width = $width;
+    }
+  }
+
+  return $max_width;
+}
+
+sub _get_x_tic_height {
+  my $self = shift;
+
+  my $labels = $self->_get_labels();
+
+  my $tic_count = (scalar @$labels) - 1;
+
+  my %text_info = $self->_text_style('legend')
+    or return;
+
+  my $max_height = 0;
+  for my $count (0 .. $tic_count) {
+    my $label = $labels->[$count];
+
+    my @box = $self->_text_bbox($label, 'legend');
+
+    my $height = $box[3] - $box[1];
+
+    # Padding + the tic
+    $height += 10;
+    if ($height > $max_height) {
+      $max_height = $height;
+    }
+  }
+
+  return $max_height;
+}
+
 sub _draw_y_tics {
   my $self = shift;
   my $min = $self->_get_min_value();
@@ -774,13 +898,17 @@ sub _draw_y_tics {
   my %text_info = $self->_text_style('legend')
     or return;
 
-  my $tic_distance = ($graph_box->[3] - $graph_box->[1]) / ($tic_count - 1);
+  my $show_gridlines = $self->_get_number('horizontal_gridlines');
+  my $tic_distance = int(($graph_box->[3] - $graph_box->[1]) / ($tic_count - 1));
   for my $count (0 .. $tic_count - 1) {
     my $x1 = $graph_box->[0] - 5;
     my $x2 = $graph_box->[0] + 5;
-    my $y1 = $graph_box->[3] - ($count * $tic_distance) + 1;
+    my $y1 = $graph_box->[3] - ($count * $tic_distance);
 
-    my $value = sprintf("%.2f", ($count*$interval)+$min);
+    my $value = ($count*$interval)+$min;
+    if ($interval < 1 || ($value != int($value))) {
+        $value = sprintf("%.2f", $value);
+    }
 
     my @box = $self->_text_bbox($value, 'legend')
       or return;
@@ -795,6 +923,16 @@ sub _draw_y_tics {
                  y    => ($y1 + ($height / 2)),
                  text => $value
                 );
+
+    if ($show_gridlines) {
+      # XXX - line styles!
+      for (my $i = $graph_box->[0]; $i < $graph_box->[2]; $i += 6) {
+        my $x1 = $i;
+        my $x2 = $i + 2;
+        if ($x2 > $graph_box->[2]) { $x2 = $graph_box->[2]; }
+        $img->line(x1 => $x1, x2 => $x2, y1 => $y1, y2 => $y1, aa => 1, color => '000000');
+      }
+    }
   }
 
 }