#==========================================================================
# Copyright (c) 1995-1998 Martien Verbruggen
#--------------------------------------------------------------------------
#
# Name:
# GIFgraph::axestype.pm
#
# $Id: axestype.pm,v 1.1.1.1 2002/02/26 10:16:37 oetiker Exp $
#
#==========================================================================
package GIFgraph::axestype;
use strict;
use GIFgraph;
use GIFgraph::legend;
use GIFgraph::utils qw(:all);
@GIFgraph::axestype::ISA = qw( GIFgraph::legend GIFgraph );
my %Defaults = (
# Set the length for the 'short' ticks on the axes.
tick_length => 4,
# Do you want ticks to span the entire width of the graph?
long_ticks => 0,
# Number of ticks for the y axis
y_tick_number => 5,
x_tick_number => undef, # CONTRIB Scott Prahl
# Skip every nth label. if 1 will print every label on the axes,
# if 2 will print every second, etc..
x_label_skip => 1,
y_label_skip => 1,
# Do we want ticks on the x axis?
x_ticks => 1,
x_all_ticks => 0,
# Where to place the x and y labels
x_label_position => 3/4,
y_label_position => 1/2,
# vertical printing of x labels
x_labels_vertical => 0,
# Draw axes as a box? (otherwise just left and bottom)
box_axis => 1,
# Use two different axes for the first and second dataset. The first
# will be displayed using the left axis, the second using the right
# axis. You cannot use more than two datasets when this option is on.
two_axes => 0,
# Print values on the axes?
x_plot_values => 1,
y_plot_values => 1,
# Space between axis and text
axis_space => 4,
# Do you want bars to be drawn on top of each other, or side by side?
overwrite => 0,
# Draw the zero axis in the graph in case there are negative values
zero_axis => 0,
# Draw the zero axis, but do not draw the bottom axis, in case
# box-axis == 0
# This also moves the x axis labels to the zero axis
zero_axis_only => 0,
# Format of the numbers on the x and y axis
y_number_format => undef,
x_number_format => undef, # CONTRIB Scott Prahl
);
{
# PUBLIC
sub plot($) # (\@data)
{
my $self = shift;
my $data = shift;
$self->check_data($data);
$self->init_graph($self->{graph});
$self->plot_legend($self->{graph});
$self->setup_coords($data);
$self->draw_text($self->{graph});
$self->draw_axes($self->{graph}, $data);
$self->draw_ticks($self->{graph}, $data);
$self->draw_data($self->{graph}, $data);
return $self->{graph}->gif
}
sub set_x_label_font($) # (fontname)
{
my $self = shift;
$self->{xlf} = shift;
$self->set(
xlfw => $self->{xlf}->width,
xlfh => $self->{xlf}->height,
);
}
sub set_y_label_font($) # (fontname)
{
my $self = shift;
$self->{ylf} = shift;
$self->set(
ylfw => $self->{ylf}->width,
ylfh => $self->{ylf}->height,
);
}
sub set_x_axis_font($) # (fontname)
{
my $self = shift;
$self->{xaf} = shift;
$self->set(
xafw => $self->{xaf}->width,
xafh => $self->{xaf}->height,
);
}
sub set_y_axis_font($) # (fontname)
{
my $self = shift;
$self->{yaf} = shift;
$self->set(
yafw => $self->{yaf}->width,
yafh => $self->{yaf}->height,
);
}
# PRIVATE
# called on construction, by new
# use inherited defaults
sub initialise()
{
my $self = shift;
$self->SUPER::initialise();
my $key;
foreach $key (keys %Defaults)
{
$self->set( $key => $Defaults{$key} );
}
$self->set_x_label_font(GD::gdSmallFont);
$self->set_y_label_font(GD::gdSmallFont);
$self->set_x_axis_font(GD::gdTinyFont);
$self->set_y_axis_font(GD::gdTinyFont);
}
# inherit check_data from GIFgraph
sub setup_coords($)
{
my $s = shift;
my $data = shift;
# Do some sanity checks
$s->{two_axes} = 0 if ( $s->{numsets} != 2 || $s->{two_axes} < 0 );
$s->{two_axes} = 1 if ( $s->{two_axes} > 1 );
delete $s->{y_label2} unless ($s->{two_axes});
# Set some heights for text
$s->set( tfh => 0 ) unless ( $s->{title} );
$s->set( xlfh => 0 ) unless ( $s->{x_label} );
if ( ! $s->{y1_label} && $s->{y_label} )
{
$s->{y1_label} = $s->{y_label};
}
$s->set( ylfh1 => $s->{y1_label} ? 1 : 0 );
$s->set( ylfh2 => $s->{y2_label} ? 1 : 0 );
$s->set( xafh => 0, xafw => 0 ) unless ($s->{x_plot_values});
$s->set( yafh => 0, yafw => 0 ) unless ($s->{y_plot_values});
$s->{x_axis_label_height} = $s->get_x_axis_label_height($data);
my $lbl = ($s->{xlfh} ? 1 : 0) + ($s->{xafh} ? 1 : 0);
# calculate the top and bottom of the bounding box for the graph
$s->{bottom} =
$s->{gify} - $s->{b_margin} - 1 -
( $s->{xlfh} ? $s->{xlfh} : 0 ) -
( $s->{x_axis_label_height} ? $s->{x_axis_label_height} : 0) -
( $lbl ? $lbl * $s->{text_space} : 0 );
$s->{top} = $s->{t_margin} +
( $s->{tfh} ? $s->{tfh} + $s->{text_space} : 0 );
$s->{top} = $s->{yafh}/2 if ( $s->{top} == 0 );
$s->set_max_min($data);
# Create the labels for the y_axes, and calculate the max length
$s->create_y_labels();
$s->create_x_labels(); # CONTRIB Scott Prahl
# calculate the left and right of the bounding box for the graph
my $ls = $s->{yafw} * $s->{y_label_len}[1];
$s->{left} = $s->{l_margin} +
( $ls ? $ls + $s->{axis_space} : 0 ) +
( $s->{ylfh1} ? $s->{ylfh} + $s->{text_space} : 0 );
$ls = $s->{yafw} * $s->{y_label_len}[2] if $s->{two_axes};
$s->{right} = $s->{gifx} - $s->{r_margin} - 1 -
$s->{two_axes} * (
( $ls ? $ls + $s->{axis_space} : 0 ) +
( $s->{ylfh2} ? $s->{ylfh} + $s->{text_space} : 0 )
);
# CONTRIB Scott Prahl
# make sure that we can generate valid x tick marks
undef($s->{x_tick_number}) if $s->{numpoints} < 2;
undef($s->{x_tick_number}) if (
!defined $s->{x_max} ||
!defined $s->{x_min} ||
$s->{x_max} == $s->{x_min}
);
# calculate the step size for x data
# CONTRIB Changes by Scott Prahl
if (defined $s->{x_tick_number})
{
my $delta = ($s->{right}-$s->{left})/($s->{x_max}-$s->{x_min});
$s->{x_offset} =
($s->{true_x_min} - $s->{x_min}) * $delta + $s->{left};
$s->{x_step} =
($s->{true_x_max} - $s->{true_x_min}) * $delta/$s->{numpoints};
}
else
{
$s->{x_step} = ($s->{right} - $s->{left})/($s->{numpoints} + 2);
$s->{x_offset} = $s->{left};
}
# get the zero axis level
my $dum;
($dum, $s->{zeropoint}) = $s->val_to_pixel(0, 0, 1);
# Check the size
die "Vertical Gif size too small"
if ( ($s->{bottom} - $s->{top}) <= 0 );
die "Horizontal Gif size too small"
if ( ($s->{right} - $s->{left}) <= 0 );
# More sanity checks
$s->{x_label_skip} = 1 if ( $s->{x_label_skip} < 1 );
$s->{y_label_skip} = 1 if ( $s->{y_label_skip} < 1 );
$s->{y_tick_number} = 1 if ( $s->{y_tick_number} < 1 );
}
sub create_y_labels
{
my $s = shift;
$s->{y_label_len}[1] = 0;
$s->{y_label_len}[2] = 0;
my $t;
foreach $t (0 .. $s->{y_tick_number})
{
my $a;
foreach $a (1 .. ($s->{two_axes} + 1))
{
my $label =
$s->{y_min}[$a] +
$t *
($s->{y_max}[$a] - $s->{y_min}[$a])/$s->{y_tick_number};
$s->{y_values}[$a][$t] = $label;
if (defined $s->{y_number_format})
{
if (ref $s->{y_number_format} eq 'CODE')
{
$label = &{$s->{y_number_format}}($label);
}
else
{
$label = sprintf($s->{y_number_format}, $label);
}
}
my $len = length($label);
$s->{y_labels}[$a][$t] = $label;
($len > $s->{y_label_len}[$a]) and
$s->{y_label_len}[$a] = $len;
}
}
}
# CONTRIB Scott Prahl
sub create_x_labels
{
my $s = shift;
return unless defined($s->{x_tick_number});
$s->{x_label_len} = 0;
my $t;
foreach $t (0..$s->{x_tick_number})
{
my $label =
$s->{x_min} +
$t * ($s->{x_max} - $s->{x_min})/$s->{x_tick_number};
$s->{x_values}[$t] = $label;
if (defined $s->{x_number_format})
{
if (ref $s->{x_number_format} eq 'CODE')
{
$label = &{$s->{x_number_format}}($label);
}
else
{
$label = sprintf($s->{x_number_format}, $label);
}
}
my $len = length($label);
$s->{x_labels}[$t] = $label;
($len > $s->{x_label_len}) and $s->{x_label_len} = $len;
}
}
sub get_x_axis_label_height
{
my $s = shift;
my $data = shift;
return $s->{xafh} unless $s->{x_labels_vertical};
my $len = 0;
my $labels = $data->[0];
my $label;
foreach $label (@$labels)
{
my $llen = length($label);
($llen > $len) and $len = $llen;
}
return $len * $s->{xafw}
}
# inherit open_graph from GIFgraph
sub draw_text($) # GD::Image
{
my $s = shift;
my $g = shift;
# Title
if ($s->{tfh})
{
my $tx =
$s->{left} +
($s->{right} - $s->{left})/2 -
length($s->{title}) * $s->{tfw}/2;
my $ty = $s->{top} - $s->{text_space} - $s->{tfh};
$g->string($s->{tf}, $tx, $ty, $s->{title}, $s->{tci});
}
# X label
if ($s->{xlfh})
{
my $tx =
$s->{left} +
$s->{x_label_position} * ($s->{right} - $s->{left}) -
$s->{x_label_position} * length($s->{x_label}) * $s->{xlfw};
my $ty = $s->{gify} - $s->{xlfh} - $s->{b_margin};
$g->string($s->{xlf}, $tx, $ty, $s->{x_label}, $s->{lci});
}
# Y labels
if ($s->{ylfh1})
{
my $tx = $s->{l_margin};
my $ty =
$s->{bottom} -
$s->{y_label_position} * ($s->{bottom} - $s->{top}) +
$s->{y_label_position} * length($s->{y1_label}) * $s->{ylfw};
$g->stringUp($s->{ylf}, $tx, $ty, $s->{y1_label}, $s->{lci});
}
if ( $s->{two_axes} && $s->{ylfh2} )
{
my $tx = $s->{gifx} - $s->{ylfh} - $s->{r_margin};
my $ty =
$s->{bottom} -
$s->{y_label_position} * ($s->{bottom} - $s->{top}) +
$s->{y_label_position} * length($s->{y2_label}) * $s->{ylfw};
$g->stringUp($s->{ylf}, $tx, $ty, $s->{y2_label}, $s->{lci});
}
}
sub draw_axes($) # GD::Image
{
my $s = shift;
my $g = shift;
my $d = shift;
my ($l, $r, $b, $t) =
( $s->{left}, $s->{right}, $s->{bottom}, $s->{top} );
if ( $s->{box_axis} )
{
$g->rectangle($l, $t, $r, $b, $s->{fgci});
}
else
{
$g->line($l, $t, $l, $b, $s->{fgci});
$g->line($l, $b, $r, $b, $s->{fgci})
unless ($s->{zero_axis_only});
$g->line($r, $b, $r, $t, $s->{fgci})
if ($s->{two_axes});
}
if ($s->{zero_axis} or $s->{zero_axis_only})
{
my ($x, $y) = $s->val_to_pixel(0, 0, 1);
$g->line($l, $y, $r, $y, $s->{fgci});
}
}
#
# Ticks and values for y axes
#
sub draw_y_ticks($$) # GD::Image, \@data
{
my $s = shift;
my $g = shift;
my $d = shift;
#
# Ticks and values for y axes
#
my $t;
foreach $t (0 .. $s->{y_tick_number})
{
my $a;
foreach $a (1 .. ($s->{two_axes} + 1))
{
my $value = $s->{y_values}[$a][$t];
my $label = $s->{y_labels}[$a][$t];
my ($x, $y) = $s->val_to_pixel(0, $value, $a);
#my ($x, $y) = $s->val_to_pixel(
# ($a-1) * ($s->{numpoints} + 2),
# $value,
# $a
#);
$x = ($a == 1) ? $s->{left} : $s->{right};
if ($s->{long_ticks})
{
$g->line(
$x, $y,
$x + $s->{right} - $s->{left}, $y,
$s->{fgci}
) unless ($a-1);
}
else
{
$g->line(
$x, $y,
$x + (3 - 2 * $a) * $s->{tick_length}, $y,
$s->{fgci}
);
}
next
if ( $t % ($s->{y_label_skip}) || ! $s->{y_plot_values} );
$x -=
(2-$a) * length($label) * $s->{yafw} +
(3 - 2 * $a) * $s->{axis_space};
$y -= $s->{yafh}/2;
$g->string($s->{yaf}, $x, $y, $label, $s->{alci});
}
}
}
#
# Ticks and values for x axes
#
sub draw_x_ticks($$) # GD::Image, \@data
{
my $s = shift;
my $g = shift;
my $d = shift;
#
# Ticks and values for X axis
#
my $i;
for $i (0 .. $s->{numpoints})
{
my ($x, $y) = $s->val_to_pixel($i + 1, 0, 1);
$y = $s->{bottom} unless $s->{zero_axis_only};
next
if ( !$s->{x_all_ticks} and
$i%($s->{x_label_skip}) and $i != $s->{numpoints} );
if ($s->{x_ticks})
{
if ($s->{long_ticks})
{
$g->line(
$x, $s->{bottom}, $x,
$s->{top},
$s->{fgci}
);
}
else
{
$g->line( $x, $y, $x, $y - $s->{tick_length},
$s->{fgci} );
}
}
next
if ( $i%($s->{x_label_skip}) and $i != $s->{numpoints} );
if ($s->{x_labels_vertical})
{
$x -= $s->{xafw};
my $yt =
$y + $s->{text_space}/2 + $s->{xafw} * length($d->[0][$i]);
$g->stringUp($s->{xaf}, $x, $yt, $d->[0][$i], $s->{alci});
}
else
{
$x -= $s->{xafw} * length($d->[0][$i])/2;
my $yt = $y + $s->{text_space}/2;
$g->string($s->{xaf}, $x, $yt, $d->[0][$i], $s->{alci});
}
}
}
# CONTRIB Scott Prahl
# Assume x array contains equally spaced x-values
# and generate an appropriate axis
#
sub draw_x_ticks_number($$) # GD::Image, \@data
{
my $s = shift;
my $g = shift;
my $d = shift;
my $i;
for $i (0 .. $s->{x_tick_number})
{
my $value = $s->{numpoints}
* ($s->{x_values}[$i] - $s->{true_x_min})
/ ($s->{true_x_max} - $s->{true_x_min});
my $label = $s->{x_values}[$i];
my ($x, $y) = $s->val_to_pixel($value + 1, 0, 1);
$y = $s->{bottom} unless $s->{zero_axis_only};
if ($s->{x_ticks})
{
if ($s->{long_ticks})
{
$g->line($x, $s->{bottom}, $x, $s->{top},$s->{fgci});
}
else
{
$g->line( $x, $y, $x, $y - $s->{tick_length}, $s->{fgci} );
}
}
next
if ( $i%($s->{x_label_skip}) and $i != $s->{x_tick_number} );
if ($s->{x_labels_vertical})
{
$x -= $s->{xafw};
my $yt =
$y + $s->{text_space}/2 + $s->{xafw} * length($d->[0][$i]);
$g->stringUp($s->{xaf}, $x, $yt, $label, $s->{alci});
}
else
{
# $x -= $s->{xafw} * length($$d[0][$i])/2;
$x -= length($d->[0][$i])/2;
my $yt = $y + $s->{text_space}/2;
$g->string($s->{xaf}, $x, $yt, $label, $s->{alci});
}
}
}
sub draw_ticks($$) # GD::Image, \@data
{
my $s = shift;
my $g = shift;
my $d = shift;
$s->draw_y_ticks($g, $d);
return unless ( $s->{x_plot_values} );
if (defined $s->{x_tick_number})
{
$s->draw_x_ticks_number($g, $d);
}
else
{
$s->draw_x_ticks($g, $d);
}
}
sub draw_data($$) # GD::Image, \@data
{
my $s = shift;
my $g = shift;
my $d = shift;
my $ds;
foreach $ds (1 .. $s->{numsets})
{
$s->draw_data_set($g, $d->[$ds], $ds);
}
}
# draw_data_set is in sub classes
sub draw_data_set()
{
# ABSTRACT
my $s = shift;
$s->die_abstract( "sub draw_data missing, ")
}
# Figure out the maximum values for the vertical exes, and calculate
# a more or less sensible number for the tops.
sub set_max_min($)
{
my $s = shift;
my $d = shift;
my @max_min;
# First, calculate some decent values
if ( $s->{two_axes} )
{
my $i;
for $i (1 .. 2)
{
my $true_y_min = get_min_y(@{$$d[$i]});
my $true_y_max = get_max_y(@{$$d[$i]});
($s->{y_min}[$i], $s->{y_max}[$i], $s->{y_tick_number}) =
_best_ends($true_y_min, $true_y_max, $s->{y_tick_number});
}
}
else
{
my ($true_y_min, $true_y_max) = $s->get_max_min_y_all($d);
($s->{y_min}[1], $s->{y_max}[1], $s->{y_tick_number}) =
_best_ends($true_y_min, $true_y_max, $s->{y_tick_number});
}
if (defined( $s->{x_tick_number} ))
{
$s->{true_x_min} = get_min_y(@{$d->[0]});
$s->{true_x_max} = get_max_y(@{$d->[0]});
($s->{x_min}, $s->{x_max}, $s->{x_tick_number}) =
_best_ends( $s->{true_x_min}, $s->{true_x_max},
$s->{x_tick_number});
}
# Make sure bars and area always have a zero offset
if (ref($s) eq 'GIFgraph::bars' or ref($s) eq 'GIFgraph::area')
{
$s->{y_min}[1] = 0 if $s->{y_min}[1] > 0;
$s->{y_min}[2] = 0 if $s->{y_min}[2] && $s->{y_min}[2] > 0;
}
# Overwrite these with any user supplied ones
$s->{y_min}[1] = $s->{y_min_value} if defined $s->{y_min_value};
$s->{y_min}[2] = $s->{y_min_value} if defined $s->{y_min_value};
$s->{y_max}[1] = $s->{y_max_value} if defined $s->{y_max_value};
$s->{y_max}[2] = $s->{y_max_value} if defined $s->{y_max_value};
$s->{y_min}[1] = $s->{y1_min_value} if defined $s->{y1_min_value};
$s->{y_max}[1] = $s->{y1_max_value} if defined $s->{y1_max_value};
$s->{y_min}[2] = $s->{y2_min_value} if defined $s->{y2_min_value};
$s->{y_max}[2] = $s->{y2_max_value} if defined $s->{y2_max_value};
$s->{x_min} = $s->{x_min_value} if defined $s->{x_min_value};
$s->{x_max} = $s->{x_max_value} if defined $s->{x_max_value};
# Check to see if we have sensible values
if ( $s->{two_axes} )
{
my $i;
for $i (1 .. 2)
{
die "Minimum for y" . $i . " too large\n"
if ( $s->{y_min}[$i] > get_min_y(@{$d->[$i]}) );
die "Maximum for y" . $i . " too small\n"
if ( $s->{y_max}[$i] < get_max_y(@{$d->[$i]}) );
}
}
# else
# {
# die "Minimum for y too large\n"
# if ( $s->{y_min}[1] > $max_min[1] );
# die "Maximum for y too small\n"
# if ( $s->{y_max}[1] < $max_min[0] );
# }
}
# return maximum value from an array
sub get_max_y(@) # array
{
my $max = undef;
my $i;
foreach $i (@_)
{
next unless defined $i;
$max = (defined($max) && $max >= $i) ? $max : $i;
}
return $max
}
sub get_min_y(@) # array
{
my $min = undef;
my $i;
foreach $i (@_)
{
next unless defined $i;
$min = ( defined($min) and $min <= $i) ? $min : $i;
}
return $min
}
# get maximum y value from the whole data set
sub get_max_min_y_all($) # \@data
{
my $s = shift;
my $d = shift;
my $max = undef;
my $min = undef;
if ($s->{overwrite} == 2)
{
my $i;
for $i (0 .. $s->{numpoints})
{
my $sum = 0;
my $j;
for $j (1 .. $s->{numsets})
{
$sum += $d->[$j][$i];
}
$max = _max( $max, $sum );
$min = _min( $min, $sum );
}
}
else
{
my $i;
for $i ( 1 .. $s->{numsets} )
{
$max = _max( $max, get_max_y(@{$d->[$i]}) );
$min = _min( $min, get_min_y(@{$d->[$i]}) );
}
}
return ($max, $min)
}
# CONTRIB Scott Prahl
#
# Calculate best endpoints and number of intervals for an axis and
# returns ($nice_min, $nice_max, $n), where $n is the number of
# intervals and
#
# $nice_min <= $min < $max <= $nice_max
#
# Usage:
# ($nmin,$nmax,$nint) = _best_ends(247, 508);
# ($nmin,$nmax) = _best_ends(247, 508, 5);
# use 5 intervals
# ($nmin,$nmax,$nint) = _best_ends(247, 508, 4..7);
# best of 4,5,6,7 intervals
sub _best_ends {
my ($min, $max, @n) = @_;
my ($best_min, $best_max, $best_num) = ($min, $max, 1);
# fix endpoints, fix intervals, set defaults
($min, $max) = ($max, $min) if ($min > $max);
($min, $max) = ($min) ? ($min * 0.5, $min * 1.5) : (-1,1)
if ($max == $min);
@n = (3..6) if (@n <= 0 || $n[0] =~ /auto/i);
my $best_fit = 1e30;
my $range = $max - $min;
# create array of interval sizes
my $s = 1;
while ($s < $range) { $s *= 10 }
while ($s > $range) { $s /= 10 }
my @step = map {$_ * $s} (0.2, 0.5, 1, 2, 5);
for (@n)
{
# Try all numbers of intervals
my $n = $_;
next if ($n < 1);
for (@step)
{
next if ($n != 1) && ($_ < $range/$n); # $step too small
my $nice_min = $_ * int($min/$_);
$nice_min -= $_ if ($nice_min > $min);
my $nice_max = ($n == 1)
? $_ * int($max/$_ + 1)
: $nice_min + $n * $_;
my $nice_range = $nice_max - $nice_min;
next if ($nice_max < $max); # $nice_min too small
next if ($best_fit <= $nice_range - $range); # not closer fit
$best_min = $nice_min;
$best_max = $nice_max;
$best_fit = $nice_range - $range;
$best_num = $n;
}
}
return ($best_min, $best_max, $best_num)
}
# Convert value coordinates to pixel coordinates on the canvas.
sub val_to_pixel($$$) # ($x, $y, $i) in real coords ($Dataspace),
{ # return [x, y] in pixel coords
my $s = shift;
my ($x, $y, $i) = @_;
my $y_min =
($s->{two_axes} && $i == 2) ? $s->{y_min}[2] : $s->{y_min}[1];
my $y_max =
($s->{two_axes} && $i == 2) ? $s->{y_max}[2] : $s->{y_max}[1];
my $y_step = abs(($s->{bottom} - $s->{top})/($y_max - $y_min));
return (
_round( ($s->{x_tick_number} ? $s->{x_offset} : $s->{left})
+ $x * $s->{x_step} ),
_round( $s->{bottom} - ($y - $y_min) * $y_step )
)
}
} # End of package GIFgraph::axestype
1;