## Xrt2d.pm is a sub-module of Graph.pm. It has all the subroutines 
## needed for the Xrt2d part of the package.
##
## $Id: Xrt2d.pm,v 1.26 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::Xrt2d;
use Exporter ();

@ISA = qw(Exporter);
@EXPORT = qw();
@EXPORT_OK = qw(&xrt2d);

use FileHandle;			# to create generic filehandles
use Carp;			# for carp() and croak()
#use POSIX ":sys_wait_h";	# for waitpid()
use Chart::Graph::Utils qw(:UTILS);	# get global subs and variables
use Chart::Graph::XrtUtils qw(:UTILS); # get Xrt subroutines

$cvs_Id = '$Id: Xrt2d.pm,v 1.26 2006/06/07 21:09:33 emile Exp $';
$cvs_Author = '$Author: emile $';
$cvs_Name = '$Name:  $';
$cvs_Revision = '$Revision: 1.26 $';

$VERSION = 3.2;

use strict;

#
# xrt graphing package
#

my %def_xrt_global_opts = (
			"output file" => "untitled-xrt2d.gif",
			"output type" => "gif",
			"x-axis title" => "x-axis",
			"y-axis title" => "y-axis",
			"set labels" => undef,
			"point labels" => undef,
			"header" => [],
			"footer" => [],
			"misc labels" => undef,
			"invert" => 0,
			"x time" => 0, # x axis labels are time indicators
			"style" => "bar",
		    );

my %def_xrt_data_opts = (
			"color" => undef,
		    );

