#!/bin/sh
version=3.2.1
license="Copyright (C) 1996-2009, 2011-2012 Dimitar Ivanov

License: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law."
#set -vx
################################################################################
#
# muplot - gnuplot-wrapper for non-interactive plotting of multiple data files
#
# This program allows multiple data files to be viewed or printed by 'gnuplot'
# on a single multi-curve plot.
#
################################################################################
#
# 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 3 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, see <http://www.gnu.org/licenses/>.
#
################################################################################
#
# Variables
#
progname=`basename $0`
cmdstr="$progname $*"
bfname="$progname"                       # Default output-base-file-name
gpe="$bfname.err"                        # Temporary error file
tmpfile="$progname.$$"                   # Temporary work file
tmpstdin="$tmpfile.stdin"                # Temporary stdin
set_file=".${progname}set"               # External file with gnuplot commands
comm_file="$set_file"
comm_file_ignore="$comm_file.noglobal"   # Ignore global command file if touched
gdevice="x11"                            # Output graphics device (default X11)
out_form="unknown"                       # PS, PNG, JPG ok - for PDF see help
psfont='"Times-Roman"'                   # PS font type
psfontsize=20                            # PS font size
pscolor=color                            # PS color
stdout=no                                # Do not send PS file to stdout
quiet=no                                 # Don't suppress info messages
opt_landscape=""                         # Landscape option for a plot viewer

################################################################################
#
# Functions
#

### Read external file with gnuplot commands and print out used blocks
#
_guplot_command_print_blocks_()
{
   comm_file=$1
   mode=$2
   if [ -f $comm_file ]; then
       if   [ $mode -gt 0 ]; then
            cat $comm_file |sed -n '/^#BEGIN/,/^#END/!p'
       elif [ -n "`grep '^#BEGIN' $comm_file`" ]; then
              # print everything btw. BEGIN and END
            echo "set out $fnstr"
            cat $comm_file |sed -n '/^#BEGIN/,/^#END/p'
            echo "replot"
       fi
  fi
}

### Read gnuplot commands from file
#
_gnuplot_commands_read_file_()
{
  part=$1
  test ! -f "$comm_file_ignore" \
     && _guplot_command_print_blocks_ "$HOME/$set_file" $part
  test "`pwd`" != "$HOME" \
     && test "$ignore_local_comm" != "yes" \
     && _guplot_command_print_blocks_ "$set_file" $part
}

### Prepare list of files to be plotted
#
_prepare_list_of_files_to_plot_()
{
   test -z "$1"  && return 1

      # Last sanity check of the cmdline file-list string syntax:
   if [ "`echo $1 |sed 's/^-..*/BAD/'`" = BAD ]; then
        echo "Error: your file name can't start with '-'"
        _clean_up_ 9
   fi
      # If input is not defined as the stdin, then evaluate file list
   if [ "x$1" = "x-" ]; then
        files="-"
        LIST_FILES="ls -1 $tmpstdin"
   else
        files=`eval ls "$1"` || _clean_up_ 10
   fi
      # If a single file, then basename is defined using it
   if   [ `ls -1 "$files" 2>/dev/null |wc -l` -eq 1 ]; then
          LIST_FILES="echo $files"
             # Define bfname only once
          if [ x$list_nr = x ]; then
               bfname="$files"
                  # Remove extension from name
               bfname=`$LIST_FILES |sed "s/\.[^\.]*$//"`
          fi
   elif [ "x$files" != "x-" ]; then
          LIST_FILES="ls -1 $files"
   fi
   list_nr=`expr $list_n + 1`
   return 0
}

### Define output file names
#
_define_output_file_names_()
{
  [ "x$ofname" != x ] && bfname=$ofname
  if   [ $out_form != "unknown" ]; then
       ofile="$bfname.$out_form"          # Output file name
       fnstr="'$bfname.$out_form'"        # Gnuplot file name string
  fi
  if [ $stdout = yes ]; then
       bfname=$tmpfile                    # Write to STDOUT - use tmp file
       ofile="$bfname.ps"
       fnstr="'$bfname.ps'"
  fi
}

