require 'matrix'
# Methods and variables for interacting with the gnuplot process. Most of
# these methods are for sending data to a gnuplot process, not for reading from
# it. Most of the methods are implemented as added methods to the built in
# classes.
module Gnuplot
# Trivial implementation of the which command that uses the PATH environment
# variable to attempt to find the given application. The application must
# be executable and reside in one of the directories in the PATH environment
# to be found. The first match that is found will be returned.
#
# bin [String] The name of the executable to search for.
#
# Return the full path to the first match or nil if no match is found.
#
def Gnuplot.which ( bin )
return bin if File::executable? bin
path = ENV['PATH'] # || ENV['WHAT_EVER_WINDOWS_PATH_VAR_IS']
path.split(File::PATH_SEPARATOR).each do |dir|
candidate = File::join dir, bin.strip
return candidate if File::executable? candidate
end
# This is an implementation that works when the which command is
# available.
#
# IO.popen("which #{bin}") { |io| return io.readline.chomp }
return nil
end
# Find the path to the gnuplot executable. The name of the executable can
# be specified using the RB_GNUPLOT environment variable but will default to
# the command 'gnuplot'.
#
# persist [bool] Add the persist flag to the gnuplot executable
#
# Return the path to the gnuplot executable or nil if one cannot be found.
def Gnuplot.gnuplot( persist = true )
cmd = which( ENV['RB_GNUPLOT'] || 'gnuplot' )
#cmd = "gnuplot"
cmd += " -background white"
cmd += " -persist" if persist
cmd
end
# Open a gnuplot process that exists in the current PATH. If the persist
# flag is true then the -persist flag is added to the command line. The
# path to the gnuplot executable is determined using the 'which' command.
#
# See the gnuplot documentation for information on the persist flag.
#
# todo Add a method to pass the gnuplot path to the function.
def Gnuplot.open( persist=true )
cmd = Gnuplot.gnuplot( persist ) or raise 'gnuplot not found'
#File.open(".gptemp#{Process.pid}", 'w'){|f| yield f}
#system "#{cmd} .gptemp#{Process.pid}"
#FileUtils.rm ".gptemp#{Process.pid}"
if $debug_gnuplot
#raise "HEELOOE"
yield(STDOUT)
else
IO::popen( cmd, "w") { |io| yield io }
end
end
end
class GraphKit
GNUPLOT_DEFAULT_TERM=ENV['GRAPHKIT_TERM'] || "x11"
GNUPLOT_DEFAULT_COLOURS = {0 => "#df0000", 1 => "#00df00", 2 => "#0000df", 3 => "#a000a0", 4 => "#0090a0", 5 => "#e59500", 6 => "#82c290", 7 => "#f76dba", 8 => "#c20f00", 9 => "#4f1099"}
class GnuplotVariables < KitHash
def apply(io)
self.each do |var,val|
ion << "#{var} = #{val}\n" if val
end
end
end
class GnuplotSetOptions < KitHash
attr_accessor :multiplot_following
alias :hash_key :key
undef :key
QUOTED = [ "title", "output", "xlabel", "ylabel", "zlabel", "x2label", "y2label", "z2label" ]
GNUPLOT_SETS = %w[ dgrid3d
angles arrow autoscale bars
bmargin border boxwidth cbdata
cbdtics cblabel cbmtics cbrange
cbtics clabel clip cntrparam
colorbox contour data datafile
date_specifiers decimalsign dummy
encoding fit fontpath format
function grid hidden3d historysize
isosamples key label lmargin
loadpath locale log logscale
macros mapping margin missing
mouse mx2tics mxtics
my2tics mytics mztics object
nosurface
offsets origin output palette
parametric pm3d pointsize polar
print rmargin rrange samples
size style surface table
term terminal termoption tics
ticscale ticslevel time_specifiers timefmt
timestamp title tmargin trange
urange view vrange x2data
x2dtics x2label x2mtics x2range
x2tics x2zeroaxis xdata xdtics
xlabel xmtics xrange xtics
xyplane xzeroaxis y2data y2dtics
y2label y2mtics y2range y2tics
y2zeroaxis ydata ydtics ylabel
ymtics yrange ytics yzeroaxis
zdata zdtics zero zeroaxis
zlabel zmtics zrange ztics
multiplot ].map{|s| s.to_sym}
# p instance_methods.sort
GNUPLOT_SETS.each do |opt|
define_method(opt + "=".to_sym) do |str|
check(["Class of #{str} supplied to #{opt}", str.class, [Array, String, FalseClass, NilClass]])
self[opt] = str
end
define_method(opt) do
self[opt]
end
end
def []=(opt, val)
raise "#{opt} is not a valid gnuplot set option" unless GNUPLOT_SETS.include? opt
super
end
def apply(io)
io << "set term #{GNUPLOT_DEFAULT_TERM}\n" unless self[:term] or self.multiplot_following
self.each do |var,val|
next unless val
next if self.multiplot_following and ["term", "output"].include? var.to_s
next if var.to_s == "multiplot"
apply_option(io, var, val)
end
apply_option(io, :multiplot, self[:multiplot]) if self[:multiplot]
end
def apply_option(io, var, val)
if val == "unset"
#eputs "Unsetting #{var}"
io << "unset #{var}\n"
return
end
if var.to_s == 'log_axis'
var = 'log'
end
if val.kind_of? Array
val.each do |vall|
io << "set #{var} #{vall}\n"
end
elsif QUOTED.include? var.to_s and not val =~ /^\s*'.*'/
#ep "quoting #{var}: #{val}"
io << "set #{var} '#{val}'\n"
else
io << "set #{var} #{val}\n"
end
end
end
class GnuplotPlotOptions < KitHash
QUOTED = ["title"]
GNUPLOT_SETS = %w[ function using axes title with ].map{|s| s.to_sym}
#%w[
#acsplines axes bezier binary
#csplines cumulative datafile errorbars
#errorlines every example frequency
#index iteration kdensity matrix
#parametric ranges sbezier smooth
#special-filenames style thru title
#unique using with].map{|s| s.to_sym}
# p instance_methods.sort
GNUPLOT_SETS.each do |opt|
define_method(opt + "=".to_sym) do |str|
check(["Class of #{str} supplied to #{opt}", str.class, [Array, String, FalseClass, NilClass]])
self[opt] = str
end
define_method(opt) do
self[opt]
end
end
def []=(opt, val)
raise "#{opt} is not a valid gnuplot set option" unless GNUPLOT_SETS.include? opt
super
end
def apply(io)
self[:function] ||= "'-'"
GNUPLOT_SETS.each do |var|
val = send(var)
next unless val
case var
when :function
io << " #{val} "
when :title
io << "#{var} '#{val}'"
else
if QUOTED.include? var.to_s and not val =~ Regexp.quoted_string
io << "#{var} '#{val}' "
else
io << "#{var} #{val} "
end
end
end
end
end
def gnuplot_sets
# gnuplot_options included for back. comp
self[:gnuplot_sets] ||= @gnuplot_options || GnuplotSetOptions.new
self[:gnuplot_sets]
end
alias :gp :gnuplot_sets
def gnuplot_variables
@gnuplot_variables ||= GnuplotVariables.new
end
alias :gv :gnuplot_variables
# Modify the graphkit according to the options hash
def apply_gnuplot_options(options)
options = options.dup # No reason to modify the original hash
logf :gnuplot
processes = %x[ps | grep 'gnuplot'].scan(/^\s*\d+/).map{|match| match.to_i}
if options[:outlier_tolerance]
raise "Can only get rid of outliers for 1D or 2D data" if naxes > 2
data.each do |datakit|
datakit.outlier_tolerance = options[:outlier_tolerance]
# datakit.calculate_outliers
datakit.exclude_outliers
end
options.delete(:outlier_tolerance)
end
self.live = options[:live]
options.delete(:live) if live
if (self.view =~ /map/ or self.pm3d =~ /map/) and naxes < 4
self.cbrange ||= self.zrange
options[:cbrange] ||= options[:zrange] if options[:zrange]
end
if options[:eval]
eval(options[:eval])
options.delete(:eval)
end
options.each do |k,v|
# ep option, val if option == :xrange
# ep k, v
set(k, v)
end
end
def apply_graphkit_standard_options_to_gnuplot
[:label, :range].each do |property|
(AXES - [:f]).each do |axis|
option = axis + property
val = self.send(option)
if val
if property == :range
val = "[#{val[0]}:#{val[1]}]"
end
gp.set(option, val)
end
end
end
[:title].each do |option|
val = send(option)
gp.set(option, val) if val
end
end
private :apply_graphkit_standard_options_to_gnuplot
def gnuplot(options={})
apply_gnuplot_options(options)
apply_graphkit_standard_options_to_gnuplot
check_integrity
if options[:io]
send_gnuplot_commands(io)
else
Gnuplot.open(true) do |io|
send_gnuplot_commands(io)
(STDIN.gets) if live
end
end
end
def send_gnuplot_commands(io)
self.pid = io.pid
gnuplot_sets.apply(io)
gnuplot_variables.apply(io)
case naxes
when 1,2
io << "plot "
when 3,4
io << "splot "
end
imax = data.size - 1
data.each_with_index do |dk,i|
next if i>0 and compress_datakits
dk.gnuplot_plot_options.with ||= dk.with #b.c.
dk.gnuplot_plot_options.title ||= dk.title #b.c.
dk.gnuplot_plot_options.apply(io)
#p 'imax', imax, i, i == imax
next if compress_datakits
io << ", " unless i == imax
end
io << "\n"
data.each_with_index do |dk,i|
dk.gnuplot(io)
unless compress_datakits and i err
puts err, pid
end
self.pid = nil
end
class DataKit
def gnuplot_plot_options
self[:gnuplot_plot_options] ||= GnuplotPlotOptions.new
end
alias :gp :gnuplot_plot_options
class TensorArray
def initialize(arr)
@arr=arr
end
def [](*args)
args.reverse.inject(@arr) do |arr,idx|
arr[idx]
end
end
end
def gnuplot(io)
axs = self.axes.values_at(*AXES).compact
#ep 'axs', axs
dl = data_length = axs[-1].shape.product
dat = axs.map{|ax| ax.data}
sh = shapes
cml_sh = sh.map do |sh|
cml = 1
sh.reverse.map{|dim| cml *= dim; cml}.reverse
end
dat = dat.map do |d|
d.kind_of?(Array) ? TensorArray.new(d) : d
end
if self.errors
raise "Errors can only be plotted for 1D or 2D data" unless ranks == [1] or ranks == [1,1]
edat = self.errors.values_at(:x, :xmin, :xmax, :y, :ymin, :ymax).compact
#ep 'edat', edat
end
case ranks
when [1], [1,1], [1,1,1], [1,1,1,1]
dl.times do |n|
dat.each{|d| io << d[n] << " "}
io << " " << edat.map{|e| e[n].to_s}.join(" ") if self.errors
io << "\n"
end
when [1,1,2]
sh[-1][0].times do |i|
sh[-1][1].times do |j|
next unless dat[2][i,j]
d = [dat[0][i], dat[1][j], dat[2][i,j]]
d.each{|dt| io << dt << " "}
io << "\n"
end
io << "\n" unless sh[-1][1] == 1
end
when [2,2,2]
sh[-1][0].times do |i|
sh[-1][1].times do |j|
next unless dat[2][i,j]
d = [dat[0][i,j], dat[1][i,j], dat[2][i,j]]
d.each{|dt| io << dt << " "}
io << "\n"
end
io << "\n" unless sh[-1][1] == 1
end
when [1,1,2,2]
sh[-1][0].times do |i|
sh[-1][1].times do |j|
next unless dat[3][i,j]
d = [dat[0][i], dat[1][j], dat[2][i,j], dat[3][i,j]]
d.each{|dt| io << dt << " "}
io << "\n"
end
io << "\n" unless sh[-1][1] == 1
end
when [1,1,1,3]
sh[-1][0].times do |i|
sh[-1][1].times do |j|
sh[-1][2].times do |k|
next unless dat[3][i,j,k]
d = [dat[0][i], dat[1][j], dat[2][k], dat[3][i,j,k]]
d.each{|dt| io << dt << " "}
io << "\n"
end
io << "\n" unless sh[-1][2] == 1
end
io << "\n" unless sh[-1][1] == 1
end
when [2,2,2,2]
sh[-1][0].times do |i|
sh[-1][1].times do |j|
next unless dat[3][i,j]
d = [dat[0][i,j], dat[1][i,j], dat[2][i,j], dat[3][i,j]]
d.each{|dt| io << dt << " "}
io << "\n"
end
io << "\n" unless sh[-1][1] == 1
end
when [3,3,3,3]
#pp dat
#pp dat
#pp sh
sh[-1][0].times do |i|
sh[-1][1].times do |j|
sh[-1][2].times do |k|
next unless dat[3][i,j,k]
#p [i,j,k]
#d = [dat[0][i,j,k], dat[1][i,j,k], dat[2][i,j,k], dat[3][i,j,k]]
io << "#{dat[0][i,j,k]} #{dat[1][i,j,k]} #{dat[2][i,j,k]} #{dat[3][i,j,k]} \n"
#d.each{|dt| io << dt << " "}
#io << "\n"
end
io << "\n" unless sh[-1][2] == 1
end
io << "\n" unless sh[-1][1] == 1
end
end
end
end
def gnuplot_write(file_name, options={})
logf :gnuplot_write
old_gp_term = gp.term
old_gp_output = gp.output
if file_name
gp.output = file_name
unless gp.term or options[:terminal]
case File.extname(file_name)
when '.pdf'
gp.term = 'pdf size 20cm,15cm'
when '.ps'
gp.term = 'post color'
when '.eps'
unless options[:latex]
gp.term = %[post eps color enhanced size #{options[:size] or "3.5in,2.33in"}]
else
gp.term ||= "epslatex color dashed size #{options[:size] or "3.5in,#{options[:height] or "2.0in"}"} colortext standalone 8"
(gp.term += " header '#{options[:preamble].inspect.gsub(/\\\n/, "\\\\\\n")}'"; options.delete(:preamble)) if options[:preamble]
end
when '.jpg'
gp.term = "jpeg size #{options[:size] or "3.5in,2.33in"}"
when '.png'
gp.term = "png size #{options[:size] or "640,480"}"
when '.gif'
gp.term = "gif size #{options[:size] or "640,480"}"
end
end
end
gp.output = file_name.sub(/\.eps/, '.tex') if options[:latex]
options.delete(:size)
gnuplot(options)
if options[:latex]
name = file_name.sub(/\.eps$/, '')
raise "No file output by gnuplot" unless FileTest.exist? name + '.tex'
raise 'latex failed' unless system "latex #{name}.tex --interaction nonstopmode --halt-on-error -q"
raise 'dvips failed' unless system "dvips #{name}.dvi"
FileUtils.rm "#{name}.eps" if FileTest.exist? "#{name}.eps"
raise 'ps2eps failed' unless system "ps2eps #{name}.ps"
end
# ep file_name
gp.term = old_gp_term
gp.output = old_gp_output
return File.basename(file_name, File.extname(file_name))
end
def self.latex_multiplot(name, options={})
name = name.sub(/\.eps$/, '')
figure_preamble = options[:preamble] || <