home *** CD-ROM | disk | FTP | other *** search
/ PC Professionell 2004 December / PCpro_2004_12.ISO / files / webserver / tsw / TSW_3.4.0.exe / Apache2 / perl / Data.pm < prev    next >
Encoding:
Perl POD Document  |  2003-06-16  |  16.7 KB  |  726 lines

  1. #==========================================================================
  2. #              Copyright (c) 1995-2000 Martien Verbruggen
  3. #--------------------------------------------------------------------------
  4. #
  5. #   Name:
  6. #       GD::Graph::Data.pm
  7. #
  8. # $Id: Data.pm,v 1.21 2003/06/17 03:28:11 mgjv Exp $
  9. #
  10. #==========================================================================
  11.  
  12. package GD::Graph::Data;
  13.  
  14. ($GD::Graph::Data::VERSION) = '$Revision: 1.21 $' =~ /\s([\d.]+)/;
  15.  
  16. use strict;
  17. use GD::Graph::Error;
  18.  
  19. @GD::Graph::Data::ISA = qw( GD::Graph::Error );
  20.  
  21. =head1 NAME
  22.  
  23. GD::Graph::Data - Data set encapsulation for GD::Graph
  24.  
  25. =head1 SYNOPSIS
  26.  
  27. use GD::Graph::Data;
  28.  
  29. =head1 DESCRIPTION
  30.  
  31. This module encapsulates the data structure that is needed for GD::Graph
  32. and friends. An object of this class contains a list of X values, and a
  33. number of lists of corresponding Y values. This only really makes sense
  34. if the Y values are numerical, but you can basically store anything.
  35. Undefined values have a special meaning to GD::Graph, so they are
  36. treated with care when stored.
  37.  
  38. Many of the methods of this module are intended for internal use by
  39. GD::Graph and the module itself, and will most likely not be useful to
  40. you. Many won't even I<seem> useful to you...
  41.  
  42. =head1 EXAMPLES
  43.  
  44.   use GD::Graph::Data;
  45.   use GD::Graph::bars;
  46.  
  47.   my $data = GD::Graph::Data->new();
  48.  
  49.   $data->read(file => '/data/sales.dat', delimiter => ',');
  50.   $data = $data->copy(wanted => [2, 4, 5]);
  51.  
  52.   # Add the newer figures from the database
  53.   use DBI;
  54.   # do DBI things, like connecting to the database, statement
  55.   # preparation and execution
  56.  
  57.   while (@row = $sth->fetchrow_array)
  58.   {
  59.       $data->add_point(@row);
  60.   }
  61.  
  62.   my $chart = GD::Graph::bars->new();
  63.   my $gd = $chart->plot($data);
  64.  
  65. or for quick changes to legacy code
  66.  
  67.   # Legacy code builds array like this
  68.   @data = ( [qw(Jan Feb Mar)], [1, 2, 3], [5, 4, 3], [6, 3, 7] );
  69.  
  70.   # And we quickly need to do some manipulations on that
  71.   my $data = GD::Graph::Data->new();
  72.   $data->copy_from(\@data);
  73.  
  74.   # And now do all the new stuff that's wanted.
  75.   while (@foo = bar_baz())
  76.   {
  77.       $data->add_point(@foo);
  78.   }
  79.  
  80. =head1 METHODS
  81.  
  82. =head2 $data = GD::Graph::Data->new()
  83.  
  84. Create a new GD::Graph::Data object.
  85.  
  86. =cut
  87.  
  88. # Error constants
  89. use constant ERR_ILL_DATASET    => 'Illegal dataset number';
  90. use constant ERR_ILL_POINT      => 'Illegal point number';
  91. use constant ERR_NO_DATASET     => 'No data sets set';
  92. use constant ERR_ARGS_NO_HASH   => 'Arguments must be given as a hash list';
  93.  
  94. sub new
  95. {
  96.     my $proto = shift;
  97.     my $class = ref($proto) || $proto;
  98.     my $self = [];
  99.     bless $self => $class;
  100.     $self->copy_from(@_) or return $self->_move_errors if (@_);
  101.     return $self;
  102. }
  103.  
  104. sub DESTROY
  105. {
  106.     my $self = shift;
  107.     $self->clear_errors();
  108. }
  109.  
  110. sub _set_value
  111. {
  112.     my $self = shift;
  113.     my ($nd, $np, $val) = @_;
  114.  
  115.     # Make sure we have empty arrays in between
  116.     if ($nd > $self->num_sets)
  117.     {
  118.         # XXX maybe do this with splice
  119.         for ($self->num_sets .. $nd - 1)
  120.         {
  121.             push @{$self}, [];
  122.         }
  123.     }
  124.     $self->[$nd][$np] = $val;
  125.  
  126.     return $self;
  127. }
  128.  
  129. =head2 $data->set_x($np, $value);
  130.  
  131. Set the X value of point I<$np> to I<$value>. Points are numbered
  132. starting with 0. You probably will never need this. Returns undef on
  133. failure.
  134.  
  135. =cut
  136.  
  137. sub set_x
  138. {
  139.     my $self = shift;
  140.     $self->_set_value(0, @_);
  141. }
  142.  
  143. =head2 $data->get_x($np)
  144.  
  145. Get the X value of point I<$np>. See L<"set_x">.
  146.  
  147. =cut
  148.  
  149. sub get_x
  150. {
  151.     my $self = shift;
  152.     my $np   = shift;
  153.     return $self->_set_error(ERR_ILL_POINT)
  154.         unless defined $np && $np >= 0;
  155.  
  156.     $self->[0][$np];
  157. }
  158.  
  159. =head2 $data->set_y($nd, $np, $value);
  160.  
  161. Set the Y value of point I<$np> in data set I<$nd> to I<$value>. Points
  162. are numbered starting with 0, data sets are numbered starting with 1.
  163. You probably will never need this. Returns undef on failure.
  164.  
  165. =cut
  166.  
  167. sub set_y
  168. {
  169.     my $self = shift;
  170.     return $self->_set_error(ERR_ILL_DATASET)
  171.         unless defined $_[0] && $_[0] >= 1;
  172.     $self->_set_value(@_);
  173. }
  174.  
  175. =head2 $data->get_y($nd, $np)
  176.  
  177. Get the Y value of point I<$np> in data set I<$nd>. See L<"set_y">. This
  178. will return undef on an error, but the fact that it returns undef does
  179. not mean there was an error (since undefined values can be stored, and
  180. therefore returned).
  181.  
  182. =cut
  183.  
  184. sub get_y
  185. {
  186.     my $self = shift;
  187.     my ($nd, $np) = @_;
  188.     return $self->_set_error(ERR_ILL_DATASET)
  189.         unless defined $nd && $nd >= 1 && $nd <= $self->num_sets;
  190.     return $self->_set_error(ERR_ILL_POINT)
  191.         unless defined $np && $np >= 0;
  192.  
  193.     $self->[$nd][$np];
  194. }
  195.  
  196. =head2 $data->get_y_cumulative($nd, $np)
  197.  
  198. Get the cumulative value of point I<$np> in data set<$nd>. The
  199. cumulative value is obtained by adding all the values of the points
  200. I<$np> in the data sets 1 to I<$nd>.
  201.  
  202. =cut
  203.  
  204. sub get_y_cumulative
  205. {
  206.     my $self = shift;
  207.     my ($nd, $np) = @_;
  208.     return $self->_set_error(ERR_ILL_DATASET)
  209.         unless defined $nd && $nd >= 1 && $nd <= $self->num_sets;
  210.     return $self->_set_error(ERR_ILL_POINT)
  211.         unless defined $np && $np >= 0;
  212.     
  213.     my $value;
  214.     for (my $i = 1; $i <= $nd; $i++)
  215.     {
  216.         $value += $self->[$i][$np] || 0;
  217.     }
  218.  
  219.     return $value;
  220. }
  221.  
  222. sub _get_min_max
  223. {
  224.     my $self = shift;
  225.     my $nd   = shift;
  226.     my ($min, $max);
  227.  
  228.     for my $val (@{$self->[$nd]})
  229.     {
  230.         next unless defined $val;
  231.         $min = $val if !defined $min || $val < $min;
  232.         $max = $val if !defined $max || $val > $max;
  233.     }
  234.  
  235.     return $self->_set_error("No (defined) values in " . 
  236.         ($nd == 0 ? "X list" : "dataset $nd"))
  237.             unless defined $min && defined $max;
  238.     
  239.     return ($min, $max);
  240. }
  241.  
  242. =head2 $data->get_min_max_x
  243.  
  244. Returns a list of the minimum and maximum x value or the
  245. empty list on failure.
  246.  
  247. =cut
  248.  
  249. sub get_min_max_x
  250. {
  251.     my $self = shift;
  252.     $self->_get_min_max(0);
  253. }
  254.  
  255. =head2 $data->get_min_max_y($nd)
  256.  
  257. Returns a list of the minimum and maximum y value in data set $nd or the
  258. empty list on failure.
  259.  
  260. =cut
  261.  
  262. sub get_min_max_y
  263. {
  264.     my $self = shift;
  265.     my $nd   = shift;
  266.  
  267.     return $self->_set_error(ERR_ILL_DATASET)
  268.         unless defined $nd && $nd >= 1 && $nd <= $self->num_sets;
  269.     
  270.     $self->_get_min_max($nd);
  271. }
  272.  
  273. =head2 $data->get_min_max_y_all()
  274.  
  275. Returns a list of the minimum and maximum y value in all data sets or the
  276. empty list on failure.
  277.  
  278. =cut
  279.  
  280. sub get_min_max_y_all
  281. {
  282.     my $self = shift;
  283.     my ($min, $max);
  284.  
  285.     for (my $ds = 1; $ds <= $self->num_sets; $ds++)
  286.     {
  287.         my ($ds_min, $ds_max) = $self->get_min_max_y($ds);
  288.         next unless defined $ds_min;
  289.         $min = $ds_min if !defined $min || $ds_min < $min;
  290.         $max = $ds_max if !defined $max || $ds_max > $max;
  291.     }
  292.  
  293.     return $self->_set_error('No (defined) values in any data set')
  294.         unless defined $min && defined $max;
  295.     
  296.     return ($min, $max);
  297. }
  298.  
  299. # Undocumented, not part of interface right now. Might expose at later
  300. # point in time.
  301.  
  302. sub set_point
  303. {
  304.     my $self = shift;
  305.     my $np = shift;
  306.     return $self->_set_error(ERR_ILL_POINT)
  307.         unless defined $np && $np >= 0;
  308.  
  309.     for (my $ds = 0; $ds < @_; $ds++)
  310.     {
  311.         $self->_set_value($ds, $np, $_[$ds]);
  312.     }
  313.     return $self;
  314. }
  315.  
  316. =head2 $data->add_point($X, $Y1, $Y2 ...)
  317.  
  318. Adds a point to the data set. The base for the addition is the current
  319. number of X values. This means that if you have a data set with the
  320. contents
  321.  
  322.   (X1,  X2)
  323.   (Y11, Y12)
  324.   (Y21)
  325.   (Y31, Y32, Y33, Y34)
  326.  
  327. a $data->add_point(Xx, Y1x, Y2x, Y3x, Y4x) will result in
  328.  
  329.   (X1,    X2,    Xx )
  330.   (Y11,   Y12,   Y1x)
  331.   (Y21,   undef, Y2x)
  332.   (Y31,   Y32,   Y3x,  Y34)
  333.   (undef, undef, Y4x)
  334.  
  335. In other words: beware how you use this. As long as you make sure that
  336. all data sets are of equal length, this method is safe to use.
  337.  
  338. =cut
  339.  
  340. sub add_point
  341. {
  342.     my $self = shift;
  343.     $self->set_point(scalar $self->num_points, @_);
  344. }
  345.  
  346. =head2 $data->num_sets()
  347.  
  348. Returns the number of data sets.
  349.  
  350. =cut
  351.  
  352. sub num_sets
  353. {
  354.     my $self = shift;
  355.     @{$self} - 1;
  356. }
  357.  
  358. =head2 $data->num_points()
  359.  
  360. In list context, returns a list with its first element the number of X
  361. values, and the subsequent elements the number of respective Y values
  362. for each data set. In scalar context returns the number of points
  363. that have an X value set, i.e. the number of data sets that would result
  364. from a call to C<make_strict>.
  365.  
  366. =cut
  367.  
  368. sub num_points
  369. {
  370.     my $self = shift;
  371.     return (0) unless @{$self};
  372.  
  373.     wantarray ?
  374.         map { scalar @{$_} } @{$self} :
  375.         scalar @{$self->[0]}
  376. }
  377.  
  378. =head2 $data->x_values()
  379.  
  380. Return a list of all the X values.
  381.  
  382. =cut
  383.  
  384. sub x_values
  385. {
  386.     my $self = shift;
  387.     return $self->_set_error(ERR_NO_DATASET)
  388.         unless @{$self};
  389.     @{$self->[0]};
  390. }
  391.  
  392. =head2 $data->y_values($nd)
  393.  
  394. Return a list of the Y values for data set I<$nd>. Data sets are
  395. numbered from 1. Returns the empty list if $nd is out of range, or if
  396. the data set at $nd is empty.
  397.  
  398. =cut
  399.  
  400. sub y_values
  401. {
  402.     my $self = shift;
  403.     my $nd   = shift;
  404.     return $self->_set_error(ERR_ILL_DATASET)
  405.         unless defined $nd && $nd >= 1 && $nd <= $self->num_sets;
  406.     return $self->_set_error(ERR_NO_DATASET)
  407.         unless @{$self};
  408.  
  409.     @{$self->[$nd]};
  410. }
  411.  
  412. =head2 $data->reset() OR GD::Graph::Data->reset()
  413.  
  414. As an object method: Reset the data container, get rid of all data and
  415. error messages. As a class method: get rid of accumulated error messages
  416. and possible other crud.
  417.  
  418. =cut
  419.  
  420. sub reset
  421. {
  422.     my $self = shift;
  423.     @{$self} = () if ref($self);
  424.     $self->clear_errors();
  425.     return $self;
  426. }
  427.  
  428. =head2 $data->make_strict()
  429.  
  430. Make all data set lists the same length as the X list by truncating data
  431. sets that are too long, and filling data sets that are too short with
  432. undef values. always returns a true value.
  433.  
  434. =cut
  435.  
  436. sub make_strict
  437. {
  438.     my $self = shift;
  439.  
  440.     for my $ds (1 .. $self->num_sets)
  441.     {
  442.         my $data_set = $self->[$ds];
  443.  
  444.         my $short = $self->num_points - @{$data_set};
  445.         next if $short == 0;
  446.  
  447.         if ($short > 0)
  448.         {
  449.             my @fill = (undef) x $short;
  450.             push @{$data_set}, @fill;
  451.         }
  452.         else
  453.         {
  454.             splice @{$data_set}, $short;
  455.         }
  456.     }
  457.     return $self;
  458. }
  459.  
  460. =head2 $data->cumulate(preserve_undef => boolean)
  461.  
  462. The B<cumulate> parameter will summarise the Y value sets as follows:
  463. the first Y value list will be unchanged, the second will contain a
  464. sum of the first and second, the third will contain the sum of first,
  465. second and third, and so on.  Returns undef on failure.
  466.  
  467. if the argument I<preserve_undef> is set to a true value, then the sum
  468. of exclusively undefined values will be preserved as an undefined value.
  469. If it is not present or a false value, undef will be treated as zero.
  470. Note that this still will leave undefined values in the first data set
  471. alone.
  472.  
  473. Note: Any non-numerical defined Y values will be treated as 0, but you
  474. really shouldn't be using this to store that sort of Y data.
  475.  
  476. =cut
  477.  
  478. sub cumulate
  479. {
  480.     my $self = shift;
  481.  
  482.     return $self->_set_error(ERR_ARGS_NO_HASH) if (@_ && @_ % 2);
  483.     my %args = @_;
  484.  
  485.     # For all the sets, starting at the last one, ending just 
  486.     # before the first
  487.     for (my $ds = $self->num_sets; $ds > 1; $ds--)
  488.     {
  489.         # For each point in the set
  490.         for my $point (0 .. $#{$self->[$ds]})
  491.         {
  492.             # Add the value for each point in lower sets to this one
  493.             for my $i (1 .. $ds - 1)
  494.             {
  495.                 # If neither are defined, we want to preserve the
  496.                 # undefinedness of this point. If we don't do this, then
  497.                 # the mathematical operation will force undef to be a 0.
  498.                 next if 
  499.                     $args{preserve_undef} &&
  500.                     ! defined $self->[$ds][$point] &&
  501.                     ! defined $self->[$i][$point];
  502.  
  503.                 $self->[$ds][$point] += $self->[$i][$point] || 0;
  504.             }
  505.         }
  506.     }
  507.     return $self;
  508. }
  509.  
  510. =head2 $data->wanted(indexes)
  511.  
  512. Removes all data sets except the ones in the argument list. It will also
  513. reorder the data sets in the order given. Returns undef on failure.
  514.  
  515. To remove all data sets except the first, sixth and second, in that
  516. order:
  517.  
  518.   $data->wanted(1, 6, 2) or die $data->error;
  519.  
  520. =cut
  521.  
  522. sub wanted
  523. {
  524.     my $self = shift;
  525.  
  526.     for my $wanted (@_)
  527.     {
  528.         return $self->_set_error("Wanted index $wanted out of range 1-"
  529.                     . $self->num_sets)
  530.             if $wanted < 1 || $wanted > $self->num_sets;
  531.     }
  532.     @{$self} = @{$self}[0, @_];
  533.     return $self;
  534. }
  535.  
  536. =head2 $data->reverse
  537.  
  538. Reverse the order of the data sets.
  539.  
  540. =cut
  541.  
  542. sub reverse
  543. {
  544.     my $self = shift;
  545.     @{$self} = ($self->[0], reverse @{$self}[1..$#{$self}]);
  546.     return $self;
  547. }
  548.  
  549. =head2 $data->copy_from($data_ref)
  550.  
  551. Copy an 'old' style GD::Graph data structure or another GD::Graph::Data
  552. object into this object. This will remove the current data. Returns undef
  553. on failure.
  554.  
  555. =cut
  556.  
  557. sub copy_from
  558. {
  559.     my $self = shift;
  560.     my $data = shift;
  561.     return $self->_set_error('Not a valid source data structure')
  562.         unless defined $data && (
  563.                 ref($data) eq 'ARRAY' || ref($data) eq __PACKAGE__);
  564.     
  565.     $self->reset;
  566.  
  567.     my $i = 0;
  568.     for my $data_set (@{$data})
  569.     {
  570.         return $self->_set_error("Invalid data set: $i")
  571.             unless ref($data_set) eq 'ARRAY';
  572.  
  573.         push @{$self}, [@{$data_set}];
  574.         $i++;
  575.     }
  576.  
  577.     return $self;
  578. }
  579.  
  580. =head2 $data->copy()
  581.  
  582. Returns a copy of the object, or undef on failure.
  583.  
  584. =cut
  585.  
  586. sub copy
  587. {
  588.     my $self = shift;
  589.  
  590.     my $new = $self->new();
  591.     $new->copy_from($self);
  592.     return $new;
  593. }
  594.  
  595. =head2 $data->read(I<arguments>)
  596.  
  597. Read a data set from a file. This will remove the current data. returns
  598. undef on failure. This method uses the standard module 
  599. Text::ParseWords to parse lines. If you don't have this for some odd
  600. reason, don't use this method, or your program will die.
  601.  
  602. B<Data file format>: The default data file format is tab separated data
  603. (which can be changed with the delimiter argument). Comment lines are
  604. any lines that start with a #. In the following example I have replaced
  605. literal tabs with <tab> for clarity
  606.  
  607.   # This is a comment, and will be ignored
  608.   Jan<tab>12<tab>24
  609.   Feb<tab>13<tab>37
  610.   # March is missing
  611.   Mar<tab><tab>
  612.   Apr<tab>9<tab>18
  613.  
  614. Valid arguments are:
  615.  
  616. I<file>, mandatory. The file name of the file to read from, or a
  617. reference to a file handle or glob.
  618.  
  619.   $data->read(file => '/data/foo.dat') or die $data->error;
  620.   $data->read(file => \*DATA) or die $data->error;
  621.   $data->read(file => $file_handle) or die $data->error;
  622.  
  623. I<no_comment>, optional. Give this a true value if you don't want lines
  624. with an initial # to be skipped.
  625.  
  626.   $data->read(file => '/data/foo.dat', no_comment => 1);
  627.  
  628. I<delimiter>, optional. A regular expression that will become the
  629. delimiter instead of a single tab.
  630.  
  631.   $data->read(file => '/data/foo.dat', delimiter => '\s+');
  632.   $data->read(file => '/data/foo.dat', delimiter => qr/\s+/);
  633.  
  634. =cut
  635.  
  636. sub read
  637. {
  638.     my $self = shift;
  639.  
  640.     return $self->_set_error(ERR_ARGS_NO_HASH) if (@_ && @_ % 2);
  641.     my %args = @_;
  642.  
  643.     return $self->_set_error('Missing required argument: file') 
  644.         unless $args{file};
  645.  
  646.     my $delim = $args{delimiter} || "\t";
  647.  
  648.     $self->reset();
  649.  
  650.     # The following will die if these modules are not present, as
  651.     # documented.
  652.     require Text::ParseWords;
  653.  
  654.     my $fh;
  655.     local *FH;
  656.  
  657.     if (UNIVERSAL::isa($args{file}, "GLOB"))
  658.     {
  659.     $fh = $args{file};
  660.     }
  661.     else
  662.     {
  663.     # $fh = \do{ local *FH }; # Odd... This dumps core, sometimes in 5.005
  664.     $fh = \*FH; # XXX Need this for perl 5.005
  665.     open($fh, $args{file}) or 
  666.         return $self->_set_error("open ($args{file}): $!");
  667.     }
  668.  
  669.     while (my $line = <$fh>)
  670.     {
  671.         chomp $line;
  672.         next if $line =~ /^#/ && !$args{no_comment};
  673.         my @fields = Text::ParseWords::parse_line($delim, 1, $line);
  674.         next unless @fields;
  675.         $self->add_point(@fields);
  676.     }
  677.     return $self;
  678. }
  679.  
  680. =head2 $data->error() OR GD::Graph::Data->error()
  681.  
  682. Returns a list of all the errors that the current object has
  683. accumulated. In scalar context, returns the last error. If called as a
  684. class method it works at a class level.
  685.  
  686. This method is inherited, see L<GD::Graph::Error> for more information.
  687.  
  688. =cut
  689.  
  690. =head2 $data->has_error() OR GD::Graph::Data->has_error()
  691.  
  692. Returns true if the object (or class) has errors pending, false if not.
  693. In some cases (see L<"copy">) this is the best way to check for errors.
  694.  
  695. This method is inherited, see L<GD::Graph::Error> for more information.
  696.  
  697. =cut
  698.  
  699. =head1 NOTES
  700.  
  701. As with all Modules for Perl: Please stick to using the interface. If
  702. you try to fiddle too much with knowledge of the internals of this
  703. module, you could get burned. I may change them at any time.
  704. Specifically, I probably won't always keep this implemented as an array
  705. reference.
  706.  
  707. =head1 AUTHOR
  708.  
  709. Martien Verbruggen E<lt>mgjv@tradingpost.com.auE<gt>
  710.  
  711. =head2 Copyright
  712.  
  713. (c) Martien Verbruggen.
  714.  
  715. All rights reserved. This package is free software; you can redistribute
  716. it and/or modify it under the same terms as Perl itself.
  717.  
  718. =head1 SEE ALSO
  719.  
  720. L<GD::Graph>, L<GD::Graph::Error>
  721.  
  722. =cut
  723.  
  724. "Just another true value";
  725.  
  726.