### Define output driver
#
_define_output_driver_()
{
  case $1 in
      ps) gdevice="postscript enh $pscolor $psfont $psfontsize"
       ;;
     png) gdevice="png $termopt"
       ;;
     jpg) gdevice="jpeg $termopt"
       ;;
     pdf) gdevice="pdfcairo"
       ;;
       *) gdevice=x11
       ;;
  esac
}

### Determine the plot style
#
_define_plot_style_()
{
   # Determine the plot style first
    case $1 in
       l|g) style=lines
         ;;
         d) style=dots
         ;;
         p) style=points
         ;;
        pp) style="points 1 6"
         ;;
         b) style=boxes
         ;;
         e) style=errorlines
            sample=1:2:3
         ;;
         a) style=dots
               # Data prepared by 'prefield' - default delimiter of cut is a TAB
            if [ "x$files" != "x-" ]; then # Input from file(s)
                 cut -f3- $files
            else                           # Make a copy of stdin
                 tee $tmpstdin |cut -f3-
            fi
            echo "set nokey"
         ;;
       u=*) style="`echo $1 |cut -f2 -d=`"
         ;;
         *) style=linespoints
               # Format in case of date/time data
            if [ x`echo $1 |grep "dt\="` != x ]; then
               echo "set xdata time"
               fmt=`echo $1 |cut -f2 -d= | cut -f1 -d@`
               fmtx=`echo $1 |cut -f2 -d= |cut -f2 -d@`
               echo "set timefmt '$fmt'"
               echo "set format x '$fmtx'"
               sample=1:2
            fi
         ;;
    esac
}

### Print gnuplot command(s) if specified from command line option
#
_print_gnuplot_cmdl_command_()
{
  cat << EOFC
set term $gdevice
set out $fnstr
$1
EOFC
}

### Find out samples to plot
#
_tell_me_samples_to_plot_()
{
  if [ -n "$1" ]; then
       lsample="$1"
       lsample=`echo $lsample |tr ',' '\040'` # Separate ranges by blanks
       rc=0
  else
       if [ -z "$sample" ]; then
            lsample="0:1-0"
       else
            lsample="$sample"
       fi
       rc=1
  fi
  echo $lsample
  return $rc
}

### Loop for multiple data ranges applied to the current set of files and
#   print out plot statements
#
_plot_various_data_ranges_()
{
  _sample=$1
  test -z "$_plot" && _plot=plot
  lastabsc=`echo $_sample |cut -f1 -d:`

  i=1
  for j in $_sample
  do   # DATA_RANGE begin
     absc=`echo $j |cut -f1 -d:`
     first=`echo $j |cut -f1 -d- |cut -f2 -d:`
     last=`echo $j |cut -f2 -d-`
     [ "$last" = "$j" ] && last=$first
     [ "$absc" = "$j" -a "$i" -eq 1 ] && absc=0
     [ "$absc" = "$j" -o "$absc" = "$first" ] && absc=$lastabsc
     lastabsc=$absc
        # In case of styles that need 3 data columns
     echo $j |egrep "^[0-9]+:[0-9]+:[0-9]+$" >/dev/null 2>&1 && absc=$j

        # FILE_LOOP: prepare and print plot statements for all files in the list
     $LIST_FILES \
     |$awk -v pl=$_plot -v sty="$style" -v a=$absc -v f=$first -v l=$last \
             'BEGIN { printf("%s ", pl) }
                { 
                  for( i=f; i<=l; i++ )
                  {
                        # In caces with 3-data columns
                     if( a ~ /^[0-9]+:[0-9]+:[0-9]+$/ )
                         axes=a
                     else
                         axes=sprintf("%d:%d", a, i)
                     printf("\"%s\" using %s with %s, ", $0, axes, sty)
                  }
                  if( l==0 && l!=f )
                      printf("\"%s\" with %s, ", $0, sty)
                }
              END { printf "\n" }' > $tmpfile

     echo "set term $gdevice"
     echo "set out $fnstr"

        # Paste the script saved in the tmpfile - remove last coma followed
        # by blank before end of line (have been excessively produced in the
        # FILE_LOOP)
     cat $tmpfile |sed 's/\, $//'

        # Plots after the first one must be re-plotted
     _plot=replot
     i=`expr $i + 1`
  done # DATA_RANGE end
}

