#============================#
# #
# Chart::Pareto #
# written by davidb bonner #
# dbonner@cs.bu.edu #
# #
#============================#
package Chart::Pareto;
use Chart::Base;
use GD;
use Carp;
use strict;
@Chart::Pareto::ISA = qw ( Chart::Base );
#==================#
# public methods #
#==================#
#===================#
# private methods #
#===================#
sub draw_legend {
my $obj = shift;
my $dataref = $obj->{'data'};
my (@labels, $legend_w, $legend_h, $color);
my ($w, $h) = (gdSmallFont->width, gdSmallFont->height);
my $black = $obj->get_color ('black');
my $max_len = 0;
#==========================#
# prepare list of labels #
#==========================#
if ($obj->{'legend_labels'}) {
@labels = @{$obj->{'legend_labels'}};
if ($#labels == 0) {
$labels[1] = "Running sum";
}
elsif ($#labels != $#{$dataref} - 1) {
croak ("Number of data set labels does not match number of data sets");
}
}
else {
$labels[0] = "Dataset";
$labels[1] = "Running sum";
}
for (@labels) {
my $str_len = length ($_);
if ($str_len > $max_len) {
$max_len = $str_len;
}
}
#===============#
# draw legend #
#===============#
$legend_h = ($#labels + 1) * ($h + 2 * $obj->{'text_space'});
$legend_w = ($max_len * $w) + 3 * $obj->{'text_space'};
$obj->{'x_max'} -= $legend_w + 2 * $obj->{'text_space'};
$obj->{'im'}->rectangle ($obj->{'x_max'} + 2 * $obj->{'text_space'},
$obj->{'y_min'},
$obj->{'x_max'} + 2 * $obj->{'text_space'}
+ $legend_w,
$obj->{'y_min'} + $legend_h,
$black);
for (0..$#labels) {
$color = $obj->data_color($_);
$obj->{'im'}->string (gdSmallFont,
$obj->{'x_max'} + 7,
$obj->{'y_min'} + $obj->{'text_space'}
+ $_ * ($h + 2 * $obj->{'text_space'}),
$labels[$_],
$color);
}
}
sub find_range {
my $obj = shift;
my $dataref = $obj->{'data'};
my $sum = 0;
my ($tmp, $i, $j);
if ($#{$dataref} != 1) {
croak "Only one data set suported for pareto graphs";
}
for $i (0..1) {
for $j (0..$#{$dataref->[$i]}) {
$sum += $dataref->[$i][$j] if ($i == 1);
}
}
$obj->{'sum'} = $sum;
if (!($obj->{'max_val'})) {
$tmp = ($sum) ? 10 ** (int (log ($sum) / log (10))) : 10;
$sum = $tmp * (int ($sum / $tmp) + 1);
$obj->{'max_val'} = $sum;
}
}
sub draw_ticks {
my $obj = shift;
my $dataref = $obj->{'data'};
my $black = $obj->get_color ('black');
my ($h, $w) = (gdSmallFont->height, gdSmallFont->width);
my $str_max = 0;
my $stag = 0;
my ($y_step, $y_diff, $x_step, $val, $str_len);
my ($x_min, $x_max, @dec, $y, $map, $y_per_step);
#===========================#
# draw the y value labels #
#===========================#
$y_diff = ($obj->{'stagger_x_labels'})
? 2 * $h + $obj->{'text_space'}
: $h + $obj->{'text_space'};
$y_step = (($obj->{'y_max'} -
($obj->{'y_min'} + $obj->{'tick_len'} + $y_diff))
/ $obj->{'y_ticks'});
for (0..$obj->{'y_ticks'}) {
$val = (($obj->{'max_val'} / $obj->{'y_ticks'}) * $_);
@dec = split /\./, $val;
if ($dec[1] && length($dec[1]) > 3) { $val = sprintf ("%.3f", $val) }
$str_len = length($val);
if ($str_len > $str_max) {
$str_max = $str_len;
}
}
for (0..$obj->{'y_ticks'}) {
$val = (($obj->{'max_val'} / $obj->{'y_ticks'}) * $_);
@dec = split /\./, $val;
if ($dec[1] && length($dec[1]) > 3) { $val = sprintf ("%.3f", $val) }
$str_len = length($val);
$obj->{'im'}->string (gdSmallFont,
$obj->{'x_min'} + ($str_max - $str_len) * $w,
$obj->{'y_max'} - $y_step * $_ - $h / 2
- $obj->{'tick_len'} - $y_diff,
$val,
$black);
}
$obj->{'x_min'} += ($str_max * $w) + 3 * $obj->{'text_space'};
#================================#
# draw the y percentage labels #
#================================#
$map = ($obj->{'y_max'} - ($obj->{'y_min'} + $y_diff + $obj->{'tick_len'}))
/ $obj->{'max_val'};
$y = $obj->{'y_max'} - $map * $obj->{'sum'};
$y_per_step = ($obj->{'y_max'} - $y) / $obj->{'y_ticks'};
for (0..$obj->{'y_ticks'}) {
$val = int (100 * (1 / $obj->{'y_ticks'}) * $_);
$str_len = length($val) + 1;
if ($str_len > $str_max) {
$str_max = $str_len;
}
}
for (0..$obj->{'y_ticks'}) {
$val = int (100 * (1 / $obj->{'y_ticks'}) * $_);
$obj->{'im'}->string (gdSmallFont,
$obj->{'x_max'} - $str_max * $w,
$obj->{'y_max'} - $y_per_step * $_ - $h / 2
- $obj->{'tick_len'} - $y_diff,
"$val\%",
$black);
}
$obj->{'x_max'} -= ($str_max * $w) + 3 * $obj->{'text_space'};
#==========================#
# draw the x tick labels #
#==========================#
if ($obj->{'nocutoff'}) { #display all the values
$x_step = (($obj->{'x_max'} - ($obj->{'x_min'} + $obj->{'tick_len'}))
/ ($#{$dataref->[0]} + 1));
($x_min, $x_max) = ($obj->{'x_min'} + $obj->{'tick_len'} + $x_step / 2,
$obj->{'x_max'} - $x_step / 2);
for (0..$#{$dataref->[0]}) {
$str_len = length ($dataref->[0][$_]);
if ($obj->{'stagger_x_labels'}) {
$y = ($stag++ % 2) ? $obj->{'y_max'} - (2 * $h)
: $obj->{'y_max'} - ($h);
}
else {
$y = $obj->{'y_max'} - (1.5 * $h);
}
$obj->{'im'}->string (gdSmallFont,
$x_min + $x_step * $_ - ($str_len * $w) / 2,
$y,
$dataref->[0][$_],
$black);
}
}
else { #group everything after the first $obj->{'cutoff'} values together
$x_step = (($obj->{'x_max'} - ($obj->{'x_min'} + $obj->{'tick_len'}))
/ ($obj->{'cutoff'} + 1));
($x_min, $x_max) = ($obj->{'x_min'} + $obj->{'tick_len'} + $x_step / 2,
$obj->{'x_max'} - $x_step / 2);
for (0..$obj->{'cutoff'}-1) {
$str_len = length ($dataref->[0][$_]);
if ($obj->{'stagger_x_labels'}) {
$y = ($stag++ % 2) ? $obj->{'y_max'} - (2 * $h)
: $obj->{'y_max'} - ($h);
}
else {
$y = $obj->{'y_max'} - (1.5 * $h);
}
$obj->{'im'}->string (gdSmallFont,
$x_min + $x_step * $_ - ($str_len * $w) / 2,
$y,
$dataref->[0][$_],
$black);
}
if ($obj->{'stagger_x_labels'}) {
$y = ($stag++ % 2) ? $obj->{'y_max'} - (2 * $h)
: $obj->{'y_max'} - ($h);
}
else {
$y = $obj->{'y_max'} - (1.5 * $h);
}
$obj->{'im'}->string (gdSmallFont,
$x_min + $x_step * $obj->{'cutoff'}
- (5 * $w) / 2,
$y,
"Other",
$black);
}
$obj->{'y_max'} -= ($obj->{'stagger_x_labels'})
? 2 * $h + $obj->{'text_space'}
: $h + $obj->{'text_space'};
#======================#
# now draw the ticks #
#======================#
for (0..$obj->{'y_ticks'}-1) {
$obj->{'im'}->line ($obj->{'x_min'} + 2 * $obj->{'text_space'},
$obj->{'y_min'} + $y_step * $_,
$obj->{'x_min'} - $obj->{'tick_len'}
+ 2 * $obj->{'text_space'},
$obj->{'y_min'} + $y_step * $_,
$black);
$obj->{'im'}->line ($obj->{'x_max'},
$obj->{'y_max'} - $obj->{'tick_len'}
- $y_per_step * ($obj->{'y_ticks'} - $_),
$obj->{'x_max'} - $obj->{'tick_len'},
$obj->{'y_max'} - $obj->{'tick_len'}
- $y_per_step * ($obj->{'y_ticks'} - $_),
$black);
}
if ($obj->{'nocutoff'}) {
for (0..$#{$dataref->[0]}) {
$obj->{'im'}->line ($x_min + $x_step * $_,
$obj->{'y_max'},
$x_min + $x_step * $_,
$obj->{'y_max'} - $obj->{'tick_len'},
$black);
}
}
else {
for (0..$obj->{'cutoff'}) {
$obj->{'im'}->line ($x_min + $x_step * $_,
$obj->{'y_max'},
$x_min + $x_step * $_,
$obj->{'y_max'} - $obj->{'tick_len'},
$black);
}
}
$obj->{'x_min'} += $obj->{'tick_len'};
$obj->{'x_max'} -= $obj->{'tick_len'};
$obj->{'y_max'} -= $obj->{'tick_len'};
}
sub draw_data {
my $obj = shift;
my $dataref = $obj->{'data'};
my $black = $obj->get_color ('black');
my ($x_step, $offset, $ref, @data, $color, @per, $per);
my ($w, $h) = (gdSmallFont->width, gdSmallFont->height);
my $j;
$obj->find_range ($dataref);
$obj->draw_ticks ($dataref);
#==============#
# bars first #
#==============#
$ref = $obj->data_map ($dataref);
@data = @{$ref};
$color = $obj->data_color (0);
if ($obj->{'nocutoff'}) {
$x_step = ($obj->{'x_max'} - $obj->{'x_min'}) / ($#{$ref} + 1);
}
else {
$x_step = ($obj->{'x_max'} - $obj->{'x_min'}) / ($obj->{'cutoff'} + 1);
}
for $j (0..$#data) {
$obj->{'im'}->filledRectangle ($obj->{'x_min'} + $x_step * $j,
$data[$j],
$obj->{'x_min'} + $x_step * ($j+1),
$obj->{'y_max'},
$color);
$obj->{'im'}->rectangle ($obj->{'x_min'} + $x_step * $j,
$data[$j],
$obj->{'x_min'} + $x_step * ($j + 1),
$obj->{'y_max'},
$black);
}
#=================================#
# now calculate the running sum #
#=================================#
undef $ref;
$ref->[1][0] = $dataref->[1][0];
$per[0] = $ref->[1][0] / $obj->{'sum'};
if ($obj->{'nocutoff'}) {
for (1..$#{$dataref->[0]}) {
$ref->[1][$_] = $ref->[1][$_-1] + $dataref->[1][$_];
$per[$_] = $ref->[1][$_] / $obj->{'sum'};
}
}
else {
for (1..$obj->{'cutoff'}) {
$ref->[1][$_] = $ref->[1][$_-1] + $dataref->[1][$_];
$per[$_] = $ref->[1][$_] / $obj->{'sum'};
}
for ($obj->{'cutoff'}+1..$#{$dataref->[1]}) {
$ref->[1][$obj->{'cutoff'}] += $dataref->[1][$_];
}
$per[$obj->{'cutoff'}] = 1;
}
#======================#
# and draw the lines #
#======================#
$ref = $obj->data_map ($ref);
$color = $obj->data_color (1);
@data = @{$ref};
$per = sprintf ("%d%%", $per[0] * 100);
$obj->{'im'}->string (gdSmallFont,
$x_step + $obj->{'x_min'} - $w * (length ($per) + 1),
$data[0] - ($h + $obj->{'pt_size'} / 2),
$per,
$color);
$obj->{'im'}->line ($obj->{'x_min'},
$obj->{'y_max'},
$x_step + $obj->{'x_min'},
$data[0],
$color);
$obj->{'im'}->filledRectangle ($obj->{'x_min'} + $x_step
- ($obj->{'pt_size'} / 2),
$data[0] - ($obj->{'pt_size'} / 2),
$obj->{'x_min'} + $x_step
+ ($obj->{'pt_size'} / 2),
$data[0] + ($obj->{'pt_size'} / 2),
$color);
for (1..$#data) {
$per = sprintf ("%d%%", $per[$_] * 100);
$obj->{'im'}->line (($_) * $x_step + $obj->{'x_min'},
$data[$_-1],
($_+1) * $x_step + $obj->{'x_min'},
$data[$_],
$color);
$obj->{'im'}->filledRectangle (($_+1) * $x_step + $obj->{'x_min'}
- ($obj->{'pt_size'} / 2),
$data[$_] - ($obj->{'pt_size'} / 2),
($_+1) * $x_step + $obj->{'x_min'}
+ ($obj->{'pt_size'} / 2),
$data[$_] + ($obj->{'pt_size'} / 2),
$color);
$obj->{'im'}->string (gdSmallFont,
($_+1) * $x_step + $obj->{'x_min'}
- $w * (length ($per) + 1),
$data[$_] - ($h + $obj->{'pt_size'} / 2),
$per,
$color) unless ($_ == $#data);
}
$obj->draw_axes;
}
sub data_map {
my $obj = shift;
my $dataref = shift;
my ($ref, $map, $i);
$map = ($obj->{'max_val'})
? ($obj->{'y_max'} - $obj->{'y_min'}) / $obj->{'max_val'}
: ($obj->{'y_max'} - $obj->{'y_min'}) / 10;
if ($obj->{'nocutoff'}) {
for $i (0..$#{$dataref->[1]}) {
$ref->[$i] = $obj->{'y_max'} - $map * $dataref->[1][$i];
}
}
else {
for $i (0..$obj->{'cutoff'}-1) {
$ref->[$i] = $obj->{'y_max'} - $map * $dataref->[1][$i];
}
for $i ($obj->{'cutoff'}..$#{$dataref->[1]}) {
$ref->[$obj->{'cutoff'}] += $dataref->[1][$i];
}
$ref->[$obj->{'cutoff'}] = ($obj->{'y_max'} -
($map * $ref->[$obj->{'cutoff'}]));
}
return $ref;
}
1;