#
#
# Subroutine: xrt2d()
#
# 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 xrt2d {
    my ($user_global_opts_ref, @data_sets) = @_;
    my (%global_opts, %data_opts);
    my $data_set_ref;
    
    # variables to be written to the command file
    my ($plot_file, $x_axis, $y_axis);
    my ($header, $footer, $misc_labels, $set_labels, $point_labels);
    my ($x_cnt, $y_cnt, $hdr_cnt, $ftr_cnt, $label_cnt);
    my ($output_file, $output_type);
    my $xrt_options = "";
    my $x_timestamps = 0;

    _make_tmpdir("_Xrt2d_");

    # set paths for external programs
    if (not _set_xrtpaths("xrt2d")) {
	_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;
    }

    # call to combine user options with default options 
    %global_opts = _mesh_opts($user_global_opts_ref, \%def_xrt_global_opts);
    
    # check for values in command file
    while (my ($key, $value) = each %global_opts) {
	
	if ($key eq "output file") {
	    if (defined($value)) {
		$output_file = $value;
		unless (defined $global_opts{"output type"}) {
		    carp "Must have an output type defined";
		    _cleanup_tmpdir();
		    return 0;
		}

		# If the file is PostScript ... what XRT makes is PostScript
		if ($global_opts{"output type"} eq "ps") {
		    $plot_file = _make_tmpfile("plot", "ps");
		  } 
		# For all raster formats XRT starts out with X-Windows XWD format.
		elsif (($global_opts{"output type"} eq "gif") or
			   ($global_opts{"output type"} eq "xwd") or
			   ($global_opts{"output type"} eq "png") or
			   ($global_opts{"output type"} eq "jpg")
			  ) {
		  $plot_file = _make_tmpfile("plot", "xwd");
		} else {
		  # Default is XWD
		  carp "Unknown output type, defaulting to xwd";
		  $plot_file = _make_tmpfile("plot", "xwd");
		}
	  }
	}
	
	if ($key eq "x-axis title") {
	    if(defined($value)) {
		$x_axis = $value;
	    }
	}
	
	if ($key eq "y-axis title") {
	    if(defined($value)) {
		$y_axis = $value;
	    }
	}
	
	if ($key eq "header") {
	    if(defined($value)) {
		$header = $value;
	    }
	}
	
	if ($key eq "footer") {
	    if(defined($value)) {
		$footer = $value;
	    }
	}

	if ($key eq "misc labels") {
	    if(defined($value)) {
		$misc_labels = $value;
	    }
	}

	if ($key eq "set labels") {
	    if(defined($value)) {
		$set_labels = $value;
	    }
	}

	if ($key eq "point labels") {
	    if(defined($value)) {
		$point_labels = $value;
	    }
	}

	if ($key eq "invert") {
	    if(defined($value) and $value) {
		$xrt_options .= " -xrm '*xrtInvertOrientation: True'";
	    }
	}

	if ($key eq "x time") {
	    if(defined($value) and $value) {
		$xrt_options .= " -xrm '*xrtXAnnotationMethod: ANNOTIMELABELS'";
		$x_timestamps = 1;
	    }
	}

	if ($key eq "style") {
	    if(defined($value)) {
		if ($value eq "stackedbar") {
		   $xrt_options .= " -xrm '*xrtType: TYPESTACKINGBAR'";
		} elsif ($value eq "bar") {
		   $xrt_options .= " -xrm '*xrtType: TYPEBAR'";
		} elsif ($value eq "pie") {
		   $xrt_options .= " -xrm '*xrtType: TYPEPIE'";
		} elsif ($value eq "area") {
		   $xrt_options .= " -xrm '*xrtType: TYPEAREA'";
		} elsif ($value eq "stackedarea") {
		   $xrt_options .= " -xrm '*xrtLegendReversed: True'";
		   $xrt_options .= " -xrm '*xrtType: TYPEAREA'" .
			" -xrm '*xrtIsStacked: True'";
		} else {
		   carp "No graph type defined, defaulting to bar";
		   $xrt_options .= " -xrm '*xrtType: TYPEBAR'";
		}
	    }
	}
    }
    # Only reverse legend if NOT inverting bars.
    if (not $global_opts{"invert"} and $global_opts{"style"} eq "stackedbar") {
	$xrt_options .= " -xrm '*xrtLegendReversed: True'";
    }
    
    # get the number of columns and number of rows
    
    $x_cnt = @{$point_labels};
    $y_cnt = @{$set_labels};
    
    # because xrt allows multiline headers
    # get the length of the header array
    # each line of the header is one index
    # in the array
    $hdr_cnt = $#{$header} + 1;
    $ftr_cnt = $#{$footer} + 1;
    $label_cnt = $#{$misc_labels} + 1;

    my @colors;
    my @all_data;
    foreach $data_set_ref (@data_sets) {
	if (ref($data_set_ref) ne "ARRAY") {
	    carp "Data set must be an array";
	    _cleanup_tmpdir();
	    return 0;
	}
	my ($user_data_opts_ref, $data_ref) = @$data_set_ref;
	my (%data_opts);
	
	## check first arg for hash
	if (ref($user_data_opts_ref) ne "HASH") {
	    carp "Data options must be a hash.";
	    _cleanup_tmpdir();
	    return 0;
	}

	push @all_data, $data_ref;

	# call to combine user options with default options   
	%data_opts = _mesh_opts($user_data_opts_ref, \%def_xrt_data_opts);

	# write data options to command file
	while (my ($key, $value) = each %data_opts) {
	    if ($key eq "color") {
		if (defined $value) {
		    push @colors, $value;
		}
	    }
	}
    }	

    if (@colors) {
	my $resource_string;
	foreach my $color (@colors) {
	    $resource_string .=
		    "(LpatSolid FpatSolid \"$color\" 1 PointNone \"$color\" 7)";
	}
	$xrt_options .= " -xrm '*xrtDataStyles: ($resource_string)'";
    }
    ##
    ## print command file using this format (for bar graphs)
    ## data separated by tabs
    ##
    
    # output.file						
    # x_cnt (number of points of data)
    # y_cnt (number of sets of info per point)
    # point1title set1 set2 ... sety
    # point2title set1 set2 ...
    # .
    # .
    # pointxtitle set1 set2 ... sety
    # Number of header lines (multiple header lines available)
    # header1
    # header2
    # ...
    # Number of header lines (multiple header lines available)
    # foot1
    # foot2
    # ...
    # Title of x-axis
    # Title of y-axis
    # label_cnt (number of extra data labels)
    # label1title point# set#
    
    # create command file and open file handle 
    my $command_file = _make_tmpfile("command");
    my $handle = new FileHandle;
    if (not $handle->open(">$command_file")) {
	carp "could not open $command_file";
	_cleanup_tmpdir();
	return 0;
    }

    print $handle "$plot_file\n";
    print $handle "$x_timestamps\n";
    print $handle "$x_cnt\n";
    print $handle "$y_cnt\n";
    _print_array($handle, @{$set_labels});
    _print_array($handle, @{$point_labels});
    _print_matrix($handle, @all_data);
    print $handle "$hdr_cnt\n";
    _print_array($handle, @{$header});
    print $handle "$ftr_cnt\n";
    _print_array($handle, @{$footer});	
    print $handle "$x_axis\n";
    print $handle "$y_axis\n";
    print $handle "$label_cnt\n";
    _print_matrix($handle, @$misc_labels);
    $handle->close();

    # call xrt and convert file to gif
    if (not _exec_xrt2d($command_file, $xrt_options)) {
	_cleanup_tmpdir();
	return 0;
    }

    my $graph_format = $global_opts{"output type"};
    if ($graph_format eq "ps") {
	  if (not _chk_status(system("cp $plot_file $output_file"))) {
	    _cleanup_tmpdir();
	    return 0;
	  }
    } elsif ($graph_format eq "xwd") {
	  if (not _chk_status(system("cp $plot_file $output_file"))) {
	    _cleanup_tmpdir();
	    return 0;
	  }
	} else {
	  if(not _convert_raster($graph_format, $plot_file, $output_file)) {
	    _cleanup_tmpdir();
	    return 0;
	  }
    }

    _cleanup_tmpdir();
    return 1;
}