### Print gnuplot script file with on terminal
#
_gnuplot_script_print_to_terminal_()
{
  if [ $stdout != yes -a $quiet != yes ]; then
       echo "### Your gnuplot script:"
       cat "$1" |grep -v "\#"
       echo ""
  fi
}

### Print gnuplot script errors
#
_gnuplot_errors_print_to_terminal_()
{
  echo "" 1>&2
  echo "### Gnuplot ERRORS:" 1>&2
  cat "$1" 1>&2
}

### Ask to display plot
#
_ask_to_display_plot_()
{
   _ofile="$1"
   _viewer="$2"

   echo "# Show picture? [y/N]"
   read answer
   if [ "$answer" = y -o "$answer" = Y ]; then
           # Consider the first word in the string to be the executable,
           # the rest are options
        viewer=`echo $_viewer |cut -f1 -d' '`
        if [ ! "`_find_exec_in_path_ $viewer`" ]; then
             echo "Warn: '$viewer' is not installed or is not in your \$PATH"
        else
             $_viewer $opt_landscape "$_ofile" &
        fi
   fi
}

### Check whether an executable program is in the path
#
_find_exec_in_path_()
{
  which $1 2>&1 |grep "^/"
}

### Look for GhostView installation
#
_look_for_ghostview_()
{
  MUPLOT_VIEWER="`_find_exec_in_path_ gv`"
  if [ ! "$MUPLOT_VIEWER" ]; then
       MUPLOT_VIEWER=ghostview
  fi

     # For better view, show PS-plots in landscape orientation
  opt_landscape="-landscape"

  if [ "`_find_exec_in_path_ $MUPLOT_VIEWER`" ]; then
       test -n "$opt_landscape" \
            && $MUPLOT_VIEWER $opt_landscape /dev/null 2>&1 \
               |grep "orientation=" > $gpe
       test -s $gpe && opt_landscape="--orientation=landscape"
  fi
}

### Print the raw plot to stdout
#
_print_raw_plot_()
{
   _ofile="$1"

   [ $out_form = ps ] \
     && cat $_ofile |sed "s;%%Title:.*;%%Title: $cmdstr;" \
     || cat $_ofile
}

### Prepare to remove various files after work finished and set a clean trap
#
_clean_up_()
{
   [ ! -s "$ofile" -o $stdout = yes ] && rm -f "$ofile" # Remove if zero size
   rm -f "$bfname.gpt" $gpe $tmpfile $tmpstdin
   exit $1
}
trap '_clean_up_ $1' 1 2 3 6 15

