NAME
PDF::Reuse::OverlayChart - Produce simple or mixed charts with PDF::Reuse
SYNOPSIS
use PDF::Reuse::OverlayChart;
use PDF::Reuse;
use strict;
prFile('myFile.pdf');
my $s = PDF::Reuse::OverlayChart->new();
$s->columns(qw(Month January February Mars April May June July));
$s->add( 'Riga', 314, 490, 322, -965, 736, 120, 239);
$s->add( 'Helsinki', 389, -865, -242, 7, 689, 294, 518);
$s->add( 'Stockholm',456, -712, 542, 367, 742, 189, 190);
$s->add( 'Oslo', 622, 533, 674, 1289, 679, -56, 345);
$s->draw(x => 10,
y => 200,
yUnit => '1000 Euros',
type => 'bars');
prEnd();
DESCRIPTION
Graphic functions
To draw charts with the help of PDF::Reuse. Currently there are 5 types: 'bars', 'totalbars','percentbars', 'lines' and 'area'.
The charts can be overlaid, so you can mix the types in the same chart. (Except for 'percentbars'. Currently it can't be freely combined with the others.) The entities shown, should have something in common, like the unit of the y-axis, but that is not really necessary. It is up to you, what you want to combine and relate.
The object you create is a collection of data, which has a common structure, and which you want to present as a unit. It should consist of arrays or arrays of arrays. All the data of an object should have the same structure, so the module can calculate sums and percentages in a consistent way.
If you want to compare data collections with different structure or very different sizes, you should create different objects, which can be presented and "scaled" more or less independently of each other.
Interactive functions
As an extra feature you can also add interactive functions to the chart. If your user has Acrobat Reader 5.0.5 or higher, he/she should be able to use the functions. The Reader needs to have the option "Allow File Open Actions and Launching File Attachments" checked under "Preferences".
If he/she uses Acrobat there is a complication. Everything should work fine as long as new files are not read via the web. Acrobat has a plug in, "webpdf.api", which converts documents, also PDF-documents, when they are fetched over the net. That is probably a good idea in some cases, but here it changes new documents, and the JavaScripts needed for the interactive functions are lost (wasn't PDF meant to be a "Portable Document Format" ?), and as an addition to the problems the procedure is painfully slow. The user will have the chart, but he/she will not be able to use the interactive functions. (In cases of real emergency, you can disable the plug in simply by removing it from the directory Plug_ins under Acrobat, put it in a safe place, and start Acrobat. And put it back next time you need it.)
Anyway, almost every computer has the Reader somewhere, and if it is not of the right version, it can be downloaded. So with a little effort, it should be possible to run these interactive functions on most computers.
Extracting the examples from this POD
In the beginning of PDF::Reuse::Tutorial there is a snippet of code which can be used to extract the examples from this POD.
Probably the best idea is to run the examples first, before looking at them in detail.
Graphic Methods
new
my $s = PDF::Reuse::OverlayChart->new();
Constructor. Mandatory.
You can also create a clone of an object like this:
my $clone = $s->new();
add
$s->add('name', value1, value2, ..., valueN);
To define data for the graph.
The name will be put to the right of the graph. It will be the identifier (case sensitive) of the series, so you can add new values afterwards. (Then the values also have to come in exactly the same order.)
The values can be numbers with '-' and '.'. You can also have '' or undef to denote a missing value. If the values contain other characters, the series is interpreted as 'columns'.
The values can be either an array, or an array of arrays (only two levels). Within one object the data should have the same structure. The elements of the top array will be put as columns in the chart.
If you have a text file ('textfile.txt') with a simple 2-dimensional table, like the one here below, you can use each line as parameters to the method. (The value in the upper left corner will refer to the columns to the right, not to the names under it.)
Month January February Mars April May June July
Riga 314 490 322 -965 736 120 239
Helsinki 389 -865 -242 7 689 294 518
Stockholm 456 -712 542 367 742 189 190
Oslo 622 533 674 1289 679 -56 345
ex. ('example.pl'):
use PDF::Reuse::OverlayChart;
use PDF::Reuse;
use strict;
prFile('myFile.pdf');
my $s = PDF::Reuse::OverlayChart->new();
open (INFILE, "textfile.txt") || die "Couldn't open textfile.txt, $!\n";
while (<INFILE>)
{ my @list = m'(\S+)'og;
$s->add(@list) if (scalar @list) ;
}
close INFILE;
$s->draw(x => 10,
y => 200,
yUnit => '1000 Euros',
type => 'bars');
prEnd();
columns
$s->columns( qw(unit column1 column2 .. columnN));
Defines what you want to write along the x-axis. The first value will be put to the right of the arrow of the axis. It could be the "unit" of the columns.
draw
This method does the actual "plotting" of the graph. The parameters are
- x
-
x-coordinate of the lower left corner in pixels, where the graph is going to be drawn. The actual graph comes still a few pixels to the right.
- y
-
y-coordinate of the lower left corner in pixels, where the graph is going to be drawn. The actual graph comes still a few pixels higher up.
- width
-
Width of the graph. 450 by default. Long texts might end up outside.
- height
-
Height of the graph. 450 by default.
- size
-
A number to resize the graph, with lines, texts and everything
(If you change the size of a graph with the parameters width and height, the font sizes, distances between lines etc. are untouched, but with 'size' they also change.)
- xsize
-
A number to resize the graph horizontally, with lines, texts and everything
- ySize
-
A number to resize the graph vertically, with lines, texts and everything
- type
-
By default: 'bars'. Can also be 'totalbars', percentbars', 'lines' and 'area', (you could abbreviate to the first character if you want).
When you have 'lines' or 'area', you get vertical lines. They show where the values of the graph are significant. The values between these points are possible, but of course not necessarily true. It is an illustration.
- yUnit
-
What to write above the y-axis
- background
-
Three RGB numbers ex. '0.95 0.95 0.95'.
- noUnits
-
If this parameter is equal to 1, no units are written.
- title
-
Title above the chart
- groupsTitle
-
Titel above the column to the right of the chart
- groupsText
-
Text under groupsTitle
- merge
-
To merge different graph-objects into one chart. The graph-objects should be put in an anonymous array. Each graph-object should use the "overlay" method. Ex :
$val->draw(x => 20, y => 460, width => 400, height => 300, type => 'lines', noMarker => 1, groupstitle => 'Left Scale', title => 'Amazon', yUnit => 'USD', merge => [ $sek->overlay ( type => 'lines', yDensity => $sekDensity, noMarker => 1), $spIndex->overlay( type => 'lines', yDensity => $spDensity, noMarker => 1), $vol->overlay ( type => 'area', rightScale => 1, yDensity => $volumeDensity, groupstitle => 'Right Scale'),] );
In the example above 4 objects are combined into 1 chart. Each object can have its' own density (but it is not necessary) and some other characteristics described under "overlay". The objects are painted in reversed order: $vol, $spIndex, $sek and at last $val, which automatically gets the left scale and also the normative density of the chart. (Interactive functions for each object are also imported.)
The merge parameter is ignored, if the type of the main object is percentbars
- noMarker
-
Only for lines. No markers will be painted.
- noGroups
-
To suppress the names of the groups, so they will not be printed to the right of the chart.
color
$s->color( ('1 1 0', '0 1 1', '0 1 0', '1 0 0', '0 0 1'));
To define colors to be used. The parameter to this method is a list of RGB numbers.
You can also use some predefined color arrays: 'bright', 'dark', 'gray' or 'light', but only one at a time. Then you get 10 predefined colors ex:
$s-color('dark');
If the module needs more colors than what has been given in the script, it creates more, just from random numbers.
overlay
Defines how an imported graph-object should be painted. See the parameter merge under draw for an example.
Parameters:
- type
-
Can be bars, totalbars, lines and area, but not percentbars.
- rightScale
-
If many objects have this parameter, only the first object will have a right scale painted, and the "yDensity" of the scale will be derived from that object.
- topScale
-
If many objects have this parameter, only the first object will have a top scale painted, and the "xDensity" of the scale will be derived from that object. If that object has some column labels defined (with the columns method), they will also be used here.
- noMarker
-
Only for lines. No markers will be painted.
- noGroups
-
To suppress the names of the groups, so they will not be printed to the right of the chart.
- xDensity
-
Density along the x-axis, a numeric value, possibly with decimals. If it is 10, it denotes that 10 units along the x-axis in this sub chart corresponds to 1 unit in the main chart. If it is 0.1, 1 unit in this chart corresponds to 10 units in the main chart
- yDensity
-
Density along the y-axis, a numeric value, possibly with decimals. If it is 10, it denotes that 10 units along the y-axis in this sub chart corresponds to 1 unit in the main chart.
Interactive Methods
These methods should work for Acrobat Reader 5.0.5 or higher
barsActions
$s->barsActions('name', 'jScript1', 'jScript2', ... 'jScriptN');
To define JavaScript actions for the bars (bars, totalbars, percentbars). The name has to be the same as 'name' in the add method.
barsToolTips
$s->barsToolTips('name', 'text1', 'text2', ... 'textN');
Defines tool tip texts for the actions defined with 'barsActions'. The name connects the texts to the right bars.
boxAction
$s->boxAction('name', 'jScript');
To define a JavaScript action for the little box with the name to the left of the graph.
boxToolTip
$s->boxToolTip('name', 'text');
Defines a tool tip text for the action defined with 'boxAction'. The name connects the texts to the right box.
columnsActions
$s->columnsActions('jScript1', 'jScript2', ... 'jScriptN');
Defines a JavaScript action to be taken for each column
columnsToolTips
$s->columnsToolTips('text1', 'text2', ... 'textN');
Defines tool tip texts for the actions defined with 'columnsActions'.
defineIArea
$s->defineIArea();
Defines the JavaScript function iArea in a new document. N.B. Mandatory for every document where you use these interactive charts.
draw
- iparam
Each time the method draw is used and you need JavaScript functions to be active the parameter iparam is needed and it has to hold the page number (starting with 0). ex.:
$s->draw(x => 45,
y => 500,
type => 'lines',
iparam => 0,
height => 300,
width => 460,
groupsTitle => 'Stations',
title => "Passengers");
Mandatory each time you want interactive functions to be active.
marginAction
$s->marginAction('JavaScript');
Defines a JavaScript action to be taken if you click in the area to the left of the chart.
marginToolTip
$s->marginToolTip('text');
Defines a tool tip text for the action defined with 'marginAction'.
A simple example
An invented company with a few offices.
use PDF::Reuse::OverlayChart;
use PDF::Reuse;
use strict;
prFile('myFile.pdf');
prCompress(1);
my $s = PDF::Reuse::OverlayChart->new();
###########
# Money in
###########
$s->columns( qw(Month January February Mars April May June July));
$s->add( qw(Riga 436 790 579 1023 964 520 390));
$s->add( qw(Helsinki 529 630 789 567 570 94 180));
$s->add( qw(Stockholm 469 534 642 767 712 399 190));
$s->add( qw(Oslo 569 833 967 1589 790 158 345));
$s->draw(x => 45,
y => 455,
yUnit => '1000 Euros',
type => 'bars',
title => 'Money In',
height => 300,
width => 460);
###################################
# Costs are to be shown separately
###################################
my $costs = PDF::Reuse::OverlayChart->new();
$costs->columns( qw(Month January February Mars April May June July));
$costs->add( qw(Riga -316 -290 -376 -823 -243 -320 -509));
$costs->add( qw(Helsinki -440 -830 -989 -671 -170 -394 -618));
$costs->add( qw(Stockholm -218 -345 -242 -467 -412 -299 -590));
$costs->add( qw(Oslo -369 -343 -567 -589 -390 -258 -459));
$costs->draw(x => 45,
y => 55,
yUnit => '1000 Euros',
type => 'bars',
title => 'Costs',
height => 300,
width => 460);
####################################
# The costs are added to 'money in'
####################################
$s->add( qw(Riga -316 -290 -376 -823 -243 -320 -509));
$s->add( qw(Helsinki -440 -830 -989 -671 -170 -394 -618));
$s->add( qw(Stockholm -218 -345 -242 -467 -412 -299 -590));
$s->add( qw(Oslo -369 -343 -567 -589 -390 -258 -459));
prPage();
$s->draw(x => 45,
y => 455,
yUnit => '1000 Euros',
type => 'bars',
title => 'Profit');
########
# Taxes
########
$s->add( qw(Riga -116 -90 -179 -230 -43 -20 -90));
$s->add( qw(Helsinki 40 -130 -190 -32 -70 -30 -18));
$s->add( qw(Stockholm 28 -45 -42 -107 -92 -99 -90));
$s->add( qw(Oslo -169 -43 -67 -189 -190 -58 -59));
$s->draw(x => 45,
y => 55,
yUnit => '1000 Euros',
type => 'bars',
title => 'After Tax');
prEnd();
An example how to mix different graph types in the same chart
In this example you let a program collect historical quotes for 'Amazon', approximately 1 year back, and also values for 'S&P 100' and then you get a chart with combined data, an area graph for volumes, and lines for the other values.
use PDF::Reuse::OverlayChart;
use Finance::QuoteHist;
use PDF::Reuse;
use strict;
#################
# Some variables
#################
my (%values, @values, %volumes, @volumes, %sp100, @sp100, $startValue,
$startSpValue);
my $maxVolume = 0;
my $maxValue = 0;
my $month = sprintf("%02d", ((localtime())[4]) + 1);
my $lastYear = sprintf("%04d", ((localtime())[5] + 1900 - 1));
my $aYearAgo = "$month/01/$lastYear";
prFile('myFile.pdf');
prCompress(1);
###########################################################
# Get historical quotes via the web for Amazon and S&P100
###########################################################
my $q = Finance::QuoteHist->new ( symbols => [qw(AMZN ^OEX)],
start_date => $aYearAgo,
end_date => 'today',);
##################################
# Accumulate the values in hashes
##################################
for my $row ($q->quote_get())
{ my ($symbol, $date, $open, $high, $low, $close, $volume) = @$row;
if ($date =~ m'(\d+\/\d+)\/(\d+)'o)
{ my $yearMonth = $1;
my $day = $2;
if ($symbol ne '^OEX')
{ $volume = sprintf("%.0f", ($volume / 1000000));
$values{$yearMonth}->[$day] = $close if ($close);
$volumes{$yearMonth}->[$day] = $volume if ($volume);
$maxVolume = $volume if $volume > $maxVolume;
$maxValue = $close if $close > $maxValue;
$startValue = $close if (! defined $startValue);
}
else
{ $sp100{$yearMonth}->[$day] = $close if ($close);
$startSpValue = $close if (!defined $startSpValue);
}
}
}
my @keys = sort (keys %volumes);
my $i;
##########################################
# Make one array of arrays of the volumes
##########################################
for my $key (@keys)
{ my @array;
for (@{$volumes{$key}})
{ push @array, $_ if $_; # Only days with trade
}
for ($i = $#array; $i < 18; $i++)
{ push @array, undef; # Fill the month, if not complete
}
push @volumes, [@array]; # One array per month is pushed to volumes
}
#################################################
# Make one array of arrays of the closing values
#################################################
for my $key (@keys)
{ my @array;
for (@{$values{$key}})
{ push @array, $_ if $_;
}
for ($i = $#array; $i < 18; $i++)
{ push @array, undef;
}
push @values, [@array];
}
##########################################################
# Make one array of arrays of the closing values of SP100
##########################################################
for my $key (@keys)
{ my @array;
for (@{$sp100{$key}})
{ push @array, $_ if $_;
}
for ($i = $#array; $i < 18; $i++)
{ push @array, undef;
}
push @sp100, [@array];
}
#########################################################################
# Calculate a suitable density for the volumes so they fill up the chart
#########################################################################
my $volumeDensity = sprintf("%.6f", ($maxVolume / $maxValue));
################################################################
# Make the density for S&P 100 so it gets a good starting point
################################################################
my $spDensity = sprintf("%.1f", ($startSpValue / $startValue));
########################################
# Create and populate the chart-objects
########################################
my $vol = PDF::Reuse::OverlayChart->new();
$vol->add('Volume (1/1000000)', ( @volumes ));
$vol->color('dark');
my $val = PDF::Reuse::OverlayChart->new();
$val->columns('Month', ( @keys ) );
$val->add('Closing Value USD', ( @values ));
$val->color('bright');
my $spIndex = PDF::Reuse::OverlayChart->new();
$spIndex->add("S&P 100 (1/$spDensity)", ( @sp100 ));
$spIndex->color( ('0 0 0') );
#####################
# Now draw the chart
#####################
$val->draw(x => 20,
y => 460,
width => 400,
height => 300,
type => 'lines',
noMarker => 1,
groupstitle => 'Left Scale',
title => 'Amazon',
merge => [ $spIndex->overlay( type => 'lines',
yDensity => $spDensity,
noMarker => 1),
$vol->overlay ( type => 'area',
rightScale => 1,
yDensity => $volumeDensity,
groupstitle => 'Right Scale')] );
prEnd();
Comments about the example:
All the values are put in arrays of arrays like this:
[ [day1 ... dayN], # This is the first month
[day1 ... dayN], # Next month
...
[day1 ... dayN]] # Last month
In the chart there will be one column for each month. The column labels will be taken from the main object.
The 3 different chart-objects have different density along the y-axis (yDensity). The main chart-object, '$val', gets a value automatically calculated. It cannot be directly influenced. The other objects can get a yDensity defined. Then it is always related to the main object. To make the S&P 100 index start in the same point as the first closing value for Amazon ($startValue), the yDensity for the index-graph is calculated like this:
my $spDensity = sprintf("%.1f", ($startSpValue / $startValue));
To make the graph for volumes fill the chart, the maximum values are coordinated in a similar way:
my $volumeDensity = sprintf("%.6f", ($maxVolume / $maxValue));
When the combined chart is drawn, the charts are painted in reversed order, so the main cart is pained last. It also automatically gets the left scale.
An interactive example
This is an example how you can "drill-down" into a chart and look at different aspects of your data. You have 5 layouts and 20 different combinations of view = 100 more or less different charts, and you can look at fractions of the data.
First run the program 'testData.pl'. It creates 12960 lines with random data.
Put 'data.dat' in the directory defined for cgi-bin in a test environment.
Copy the other programs to the same directory.
Change or remove the shebang-line inside 'aspects.pl'. Run "aspects.pl" to generate 20 new programs.
You should have your local web server running.
Run one of the new programs e.g. "offmon.pl" and you get the PDF-file "offmon.pdf".
Open the file with Acrobat Reader and click on the bars, boxes or margins of the chart to change aspects or layout.
# testData.pl
use strict;
my @offices = ('Helsinki', 'Oslo', 'Riga', 'St Petersburg',
'Stockholm', 'Tallinn');
my @deps = (qw(Consulting Hardware Sales Software Staff));
my @projects = (qw(Grid HospitalSystem NotSpecified OffShore
PowerPlant Switches ));
my @types = (qw(Admin Debited Development Education Salary Travel));
my @months = (qw(2003-01 2003-02 2003-03 2003-04 2003-05 2003-06
2003-07 2003-08 2003-09 2003-10 2003-11 2003-12));
my @factor = ( 5, 7, 3, 8, 9, 4);
my $i = -1;
srand(time);
my $number;
open (OUT, ">data.dat") || die "Couldn't open data.dat $!\n";
for my $office (@offices)
{ $i++;
for my $dep (@deps)
{ for my $project (@projects)
{ for my $type (@types)
{ for my $month (@months)
{ if ($type eq 'Debited')
{ $number = 97;
}
elsif ($type eq 'Salary')
{ $number = 19;
}
elsif ($type eq 'Travel')
{ $number = 5;
}
else
{ $number = 9;
}
$number /= 2 if ($dep eq 'Staff');
$number /= 1.5 if ($project eq 'NotSpecified');
$number *= $factor[$i];
my $sum = sprintf("%.0f", rand($number)) + $factor[$i];
if (($sum % 12) < 10)
{ if ($type ne 'Debited')
{ $sum *= -1;
}
}
else
{ $sum *= -1;
}
print OUT "$office;$dep;$project;$type;$month;$sum\n";
}
}
}
}
}
close OUT;
And then we need a JavaScript with a popup menu for the interactive areas of the chart. ('aspects.js')
function aspects(sentence)
{ var target;
var b = 'http://127.0.0.1:80/cgi-bin/';
var a = ['offmon.pl', 'offdep.pl', 'offpro.pl', 'offtyp.pl',
'depmon.pl', 'depoff.pl', 'deppro.pl', 'deptyp.pl',
'promon.pl', 'prooff.pl', 'prodep.pl', 'protyp.pl',
'typmon.pl', 'typoff.pl', 'typdep.pl', 'typpro.pl',
'monoff.pl', 'mondep.pl', 'monpro.pl', 'montyp.pl',
'bars', 'totalbars', 'percentbars', 'lines', 'area'];
var n = ['(O)Month', '(O)Department', '(O)Project', '(O)Profit/Cost Type',
'(D)Month','(D)Office', '(D)Project', '(D)Profit/Cost Type',
'(P)Month', '(P)Office', '(P)Department', '(P)Profit/Cost Type',
'(C)Month', '(C)Office', '(C)Department', '(C)Project',
'Office', 'Department', 'Project', 'Profit/Cost',
'Bars', 'Stapled total bars', 'Stapled percentual bars',
'Lines', 'Area'];
var c = app.popUpMenu(['Office', n[0], n[1], n[2], n[3]],
['Department', n[4], n[5], n[6], n[7]],
['Project', n[8], n[9], n[10], n[11]],
['Profit/Cost Type', n[12], n[13], n[14], n[15]],
['Month', n[16], n[17], n[18], n[19]], '-',
['Layout', n[20], n[21], n[22], n[23], n[24]]);
for (var i = 0; i < n.length; i++)
{ if (c == n[i])
{ if (i < 20)
target = b + a[i] + '?sen=' + hexEncode(sentence) +
'&chart=' + chartVariant() + '&sel=' + hexEncode(sel()) + '&';
else
target = b + current() + '?chart=' + a[i] + '&sel='
+ hexEncode(sel()) + '&sen=' + hexEncode(sentence) + '&';
this.getURL(target, false);
break;
}
}
}
And a little JavaScript to hex encode the strings sent to the server ('hexEncode.js')
function hexEncode(str)
{ var out = '';
for (var i = 0; i < str.length; i++)
{ var num = str.charCodeAt(i);
if ((num < 48) || (num > 122) || ((num > 57) && (num < 65))
|| ((num > 90) && (num < 97)))
out = out + '%' + util.printf("%x", num);
else
out = out + str[i];
}
return out;
}
And finally we have a program ('aspects.pl') that generates 20 other programs. (Of course it could have made it with one single program, but it happened to be done like this.) The generated programs are probably easier to read than this one.
use strict;
my @dimension1 = (qw(Office Department Project Type Month)); # "Groups"
my @dimension2 = (qw(Month Type Project Department Office)); # "Columns"
my ($dim1, $dim2, $short1, $short2, $dimStr1, $dimStr2, $aspect, $column,
$groupsTitle);
for $dim1 (@dimension1)
{ for $dim2 (@dimension2)
{ if ($dim1 eq $dim2)
{ next;
}
$short1 = lc(substr($dim1, 0, 3));
$short2 = lc(substr($dim2, 0, 3));
$dimStr1 = '$' . $dim1;
$dimStr2 = '$' . $dim2;
$column = $dim2;
$groupsTitle = $dim1;
$aspect = "$dim1/s split into $dim2/s";
my $fileName = $short1 . $short2 . '.pl';
open (OUT, ">$fileName") || die "Couldn't open $fileName $!\n";
my $string = getText();
print OUT $string;
close OUT;
}
}
sub getText
{ my $str =<<"EOF";
\#!C:/Perl5.8/bin/perl
use PDF::Reuse::OverlayChart;
use PDF::Reuse;
use strict;
my (\$string, \%data, \$value, \$key, \$doc, \%accum, \%aspect1, \%aspect2);
my \$tot = 0;
my \$selection = '';
my \%sel = ( minoff => 'Helsinki',
maxoff => 'Tallinn',
mindep => 'Consulting',
maxdep => 'Staff',
mintyp => 'Admin',
maxtyp => 'Travel',
minpro => 'Grid',
maxpro => 'Switches',
minmon => '2003-01',
maxmon => '2003-12');
###############################
# First get data to work with
###############################
if ( \$ENV{'REQUEST_METHOD'} eq "GET"
&& \$ENV{'QUERY_STRING'} ne '')
{ \$string = \$ENV{'QUERY_STRING'};
}
else
###################################
# If the program is run "manually"
###################################
{ \$doc = substr(\$0, 0, index(\$0, '.')) . '.pdf';
}
###############################################
# Split and decode the hex-encoded strings
# Create a hash with user data
###############################################
for my \$pair (split('&', \$string))
{ if (\$pair =~ /(.*)=(.*)/) # found key = value;
{ (\$key,\$value) = (\$1,\$2); # get key, value.
\$value =~ s/\\+/ /g;
\$value =~ s/%(..)/pack('C',hex(\$1))/eg;
\$data{\$key} = \$value; # Create the hash.
}
}
################################################################
# If there was a requesting program, the selection will replace
# the default one, and a limiting sentence will be added
################################################################
if (\$string)
{ \%sel = split(/:/, \$data{sel}); # selection
my \@add = split(/:/, \$data{sen}); # sentence
my \$i = 0;
while(defined \$add[\$i])
{ \$sel{\$add[\$i]} = \$add[\$i + 1] if defined \$add[\$i + 1];
\$i += 2;
}
}
my \$chartType = \$data{chart} || 'bars';
##################################################
# The new selection will be prepared as a string
##################################################
while ( my (\$key, \$value) = each \%sel)
{ \$selection .= "\$key:\$value:";
}
############################################################################
# Create new output. If no document name was defined, send output to STDOUT
############################################################################
if (! defined \$doc)
{ \$| = 1;
print STDOUT "Cache-Control: no-transform\\n";
print STDOUT "Content-Type: application/pdf \\n\\n";
prFile();
}
else
{ prFile(\$doc);
}
prCompress(1);
prJs('aspects.js'); # The file with the pop-up menu
prJs('hexEncode.js'); # To hex-encode with JavaScript
#####################################################################
# Three small JavaScript functions will be added to the new document
# They will have information about the program that created the new
# chart: program name, chart type and used data selection
#####################################################################
prJs('function current() { return "$short1$short2.pl"; }');
prJs("function chartVariant() { return '\$chartType'; }");
prJs("function sel() { return '\$selection'; }");
my \$s = PDF::Reuse::OverlayChart->new();
##################################################################
# To create definitions for interactive areas in the new document
##################################################################
\$s->defineIArea();
#####################################
# What to do and display to the left
#####################################
\$s->marginAction('aspects("dummy:nothing");');
\$s->marginToolTip('Click and change aspect');
#################################
# Selection from the "database"
#################################
open (IN, "data.dat") || die ("Couldn't open data.dat \$! \\n");
while (my (\$Office, \$Department, \$Project, \$Type, \$Month, \$sum)
= split(/;/, <IN>))
{
if ( (\$Office ge \$sel{minoff}) && (\$Office le \$sel{maxoff})
&& (\$Department ge \$sel{mindep}) && (\$Department le \$sel{maxdep})
&& (\$Project ge \$sel{minpro}) && (\$Project le \$sel{maxpro})
&& (\$Type ge \$sel{mintyp}) && (\$Type le \$sel{maxtyp})
&& (\$Month ge \$sel{minmon}) && (\$Month le \$sel{maxmon}))
{ \$accum{$dimStr1}->{$dimStr2} += \$sum;
\$tot += \$sum;
\$aspect1{$dimStr1} += \$sum;
\$aspect2{$dimStr2} += \$sum;
}
}
close IN;
my \@groups = sort (keys \%aspect1);
my \@columns = sort (keys \%aspect2);
#######################################################################
# Define columns, actions and tool tips for the areas under the y-axis
#######################################################################
\$s->columns( "$column", \@columns);
\$s->columnsActions( map("aspects('min$short2:\$_:max$short2:\$_');", \@columns));
\$s->columnsToolTips( map("\$_: \$aspect2{\$_}", \@columns));
for my \$group (\@groups)
{ ########################################################################
# Define data, actions ( the string sent as a sentence, describe how
# the data is limited for each specific bar) and tool tips for the bars
########################################################################
my \@barValues = map( \$accum{\$group}->{\$_}, (sort(keys \%{\$accum{\$group}})));
my \@barsActions =
map("aspects('min$short1:\$group:max$short1:\$group:min$short2:\$_:max$short2:\$_');",
\@columns);
\$s->add(\$group, \@barValues);
\$s->barsActions(\$group, \@barsActions );
\$s->barsToolTips(\$group, map( "\$group: \$_ ", \@barValues) );
##################################################################
# Each box to the right of the graph gets its action and tool tip
##################################################################
\$s->boxAction(\$group, "aspects('min$short1:\$group:max$short1:\$group');");
\$s->boxToolTip(\$group, "\$group: \$aspect1{\$group}");
}
my \$yUnit = (\$chartType eq 'percentbars') ? 'Percent' : '1000 Euros';
\$s->draw(x => 45,
y => 500,
type => \$chartType,
iparam => 0,
height => 300,
width => 460,
yUnit => \$yUnit,
groupsTitle => "$groupsTitle",
title => "$aspect");
##############################################################
# Texts under the chart, telling what selection has been used
##############################################################
prText( 45, 450, "Office \$sel{minoff}");
prText(190, 450, "- \$sel{maxoff}");
prText(300, 450, "Sum of processed values: \$tot");
prText( 45, 435, "Department \$sel{mindep}");
prText(190, 435, "- \$sel{maxdep}");
prText( 45, 420, "Project \$sel{minpro}");
prText(190, 420, "- \$sel{maxpro}");
prText( 45, 405, "Type \$sel{mintyp}");
prText(190, 405, "- \$sel{maxtyp}");
prText( 45, 390, "Month \$sel{minmon}");
prText(190, 390, "- \$sel{maxmon}");
prFontSize(9);
prText(45,360,
'You need Acrobat Reader 5.0.5 or higher to use the interactive functions of the chart,');
prText(45, 345,
'and the Reader needs to have the option "Allow File Open Actions and Launching File ');
prText(45, 330, 'Attachments" checked under "Preferences"');
prText(45, 315,
'If you use Acrobat, a plug-in, "webpdf.api", converts documents fetched over the net,');
prText(45, 300, 'and necessary JavaScripts are lost. ');
prEnd();
##############################################
# Next word has to be put in the first column
##############################################
EOF
return $str;
}
SEE ALSO
PDF::Reuse
PDF::Reuse::Tutorial
AUTHOR
Lars Lundberg, <elkelund @ worldonline . se>
COPYRIGHT AND LICENSE
Copyright 2004 by Lars Lundberg
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
DISCLAIMER
You get this module free as it is, but nothing is guaranteed to work, whatever implicitly or explicitly stated in this document, and everything you do, you do at your own risk - I will not take responsibility for any damage, loss of money and/or health that may arise from the use of this module.