sub _xrt_data_check {
    my ($user_data_opts_ref, @data) = @_;
    my (%data_opts);
    
    my ($result, $color);
    
    ## 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_xrt_data_opts);

    # write data options to command file
    while (my ($key, $value) = each %data_opts) {
	if ($key eq "color") {
	    $color = $value;
	}
    }
}

# # 
# # Subroutine: set_xrtpaths()
# # 
# # Description: set paths for external programs required by xrt()
# #              if they are not defined already
# #
# sub _set_xrtpaths {

#     my $xrt = shift;

#     if (not defined($ppmtogif)) {
# 	if (not $ppmtogif = _get_path("ppmtogif")) {
# 	    return 0;
# 	}
#     }

#     if (not defined($xrt)) {
# 	if (not $xrt = _get_path("graph_2d")) {
# 	    return 0;
# 	}
#     }

#     if (not defined($xwdtopnm)) {
# 	if (!($xwdtopnm = _get_path("xwdtopnm"))) {
# 	    return 0;
# 	}
#     }

#     if (not defined($xvfb)) {
# 	if (not $xvfb = _get_path("Xvfb")) {
# 	    return 0;
# 	}
#     }

#     # make sure /usr/dt/lib is in the library path
#     _set_ldpath("/usr/dt/lib");

#     return 1;
# }

# #
# # Subroutine: set_ldpath()
# #
# # Description: Xvfb has trouble finding libMrm, so we have to add
# #              /usr/dt/lib to LD_LIBRARY_PATH
# #

# sub _set_ldpath {
#     my ($libpath) = @_;
    
#     if (not defined($ENV{LD_LIBRARY_PATH})) {
# 	$ENV{LD_LIBRARY_PATH} = "$libpath";
# 	return 1;
#     }

#     my @ldpath = split (/:/, $ENV{LD_LIBRARY_PATH});

#     # make sure library path isn't already defined
#     foreach my $i(@ldpath){
# 	if ($i eq $libpath) {
# 	    return 1;
# 	}
#     }

#     # add library path to LD_LIBRARY_PATH
#     $ENV{LD_LIBRARY_PATH} = "$libpath:$ENV{LD_LIBRARY_PATH}";
#     return 1;
# }

# # 
# # Subroutine: print_matrix() 
# # 
# # Description: print out all the elements 
# #              in a X by Y  matrix, row by row
# #

# sub _print_matrix {
#     my ($handle, @matrix) = @_;
    
#     foreach my $row (@matrix){
# 	foreach my $i (@{$row}){
# 	    print $handle "$i\t";
# 	}
# 	print $handle "\n";
#     }
#     return 1;
# }
# # 
# # Subroutine: print_array()
# # 
# # Description:  print out each element of array, one per line
# #

