GDGraph

 view release on metacpan or  search on metacpan

Graph.pm  view on Meta::CPAN

# GD::Graph
#
# Parent class containing data all graphs have in common.
#

package GD::Graph;

($GD::Graph::prog_version) = '$Revision: 1.55 $' =~ /\s([\d.]+)/;
$GD::Graph::VERSION = '1.56';

use strict;
use GD;
use GD::Text::Align;
use GD::Graph::Data;
use GD::Graph::Error;
use Carp;

@GD::Graph::ISA = qw(GD::Graph::Error);

# Some tools and utils
use GD::Graph::colour qw(:colours);

my %GDsize = ( 
    'x' => 400, 
    'y' => 300 
);

my %Defaults = (

    # Set the top, bottom, left and right margin for the chart. These 
    # margins will be left empty.
    t_margin      => 0,
    b_margin      => 0,
    l_margin      => 0,
    r_margin      => 0,

    # Set the factor with which to resize the logo in the chart (need to
    # automatically compute something nice for this, really), set the 
    # default logo file name, and set the logo position (UR, BR, UL, BL)
    logo          => undef,
    logo_resize   => 1.0,
    logo_position => 'LR',

    # Do we want a transparent background?
    transparent   => 1,

    # Do we want interlacing?
    interlaced    => 1,

    # Set the background colour, the default foreground colour (used 
    # for axes etc), the textcolour, the colour for labels, the colour 
    # for numbers on the axes, the colour for accents (extra lines, tick
    # marks, etc..)
    bgclr         => 'white',   # background colour
    fgclr         => 'dblue',   # Axes and grid
    boxclr        => undef,     # Fill colour for box axes, default: not used
    accentclr     => 'gray',    # bar, area and pie outlines.

    labelclr      => 'dblue',   # labels on axes
    axislabelclr  => 'dblue',   # values on axes
    legendclr     => 'dblue',   # Text for the legend
    textclr       => 'dblue',   # All text, apart from the following 2

    valuesclr     => 'dblue',   # values printed above the points
    
    # data set colours
    dclrs => [qw(lred lgreen lblue lyellow lpurple cyan lorange)], 

    # number of pixels to use as text spacing
    text_space    => 4,

    # These have undefined values, but are here so that the set method
    # knows about them:
    title       => undef,
);

sub _has_default { 
    my $self = shift;
    my $attr = shift || return;
    exists $Defaults{$attr} 
}

#
# PUBLIC methods, documented in pod.
#
sub new  # ( width, height ) optional;
{
    my $type = shift;
    my $self = {};
    bless $self, $type;

    if (@_) 
    {
        # If there are any parameters, they should be the size
        return GD::Graph->_set_error(
            "Usage: GD::Graph::<type>::new(width, height)") unless @_ >= 2;

        $self->{width} = shift;
        $self->{height} = shift;
    } 
    else 
    {
        # There were obviously no parameters, so use defaults
        $self->{width} = $GDsize{'x'};
        $self->{height} = $GDsize{'y'};
    }

    # Initialise all relevant parameters to defaults
    # These are defined in the subclasses. See there.
    $self->initialise() or return;

    return $self;
}

sub get
{
    my $self = shift;
    my @wanted = map $self->{$_}, @_;
    wantarray ? @wanted : $wanted[0];
}

Graph.pm  view on Meta::CPAN


    $self->{_data} = GD::Graph::Data->new($data) 
        or return $self->_set_error(GD::Graph::Data->error);
    
    $self->{_data}->make_strict;

    $self->{_data}->num_sets > 0 && $self->{_data}->num_points > 0
        or return $self->_set_error('No data sets or points');
    
    if ($self->{show_values})
    {
        # If this isn't a GD::Graph::Data compatible structure, then
        # we'll just use the data structure.
        #
        # XXX We should probably check a few more things here, e.g.
        # similarity between _data and show_values.
        #
        my $ref = ref($self->{show_values});
        if (! $ref || ($ref ne 'GD::Graph::Data' && $ref ne 'ARRAY'))
        {
            $self->{show_values} = $self->{_data}
        }
        elsif ($ref eq 'ARRAY')
        {
            $self->{show_values} =
                GD::Graph::Data->new($self->{show_values})
                or return $self->_set_error(GD::Graph::Data->error);
        }
    }

    return $self;
}

# Open the graph output canvas by creating a new GD object.

sub open_graph
{
    my $self = shift;
    return $self->{graph} if exists $self->{graph};
    $self->{graph} = 2.0 <= $GD::VERSION 
        ?   GD::Image->newPalette($self->{width}, $self->{height})
        :   GD::Image->new($self->{width}, $self->{height});

}

