#!/usr/bin/perl # # An easy-to-use wrapper program for Gnuplot. # Copyright (C) 1995-2004 Hiroyuki Ohsaki. # All rights reserved. # # $Id: xdoplot,v 1.54 2010/03/29 12:06:26 oosaki Exp $ # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # # $Log: xdoplot,v $ # Revision 1.54 2010/03/29 12:06:26 oosaki # *** empty log message *** # # Revision 1.53 2010/02/17 01:01:00 oosaki # *** empty log message *** # # Revision 1.52 2009/05/15 06:00:23 oosaki # *** empty log message *** # # Revision 1.51 2006/07/26 08:55:45 oosaki # Back to Getopt::Std for avoiding an imcompatibility isssue (-te is not allowed with Getopts::Long). # # Revision 1.50 2006/07/25 13:35:55 oosaki # Changed to use Getopt::Long and Carp. # Allowed mixture of lines with/without errorbars. # # # usage: xdoplot [-vds] [-t device] [-I dir] file... use Carp; use English; use File::Basename; use Getopt::Std; use POSIX qw/getpgrp tcgetpgrp/; use strict; no strict 'subs'; no warnings; # user customizable section my $GNUPLOT_PROGRAM = 'gnuplot'; my $CPP_PROGRAM = 'cc -E -'; my $CAT_PROGRAM = 'cat'; my $ZCAT_PROGRAM = 'gzip -cd'; my $PS_FONT_NAME = 'Helvetica'; # font name for PostScript device my $PS_FONT_SIZE = 14; # font size for PostScript device my $PS_COLOR = 'color'; # set to 'color' for color PostScript my $TEMP_PREFIX = "/tmp/xdoplot$$"; my %TERM = ( 'd' => 'dumb', 'e' => qq(postscript eps enhanced $PS_COLOR linewidth 2.0 "$PS_FONT_NAME" $PS_FONT_SIZE), 'p' => qq(postscript $PS_COLOR), 'f' => 'file', 't' => 'tgif', 'a' => 'aifm', 'g' => 'png', 'w' => qq(emf color 'Helvetica' 16),); sub usage { my $prog = basename($0); croak <; unlink 'fit.log'; exit 0; } sub get_fit_params { my ($prefix, $block) = @_; # run gnuplot for obtaining fitting result open(PIPE, "| $GNUPLOT_PROGRAM 2>/dev/null") or croak "$GNUPLOT_PROGRAM: $!\n"; print PIPE "f1(x) = a1 * x + b1\n"; print PIPE "fit f1(x) '$prefix.$block' via a1, b1\n"; close(PIPE); # extract parameters from log file open(LOG, 'fit.log') or croak "fit.log: $!\n"; my ($a1, $b1); while () { chomp; @_ = split; $a1 = $_[2] if ($_[0] eq 'a1' and $_[3] eq '+/-'); $b1 = $_[2] if ($_[0] eq 'b1' and $_[3] eq '+/-'); } close(LOG); unlink 'fit.log'; return ($a1, $b1); } # set signal handler $SIG{'INT'} = 'handler'; $SIG{'QUIT'} = 'handler'; # parse command-line options my %opts; getopts('vdst:I:', \%opts) or usage; my $verbose = $opts{v}; my $debug = $opts{d}; my $use_splot = $opts{s}; my $terminal = $opts{t} || 'x11'; # use X11 by default my $incl_dir = $opts{I} || '.'; # expand abbreviated terminal name $terminal = $TERM{$terminal} if (defined $TERM{$terminal}); # data file is mandatory @ARGV or usage; my $prefix = ($terminal eq 'file') ? basename($ARGV[0], '.res') : $TEMP_PREFIX; # pre-process the input file my $cat = ($ARGV[0] =~ /\.gz$/) ? $ZCAT_PROGRAM : $CAT_PROGRAM; my $dir = dirname($ARGV[0]) || '.'; open(IN, "$cat @ARGV | sed '/^#[ 0-9]/d' | $CPP_PROGRAM -I$dir -I$incl_dir |") or croak "@ARGV: $!\n"; my ($title, $xlabel, $ylabel, @global_opts); my (@show_list, %fit_tbl, %errorbar_tbl); my (@label, @local_opts, @eval_commands, @fit_label); my ($block_no, $in_block) = (1, 0); # parse xdoplot commands while () { chomp; next if /^\#/; next if /nan/i; # invalid data s/\s+/ /g; # shrink white spaces s/\s+\}\s+/\}/g; # remove space before closing paren # remove braces s:{/\w+\s*(.*?)\s*}\s*:$1:g unless ($terminal =~ /enhanced/); ($title = $POSTMATCH, next) if /^title:\s*/i; ($xlabel = $POSTMATCH, next) if /^xlabel:\s*/i; ($ylabel = $POSTMATCH, next) if /^ylabel:\s*/i; # global and local options if (/^option:\s*/i) { if ($in_block) { push(@{$local_opts[$block_no]}, $POSTMATCH); } else { push(@global_opts, $POSTMATCH); } next; } # eval command if (/^eval:\s*/i) { if ($POSTMATCH eq 'undef') { @eval_commands = (); } else { push(@eval_commands, $POSTMATCH); } next; } # select valid data blocks if (/^show:\s*/i) { for (split(/[\s,]+/, $POSTMATCH)) { push(@show_list, $_); } next; } # select data blocks to be fitted if (/^fit:\s*/i) { for (split(/[\s,]+/, $POSTMATCH)) { $fit_tbl{$_} = 1; } next; } # end of the data block if ($in_block and (/^\s*$/ or /^name:/)) { if (!$use_splot) { close(TEMP); $in_block = 0; $block_no++; } else { print TEMP "\n"; } } # emulate `name:' line if omitted if (!$in_block and /^[+-]?[\d.]+/) { $label[$block_no] = ''; $in_block = 1; open(TEMP, ">$prefix.$block_no") or croak "$prefix.$block_no: $!\n"; } # in the data block if ($in_block) { s/\#.*$//; # remove comments s://.*::; # remove comments next unless /\s/; # evaluate eval commands and dump to temporary file @_ = split(/\s+/, $_); for my $expr (@eval_commands) { eval $expr; warn $@ if $@; } print TEMP join("\t", @_), "\n"; # check if errorbar is necessary $errorbar_tbl{$block_no} = 1 if ($use_splot ? @_ >= 4 : @_ >= 3); next; } # start of a data block if (/^name:\s*/i) { $label[$block_no] = $POSTMATCH; $label[$block_no] =~ s/\s+/ /g; # shrink white spaces $label[$block_no] =~ s/\s*=\s*/=/g; # remove spaces around equal $in_block = 1; open(TEMP, ">$prefix.$block_no") or croak "$prefix.$block_no: $!\n"; next; } } if ($in_block) { close(TEMP); } else { $block_no--; } # check if this program is running as a foreground process my $foreground = 1; if (open(TTY, "/dev/tty") and tcgetpgrp(TTY) != getpgrp()) { $foreground = 0; } if ($terminal eq 'file') { open(PLOT, ">$prefix.plt") or croak "$prefix.plt: $!\n"; } else { my $option = (!$foreground and $terminal eq 'x11') ? '-persist' : ''; # open pipe to gnuplot program open(PLOT, "| $GNUPLOT_PROGRAM $option") or croak "$GNUPLOT_PROGRAM: $!\n"; # output terminal specific commands print PLOT "set terminal $terminal\n" if $terminal; print PLOT "set size 0.5, 0.5\n" if ($terminal =~ /enhanced/); } select(PLOT) unless $debug; # output title, xlabel, ylabel if specified if ($title) { my ($text, $opts) = split(/--/, $title, 2); print qq(set title "$text" ) . ($opts || '') . "\n"; } if ($xlabel) { my ($text, $opts) = split(/--/, $xlabel, 2); print qq(set xlabel "$text" ) . ($opts || '') . "\n"; } if ($ylabel) { my ($text, $opts) = split(/--/, $ylabel, 2); print qq(set ylabel "$text" ) . ($opts || '') . "\n"; } # if show command is not specified, plot all data blocks if (!@show_list) { for (1 .. $block_no) { push(@show_list, $_); } } # perform line fitting before plotting for my $block (@show_list) { next unless $fit_tbl{$block}; my ($a, $b) = get_fit_params($prefix, $block); # disable fitting if gnuplot fails fitting ($fit_tbl{$block} = 0, next) unless ($a and $b); print "f$block(x) = a$block * x + b$block\n"; print "fit f$block(x) '$prefix.$block' via a$block, b$block\n"; print "\n"; $a =~ s/(\.\d{2})\d+/$1/; $b =~ s/(\.\d{2})\d+/$1/; $fit_label[$block] = sprintf "$b + ($a)*x"; } # dump global options print join("\n", @global_opts), "\n\n" if @global_opts; # compose plot/splot command print $use_splot ? 'splot' : 'plot'; print " \\\n"; for (@show_list) { # plot original data print "'$prefix.$_'"; print qq( title "$label[$_]") if $label[$_]; print ' ', join(' ', @{$local_opts[$_]}) if $local_opts[$_]; # add errorbars if ($errorbar_tbl{$_}) { print qq( , '' using 1:2:3 notitle with errorbars); } # add fitted line if ($fit_tbl{$_}) { print ", \\\n"; print "f$_(x) "; print qq(title "$fit_label[$_]" ); } print ",\\\n" if ($_ != $show_list[-1]); } print "\n"; print "pause 9999\n" if ($foreground and $terminal eq 'x11'); close(PLOT); handler unless ($terminal eq 'file'); __END__ =head1 NAME xdoplot - An easy-to-use wrapper program for Gnuplot =head1 SYNOPSIS xdoplot [-vds] [-t device] [-I dir] file... =head1 DESCRIPTION This manual page documents B. This program is a simple wrapper program for B, a command-driven interactive function plotting program. Although its usage is limited to either 2D or 3D graph, B makes it much easier for users to use B and enhances its features in the following ways. First, the most distinguishing feature of B is that the input file for B can store both B commands and data for I lines, whereas B usually requires separate files for B commands and every data line. B supports inline data by specifying C<'-'> as a data file, but its usage is cumbersome and not handy. On the contrary, the data format of B is simple and straightforward. Second, B is well-suited for non-interactive uses. With B, the output device can be easily switched using I<-t> option, which is convenient for plotting in, for instance, the PostScript format. Third, B supports B-compressed data files for saving disk space, and inclusion of external files and macro expansion using B, a pre-processor program for standard C compilers. This pre-processor feature enables conditional operation using C<#ifdef> and C<#endif> directives, and function definition using C<#define> directive. Moreover, using C<#include> directive makes maintaining a number of data files quite easy. B is a simple wrapper program for B. For detailed usage, please consult B document and manual page. =head1 OPTIONS =over 4 =item I<-v> Enable verbose mode. =item I<-d> Enable debug mode; without invoking B, dump B commands to be executed in the standard output. =item I<-s> Use C (3D plotting command) instead of C (2D plotting command). By default, C (2D plotting) is used. =item I<-t> device Set output device to I. Any valid output devices in B can be specified. The default output device is C. For convenience, B supports the following abbreviations. =over 4 =item I dumb =item I postscript eps enhanced $PS_COLOR "$PS_FONT_NAME" $PS_FONT_SIZE =item I