# sub _print_array {
#     my ($handle, @array) = @_;
#     my $i;
    
#     foreach $i (@array) {
# 	print $handle "$i\n";
#     }
#     return 1;
# }

# # 
# # Subroutine: verify_ticks();
# #   
# # Description: check that the number of tick labels is the same
# #              as the number of xy rows and columns. we can only have
# #              as many ticks as the number of rows or columns
# #              we make this subroutine so that the calling subroutine
# #              is kept cleaner.

# sub _verify_ticks {
#     my ($cnt, $ticks_ref) = @_;

#     # if no ticks are given then just
#     # give the xrt binary "1, 2,..."
#     if (not defined($ticks_ref)) {
# 	my @def_ticks;
# 	for (my $i = 0; $i < $cnt; $i++) {
# 	    $def_ticks[$i] = $i + 1;
#         }
# 	$ticks_ref = \@def_ticks;
#     }

#     my $tick_cnt = @{$ticks_ref};

#     if ($cnt ne $tick_cnt){
# 	carp "number of tick labels must equal the number of xy rows and columns";
# 	return 0;
#     }
#     return 1;
# }

# # 
# # Subroutine: exec_xrt()
# #
# # Description: execute the xrt program on the command file.
# #              xrt generates a xwd file.
# # 
# sub _exec_xrt {
#     my ($command_file, $options) = @_;
#     my ($output);
#     my ($childpid, $port);
#     my $display_env = $ENV{DISPLAY};
#     my $status;

#     if ($use_xvfb) {
# 	# start the virtual X server
# 	($childpid, $port) = _exec_xvfb();
# 	printf STDERR "\tXRT is $xrt\n";
# 	my $status = system("$xrt -display ipn:$port.0 < $command_file $options");
#     } else {
# 	# use the local X server
# 	# warning: colors might be messed up 
# 	# depending on your current setup
# 	$status = system("$xrt -display $display_env < $command_file $options");
#     }

#     if (not _chk_status($status)) {
# 	return 0;
#     }

#     kill('KILL', $childpid);
#     return 1;
# }
	
# # 
# # Subroutine: exec_xwdtogif 
# # 
# # Description: convert the xwd file generated by xrt into a gif
# #              this is a 2-step process. the xwd must be converted into 
# #              a pnm and then into a gif.
# sub _exec_xwdtogif {
#     my ($xwd_file, $gif_file) = @_;
#     my ($status);
    
#     if ($Chart::Graph::debug) {
# 	$status = system("$xwdtopnm $xwd_file | $ppmtogif > $gif_file");
#     } else {
# 	$status = system("( $xwdtopnm $xwd_file | $ppmtogif > $gif_file; ) 2> /dev/null");
#     }
    
#     if (not _chk_status($status)) {
# 	return 0;
#     }
#     return 1;
# }

# # 
# # Subroutine: exec_xvfb()
# #
# # Description:  this starts the vitualX server(X is required by xrt, so 
# #               we fake out xrt with Xvfb, for speed and compatability)
# #
# #
# sub _exec_xvfb {
#     my $port = 99;
#     my $childpid;
#     my $sleep_time = 1;
    

#     # starting with port 100, we try to start
#     # the virtual server until we find an open port
#     # because of the nature of the virtual x server
#     # we use, in order to know if we have found an 
#     # open port, we have to sleep.
#     # we check the pid of the virtual x process we started
#     # and see if it died or not.
#     while (_childpid_dead($childpid)) {
# 	$port++;
# 	$childpid = _try_port($port);	
# 	sleep($sleep_time);
#     }
  
#     # save the childpid so we can stop the virtual server later
#     # save the $port so we can tell xrt where the virtual server is.
#     return ($childpid, $port);
# }
# # 
# # Subroutine: try_port();
# #
# # Description:  will try to start Xvfb on specified port
# sub _try_port {

#     my ($port) = @_;
#     my ($childpid);
    
