plotpipe: Updated to support >3 columns, titles, and headers.
This commit is contained in:
parent
bb5f8e269a
commit
30f10bbdf1
6
plotpipe/example1.csv
Normal file
6
plotpipe/example1.csv
Normal file
|
@ -0,0 +1,6 @@
|
|||
Header1,header2,header3
|
||||
1,2,3
|
||||
2,4,5
|
||||
3,4,7
|
||||
4,2,1
|
||||
5,1,2
|
|
6
plotpipe/example2.csv
Normal file
6
plotpipe/example2.csv
Normal file
|
@ -0,0 +1,6 @@
|
|||
Header1;header2;header3
|
||||
1;2;3
|
||||
2;4;5
|
||||
3;4;7
|
||||
4;2;1
|
||||
5;1;2
|
|
9
plotpipe/example3.csv
Normal file
9
plotpipe/example3.csv
Normal file
|
@ -0,0 +1,9 @@
|
|||
#Title line1 with "
|
||||
#Title line2 with '
|
||||
Header1 header 2 header3
|
||||
1 2 3
|
||||
2 100 0
|
||||
3 4 7
|
||||
5 1
|
||||
6 1
|
||||
6 4 4
|
Can't render this file because it contains an unexpected character in line 1 and column 19.
|
6
plotpipe/example4.csv
Normal file
6
plotpipe/example4.csv
Normal file
|
@ -0,0 +1,6 @@
|
|||
Header1 header2 header3
|
||||
1 2 3
|
||||
2 4 5
|
||||
3 4 7
|
||||
4 2 1
|
||||
5 1 2
|
|
6
plotpipe/example5.csv
Normal file
6
plotpipe/example5.csv
Normal file
|
@ -0,0 +1,6 @@
|
|||
Header1 header2 header3
|
||||
1 4 3
|
||||
2 4 5
|
||||
3 4 7
|
||||
4 2 1
|
||||
5 1 2
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
=head1 NAME
|
||||
|
||||
plotpipe - Plot 1D-data or 2D-data from a pipe
|
||||
plotpipe - Plot CSV data from a pipe
|
||||
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
@ -16,6 +16,9 @@ I<datagenerator> | B<plotpipe> [-H] [-0] [-C str] [-h] [-V]
|
|||
|
||||
B<plotpipe> is a simple wrapper for GNUPlot to simply plot 1D and 2D-data.
|
||||
|
||||
The input is a CSV-file. Lines starting with '#' will be used as
|
||||
titles on the plot.
|
||||
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
|
@ -25,14 +28,17 @@ B<plotpipe> is a simple wrapper for GNUPlot to simply plot 1D and 2D-data.
|
|||
|
||||
=item B<-C> I<string>
|
||||
|
||||
Use I<string> as column separator.
|
||||
Use I<string> as column separator. B<plotpipe> will try to autoguess
|
||||
the separator. If it guesses wrong, use B<--colsep>.
|
||||
|
||||
|
||||
=item B<--header>
|
||||
|
||||
=item B<-H>
|
||||
|
||||
Ignore first line of input (it is a header).
|
||||
Use the first line as legend for data in columns. B<plotpipe> will try
|
||||
to autoguess if the first line is a header. If it guesses wrong, use
|
||||
B<--header>.
|
||||
|
||||
|
||||
=item B<--help>
|
||||
|
@ -65,10 +71,40 @@ Plot the points (1,101) .. (100,200):
|
|||
|
||||
paste <(seq 100) <(seq 101 200) | plotpipe
|
||||
|
||||
|
||||
Plot the points (1,101) .. (100,200) and (1,300) .. (100,102):
|
||||
|
||||
paste <(seq 100) <(seq 101 200) <(seq 300 -2 102) | plotpipe
|
||||
|
||||
=head1 EXAMPLE
|
||||
|
||||
input.csv:
|
||||
|
||||
#Title line 1
|
||||
#This is title line 2
|
||||
X-axis-header Values1 Values2
|
||||
1 28 32
|
||||
2 12 35
|
||||
3 3.5 3.5
|
||||
|
||||
cat input.csv | plotpipe
|
||||
|
||||
=head1 EXAMPLE
|
||||
|
||||
input.csv:
|
||||
|
||||
#Title line 1
|
||||
#This is title line 2
|
||||
X axis header,Values 1,Values 2
|
||||
1,28,32
|
||||
2,12,35
|
||||
3,3.5,3.5
|
||||
|
||||
cat input.csv | plotpipe
|
||||
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright (C) 2019 Ole Tange,
|
||||
Copyright (C) 2019-2020 Ole Tange,
|
||||
http://ole.tange.dk and Free Software Foundation, Inc.
|
||||
|
||||
|
||||
|
@ -150,25 +186,168 @@ sub help() {
|
|||
"",);
|
||||
}
|
||||
|
||||
sub shell_quote_scalar_default($) {
|
||||
# Quote for other shells (Bourne compatibles)
|
||||
# Inputs:
|
||||
# $string = string to be quoted
|
||||
# Returns:
|
||||
# $shell_quoted = string quoted as needed by the shell
|
||||
my $s = $_[0];
|
||||
if($s =~ /[^-_.+a-z0-9\/]/i) {
|
||||
$s =~ s/'/'"'"'/g; # "-quote single quotes
|
||||
$s = "'$s'"; # '-quote entire string
|
||||
$s =~ s/^''//; # Remove unneeded '' at ends
|
||||
$s =~ s/''$//; # (faster than s/^''|''$//g)
|
||||
return $s;
|
||||
} elsif ($s eq "") {
|
||||
return "''";
|
||||
} else {
|
||||
# No quoting needed
|
||||
return $s;
|
||||
}
|
||||
}
|
||||
|
||||
sub Q($) {
|
||||
return shell_quote_scalar_default($_[0]);
|
||||
}
|
||||
|
||||
sub my_dump(@) {
|
||||
# Returns:
|
||||
# ascii expression of object if Data::Dump(er) is installed
|
||||
# error code otherwise
|
||||
my @dump_this = (@_);
|
||||
eval "use Data::Dump qw(dump);";
|
||||
if ($@) {
|
||||
# Data::Dump not installed
|
||||
eval "use Data::Dumper;";
|
||||
if ($@) {
|
||||
my $err = "Neither Data::Dump nor Data::Dumper is installed\n".
|
||||
"Not dumping output\n";
|
||||
::status($err);
|
||||
return $err;
|
||||
} else {
|
||||
return Dumper(@dump_this);
|
||||
}
|
||||
} else {
|
||||
# Create a dummy Data::Dump:dump as Hans Schou sometimes has
|
||||
# it undefined
|
||||
eval "sub Data::Dump:dump {}";
|
||||
eval "use Data::Dump qw(dump);";
|
||||
return (Data::Dump::dump(@dump_this));
|
||||
}
|
||||
}
|
||||
|
||||
sub max(@) {
|
||||
# Returns:
|
||||
# Maximum value of array
|
||||
my $max;
|
||||
for (@_) {
|
||||
# Skip undefs
|
||||
defined $_ or next;
|
||||
defined $max or do { $max = $_; next; }; # Set $_ to the first non-undef
|
||||
$max = ($max > $_) ? $max : $_;
|
||||
}
|
||||
return $max;
|
||||
}
|
||||
|
||||
sub find_sep(@) {
|
||||
# Try common find the separators.
|
||||
# Do we get the same for each line?
|
||||
my @csv = grep { not /^#/ } @_;
|
||||
my @sep = (",", "\t", ";", '\s+');
|
||||
my $columns;
|
||||
my %col;
|
||||
for my $sep (@sep) {
|
||||
for my $line (@csv) {
|
||||
$columns = split /$sep/, $line;
|
||||
if($columns > 1) {
|
||||
$col{$sep."\0".$columns}++
|
||||
}
|
||||
}
|
||||
}
|
||||
# Find max $col{$sep,$columns}
|
||||
my $most_lines = max(values %col);
|
||||
|
||||
my %sepcol = (map { split /\0/, $_ }
|
||||
grep { $col{$_} == $most_lines } keys %col);
|
||||
my $most_cols = max(values %sepcol);
|
||||
return ((grep { $sepcol{$_} == $most_cols } keys %sepcol)[0]);
|
||||
}
|
||||
|
||||
|
||||
Getopt::Long::Configure("bundling","require_order");
|
||||
my $retval = GetOptions(options_hash());
|
||||
$Global::progname = "plotpipe";
|
||||
$Global::version = 20200810;
|
||||
$Global::version = 20201005;
|
||||
if($opt::version) { version(); exit 0; }
|
||||
if($opt::help) { help(); exit 0; }
|
||||
if($opt::null) { $/ = "\0"; }
|
||||
$opt::colsep ||= '\s+';
|
||||
if($opt::header) { <> }
|
||||
$line1 = <>;
|
||||
@col = split /$opt::colsep/, $line1;
|
||||
if($#col == 1) {
|
||||
# 2 col
|
||||
open GNUPLOT,"|-", q(gnuplot -p -e 'plot "/dev/stdin"') or die;
|
||||
} elsif($#col == 2) {
|
||||
# 3 col (3rd = color)
|
||||
open GNUPLOT,"|-", q(gnuplot -p -e 'plot "/dev/stdin" using 1:2:3 with points palette') or die;
|
||||
} else {
|
||||
die "$#col,@col,$line1";
|
||||
|
||||
# Read csv
|
||||
my @csv = <>;
|
||||
|
||||
# Title = lines starting with #
|
||||
my @title = map { s/^#//; s/"/''/g; $_ } map { "$_" } grep { /^#/ } @csv;
|
||||
if(@title) { chomp($title[$#title]); }
|
||||
@csv = grep { not /^#/ } @csv;
|
||||
|
||||
# Autoguess separator
|
||||
if(not defined $opt::colsep) {
|
||||
$opt::colsep = find_sep(@csv);
|
||||
}
|
||||
print GNUPLOT $line1, <>;
|
||||
|
||||
# Autoguess header
|
||||
my @header;
|
||||
if(not defined $opt::header) {
|
||||
# Autodetect header
|
||||
# if line 1 contains a-z => header
|
||||
@header = split /$opt::colsep/, $csv[0];
|
||||
for(@header) {
|
||||
if(/[a-z]/i) { $opt::header = 1; last; }
|
||||
}
|
||||
}
|
||||
if($opt::header) {
|
||||
@header = split /$opt::colsep/, $csv[0];
|
||||
chomp(@header);
|
||||
shift @csv;
|
||||
} else {
|
||||
@header = ();
|
||||
}
|
||||
|
||||
# Save data to tmpfile that will be read by Gnuplot
|
||||
use File::Temp qw(tempfile);
|
||||
$ENV{'TMPDIR'} ||= "/tmp";
|
||||
my($filehandle,$filename) =
|
||||
tempfile(DIR=>$ENV{'TMPDIR'}, TEMPLATE => 'plotXXXXX');
|
||||
for(@csv) {
|
||||
chomp;
|
||||
# print((join "\t", split /$opt::colsep/, $_),"\n");
|
||||
print $filehandle ((join "\001", split /$opt::colsep/, $_),"\n");
|
||||
}
|
||||
|
||||
# Generate the variant part of Gnuplot script
|
||||
my $ncols = split /$opt::colsep/, $csv[0];
|
||||
for(my $col = 2; $col <= $ncols; $col++) {
|
||||
my $legend;
|
||||
if($opt::header) {
|
||||
$legend = qq( title "$header[$col-1]");
|
||||
}
|
||||
push @plotscript, qq("$filename" using 1:$col with lines $legend,);
|
||||
}
|
||||
|
||||
# Make full Gnuplot script
|
||||
my $plotscript=<<_EOS ;
|
||||
set title "@title";
|
||||
set xtics rotate;
|
||||
set autoscale;
|
||||
set xlabel "$header[0]";
|
||||
set grid;
|
||||
set key right center;
|
||||
set datafile separator "\001";
|
||||
plot @plotscript
|
||||
_EOS
|
||||
|
||||
open GNUPLOT,"|-", "gnuplot -p -e ".Q($plotscript) or die;
|
||||
# print "gnuplot -p -e ".($plotscript);
|
||||
close GNUPLOT;
|
||||
unlink $filename;
|
||||
|
|
Loading…
Reference in a new issue