postscript $PS_COLOR =item I file =item I tgif =item I aifm =item I png =back =item I<-I> dir Add I

to the include directory for B. =back =head1 COMMANDS =over 4 =item I string [-- options] Specify the title text as I, followed by its options I (e.g., positional offsets, font family, and font size). See C in B for details of the title format and its options. =item I string [-- options] Specify the xlabel text as I, followed by its options I (e.g., positional offsets, font family, and font size). See C in B for details of the xlabel format and its options. =item I string [-- options] Specify the ylabel text as I, followed by its options I (e.g., positional offsets, font family, and font size). See C in B for details of the ylabel format and its options. =item I string Specify global and local options. In the header block (i.e., before the first occurrence of C) , all strings specified by I are global options, which will be directly passed to B. In the data block, all strings specified by I are local options, which will be inserted after the data file of C command. =item I expression For each line of the data block, I is executed before writing to a temporary file. I can be an arbitrary Perl expression. I can access and overwrite the variable C<@_>, which stores values for each column. =item I [n [,n]...] Select and deselect data blocks to be plotted. If I command is omitted, B plots all lines. Note that the first data block is numbered as 1. =item I [n [,n]...] Plot fitted lines (regression line) for specified data blocks. If I command is omitted, B plots not fitted line. Note that the first data block is numbered as 1. =item I string This command specifies the beginning of a data block, and its corresponding line title as I. See C in B for details of the title format. =back =head1 EXAMPLE USAGE =over 4 =item - By default, B plots a 2D graph with X11 xdoplot file.res =item - B supports a B'ed data file xdoplot file.res.gz =item - Save the output in the EPS (Encapsulated PostScript) format xdoplot -te file.res >file.eps =item - B can include external files from other directories xdoplot -I.. -I../macro file.res =back =head1 EXAMPLE DATA FILE =over 4 =item - macro.def #define bold(x) {/Helvetica-Bold x} #define italic(x) {/Helvetica-Oblique x} #define symbol(x) {/Symbol x} #define maxp italic(max_p) #define minth italic(min_{th}) #define maxth italic(max_{th}) #define wq italic(w_q) #define tau symbol(t) =item - time-queue.def #include "macro.def" xlabel: Time [s] ylabel: Queue Length [packet] option: set data style linespoints option: set key bottom right =item - file.res #include "time-queue.def" option: set xrange [0:1] option: set yrange [0:10] show: 1, 3 name: maxp = 0.1, minth = 5, maxth = 15, tau = 0.1 0.0 0 0.1 4 0.2 3 0.3 8 0.4 12 name: maxp = 0.2, minth = 5, maxth = 15, tau = 0.1 0.0 0 0.1 3 0.2 7 0.3 2 0.4 5 name: maxp = 0.3, minth = 5, maxth = 15, tau = 0.1 0.0 0 0.1 3 0.2 2 0.3 4 0.4 2 =back =head1 AVAILABILITY The latest version of B is available at http://www.ispl.jp/~oosaki/software/xdoplot/xdoplot =head1 AUTHOR Hiroyuki Ohsaki =cut