#     #fork a process
#     if (not defined($childpid = fork())){
# 	# the fork failed
# 	carp "cannot fork: $!";
# 	return 0;
#     } elsif ($childpid == 0) {
# 	# we are in the child process
# 	if ($Chart::Graph::debug) {
# 	    exec "$xvfb :$port";
# 	}
# 	else {
# 	    exec "exec $xvfb :$port 2> /dev/null";
# 	}

# 	die "should never reach here\n";
#     } else {
# 	# we are in the parent, return the childpid
# 	# so re can kill it later.
# 	return $childpid;
#     }
    
# }

# # 
# # Subroutine: childpid_dead
# # 
# # Description: check to see if a PID has died or not
# #
# #
# sub _childpid_dead {
#     my ($childpid) = @_;
    
#     if (not defined($childpid)) {
# 	return 1;
#     }

#     # WNOHANG: waitpid() will not suspend execution  of
#     # the  calling  process  if  status is not
#     # immediately available  for  one  of  the
#     #  child processes specified by pid.
#     return waitpid($childpid, &WNOHANG);
# }

1;


__END__

=head1 NAME

Chart::Graph::Xrt2d

=head1 SYNOPSIS

 #Include module
 use Chart::Graph::Xrt2d qw(xrt2d);

 # Function call
 xrt2d(\%options,
       [\%data_options1, \@data_set1],
       [\%data_options2, \@data_set2],
	.
	.
      );


=head1 DESCRIPTION

This module is unmaintained, it worked with Sitraka's XRT, and hasn't been
tested against newer versions.

Sitraka (now Quest) makes a number of graphics packages for UNIX systems.  XRT is
a Motif-based commercial software product that has been adapted by
CAIDA using a combination of C drivers and Perl function I<xrt2d()>.
The Perl function I<xrt2d()> provides access to the two dimensional
graphing capabilities of XRT from Perl.  To access the three dimensional
graphing using XRT, use I<xrt3d()> also supplied in the
I<Chart::Graph> package.

=head1 ARGUMENTS

The options to I<xrt2d()> are listed below.  Additional control over the
resulting graph is possible by using the XRT application itself once
the graph has been created.

 +--------------------------------------------------------------------------+
 |                                OPTIONS                                   |
 +----------------+--------------------------+------------------------------+
 | Name           |  Options                 | Default                      |
 +----------------+--------------------------+------------------------------+
 |"output file"   |  (set your own)          | "untitled-xrt2d.gif"         |
 |"output type"   |  "ps","xwd", "png", "jpg"| "xwd"                        |
 |"x-axis title"  |  (set your own)          | "x-axis"                     |
 |"y-axis title"  |  (set your own)          | "y-axis"                     |
 |"set labels"    |  (set your own bar chart | none                         |
 |                |   labels for each set of |                              |
 |                |   data)                  |                              |
 |"point labels"  |  (set your own labels for| none                         |
 |                |   bars themselves)       |                              |
 |"misc labels"   |  (misc annotation that   | none                         |
 |                |   can be added to bar    |                              |
 |                |   chart)                 |                              |
 |"x time"        |  timescale if appropriate| "0"                          |
 |"invert"        |  run bars horizontally   | "0" (vertically)             |
 |                |  instead of vertically.  |                              |
 |                |  1 = horizonally.        |                              |
 |"header"        |  (set your own - can     | "header"                     |
 |                |   be multiple lines)     |                              |
 |"footer"        |  (set your own - can     | "footer"                     |
 |                |   be multiple lines)     |                              |
 |"style"         |  Style of chart - types  | "bar"                        |
 |                |  include: bar, pie, area |                              |
 |                |  stackedbar, stackedarea |                              |
 +----------------+--------------------------+------------------------------+

The I<xrt2d()> function only accepts data in one form.  C<\@data_sets>:
a one dimensional array with a prefix of data options.: 
C<[[\%data1_opts, \@data1], [\%data2_opts, \@data2], [\%data3_opts, \@data3]]>
-- see example for more details on the syntax.

The data options are listed below.

 +--------------------------------------------------------------------------+
 |                             DATA OPTIONS                                 |
 +----------------+--------------------------+------------------------------+
 | Name           |  Options                 | Default                      |
 +----------------+--------------------------+------------------------------+
 | "color"        | Any valid web page color | none                         |
 +----------------+--------------------------+------------------------------+

