## Gnuplot.pm is a sub-module of Graph.pm. It has all the subroutines
## needed for the gnuplot part of the package.
##
## $Id: Gnuplot.pm,v 1.48 2006/06/07 21:09:33 emile Exp $ $Name: $
##
## This software product is developed by Michael Young and David Moore,
## and copyrighted(C) 1998 by the University of California, San Diego
## (UCSD), with all rights reserved. UCSD administers the CAIDA grant,
## NCR-9711092, under which part of this code was developed.
##
## There is no charge for this software. You can redistribute it and/or
## modify it under the terms of the GNU General Public License, v. 2 dated
## June 1991 which is incorporated by reference herein. This software is
## distributed WITHOUT ANY WARRANTY, IMPLIED OR EXPRESS, OF MERCHANTABILITY
## OR FITNESS FOR A PARTICULAR PURPOSE or that the use of it will not
## infringe on any third party's intellectual property rights.
##
## You should have received a copy of the GNU GPL along with this program.
##
##
## IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY
## PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL
## DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS
## SOFTWARE, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF
## THE POSSIBILITY OF SUCH DAMAGE.
##
## THE SOFTWARE PROVIDED HEREIN IS ON AN "AS IS" BASIS, AND THE
## UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE,
## SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. THE UNIVERSITY
## OF CALIFORNIA MAKES NO REPRESENTATIONS AND EXTENDS NO WARRANTIES
## OF ANY KIND, EITHER IMPLIED OR EXPRESS, INCLUDING, BUT NOT LIMITED
## TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A
## PARTICULAR PURPOSE, OR THAT THE USE OF THE SOFTWARE WILL NOT INFRINGE
## ANY PATENT, TRADEMARK OR OTHER RIGHTS.
##
##
## Contact: graph-dev@caida.org
##
##
package Chart::Graph::Gnuplot;
use Exporter ();
@ISA = qw(Exporter);
@EXPORT = qw();
@EXPORT_OK = qw(&gnuplot);
use Carp; # for carp() and croak()
use Chart::Graph::Utils qw(:UTILS); # get global subs and variable
use POSIX 'strftime';
use FileHandle;
$cvs_Id = '$Id: Gnuplot.pm,v 1.48 2006/06/07 21:09:33 emile Exp $';
$cvs_Author = '$Author: emile $';
$cvs_Name = '$Name: $';
$cvs_Revision = '$Revision: 1.48 $';
$VERSION = 3.2;
use strict;
use vars qw($show_year $show_seconds);
# these variables hold default options for gnuplot
my %def_gnu_global_opts = (
"title" => "untitled",
"output type" => "png",
"output file" => "untitled-gnuplot.png",
"x-axis label" => "x-axis",
"y-axis label" => "y-axis",
"x2-axis label" => undef,
"y2-axis label" => undef,
"logscale x" => "0",
"logscale y" => "0",
"logscale x2" => "0",
"logscale y2" => "0",
"xtics" => undef,
"ytics" => undef,
"x2tics" => undef,
"y2tics" => undef,
"xdata" => undef,
"ydata" => undef,
"x2data" => undef,
"y2data" => undef,
"timefmt" => undef,
"format" => undef,
"xrange" => undef,
"yrange" => undef,
"extra_opts" => undef,
"uts" => undef,
"uts_normalize" => undef,
"size"=> undef,
);
my %def_gnu_data_opts = (
"title" => "untitled data",
"style" => "points", # points, lines...
"axes" => "x1y1",
"type" => undef,
"using" => "1:2",
);
#
#
# Subroutine: gnuplot()
#
# Description: this is the main function you will be calling from
# our scripts. please see
# www.caida.org/Tools/Graph/ for a full description
# and how-to of this subroutine
#
sub gnuplot {
my ($user_global_opts_ref, @data_sets) = @_;
my (%data_opts, %global_opts,);
my ($plottype, $output_file, $plot_file, $output_type, $data_set_ref);
# create a new filehandle to be used throughout package
my $handle = new FileHandle;
# create tmpdir
_make_tmpdir("_Gnuplot_");
# set paths for external programs
if (not _set_gnupaths()) {
_cleanup_tmpdir();
return 0;
}
# check first arg for hash
if (ref($user_global_opts_ref) ne "HASH") {
carp "Global options must be a hash";
_cleanup_tmpdir();
return 0;
}
# check for data sets
if (not @data_sets) {
carp "no data sets";
$handle->close;
_cleanup_tmpdir();
return 0;
}
# call to combine user options with default options
%global_opts = _mesh_opts($user_global_opts_ref, \%def_gnu_global_opts);
my $command_file = _make_tmpfile("command");
#remember to close the file if we return
if (not $handle->open(">$command_file")) {
carp "could not open file: $command_file";
_cleanup_tmpdir();
return 0;
}
# check if uts option is chosen and process first
if (my $value = $global_opts{uts}) {
if (defined($value) and ref($value) eq "ARRAY") {
if (@{$value} < 2 || @{$value} > 4) {
carp "out of range for 'uts': [start, end, , ]\n";
_cleanup_tmpdir();
return 0;
}
# set x tics to human readable time stamps
_gnuplot_date_utc($value->[0], $value->[1], $value->[2], $value->[3], \%global_opts);
} else {
carp "Invalid value for 'uts', give [start, end, , ]\n";
}
}
# uts_normalize will be removed in a future version, so don't use it
if (my $value = $global_opts{uts_normalize}) {
if (defined($value) and ref($value) eq "ARRAY") {
if (@{$value} < 2 || @{$value} > 3) {
carp "out of range for 'uts_normalize': [start, end, ]\n";
_cleanup_tmpdir();
return 0;
}
# set x tics to human readable time stamps
_gnuplot_date_utc_normalize($value->[0], $value->[1], $value->[2], \%global_opts);
} else {
carp "Invalid value for 'uts_normalize', give [start, end]";
}
}
# Check if we have options for reading data as date/time formats
# these must be in command file before any others.
foreach my $time_set ("xdata", "ydata", "x2data", "y2data") {
if (defined($global_opts{$time_set})) {
print $handle "set $time_set time\n";
}
}
# Set the format for reading data/time data. Only one format for all axes.
if (my $value = $global_opts{timefmt}) {
if (defined($value)) {
print $handle "set timefmt \"$value\"\n";
}
}
# Now write remain global options to command file
while (my ($key, $value) = each %global_opts) {
## Generic pass-thru, stuff random Gnuplot commands in this key
if ($key eq "extra_opts") {
if (defined $value) {
if (ref($value) eq 'ARRAY') {
# arrayref
print $handle join("\n", @$value);
print $handle "\n";
} else {
# assume it's a string and user provided \n's
print $handle "$value\n";
}
}
}
if ($key eq "title") {
print $handle "set title \"$value\"\n";
}
if ($key eq "x-axis label") {
print $handle "set xlabel \"$value\"\n";
}
if ($key eq "y-axis label") {
print $handle "set ylabel \"$value\"\n";
}
if ($key eq "x2-axis label") {
if (defined($value)) {
print $handle "set x2label \"$value\"\n";
}
}
if ($key eq "y2-axis label") {
if (defined($value)) {
print $handle "set y2label \"$value\"\n";
}
}
if ($key eq "logscale x") {
if ($value == 1) {
print $handle "set logscale x\n";
}
}
if ($key eq "logscale y") {
if ($value == 1) {
print $handle "set logscale y\n";
}
}
if ($key eq "logscale x2") {
if ($value == 1) {
print $handle "set logscale x2\n";
}
}
if ($key eq "logscale y2") {
if ($value == 1) {
print $handle "set logscale y2\n";
}
}
# tics are not required so we can fall through if we want
if ($key eq "xtics") {
if (defined($value) and ref($value) eq "ARRAY") {
_print_tics($handle, $key, @{$value});
}
}
if ($key eq "ytics") {
if (defined($value) and ref($value) eq "ARRAY") {
_print_tics($handle, $key, @{$value});
}
}
if ($key eq "x2tics") {
if (defined($value)) {
if (ref($value) eq "ARRAY") {
_print_tics($handle, $key, @{$value});
}
if ($value eq "on") {
print $handle "set $key\n";
}
}
}
if ($key eq "y2tics") {
if (defined($value)) {
if (ref($value) eq "ARRAY") {
_print_tics($handle, $key, @{$value});
}
if ($value eq "on") {
print $handle "set $key\n";
}
}
}
if ($key eq 'xrange' || $key eq 'yrange' ) {
if (defined($value)) {
if (ref($value) eq 'ARRAY') {
# arrayref
print $handle "set $key [$value->[0] : $value->[1]]\n";
} else {
# assume string
print $handle "set $key $value\n";
}
}
}
# The only time related Gnuplot code that doesn't need to be
# output first.
if ($key eq "format") {
if (defined($value)) {
if(ref($value) eq "ARRAY") {
# Print value supplying quotes for time format.
print $handle "set $key ", $$value[0]," \"",
$$value[1], "\" \n";
} else {
carp "Invalid setting for format option";
}
}
}
# Date/Time and UTS keys which are processed first.
if ($key eq "timefmt") {
# already processed
}
if ($key eq "xdata") {
# already processed
}
if ($key eq "ydata") {
# already processed
}
if ($key eq "x2data") {
# already processed
}
if ($key eq "y2data") {
# already processed
}
if ($key eq "uts") {
# already been processed
}
if ($key eq "uts_normalize") {
# already been processed
}
if ($key eq "size" && defined $value) {
if (ref($value) eq 'ARRAY' && @{$value} == 2) {
print $handle "set $key ". @{$value}[0] . "," . @{$value}[1] . "\n";
}
else {
print STDERR "option `size' must be given a two element array\n";
}
}
if ($key eq "output file") {
$output_file = $value;
}
if ($key eq "output type") {
if (!($value =~ /^(pbm|gif|tgif|png|svg|eps(:? .*)?)$/)) {
carp "invalid output type: $value";
$handle->close();
_cleanup_tmpdir();
return 0;
}
$output_type = $value;
}
}
# create the data file
if ($output_type =~ /^eps( .*)?$/) {
my $options = $1 || "";
if (defined $output_file) {
$plot_file = _make_tmpfile("plot", "eps");
print $handle "set output \"$plot_file\"\n";
}
#print $handle "set terminal postscript eps color \"Arial\" 18\n";
print $handle "set terminal postscript eps $options\n";
} elsif ($output_type eq "pbm" ) {
if (defined $output_file) {
$plot_file = _make_tmpfile("plot", "pbm");
print $handle "set output \"$plot_file\"\n";
}
print $handle "set terminal pbm small color\n";
} elsif ($output_type eq "gif") {
# always needs the tempfile because of conversion later on
$plot_file = _make_tmpfile("plot", "pbm");
print $handle "set output \"$plot_file\"\n";
print $handle "set terminal pbm small color\n";
} elsif ($output_type eq "png") {
if (defined $output_file) {
$plot_file = _make_tmpfile("plot", "png");
print $handle "set output \"$plot_file\"\n";
}
print $handle "set terminal png small\n";
} elsif ($output_type eq "tgif") {
if (defined $output_file) {
$plot_file = _make_tmpfile("plot", "obj");
print $handle "set output \"$plot_file\"\n";
}
print $handle "set terminal tgif\n";
} elsif ($output_type eq 'svg') {
if (defined $output_file) {
$plot_file = _make_tmpfile("plot", "svg");
print $handle "set output \"$plot_file\"\n";
}
print $handle "set terminal svg\n";
}
# process data sets
print $handle "plot ";
while (@data_sets) {
$data_set_ref = shift @data_sets;
if (ref($data_set_ref) ne "ARRAY") {
carp "Data set must be an array";
$handle->close();
_cleanup_tmpdir();
return 0;
}
if (not _gnuplot_data_set($handle, @{$data_set_ref})) {
## already printed error message
$handle->close();
_cleanup_tmpdir();
return 0;
}
if (@data_sets) {
print $handle ", ";
}
}
$handle->close();
# gnuplot and convert pbm file to gif
if (not _exec_gnuplot($command_file)) {
_cleanup_tmpdir();
return 0;
}
if ($output_type eq "gif") {
if(not _exec_pbmtogif($plot_file, $output_file)) {
_cleanup_tmpdir();
return 0;
}
} elsif (defined $output_file && $output_type =~ /^(pbm|eps(?: .*)?|png|tgif)$/) {
#try to get rid of the ugly warnings when moving a file on freebsd
if ($^O eq 'freebsd') {
eval { #this is opportunistic, if it fails it doesn't really matter
my $gid = `/usr/bin/id -g`;
chomp($gid);
system('/usr/bin/chgrp',$gid,$plot_file);
};
}
my $status = system("mv", "$plot_file", "$output_file");
if (not _chk_status($status)) {
if ($Chart::Graph::debug) {
print STDERR "Couldn't mv $plot_file to $output_file: $!\n";
}
_cleanup_tmpdir();
return 0;
}
}
_cleanup_tmpdir();
return 1;
}
#
#
# Subroutine: gnuplot_data_set()
#
# Description: this functions processes the X number
# of data sets that a user gives as
# arguments to gnuplot(). Again, please
# see http://www.caida.org/Tools/Graph/
# for the format of the dataset.
#
sub _gnuplot_data_set {
my ($handle, $user_data_opts_ref, @data) = @_;
my (%data_opts);
# set these values with empty string because we print them out later
# we don't want perl to complain of uninitialized value.
my ($title, $style, $axes, $ranges, $type,) = ("", "", "", "", "");
my ($using) = ("");
my $result;
my $filename = _make_tmpfile("data");
## check first arg for hash
if (ref($user_data_opts_ref) ne "HASH") {
carp "Data options must be a hash.";
return 0;
}
# call to combine user options with default options
%data_opts = _mesh_opts($user_data_opts_ref, \%def_gnu_data_opts);
# write data options to command file
while (my ($key, $value) = each %data_opts) {
if ($key eq "using") {
$using = "using $value";
}
if ($key eq "title") {
$title = "title \"$value\"";
}
if ($key eq "style") {
$style = "with $value"
}
if ($key eq "axes") {
$axes = "axes $value";
}
if ($key eq "type") {
$type = $value;
}
}
if ($type eq "function") {
#$ranges = "[t=:]"; # XXX ?
print $handle "$ranges " . $data[0] . " $axes $title $style";
return 1;
} else {
print $handle "$ranges \"$filename\" $using $axes $title $style";
# we give the user 3 formats for supplying the data set
# 1) matrix
# 2) column
# 3) file
# please see the online docs for a description of these
# formats
if ($type eq "matrix") {
$result = _matrix_to_file($filename, @data);
} elsif ($type eq "columns") {
$result = _columns_to_file($filename, @data);
} elsif ($type eq "file") {
$result = _file_to_file($filename, @data);
} elsif ($type eq "") {
carp "Need to specify data set type";
return 0;
} else {
carp "Illegal data set type: $type";
return 0;
}
}
return $result;
}
#
# Subroutine: set_gnupaths()
#
# Description: set paths for external programs required by gnuplot()
# if they are not defined already
#
sub _set_gnupaths {
if (not defined($gnuplot)) {
if (not $gnuplot = _get_path("gnuplot")) {
return 0;
}
}
if (not defined($ppmtogif)) {
if (not $ppmtogif = _get_path("ppmtogif")) {
return 0;
}
}
return 1;
}
#
#
# Subroutine: print_tics()
# Description: this subroutine takes an array
# of graph tic labels and prints
# them to the gnuplot command file.
# This subroutine is called by gnuplot().
#
# Arguments: $tic_type: which axis to print the tics on
# @tics: the array of tics to print to the file
#
sub _print_tics {
my ($handle, $tic_type, @tics) = @_;
my (@tic_array, $tics_formatted, $tic_label, $tic_index);
# no tic set found, user entered empty tic array
if (not @tics) {
carp "Warning: empty tic set found";
return 1;
}
foreach my $tic (@tics) {
#tics can come in two formats
#this one is [["label1", 10], ["label2", 20],...]
if (ref($tic) eq "ARRAY") {
if ($#{$tic} != 1) {
carp "invalid tic format";
return 0;
}
$tic_label = $tic->[0];
$tic_index = $tic->[1];
push (@tic_array, "\"$tic_label\" $tic_index");
# this one is [10, 20,...]
} else {
push (@tic_array, "$tic");
}
}
$tics_formatted = join(",", @tic_array);
print $handle "set $tic_type ($tics_formatted)\n";
return 1;
}
#
#
# Subroutine: matrix_to_file()
#
# Description: converts the matrix data input into a the gnuplot
# data file format. See www for the specific on the
# matrix format
#
#
sub _matrix_to_file {
my ($file, $matrix_ref) = @_;
my $entry_ref;
my $matrix_len;
if (ref($matrix_ref) ne "ARRAY") {
carp "Matrix data must be a reference to an array";
return 0;
}
open (DATA, ">$file");
$matrix_len = @{$matrix_ref};
for (my $i = 0; $i < $matrix_len; $i++) {
$entry_ref = $matrix_ref->[$i];
if (ref($entry_ref) ne "ARRAY") {
carp "Matrix entry must be a reference to an array";
close DATA;
return 0;
}
# prints blank lines for blank entries, this allows
# the user to tell gnuplot to not connect lines between
# all points when displaying data with lines.
if (@{$entry_ref} == 0)
{
print DATA "\n";
}
else
{
if (0) {
# check that each entry ONLY has two entries
if (@{$entry_ref} != 2) {
carp "Each entry must be an array of size 2";
return 0;
}
print DATA $entry_ref->[0], "\t", $entry_ref->[1], "\n";
}
# XXX
print DATA join("\t", @{$entry_ref}), "\n";
}
}
close DATA;
return 1;
}
#
#
# Subroutine: columns_to_file()
#
# Description: converts the column data input into a the gnuplot
# data file format. please see www page for specifics
# on this format.
#
sub _columns_to_file {
my ($file, @columns) = @_;
foreach my $dataset ( @columns ) {
if (!(ref($dataset) eq "ARRAY")) {
carp "Column data must be a reference to an array";
return 0;
}
if ($#{$dataset} != $#{$columns[$[]}) {
carp "All columns must be of same length";
return 0;
}
}
if ($#{$columns[$[]} == 0) {
carp "Warning: Columns have no data!";
}
open (DATA, ">$file");
for (my $i = 0; $i <= $#{$columns[$[]}; $i++) {
foreach my $dataset ( @columns ) {
print DATA "$dataset->[$i]\t";
}
print DATA "\n";
}
close DATA;
return 1;
}
#
# Subroutine: file_to_file()
#
# Description: If a gnuplot data set was given in
# file format, we simply copy the data
# and read it into
#
sub _file_to_file {
my ($file_out, $file_in) = @_;
if (not $file_in) {
carp "Data set file missing";
return 0;
}
if (not -f $file_in) {
carp "Data set file, '$file_in', does not exist.";
return 0;
}
my $status = system("cp", "$file_in", "$file_out");
if (not _chk_status($status)) {
return 0;
}
return 1;
}
#
# Subroutine: exec_gnuplot()
#
# Description: this executes gnuplot on the command file
# and data sets that we have generated.
#
sub _exec_gnuplot {
my ($command_file) = @_;
my $status = system("$gnuplot", "$command_file");
if (not _chk_status($status)) {
return 0;
}
return 1;
}
#
# Subroutine: exec_pbmtogif()
#
# Description: convert pbm file that gnuplot makes into
# a gif. usually used for web pages
#
sub _exec_pbmtogif {
my ($pbm_file, $gif_file) = @_;
my $status;
my $cmd = "$ppmtogif $pbm_file ";
if ($gif_file) {
$cmd .= "> $gif_file ";
}
unless ($Chart::Graph::debug) {
$cmd .= "2> /dev/null ";
}
$status = system($cmd);
if (not _chk_status($status)) {
return 0;
}
return 1;
}
#
# Subroutine: gnuplot_date_utc()
#
# Description: wrapper function that handles UNIX
# time stamps as x values nicely
#
# Author: Ryan Koga - rkoga@caida.org
#
sub _gnuplot_date_utc {
my ($start, $end, $samp_scale, $use_local_tz, $global_options) = @_;
my $min_len = 60;
my $hour_len = $min_len*60;
my $day_len = $hour_len*24;
my $interval = $end - $start;
my $min_samp;
my @tics;
if (!defined($samp_scale)) {
$samp_scale = 1;
}
if ($interval < 10) {
$min_samp = 1;
} elsif ($interval < 30) {
$min_samp = 4;
} elsif ($interval < $min_len) {
$min_samp = 10;
} elsif ($interval < 3*$min_len) {
$min_samp = 30;
} elsif ($interval < 10*$min_len) {
$min_samp = $min_len;
} elsif ($interval < $hour_len) {
$min_samp = 5*$min_len;
} elsif ($interval < 2*$hour_len) {
$min_samp = 10*$min_len;
} elsif ($interval < 3*$hour_len) {
$min_samp = 15*$min_len;
} elsif ($interval < 4*$hour_len) {
$min_samp = 20*$min_len;
} elsif ($interval < 5*$hour_len) {
$min_samp = 30*$min_len;
} elsif ($interval < 12*$hour_len) {
$min_samp = $hour_len;
} elsif ($interval < $day_len) {
$min_samp = 2*$hour_len;
} elsif ($interval < 2*$day_len) {
$min_samp = 4*$hour_len;
} elsif ($interval < 5*$day_len) {
$min_samp = 12*$hour_len;
} elsif ($interval < 7*$day_len) {
$min_samp = $day_len;
} elsif ($interval < 15*$day_len) {
$min_samp = 2*$day_len;
} elsif ($interval < 30*$day_len) {
$min_samp = 7*$day_len;
} elsif ($interval < 365*$day_len) {
$min_samp = 30*$day_len;
} elsif ($interval < 2*365*$day_len) {
$min_samp = 60*$day_len;
} else {
$min_samp = 120*$day_len;
}
$min_samp /= $samp_scale;
my $start_min = int($start/$min_samp);
my $end_min = int($end/$min_samp);
for (my $curr_min = $start_min; $curr_min <= $end_min; $curr_min++) {
my $bucket = $curr_min*$min_samp;
my ($bucket_str,@time_data);
if ( $use_local_tz ) {
@time_data = localtime($bucket);
} else {
@time_data = gmtime($bucket);
}
$time_data[$#time_data] = -1; # unset dst data, broken strftime
# keep compatibility with the undocumented 'utc_seconds' global var
$Chart::Graph::Gnuplot::show_seconds = $Chart::Graph::Gnuplot::utc_seconds;
if ($min_samp >= $min_len && !$Chart::Graph::Gnuplot::show_seconds) {
$bucket_str = strftime("%H:%M", @time_data);
} else {
$bucket_str = strftime("%H:%M:%S", @time_data);
}
if ($bucket_str =~ /^00:00(:00)?$/ || $curr_min == $start_min + 1) {
$bucket_str .= strftime('\n%m/%d', @time_data);
if ($Chart::Graph::Gnuplot::show_year) {
$bucket_str .= strftime('\n%Y', @time_data);
}
}
push @tics, [ $bucket_str, $bucket ];
}
# must check to see if xtics were previously set in the globals
# if they are, we'll append them to the time stamp tics
# note: collisions are handled by the user
if (defined ($global_options->{"xtics"})) {
push @tics, @{$global_options->{"xtics"}};
}
$global_options->{"xtics"} = \@tics;
return 1;
}
sub _gnuplot_date_utc_normalize {
my ($start, $end, $samp_scale, $global_options) = @_;
### this code used to be used as a workaround for an old gnuplot bug
### newer versions don't need it
carp "'uts_normalize' is going to be depreciated in a future release\n";
my $min_len = 60;
my $hour_len = $min_len*60;
my $day_len = $hour_len*24;
my $interval = $end - $start;
my $min_samp;
if (!defined($samp_scale)) {
$samp_scale = 1;
}
if ($interval < 10) {
$min_samp = 1;
} elsif ($interval < 30) {
$min_samp = 4;
} elsif ($interval < $min_len) {
$min_samp = 10;
} elsif ($interval < 3*$min_len) {
$min_samp = 30;
} elsif ($interval < 10*$min_len) {
$min_samp = $min_len;
} elsif ($interval < $hour_len) {
$min_samp = 5*$min_len;
} elsif ($interval < 2*$hour_len) {
$min_samp = 10*$min_len;
} elsif ($interval < 3*$hour_len) {
$min_samp = 15*$min_len;
} elsif ($interval < 4*$hour_len) {
$min_samp = 20*$min_len;
} elsif ($interval < 5*$hour_len) {
$min_samp = 30*$min_len;
} elsif ($interval < 12*$hour_len) {
$min_samp = $hour_len;
} elsif ($interval < $day_len) {
$min_samp = 2*$hour_len;
} elsif ($interval < 2*$day_len) {
$min_samp = 4*$hour_len;
} elsif ($interval < 5*$day_len) {
$min_samp = 12*$hour_len;
} elsif ($interval < 7*$day_len) {
$min_samp = $day_len;
} elsif ($interval < 15*$day_len) {
$min_samp = 2*$day_len;
} elsif ($interval < 30*$day_len) {
$min_samp = 3*$day_len;
} else {
$min_samp = 30*$day_len;
}
$min_samp /= $samp_scale;
my $start_min = int($start/$min_samp);
my $end_min = int($end/$min_samp);
my @tics;
my $first_date_shown = 0;
for (my $curr_min = $start_min; $curr_min <= $end_min; $curr_min++) {
my $bucket = $curr_min*$min_samp;
my $bucket_str;
my @time_data = gmtime($bucket);
$time_data[$#time_data] = -1; # unset dst data, broken strftime
if ($min_samp >= $min_len) {
$bucket_str = strftime('%H:%M', @time_data);
} else {
$bucket_str = strftime('%H:%M:%S', @time_data);
}
my $show_date = 0;
if ($bucket_str =~ /^00:00(:00)?$/) {
$show_date = 1;
$first_date_shown = 1;
}
if ($curr_min == $start_min + 1 && !$first_date_shown) {
$show_date = 1;
}
if ($show_date) {
$bucket_str .= strftime('\n%m/%d', @time_data);
}
push @tics, [ $bucket_str, ($bucket - $start) / $end ];
}
# must check to see if xtics were previously set in the globals
# if they are, we'll append them to the time stamp tics
# note: collisions are handled by the user
if (defined ($global_options->{"xtics"})) {
push @tics, @{$global_options->{"xtics"}};
}
$global_options->{"xtics"} = \@tics;
return 1;
}
1;
__END__
=head1 NAME
Chart::Graph::Gnuplot
=head1 SYNOPSIS
use Chart::Graph::Gnuplot qw(&gnuplot);
gnuplot(\%global_options, [\%data_set_options, \@matrix],
[\%data_set_options, \@x_column, \@y_column],
[\%data_set_options, < filename >], ... );
=head1 DESCRIPTION
I is a function in module Chart::Graph that lets you
generate graphs on the fly in perl. It was written as a front-end
application to gnuplot for hassle-free generation of
graphs. I can be supplied with many of the same options and
arguments that can be given to gnuplot. For more information on
I see the end of this section.
=head1 OPTIONS
I has a very large number of options corresponding to
options available with the gnuplot application itself. This Perl
wrapper provides a large subset of the functionality of the
application.
+----------------------------------------------------------------------------+
| GLOBAL OPTIONS: |
+----------------+-----------------------------+-----------------------------+
| NAME | OPTIONS | DEFAULT |
+----------------+-----------------------------+-----------------------------+
|'title' | set your own title | 'untitled' |
|'output type' | 'pbm','gif','tgif','png', | 'png' |
| | 'svg' or "eps $epsoptions"| |
|'output file' | set your own output file, | 'untitled-gnuplot.png' |
| | undef to output to STDOUT | |
|'x-axis label' | set your own label | 'x-axis' |
|'y-axis label' | set your own label | 'y-axis' |
|'x2-axis label' | set your own label | none |
|'y2-axis label' | set your own label | none |
|'logscale x' | 0 or 1 | 0 |
|'logscale y' | 0 or 1 | 0 |
|'logscale x2' | 0 or 1 | 0 |
|'logscale y2' | 0 or 1 | 0 |
| 'xtics' | set your own tics on x-axis | none |
| | (see example below) | |
| 'x2tics' | set your own tics on x2-axis| none |
| | (see example below) | |
| 'ytics' | set your own tics on y-axis | none |
| | (see example below) | |
| 'y2tics' | set your own tics on y2-axis| none |
| | (see example below) | |
| 'xrange' | set xrange, accepts both | none |
| | string '[$xmin:$xmax]' | |
| | or arrayref [$xmin,$xmax] | |
| 'yrange' | set yrange, see xrange | none |
| | | |
| 'uts' | set your own range in unix | none |
| | timestamps, array ref: | |
| | [start_ts,end_ts,, | |
| | ] | |
| | see UNIX TIMESTAMPS example| |
| 'xdata' | 'time' to indicate that | none |
| | x-axis is date/time data | |
| 'ydata' | 'time' to indicate that | none |
| | y-axis is date/time data | |
| 'x2data' | 'time' to indicate that | none |
| | x2-axis is date/time data | |
| 'y2data' | 'time' to indicate that | none |
| | y2-axis is date/time data | |
| 'timefmt' | "Input date/time string" | none |
| | see Gnuplot manual for info| |
| 'format' | array ref: First element is | |
| | axis: 'x', 'y', 'x2', 'y2'.| |
| | Second element is | |
| | 'output date/time string" | |
| | see Gnuplot manual for info| |
| 'extra_opts' | set your own Gnuplot | none |
| | options, either an arrayref| |
| | or string ("\n"-separated) | |
| 'size' | scale the display size of | none |
| | the plot, arrayref [$x, $y]| |
+----------------+-----------------------------+-----------------------------+
+----------------------------------------------------------------------------+
| Data Set Options: |
+----------------+-----------------------------+-----------------------------+
| Name | Options | Default |
+----------------+-----------------------------+-----------------------------+
| 'type' | 'matrix', 'columns', 'file',| none |
| | 'function', see examples | |
| | below | |
| 'title' | set your own title | 'untitled data' |
| 'style' | 'points','lines','impulses' | 'points' |
| | 'errorbars', etc... | |
| | see ERRORBARS example | |
| 'axes' | 'x1y1', 'x2y2', 'x1y2', etc.| 'x1y1' |
| 'using' | map data to what will be | '1:2' |
| | plotted, see ERRORBARS | |
| | example | |
+----------------+-----------------------------+-----------------------------+
Data can be presented to I in one of 3 formats for
the convenience of the user:
\@matrix: an array reference of [x,y] pairs of data
Alternatively:
\@x_column, \@y_column: two array references of data of equal length.
\@x_column is the x-axis data. \@y_column is the y-axis data.
Finally, data can be stored in a file.
=head2 USING GNUPLOT TO READ AND PLOT DATE/TIME DATA DIRECTLY
I now has the capability to read date/time data and to create
graphs which display date/time on any axis. Unfortunately, mechanism
for reading data is less sophisticated than the mechanism for writing
data. I implements date/time data in the same
way as I itself is presently implemented for consistency with
the application.
Any axis can be set to read date/time data instead of numerical
data. This is done by setting the options C, C,
C, or C to the value C
gnuplot1.png
=head2 ERRORBARS
I supports errorbars to aid in data interpretation. To use an
arbitrary number of data columns (for errorbars), set C