# Initialise the graph output canvas, setting colours (and getting back
# index numbers for them) setting the graph to transparent, and 
# interlaced, putting a logo (if defined) on there.

sub init_graph
{
    my $self = shift;

    $self->{bgci} = $self->set_clr(_rgb($self->{bgclr}));
    $self->{fgci} = $self->set_clr(_rgb($self->{fgclr}));
    $self->{tci}  = $self->set_clr(_rgb($self->{textclr}));
    $self->{lci}  = $self->set_clr(_rgb($self->{labelclr}));
    $self->{alci} = $self->set_clr(_rgb($self->{axislabelclr}));
    $self->{acci} = $self->set_clr(_rgb($self->{accentclr}));
    $self->{valuesci} = $self->set_clr(_rgb($self->{valuesclr}));
    $self->{legendci} = $self->set_clr(_rgb($self->{legendclr}));
    $self->{boxci} = $self->set_clr(_rgb($self->{boxclr})) 
        if $self->{boxclr};

    $self->{graph}->transparent($self->{bgci}) if $self->{transparent};
    $self->{graph}->interlaced( $self->{interlaced} || undef ); # required by GD.pm

    # XXX yuck. This doesn't belong here.. or does it?
    $self->put_logo();

    return $self;
}

sub _read_logo_file
{
    my $self = shift;
    my $glogo;
    local (*LOGO);
    my $logo_path = $self->{logo};
    open(LOGO, $logo_path) 
        or do { carp "Unable to open logo file '$logo_path': $!";return};
    binmode(LOGO);
    # if the file has an extension, use that importer
    my $gdimport;
    my @tried;
    # possibly forward-compatible: just try whatever file extension
    if ( $logo_path =~ /\.(\w+)$/i) {
        my $fmt = lc $1;
        $fmt = "jpeg" if 'jpg' eq $fmt;
        push @tried, uc $fmt;
        if ($gdimport = GD::Image->can("newFrom\u$fmt")) {
            if ('xpm' ne $fmt) { $glogo = GD::Image->$gdimport(\*LOGO) }
            else { $glogo = GD::Image->$gdimport($logo_path) } # quirky special case
        }
    } 
    # if that didn't work, try using magic numbers
    if (!$glogo) {
        my $logodata;
        read LOGO,$logodata, -s LOGO;
        my %magic = (
            pack("H8",'ffd8ffe0') => "jpeg",
            'GIF8' => "gif",
            '.PNG' => "png",
            '/* X' => "xpm", # technically '/* XPM */', but I'm hashing, here
            '#def' => "xbm",
        );
        if (my $match = $magic{ substr $logodata, 0, 4 }) {
            push @tried, $match;
            my $matchmethod = "newFrom\u$match";
            if ($gdimport = GD::Image->can($matchmethod . "Data")) {
                $glogo = GD::Image->$gdimport($logodata);
            } elsif ($gdimport = GD::Image->can($matchmethod)) {
                if ('xpm' eq $match) { 
                    $glogo = GD::Image->$gdimport($logo_path);
                } else {
                    seek LOGO,0,0;
                    $glogo = GD::Image->$gdimport(\*LOGO);
                }
            }
        # should this actually be "if (!$glogo), rather than an else?            
        } else { # Hail Mary, full of Grace!  Blessed art thou among women...

Graph.pm  view on Meta::CPAN


=item t_margin, b_margin, l_margin, r_margin

Top, bottom, left and right margin of the canvas. These margins will be
left blank.
Default: 0 for all.

=item logo

Name of a logo file. Generally, this should be the same format as your
version of GD exports images in.  Currently, this file may be in any 
format that GD can import, but please see L<GD> if you use an
XPM file and get unexpected results.

Default: no logo.

=item logo_resize, logo_position

Factor to resize the logo by, and the position on the canvas of the
logo. Possible values for logo_position are 'LL', 'LR', 'UL', and
'UR'.  (lower and upper left and right). 
Default: 'LR'.

=item transparent

If set to a true value, the produced image will have the background
colour marked as transparent (see also option I<bgclr>).  Default: 1.

=item interlaced

If set to a true value, the produced image will be interlaced.
Default: 1.

B<Note>: versions of GD higher than 2.0 (that is, since GIF support
was restored after being removed owing to patent issues) do not support
interlacing of GIF images.  Support for interlaced PNG and progressive
JPEG images remains available using this option.

=back

=head2 Colours

=over 4

=item bgclr, fgclr, boxclr, accentclr, shadowclr

Drawing colours used for the chart: background, foreground (axes and
grid), axis box fill colour, accents (bar, area and pie outlines), and
shadow (currently only for bars).

All colours should have a valid value as described in L<"COLOURS">,
except boxclr, which can be undefined, in which case the box will not be
filled. 

=item shadow_depth

Depth of a shadow, positive for right/down shadow, negative for left/up
shadow, 0 for no shadow (default).
Also see the C<shadowclr> and C<bar_spacing> options.

=item labelclr, axislabelclr, legendclr, valuesclr, textclr

Text Colours used for the chart: label (labels for the axes or pie),
axis label (misnomer: values printed along the axes, or on a pie slice),
legend text, shown values text, and all other text.

All colours should have a valid value as described in L<"COLOURS">.

=item dclrs (short for datacolours)

This controls the colours for the bars, lines, markers, or pie slices.
This should be a reference to an array of colour names as defined in
L<GD::Graph::colour> (S<C<perldoc GD::Graph::colour>> for the names available).

    $graph->set( dclrs => [ qw(green pink blue cyan) ] );

The first (fifth, ninth) data set will be green, the next pink, etc.

A colour can be C<undef>, in which case the data set will not be drawn.
This can be useful for cumulative bar sets where you want certain data
series (often the first one) not to show up, which can be used to
emulate error bars (see examples 1-7 and 6-3 in the distribution).

Default: [ qw(lred lgreen lblue lyellow lpurple cyan lorange) ] 

=item borderclrs

This controls the colours of the borders of the bars data sets. Like
dclrs, it is a reference to an array of colour names as defined in
L<GD::Graph::colour>.
Setting a border colour to C<undef> means the border will not be drawn.

=item cycle_clrs

If set to a true value, bars will not have a colour from C<dclrs> per
dataset, but per point. The colour sequence will be identical for each
dataset. Note that this may have a weird effect if you are drawing more
than one data set. If this is set to a value larger than 1 the border
colour of the bars will cycle through the colours in C<borderclrs>.

=item accent_treshold

Not really a colour, but it does control a visual aspect: Accents on
bars are only drawn when the width of a bar is larger than this number
of pixels. Accents inside areas are only drawn when the horizontal
distance between points is larger than this number.
Default 4

=back

=head2 Options for graphs with axes.

options for I<bars>, I<lines>, I<points>, I<linespoints>, I<mixed> and 
I<area> charts.

=over 4

=item x_label, y_label

The labels to be printed next to, or just below, the axes. Note that if
you use the two_axes option that you need to use y1_label and y2_label.

=item long_ticks, tick_length

If I<long_ticks> is a true value, ticks will be drawn the same length

Graph.pm  view on Meta::CPAN

    [ 1, undef, 2, undef, 3, undef, 4, undef, 5, 6 ]
  );

This option is useful when you have a consecutive gap in your data, or
with linespoints charts. If you have data where you have intermittent
gaps, be careful when you use this.
Default value: 0

=back

=head2 Options for graphs with points

=over 4

=item markers

This controls the order of markers in I<points> and I<linespoints>
graphs.  This should be a reference to an array of numbers:

    $graph->set( markers => [3, 5, 6] );

Available markers are: 1: filled square, 2: open square, 3: horizontal
cross, 4: diagonal cross, 5: filled diamond, 6: open diamond, 7:
filled circle, 8: open circle, 9: horizontal line, 10: vertical line.
Note that the last two are not part of the default list.

Default: [1,2,3,4,5,6,7,8]

=item marker_size

The size of the markers used in I<points> and I<linespoints> graphs,
in pixels.  Default: 4.

=back

=head2 Options for mixed graphs

=over 4

=item types

A reference to an array with graph types, in the same order as the
data sets. Possible values are:

  $graph->set( types => [qw(lines bars points area linespoints)] );
  $graph->set( types => ['lines', undef, undef, 'bars'] );

values that are undefined or unknown will be set to C<default_type>.

Default: all set to C<default_type>

=item default_type

The type of graph to draw for data sets that either have no type set,
or that have an unknown type set.

Default: lines

=back

=head2 Graph legends (axestype graphs only)

At the moment legend support is minimal.

B<Methods>

=over 4

=item $graph-E<gt>set_legend(I<@legend_keys>);

Sets the keys for the legend. The elements of @legend_keys correspond
to the data sets as provided to I<plot()>.

If a key is I<undef> or an empty string, the legend entry will be skipped.

=item $graph-E<gt>set_legend_font(I<font name>);

Sets the font for the legend text (see L<"FONTS">).
Default: GD::gdTinyFont.

=back

B<Options>

=over 4

=item legend_placement

Where to put the legend. This should be a two letter key of the form:
'B[LCR]|R[TCB]'. The first letter indicates the placement (I<B>ottom or
I<R>ight), and the second letter the alignment (I<L>eft,
I<R>ight, I<C>enter, I<T>op, or I<B>ottom).
Default: 'BC'

If the legend is placed at the bottom, some calculations will be made
to ensure that there is some 'intelligent' wrapping going on. if the
legend is placed at the right, all entries will be placed below each
other.

=item legend_spacing

The number of pixels to place around a legend item, and between a
legend 'marker' and the text.
Default: 4

=item legend_marker_width, legend_marker_height

The width and height of a legend 'marker' in pixels.
Defaults: 12, 8

=item lg_cols

If you, for some reason, need to force the legend at the bottom to
have a specific number of columns, you can use this.
Default: computed

=back


=head2 Options for pie graphs

=over 4

=item 3d

If set to a true value, the pie chart will be drawn with a 3d look.
Default: 1.

=item pie_height

The thickness of the pie when I<3d> is true.
Default: 0.1 x height.

=item start_angle

The angle at which the first data slice will be displayed, with 0 degrees
being "6 o'clock".
Default: 0.

=item suppress_angle

If a pie slice is smaller than this angle (in degrees), a label will not
be drawn on it. Default: 0.

=item label

Print this label below the pie. Default: undef.

=back

=head1 COLOURS

All references to colours in the options for this module have been
shortened to clr. The main reason for this was that I didn't want to
support two spellings for the same word ('colour' and 'color')

Wherever a colour is required, a colour name should be used from the
package L<GD::Graph::colour>. S<C<perldoc GD::Graph::colour>> should give
you the documentation for that module, containing all valid colour
names. I will probably change this to read the systems rgb.txt file if 
it is available.

=head1 FONTS

Depending on your version of GD, this accepts both GD builtin fonts or
the name of a TrueType font file. In the case of a TrueType font, you
must specify the font size. See L<GD::Text> for more details and other
things, since all font handling in GD::Graph is delegated to there.

Examples:

    $graph->set_title_font('/fonts/arial.ttf', 18);
    $graph->set_legend_font(gdTinyFont);
    $graph->set_legend_font(
        ['verdana', 'arial', gdMediumBoldFont], 12)

(The above discussion is based on GD::Text 0.65. Older versions have
more restrictive behaviour).

=head1 HOTSPOTS

I<Note that this is an experimental feature, and its interface may, and
likely will, change in the future. It currently does not work for area
charts or pie charts.>

I<A known problem with hotspots for GD::Graph::hbars is that the x and y
coordinate come out transposed. This probably won't be fixed until the
redesign of this section>

GD::Graph keeps an internal set of coordinates for each data point and
for certain features of a chart, like the title and axis labels. This
specification is very similar to the HTML image map specification, and
in fact exists mainly for that purpose. You can get at these hotspots
with the C<get_hotspot> method for data point, and
C<get_feature_coordinates> for the chart features. 

The <get_hotspot> method accepts two optional arguments, the number of
the dataset you're interested in, and the number of the point in that
dataset you're interested in. When called with two arguments, the
method returns a list of one of the following forms:

  'rect', x1, y1, x2, y2
  'poly', x1, y1, x2, y2, x3, y3, ....
  'line', xs, ys, xe, ye, width

The parameters for C<rect> are the coordinates of the corners of the
rectangle, the parameters for C<poly> are the coordinates of the
vertices of the polygon, and the parameters for the C<line> are the
coordinates for the start and end point, and the line width.  It should
be possible to almost directly translate these lists into HTML image map
specifications.

If the second argument to C<get_hotspot> is omitted, a list of
references to arrays will be returned. This list represents all the
points in the dataset specified, and each array referred to is of the
form outlined above.

  ['rect', x1, y1, x2, y2 ], ['rect', x1, y1, x2, y2], ...

if both arguments to C<get_hotspot> are omitted, the list that comes
back will contain references to arrays for each data set, which in
turn contain references to arrays for each point.

  [
    ['rect', x1, y1, x2, y2 ], ['rect', x1, y1, x2, y2], ...
  ],
  [
    ['line', xs, ys, xe, ye, w], ['line', xs, ys, xe, ye, w], ...
  ],...

The C<get_feature> method, when called with the name of a feature,
returns a single array reference with a type and coordinates as
described above. When called with no arguments, a hash reference is
returned with the keys being all the currently defined and set



( run in 0.611 second using v1.01-cache-2.11-cpan-e1769b4cff6 )