=head2 DETAILS ON GRAPHICS CONVERTER OPTIONS

The xrt package supports only two graphics formats internally:
Postscript and the X windows format XWD.  Additional raster graphics
formats are supported with Chart::Graph by using one of two graphics
converter packages: I<Imagemagick> and I<Netpbm>.

If you need to install a converter package, I<Imagemagick>
I<http://www.imagemagick.org/> is probably preferable
simply for its comparatively simplicity.  It uses one program
I<convert> for all of it's conversion needs, so it is easy to manage
and simple for Chart::Graph to use.  Many UNIX systems come with some
collection of the I<Netpbm> utilities already installed, thus users
may be able to start using Chart::Graph without adding any additional
converters.  Alas, it is unlikely any distributions would include all
the converters for the newest graphics formats used by Chart::Graph.
In that case it may still preferable to use I<Imagemagick> simply for
the sake of avoiding installing over 80 utilities that come with
current distributions of I<Netpbm>.  For more information on the
current distribution of I<Netpbm> go to the current website at:
I<http://netpbm.sourceforge.net/>

The xrt package also allows for multiple header and footers with each
graph.  As a result, instead of just the usual string, an array
reference containing the multiple strings for the header and footer
text.

=head1 EXAMPLES

The following two examples show Chart::Graph::Xrt2d in different roles
and producing different styles of output.

=head2 HORIZONTAL BAR GRAPH

The following example creates a two, two dimensional bars charts of
fictitious stock data from rival restaurants that is displayed in a
single graphic file F<xrt2d-1.jpg>.

 #make sure to include Chart::Graph
 use Chart::Graph::Xrt2d qw(xrt2d);

 # Call to xrt2d with two data sources.
 xrt2d({"output file" => "xrt2d-1.jpg",
        "output type" => "jpg",
        "set labels"=> ["Joe's", "Ralph's"],
        # Flip graph from vertical to horizontal
	"invert" => 1,
	"point labels" => ["Jan/Feb", "Mar/Apr", "May/Jun", "Jul/Aug",
                         "Sep/Oct", "Nov/Dec"],
        "x-axis title" => "Month's tracked",
        "y-axis title" => "Stock prices for Rival restaurant chains"
       },
       [{"color" => "MistyRose"}, ["8", "13", "20", "45", "50", "100"]],
       [{"color" => "#000000"},   ["75", "50", "25", "25", "50", "75"]]
    )
 )

=for html
<p><center><img src="http://www.caida.org/tools/utilities/graphing/xrt2d-1.jpg"></center></p>
<p><center><em>xrt2d-1.jpg</em></center></p>

=head2 VERTICAL BAR GRAPH

The following example is simply a more elaborate instance of the first
example. which produces the file F<xrt2-2.gif>.  Note the relationship
between sets and points.  Accidentally reversing the order will cause
unpredicable results.

 #make sure to include Chart::Graph
 use Chart::Graph::Xrt2d qw(xrt2d);

 xrt2d({"output file" => "xrt2d-2.gif",
			 "output type" => "gif",
		"set labels" => ["set1", "set2", "set3", "set4"],
		"point labels" => ["point1", "point2", "point3"]},
                # Each entry here corresponds to a set
		[{"color" => "MistyRose"}, ["15", "23", "10"]],
		[{"color" => "#0000FF"}, ["13", "35", "45"]],
		[{"color" => "#00FF00"}, ["15", "64", "24"]],
		[{"color" => "Navy"}, ["18", "48", "32"]],
      );

=for html
<p><center><img src="http://www.caida.org/tools/utilities/graphing/xrt2d-2.gif"></center></p>
<p><center><em>xrt2d-2.gif</em></center></p>


=head1 MORE INFO

For more information on XRT:

 http://www.quest.com/xrt_pds/

=head1 CONTACT

Send email to graph-dev@caida.org is you have problems, questions,
or comments. To subscribe to the mailing list send mail to
graph-dev-request@caida.org with a body of "subscribe your@email.com"

=head1 AUTHOR

 CAIDA Perl development team (cpan@caida.org)

=cut