Home > Archive > PERL Beginners > November 2006 > create arrays for GD::Graph::lines
You are viewing an archived Text-only version of the thread.
To view this thread in it's original format and/or if you want to reply to
this thread please [click here]
| Author |
create arrays for GD::Graph::lines
|
|
| Beginner 2006-11-22, 6:57 pm |
| Hi,
I am trying to create a line graph with GD::Graph::Lines.
I have data being passed by CGI in the format:
"ancode_1" = "ADV "
"ADV_2006" = "117216 "
"ADV_2005" = "104776 "
"ancode_2" = "BAP "
"BAP_2006" = "0 "
"BAP_2005" = "270 "
"ancode_3" = "BOO "
"BOO_2006" = "746854 "
"BOO_2005" = "673151 "
"ancode_4" = "BUS "
"BUS_2006" = "0 "
"BUS_2005" = "2476 "
"ancode_5" = "COM "
"COM_2006" = "87787 "
"COM_2005" = "97009 "
The number of years can vary so you might get data from 2006->1990.
The data looks like it is suited to a hash but GD::lines wants the
data passed to in as arrays references (\@array). That is one array
with all the "ancode_n" and foreach code, an array of all the yearly
values. I have managed to get my data into this type of structure
'ADV ' => [
'117216',
'104776',
]
'BAP ' => [
'0',
'270',
],
But I am stuck trying to get it out into n number of arrays that I
can pass to GD. I don't want to pre-declare n number of arrays as the
number may vary and because I am using strict, I don't know how I can
pass the data out of whatever loop I use to get to the values.
my $gd = GD::Graph::lines->new(600,400);
my @data = (\@years,\@ancodes, a number of arrays equal to size of
@ancodes);
This is the bit of code I use to generate the hash of arrays
use GD::Graph::hbars;
use GD::Graph::lines;
use GD::Graph::Data;
use CGI qw/:standard/;
use Data::Dumper;
use strict;
use warnings;
my $q = new CGI;
my @par_names = $q->param;
my (@cl_type,@values,@years,);
my %yrs;
my $current_code;
for (my $i = 0; $i < $#par_names; ++$i) {
if ($par_names[$i] =~ /ancode_\d+/) {
$current_code = $q->param($par_names[$i]);
push(@cl_type,$q->param($par_names[$i]) );
}
if ($par_names[$i] =~ /\w{3}_\d{4}/) {
(my $y) = ($par_names[$i] =~ /\w{3}_(\d{4})/);
if (! exists($yrs{$y}) ) { # only accept yrs not
seen.
$yrs{$y} = 0;
}
my $val = $q->param($par_names[$i]);
(my $value = $val) =~ s/\s+$//;
push @{$values{$current_code}},$value;
}
}
Can someone offer me some pointers here? I am struggling to do this
and I expect there is a short-hand way to achieve what I want.
TIA.
Dp.
| |
|
| In article <4564694F.17549.26A21FD9@dermot.sciencephoto.com>,
dermot@sciencephoto.com (Beginner) wrote:
> Hi,
>
> I am trying to create a line graph with GD::Graph::Lines.
>
> I have data being passed by CGI in the format:
>
> "ancode_1" = "ADV "
> "ADV_2006" = "117216 "
> "ADV_2005" = "104776 "
> "ancode_2" = "BAP "
> "BAP_2006" = "0 "
> "BAP_2005" = "270 "
> "ancode_3" = "BOO "
> "BOO_2006" = "746854 "
> "BOO_2005" = "673151 "
> "ancode_4" = "BUS "
> "BUS_2006" = "0 "
> "BUS_2005" = "2476 "
> "ancode_5" = "COM "
> "COM_2006" = "87787 "
> "COM_2005" = "97009 "
>
> The number of years can vary so you might get data from 2006->1990.
Simplifying the problem, it seems to me you want a plot that will use
the years as the x values, and for each year you would have several y
values. Is that correct? If that is true, then I would think a
"manual" way of getting the plot would be:
@data = (
[ 2006, 2005 ],
[ 117216, 104776 ],
[ 0, 270 ],
[ 746854, 673151 ],
....
);
So this should be the output from your array-producing code.
Maybe you can give more details of your data structure. I'm not
following the presentation.
Boyd
| |
| Beginner 2006-11-22, 6:57 pm |
| On 22 Nov 2006 at 15:14, Beginner wrote:
> "ancode_1" = "ADV "
> "ADV_2006" = "117216 "
> "ADV_2005" = "104776 "
> "ancode_2" = "BAP "
> "BAP_2006" = "0 "
> "BAP_2005" = "270 "
> "ancode_3" = "BOO "
> "BOO_2006" = "746854 "
> "BOO_2005" = "673151 "
> "ancode_4" = "BUS "
> "BUS_2006" = "0 "
> "BUS_2005" = "2476 "
> "ancode_5" = "COM "
> "COM_2006" = "87787 "
> "COM_2005" = "97009 "
>
> The number of years can vary so you might get data from 2006->1990.
>
> The data looks like it is suited to a hash but GD::lines wants the
> data passed to in as arrays references (\@array). That is one array
> with all the "ancode_n" and foreach code, an array of all the yearly
> values. I have managed to get my data into this type of structure
>
> 'ADV ' => [
> '117216',
> '104776',
> ]
> 'BAP ' => [
> '0',
> '270',
> ],
>
> But I am stuck trying to get it out into n number of arrays that I
> can pass to GD. I don't want to pre-declare n number of arrays as the
> number may vary and because I am using strict, I don't know how I can
> pass the data out of whatever loop I use to get to the values.
>
> my $gd = GD::Graph::lines->new(600,400);
> my @data = (\@years,\@ancodes, a number of arrays equal to size of
> @ancodes);
>
> This is the bit of code I use to generate the hash of arrays
>
> use GD::Graph::hbars;
> use GD::Graph::lines;
> use GD::Graph::Data;
> use CGI qw/:standard/;
> use Data::Dumper;
> use strict;
> use warnings;
>
> my $q = new CGI;
>
> my @par_names = $q->param;
> my (@cl_type,@values,@years,);
> my %yrs;
>
> my $current_code;
> for (my $i = 0; $i < $#par_names; ++$i) {
> if ($par_names[$i] =~ /ancode_\d+/) {
> $current_code = $q->param($par_names[$i]);
> push(@cl_type,$q->param($par_names[$i]) );
> }
> if ($par_names[$i] =~ /\w{3}_\d{4}/) {
> (my $y) = ($par_names[$i] =~ /\w{3}_(\d{4})/);
> if (! exists($yrs{$y}) ) { # only accept yrs not
> seen.
> $yrs{$y} = 0;
> }
> my $val = $q->param($par_names[$i]);
> (my $value = $val) =~ s/\s+$//;
> push @{$values{$current_code}},$value;
> }
> }
>
> Can someone offer me some pointers here? I am struggling to do this
> and I expect there is a short-hand way to achieve what I want.
>
I managed to do it with this:
my (@data,@leg);
foreach my $code (keys %values) {
push @leg,$code;
my @temparray;
for (my $i = 0;$i <$no_years;++$i) {
push @temparray, $values{$code}[$i];
}
my @foo = reverse @temparray;
push @data,\@foo;
}
That's the best golf I can do. Seems a bit verbose but it works.
Dp.
| |
| Rob Dixon 2006-11-22, 6:57 pm |
| Beginner wrote:
>
> Hi,
>
> I am trying to create a line graph with GD::Graph::Lines.
>
> I have data being passed by CGI in the format:
>
> "ancode_1" = "ADV "
> "ADV_2006" = "117216 "
> "ADV_2005" = "104776 "
> "ancode_2" = "BAP "
> "BAP_2006" = "0 "
> "BAP_2005" = "270 "
> "ancode_3" = "BOO "
> "BOO_2006" = "746854 "
> "BOO_2005" = "673151 "
> "ancode_4" = "BUS "
> "BUS_2006" = "0 "
> "BUS_2005" = "2476 "
> "ancode_5" = "COM "
> "COM_2006" = "87787 "
> "COM_2005" = "97009 "
>
> The number of years can vary so you might get data from 2006->1990.
>
> The data looks like it is suited to a hash but GD::lines wants the
> data passed to in as arrays references (\@array). That is one array
> with all the "ancode_n" and foreach code, an array of all the yearly
> values. I have managed to get my data into this type of structure
>
> 'ADV ' => [
> '117216',
> '104776',
> ]
> 'BAP ' => [
> '0',
> '270',
> ],
>
> But I am stuck trying to get it out into n number of arrays that I
> can pass to GD. I don't want to pre-declare n number of arrays as the
> number may vary and because I am using strict, I don't know how I can
> pass the data out of whatever loop I use to get to the values.
>
> my $gd = GD::Graph::lines->new(600,400);
> my @data = (\@years,\@ancodes, a number of arrays equal to size of
> @ancodes);
>
> This is the bit of code I use to generate the hash of arrays
>
> use GD::Graph::hbars;
> use GD::Graph::lines;
> use GD::Graph::Data;
> use CGI qw/:standard/;
> use Data::Dumper;
> use strict;
> use warnings;
>
> my $q = new CGI;
>
> my @par_names = $q->param;
> my (@cl_type,@values,@years,);
> my %yrs;
>
> my $current_code;
> for (my $i = 0; $i < $#par_names; ++$i) {
> if ($par_names[$i] =~ /ancode_\d+/) {
> $current_code = $q->param($par_names[$i]);
> push(@cl_type,$q->param($par_names[$i]) );
> }
> if ($par_names[$i] =~ /\w{3}_\d{4}/) {
> (my $y) = ($par_names[$i] =~ /\w{3}_(\d{4})/);
> if (! exists($yrs{$y}) ) { # only accept yrs not
> seen.
> $yrs{$y} = 0;
> }
> my $val = $q->param($par_names[$i]);
> (my $value = $val) =~ s/\s+$//;
> push @{$values{$current_code}},$value;
> }
> }
>
> Can someone offer me some pointers here? I am struggling to do this
> and I expect there is a short-hand way to achieve what I want.
Hi Dermot
You're getting aren't you! A big problem is that you're losing the data
x-values (the years) as you process the CGI parameter list. You need a hash of
hashes that relates each ancode to a list of years and their corresponding
values.
It looks to me as if there's no need to process the ancode parameter values, as
all they tell you is the code for the following data, which is in the parameter
name anyway. Something like this:
my %values;
foreach my $par ($q->param) {
if ($par =~ /(\w{3})_(\d{4})/ ) {
my ($ancode, $yr) = ($1, $2);
$values{$ancode}{$yr} = $q->param($par);
}
}
puts the data into a structure like I've described and I think is self-
explanatory.
One other thing, your line
my @data = (\@years,\@ancodes, a number of arrays equal to size of @ancodes);
can't work as @ancodes is a list of labels for the data sets when it needs to be
a set of y-values corresponding to the x-values (years) in the first parameter;
you need to add these separately with set_legend.
The complete code below works for me: see what you think.
HTH,
Rob
use strict;
use warnings;
use GD::Graph::lines;
use GD::Graph::Data;
use CGI qw/:standard/;
my $q = new CGI;
my %values;
foreach my $par ($q->param) {
my $val = $q->param($par);
$val =~ s/\s+$//;
if ($par =~ /(\w{3})_(\d{4})/ ) {
my ($ancode, $yr) = ($1, $2);
$values{$ancode}{$yr} = $val;
}
}
my @ancodes = sort keys %values;
my %years;
foreach my $ancode (@ancodes) {
my $data = $values{$ancode};
@years{keys %$data} = ();
}
my $gdata = new GD::Graph::Data;
my @data = @values{@ancodes};
foreach my $yr (sort keys %years) {
$gdata->add_point($yr, map $_->{$yr}, @data);
}
my $graph = GD::Graph::lines->new(600, 400);
$graph->set_legend(@ancodes);
my $gd = $graph->plot($gdata);
open my $fh, '> plot.gif' or die $!;
binmode $fh;
print $fh $gd->gif;
close $fh;
| |
| Rob Dixon 2006-11-22, 6:57 pm |
| Beginner wrote:
> On 22 Nov 2006 at 15:14, Beginner wrote:
[snip][color=darkred]
[snip][color=darkred]
>
> I managed to do it with this:
>
> my (@data,@leg);
>
> foreach my $code (keys %values) {
> push @leg,$code;
> my @temparray;
> for (my $i = 0;$i <$no_years;++$i) {
> push @temparray, $values{$code}[$i];
> }
> my @foo = reverse @temparray;
> push @data,\@foo;
> }
>
>
> That's the best golf I can do. Seems a bit verbose but it works.
> Dp.
OK that should work, you've got your codes into a list of legends now. There's
no need for the reverse if you use unshift instead of push to add the values to
the array. And what are you doing for a list of x-values (the years)?
I understood you to mean that there may be gaps in the data, which is another
reason why I said you needed to store hashes of data relating to the years. It
also looked from your code like the data wasn't very well behaved, as you had
allowed for a given year being repeated in some cases. If it's always complete
and in reverse-year order then things are a lot easier. This program keeps your
original data format and produces the same output as my previous effort.
Cheers,
Rob
use strict;
use warnings;
use GD::Graph::lines;
use CGI qw/:standard/;
my $q = new CGI;
my %years;
my %values;
foreach my $par ($q->param) {
my $val = $q->param($par);
$val =~ s/\s+$//;
if ($par =~ /(\w{3})_(\d{4})/ ) {
my ($ancode, $yr) = ($1, $2);
$years{$yr}++;
unshift @{$values{$ancode}}, $val;
}
}
my @ancodes = sort keys %values;
my $gdata = [
[sort keys %years],
@values{@ancodes},
];
my $graph = GD::Graph::lines->new(600, 400);
$graph->set_legend(@ancodes);
my $gd = $graph->plot($gdata);
open my $fh, '> plot.gif' or die $!;
binmode $fh;
print $fh $gd->gif;
close $fh;
| |
| Beginner 2006-11-22, 6:57 pm |
| On 22 Nov 2006 at 17:35, Rob Dixon wrote:
> Beginner wrote:
> [snip]
> [snip]
>
>
> OK that should work, you've got your codes into a list of legends now. There's
> no need for the reverse if you use unshift instead of push to add the values to
> the array. And what are you doing for a list of x-values (the years)?
>
> I understood you to mean that there may be gaps in the data, which is another
> reason why I said you needed to store hashes of data relating to the years. It
> also looked from your code like the data wasn't very well behaved, as you had
> allowed for a given year being repeated in some cases. If it's always complete
> and in reverse-year order then things are a lot easier. This program keeps your
> original data format and produces the same output as my previous effort.
>
> Cheers,
>
> Rob
>
>
>
> use strict;
> use warnings;
>
> use GD::Graph::lines;
> use CGI qw/:standard/;
>
> my $q = new CGI;
>
> my %years;
> my %values;
>
> foreach my $par ($q->param) {
>
> my $val = $q->param($par);
> $val =~ s/\s+$//;
>
> if ($par =~ /(\w{3})_(\d{4})/ ) {
> my ($ancode, $yr) = ($1, $2);
> $years{$yr}++;
> unshift @{$values{$ancode}}, $val;
> }
> }
>
> my @ancodes = sort keys %values;
> my $gdata = [
> [sort keys %years],
> @values{@ancodes},
> ];
>
> my $graph = GD::Graph::lines->new(600, 400);
> $graph->set_legend(@ancodes);
> my $gd = $graph->plot($gdata);
>
> open my $fh, '> plot.gif' or die $!;
> binmode $fh;
> print $fh $gd->gif;
> close $fh;
>
Thanx Rob,
I only noticed the year being out of kilter after I posted. I don't
think I had the years sort in the post either! I did spot it
though..honest.
I am going to stick with your first example. It works fine by the way
and it a nice example of map (which I have to constantly refer back
to the alpaca book for) and also uses GD:Data, again something I
struggled with. The version above is, of course, is shorter and dare
I say it, easier on the eye.
Thanx again.
Dp.
| |
| Dr.Ruud 2006-11-22, 6:57 pm |
| "Beginner" schreef:
> On 22 Nov 2006 at 15:14, Beginner wrote:
>
> I managed to do it with this:
>
> my (@data,@leg);
>
> foreach my $code (keys %values) {
> push @leg,$code;
> my @temparray;
> for (my $i = 0;$i <$no_years;++$i) {
> push @temparray, $values{$code}[$i];
> }
> my @foo = reverse @temparray;
> push @data,\@foo;
> }
Maybe this helps:
#!/usr/bin/perl
use warnings ;
use strict ;
use Data::Dumper;
my ($an, @ancodes, %yrs, %values) ;
for (<DATA> )
{
if (/"ancode_(\d+)" = "(\w{3}) "$/)
{
$an = $2 ;
push @ancodes, $an ;
}
elsif (/^"${an}_(\d{4})" = "([^"]*?[^" ]) *"$/)
{
$yrs{ $1 } ++ ;
$values{ $an }{ $1 } = $2 ;
}
}
{ local $\ = "\n" ;
print Dumper( \@ancodes ) ;
print Dumper( \%yrs ) ;
print Dumper( \%values ) ;
}
for $an (sort @ancodes)
{
for my $y (sort keys %yrs)
{
print "$an,$y,", $values{ $an }{ $y }, "\n" ;
}
}
__DATA__
"ancode_1" = "ADV "
"ADV_2006" = "117216 "
"ADV_2005" = "104776 "
"ancode_2" = "BAP "
"BAP_2006" = "0 "
"BAP_2005" = "270 "
"ancode_3" = "BOO "
"BOO_2006" = "746854 "
"BOO_2005" = "673151 "
"ancode_4" = "BUS "
"BUS_2006" = "0 "
"BUS_2005" = "2476 "
"ancode_5" = "COM "
"COM_2006" = "87787 "
"COM_2005" = "97009 "
--
Affijn, Ruud
"Gewoon is een tijger."
| |
| Beginner 2006-11-23, 6:57 pm |
| On 22 Nov 2006 at 17:35, Rob Dixon wrote:
> Beginner wrote:
> [snip]
> [snip]
>
> OK that should work, you've got your codes into a list of legends now. There's
> no need for the reverse if you use unshift instead of push to add the values to
> the array. And what are you doing for a list of x-values (the years)?
>
> I understood you to mean that there may be gaps in the data, which is another
> reason why I said you needed to store hashes of data relating to the years. It
> also looked from your code like the data wasn't very well behaved, as you had
> allowed for a given year being repeated in some cases. If it's always complete
> and in reverse-year order then things are a lot easier. This program keeps your
> original data format and produces the same output as my previous effort.
>
>
> use strict;
> use warnings;
>
> use GD::Graph::lines;
> use CGI qw/:standard/;
>
> my $q = new CGI;
>
> my %years;
> my %values;
>
> foreach my $par ($q->param) {
>
> my $val = $q->param($par);
> $val =~ s/\s+$//;
>
> if ($par =~ /(\w{3})_(\d{4})/ ) {
> my ($ancode, $yr) = ($1, $2);
> $years{$yr}++;
> unshift @{$values{$ancode}}, $val;
> }
> }
>
> my @ancodes = sort keys %values;
> my $gdata = [
> [sort keys %years],
> @values{@ancodes},
> ];
>
> my $graph = GD::Graph::lines->new(600, 400);
> $graph->set_legend(@ancodes);
> my $gd = $graph->plot($gdata);
>
I have come unstuck. I showed the results to the MD and he said
"perhaps it would look better if all the years were sorted" ,highest
to lowest based on the current year. So i began tinkering with Rob's
last post so I had a hash with the keys as the ancode (ADV) and their
values are a hash of the years and their values. It looks a bit like
this:
$VAR1 = {
'ADV' => {,
'2005' => '104776',
'2004' => '85515',
'2006' => '117619',
'2003' => '117007',
},
'PRO' => {,
'2005' => '49684',
'2004' => '39446',
'2006' => '49876',
'2003' => '45674',
},
which I got with this
use strict;
use warnings;
...snip
my $current_year;
my $year_counter = 0;
foreach my $par ($q->param) {
++$year_counter;
my $val = $q->param($par);
$val =~ s/\s+$//;
if ($par =~ /(\w{3})_(\d{4})/ ) {
my ($ancode, $yr) = ($1, $2);
($current_year) = $yr if ($year_counter == 2 ); # current year is
always 2nd param.
$values{$ancode}{$yr} = $val;
}
}
print STDERR "Current year is $current_year\n";
So I need to sort all the code values based on 2006 and then output
the codes in that order.
This seems to sort all the codes in order based on 2006:
my @ancodes = keys %values;
my @sorted_crnt_year = sort { $values{$a}{$current_year} <=>
$values{$b}{$current_year} } @ancodes;
What I can work out of how to pass the data to GD::Graph::Data. What
I have at the moment gives me all the values for a given code
together, rather than separated by years.
my $gdata = new GD::Graph::Data;
foreach my $code (@sorted_crnt_year) {
foreach my $y (keys %years) {
print STDERR "Code=$code,YR=$y,
Val=",$values{$code}{$y},"\n";
$gdata->add_point($code,$values{$code}{$y});
}
}
Can anyone help me with mapping the add_point so that the data is
segragated into years, but keeping the order of @sorted_crnt_year?
TIA.
Dp.
========= As it stands ===========
use strict;
use warnings;
use GD::Graph::bars;
use GD::Graph::Data;
use CGI qw/:standard/;
use Data::Dumper;
my $q = new CGI;
my %values;
my %years;
my $values;
my @param_names = $q->param;
my $current_year;
my $year_counter = 0;
foreach my $par ($q->param) {
++$year_counter;
my $val = $q->param($par);
$val =~ s/\s+$//;
if ($par =~ /(\w{3})_(\d{4})/ ) {
my ($ancode, $yr) = ($1, $2);
($current_year) = $yr if ($year_counter == 2 );
$years{$yr}++;
$values{$ancode}{$yr} = $val;
}
}
print STDERR "Current year is $current_year\n";
my @ancodes = sort keys %values;
# Sort the values of the current year nad store the code
my @sorted_crnt_year = sort { $values{$b}{$current_year} <=>
$values{$a}{$current_year} } @ancodes;
my $gdata = new GD::Graph::Data;
foreach my $code (@sorted_crnt_year) {
foreach my $y (keys %years) {
print STDERR "Code=$code,YR=$y,
Val=",$values{$code}{$y},"\n";
$gdata->add_point($code,$values{$code}{$y});
}
}
#open my $fh, '> plot.gif' or die $!;
#binmode $fh;
#print $fh $gd->gif;
#close $fh;
my $format = $graph->export_format;
print header("image/$format");
binmode STDOUT;
print $graph->plot($gdata)->$format or die $graph->error;
======================
|
|
|
|
|