### Show usage
#
_show_usage_()
{
d=$2
cat << END_HELP
$separator
$1
$separator

Usage: $progname [OPTION]... [STYLE] [FILE] [AXES] [FILE] [AXES] ...

Options:
   --help|-H $d display help
   --version $d output version and license message
   -h        $d display short help
   -V        $d print program version number
   -s        $d create PostScript file
   -S        $d send PostScript output to STDOUT (the same as '-s -o -')
   -n        $d create PNG file
   -j        $d create JPEG file
   -p        $d create PDF file (requires the gnuplot "pdfcairo" driver)
   -c <cmd>  $d execute gnuplot command(s) (the default plot style is used)
   -m        $d monochrome plot (valid only for PostScript)
   -l        $d set plot size to 800x600 (valid for PNG and JPEG)
   -o        $d base name of the output file
   -q        $d quiet mode (all messages except errors to be suppressed)
   -i        $d ignore local command file './$comm_file'
   -I <file> $d specify an alternative command file instead of './$comm_file'

Styles:
   l         $d lines
   p         $d points
   lp        $d lines and points (default)
   pp        $d circle points
   d         $d dots
   b         $d boxes
   g         $d grid
   e         $d errorbars - default used columns are 1:2:3 (x:y:yerror)
   a         $d fields with arrows;
               The data file has a special format in this case. Use 'prefield'
               to prepare such data files.
   dt=<fmt>  $d date/time series with the specified format;
               For example: dt="%H:%M.%S@%H:%M" where the first part, in front
               of "@", defines the data format, and the second part defines the
               format that will be used for tic labels. Here, hours and minutes
               are separated by \`:', respectively minutes and seconds by \`.'
               Another example could be a date: dt="%Y-%m-%d".
   u=<fmt>   $d user specified format as defined in Gnuplot
   
Axes:
   x:y,x:y-z $d columns in the file defining the x/y-axes of the curve(s);
               Default are 1:2 or 1:2:3 for data with errors. In case that only
               one column is provided the default axes are 0:1 - the x-axis
               will be a simple index then.


File(s) could be a single file name whereas '-' means <stdin>, many files
enclosed in '' or "" like "file1 file2 file3", or any valid shell pattern
as for example "*.dat". The files '\$HOME/$comm_file' and './$comm_file', if
existing, will be included at the beginning of the gnuplot script. The command
block between "#BEGIN" and "#END" in those files will be pasted to the end of
the script. If you want that the global '\$HOME/$comm_file' is ignored, create
in your local directory a file named '$comm_file_ignore'. In case you want
to view the output, define the env variable MUPLOT_VIEWER and export it,
for example:

   MUPLOT_VIEWER="xpdf -z page"; export MUPLOT_VIEWER

Then the program will prompt you to view the plot, and after confirmation the
viewer will present the graphics. If the postscript file format is chosen
('-s' option), and MUPLOT_VIEWER is not defined, the viewer is preset to 'gv',
and per default you are prompted to view the output. To disable this behavior,
set MUPLOT_VIEWER="".


Examples:

1) On X-terminal view a multi-curve plot of data files with extension 'dat'

   $progname l "*.dat"

2) Print a sinus curve in black-and-white color on a PostScript printer

   $progname -m -S -c "set title 'Function f(x)=sin(x)'; plot sin(x);" | lpr

3) Plot data from file "example.dat" using columns 1:2, 3:4, and 3:5 as x/y-axes in the multi-curve plot; a PostScript file with the name "example.ps" is automatically created.

   $progname -s lp example.dat 1:2,3:4-5

4) Create graphics in PDF format reading data from file "example.1.dat" (columns 1:2), and from file "example.2.dat" (columns 3:4)

   $progname -p lp example.1.dat 1:2 example.2.dat 3:4

5) View data where the third column is a date of the form 'yyyy-mm-dd'

   cat example_counts_per_day.dat | $progname dt="%Y-%m-%d" - 3:1


Report bugs to <gnu@mirendom.net>

END_HELP

}

################################################################################
#
# Help
#

if [ $# -lt 2 ]; then
       # Print out version number and license and exit
     if [ x$1 = x--version ]; then
          cat << _VERSION_
$progname $version
$license
_VERSION_
exit 0
     fi

       # Print out version number and exit
     if [ "x$1" = x-V ]; then
        echo $version
        exit 0
     fi

       # Display help
     if [ "x$1" = x--help -o "x$1" = x-H ]; then
          separator=""
          header_text="Muplot is a simple, non-interactive gnuplot-wrapper to plot a multi-curve figure from multiple data (files). It can produce PostScript, PDF, PNG or JPEG output file formats."
             # Print out usage
          _show_usage_ "$header_text" " "
     else
          separator=`echo |awk '{printf( "%080s", 0 )}' |tr 0 -`
          header_text="$progname $version: plot a multi-curve figure from multiple data by using Gnuplot"
             # Print out short help and exit
          _show_usage_ "$header_text" "-" \
               |egrep "^($progname|Usage:|Options:|Styles:|Axes:|.*--|.*[\ >]\ -\ |.*\,x:y-z)"
     fi

exit 0
fi

################################################################################
#
# MAIN
#

   # We need an AWK supporting assignments
exec 3>&2 2>&-
for a in gawk nawk awk
do
  [ "`set +x; echo |$a -v a=a '{}' 2>&1`" = "" ] && awk=$a
done
exec 2>&3
[ x"$awk" = x ] && \
  echo "Error: can't find awk supporting assignments" && \
  exit 12

   # Check whether gnuplot is available
[ ! `which gnuplot 2>&1 |grep "^/"` ] \
    && echo 'Gnuplot is not installed or is not in your $PATH' \
    && _clean_up_ 7

   # Process cmdline options
while [ 0 ]
do
case $1 in
    -s) out_form=ps
        [ "`env |grep ^MUPLOT_VIEWER`" ] || _look_for_ghostview_
     ;;
    -S) out_form=ps
        stdout=yes
     ;;
    -n) out_form=png
     ;;
    -j) out_form=jpg
     ;;
    -p) out_form=pdf
     ;;
    -c) gpt_command="$2"
        shift
     ;;
    -l) termopt="large size 800,600"
     ;;
    -m) pscolor=monochrome
     ;;
    -o) [ "x$2" != "x-" ] \
           && ofname=$2 \
           || { stdout=yes; quiet=yes ;}
        shift
     ;;
    -q) quiet=yes
     ;;
    -i) ignore_local_comm=yes
     ;;
    -I) set_file="$2"
        shift
        [ ! -e "$set_file" ] \
          && echo "$progname: no such file '$set_file'" \
          && exit 13
     ;;
    -*) exec 1>&2
        echo "$progname: invalid option '$1'"
        echo "Try \`$progname -H' for help."
        exit 8
     ;;
     *) break
     ;;
esac
shift
done

   # In case of gnuplot command '-c' you don't need a file name
if [ -z "$gpt_command" ]; then
     _prepare_list_of_files_to_plot_ "$2" && data_file_set=defined
fi

   # Define output driver
_define_output_driver_ $out_form

   # Define output file names
_define_output_file_names_

   # Create script file for gnuplot
echo "set notime" > "$bfname.gpt"

   # Check for grid
[ "$1" = g ] && echo "set grid" >> "$bfname.gpt"

   # Read gnuplot commands from file - outside #BEGIN ... #END block
_gnuplot_commands_read_file_ 1 >> "$bfname.gpt"

   # Check for native gnuplot command(s) specified
if [ -n "$gpt_command" ]; then
      _print_gnuplot_cmdl_command_ "$gpt_command" >> "$bfname.gpt"
else
   # Determine the plot style and process the list of files
     _define_plot_style_ $1 >> "$bfname.gpt"
     shift

        # Loop over multiple file sets
     while [ $data_file_set ]
     do    # FILE_SET begin
        shift
           # If input is piped in, then start using the temporary file
        [ "x$files" = "x-" ] && cat ->> $tmpstdin && exec <&1
   
           # Check for samples and ranges
        sample=`_tell_me_samples_to_plot_ "$1"` \
        && shift

           # Look for multiple data ranges and print out plot commands
        _plot_various_data_ranges_ "$sample" >> "$bfname.gpt"

           # Look for next data file set and prepare a list
        _prepare_list_of_files_to_plot_ "$1" || data_file_set=""

     done  # FILE_SET end
fi

   # Read gnuplot commands from file - inside #BEGIN ... #END block
_gnuplot_commands_read_file_ 0 >> "$bfname.gpt"

   # "pause" if the terminal is X11
[ "$gdevice" = x11 ] && echo "pause -1" >> "$bfname.gpt"

   # Execute GNUPLOT
gnuplot "$bfname.gpt" > $gpe 2>&1

   # Print out the gnuplot script
_gnuplot_script_print_to_terminal_ "$bfname.gpt"

   # If gnuplot failed with errors, report them and exit
if [ -s "$gpe" ]; then
     _gnuplot_errors_print_to_terminal_ "$gpe"
     _clean_up_ 11
fi

if [ $stdout = yes ]; then
        # Print the raw plot to STDOUT if chosen
     _print_raw_plot_ "$ofile"
else
        # Print the name of the output file
     if [ -n "$fnstr" -a $quiet != yes ]; then
          echo "# Your plot file is $fnstr."
     fi
        # Ask user whether he wants to view the plot
     if [ $quiet != yes -a "$gdevice" != x11 ]; then
          if [ -n "$MUPLOT_VIEWER" ] ;then
               _ask_to_display_plot_ "$ofile" "$MUPLOT_VIEWER"
          fi
     fi
fi

_clean_up_ 0
