diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..c80ee36 --- /dev/null +++ b/Gemfile @@ -0,0 +1,3 @@ +source "http://rubygems.org" + +gemspec diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..df32583 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,16 @@ +PATH + remote: . + specs: + gnuplot (2.6.1) + +GEM + remote: http://rubygems.org/ + specs: + rake (10.1.0) + +PLATFORMS + ruby + +DEPENDENCIES + gnuplot! + rake diff --git a/Rakefile b/Rakefile index 850c6d3..ed7c972 100644 --- a/Rakefile +++ b/Rakefile @@ -1,12 +1,5 @@ -require 'jeweler2' -Jeweler::Tasks.new do |s| - s.name = 'gnuplot' - s.description = s.summary = "Utility library to aid in interacting with gnuplot from ruby" - s.version = "2.6.1" - s.authors='roger pack' - s.email = "rogerpack2005@gmail.com" - s.homepage = "http://github.com/rdp/ruby_gnuplot/tree/master" -end +require 'bundler' +Bundler::GemHelper.install_tasks desc 'run unit tests' task :test do @@ -16,4 +9,4 @@ task :test do end end -Jeweler::RubygemsDotOrgTasks.new \ No newline at end of file +task :default => :test diff --git a/gnuplot.gemspec b/gnuplot.gemspec new file mode 100644 index 0000000..3b0d71f --- /dev/null +++ b/gnuplot.gemspec @@ -0,0 +1,14 @@ +# -*- encoding: utf-8 -*- +$:.push File.expand_path("../lib", __FILE__) +require "gnuplot/version" + +Gem::Specification.new do |s| + s.name = 'gnuplot' + s.description = s.summary = "Utility library to aid in interacting with gnuplot from ruby" + s.version = Gnuplot::VERSION + s.authors = 'roger pack' + s.email = "rogerpack2005@gmail.com" + s.homepage = "http://github.com/rdp/ruby_gnuplot/tree/master" + + s.add_development_dependency 'rake' +end diff --git a/lib/gnuplot.rb b/lib/gnuplot.rb index f8faf42..ddb4196 100644 --- a/lib/gnuplot.rb +++ b/lib/gnuplot.rb @@ -1,21 +1,26 @@ # 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 +# it. Most of the methods are implemented as added methods to the built in # classes. require 'matrix' - +require 'gnuplot/array' +require 'gnuplot/matrix' +require 'gnuplot/dataset' +require 'gnuplot/plot' +require 'gnuplot/splot' + 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 ) if RUBY_PLATFORM =~ /mswin|mingw/ all = [bin, bin + '.exe'] @@ -41,18 +46,18 @@ def Gnuplot.which_helper bin # This is an implementation that works when the which command is # available. - # + # # IO.popen("which #{bin}") { |io| return io.readline.chomp } return nil - end + 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'. - # + # 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 ) exe_loc = which( ENV['RB_GNUPLOT'] || 'gnuplot' ) @@ -61,15 +66,15 @@ def Gnuplot.gnuplot( persist = true ) 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. + # 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 ) IO::popen( cmd, "w+") { |io| @@ -77,315 +82,6 @@ def Gnuplot.open( persist=true ) io.close_write @output = io.read } - return @output - end - - - - # Holds command information and performs the formatting of that command - # information to a Gnuplot process. When constructing a new plot for - # gnuplot, this is the first object that must be instantiated. On this - # object set the various properties and add data sets. - - class Plot - attr_accessor :cmd, :data, :settings - - QUOTED = [ "title", "output", "xlabel", "x2label", "ylabel", "y2label", "clabel", "cblabel", "zlabel" ] - - def initialize (io = nil, cmd = "plot") - @cmd = cmd - @settings = [] - @arbitrary_lines = [] - @data = [] - @styles = [] - yield self if block_given? - puts "writing this to gnuplot:\n" + to_gplot + "\n" if $VERBOSE - - if io - io << to_gplot - io << store_datasets - end - end - attr_accessor :arbitrary_lines - - # Invoke the set method on the plot using the name of the invoked method - # as the set variable and any arguments that have been passed as the - # value. See the +set+ method for more details. - - def method_missing( methId, *args ) - set methId.id2name, *args - end - - - # Set a variable to the given value. +Var+ must be a gnuplot variable and - # +value+ must be the value to set it to. Automatic quoting will be - # performed if the variable requires it. - # - # This is overloaded by the +method_missing+ method so see that for more - # readable code. - - def set ( var, value = "" ) - value = "\"#{value}\"" if QUOTED.include? var unless value =~ /^'.*'$/ - @settings << [ :set, var, value ] - end - - # Unset a variable. +Var+ must be a gnuplot variable. - def unset ( var ) - @settings << [ :unset, var ] - end - - - # Return the current value of the variable. This will return the setting - # that is currently in the instance, not one that's been given to a - # gnuplot process. - - def [] ( var ) - v = @settings.rassoc( var ) - if v.nil? or v.first == :unset - nil - else - v[2] - end - end - - class Style - attr_accessor :linestyle, :linetype, :linewidth, :linecolor, - :pointtype, :pointsize, :fill, :index - - alias :ls :linestyle - alias :lt :linetype - alias :lw :linewidth - alias :lc :linecolor - alias :pt :pointtype - alias :ps :pointsize - alias :fs :fill - - alias :ls= :linestyle= - alias :lt= :linetype= - alias :lw= :linewidth= - alias :lc= :linecolor= - alias :pt= :pointtype= - alias :ps= :pointsize= - alias :fs= :fill= - - STYLES = [:ls, :lt, :lw, :lc, :pt, :ps, :fs] - - def Style.increment_index - @index ||= 0 - @index += 1 - - @index - end - - def initialize - STYLES.each do |s| - send("#{s}=", nil) - end - yield self if block_given? - - # only set the index if the user didn't do it - @index = Style::increment_index if index.nil? - end - - def to_s - str = "set style line #{index}" - STYLES.each do |s| - style = send(s) - if not style.nil? - str << " #{s} #{style}" - end - end - - str - end - end - - # Create a gnuplot linestyle - def style &blk - s = Style.new &blk - @styles << s - s - end - - def add_data ( ds ) - @data << ds - end - - - def to_gplot (io = "") - @settings.each do |setting| - io << setting.map(&:to_s).join(" ") << "\n" - end - @styles.each{|s| io << s.to_s << "\n"} - @arbitrary_lines.each{|line| io << line << "\n" } - - io - end - - def store_datasets (io = "") - if @data.size > 0 - io << @cmd << " " << @data.collect { |e| e.plot_args }.join(", ") - io << "\n" - - v = @data.collect { |ds| ds.to_gplot } - io << v.compact.join("e\n") - end - - io - end - end - - # Analogous to Plot class, holds command information and performs the formatting of that command - # information to a Gnuplot process. Should be used when for drawing 3D plots. - - class SPlot < Plot - - def initialize (io = nil, cmd = "splot") - super - end - - # Currently using the implementation from parent class Plot. - # Leaving the method explicit here, though, as to allow an specific - # implementation for SPlot in the future. - def to_gplot (io = "") - super - end - - end - - - # Container for a single dataset being displayed by gnuplot. Each object - # has a reference to the actual data being plotted as well as settings that - # control the "plot" command. The data object must support the to_gplot - # command. - # - # +data+ The data that will be plotted. The only requirement is that the - # object understands the to_gplot method. - # - # The following attributes correspond to their related string in the gnuplot - # command. See the gnuplot documentation for more information on this. - # - # title, with - # - # @todo Use the delegator to delegate to the data property. - - class DataSet - attr_accessor :title, :with, :using, :data, :linewidth, :linecolor, :matrix, :smooth, :axes, :index, :linestyle - - alias :ls :linestyle - alias :ls= :linestyle= - - def initialize (data = nil) - @data = data - @linestyle = @title = @with = @using = @linewidth = @linecolor = @matrix = - @smooth = @axes = @index = nil # avoid warnings - yield self if block_given? - end - - def notitle - @title = "notitle" - end - - def plot_args (io = "") - - # Order of these is important or gnuplot barfs on 'em - - io << ( (@data.instance_of? String) ? @data : "'-'" ) - - io << " index #{@index}" if @index - - io << " using #{@using}" if @using - - io << " axes #{@axes}" if @axes - - io << case @title - when /notitle/ then " notitle" - when nil then "" - else " title '#{@title}'" - end - - io << " matrix" if @matrix - io << " smooth #{@smooth}" if @smooth - io << " with #{@with}" if @with - io << " linecolor #{@linecolor}" if @linecolor - io << " linewidth #{@linewidth}" if @linewidth - io << " linestyle #{@linestyle.index}" if @linestyle - io - end - - def to_gplot - case @data - when nil then nil - when String then nil - else @data.to_gplot - end - end - - def to_gsplot - case @data - when nil then nil - when String then nil - else @data.to_gsplot - end - end - - end -end - -class Array - def to_gplot - if ( self[0].kind_of? Array ) then - tmp = self[0].zip( *self[1..-1] ) - tmp.collect { |a| a.join(" ") }.join("\n") + "\ne" - elsif ( self[0].kind_of? Numeric ) then - s = "" - self.length.times { |i| s << "#{self[i]}\n" } - s - else - self[0].zip( *self[1..-1] ).to_gplot - end + return @output end - - def to_gsplot - f = "" - - if ( self[0].kind_of? Array ) then - x = self[0] - y = self[1] - d = self[2] - - x.each_with_index do |xv, i| - y.each_with_index do |yv, j| - f << [ xv, yv, d[i][j] ].join(" ") << "\n" - end - # f << "\n" - end - elsif ( self[0].kind_of? Numeric ) then - self.length.times do |i| f << "#{self[i]}\n" end - else - self[0].zip( *self[1..-1] ).to_gsplot - end - - f - end -end - -class Matrix - def to_gplot (x = nil, y = nil) - xgrid = x || (0...self.column_size).to_a - ygrid = y || (0...self.row_size).to_a - - f = "" - ygrid.length.times do |j| - y = ygrid[j] - xgrid.length.times do |i| - if ( self[j,i] ) then - f << "#{xgrid[i]} #{y} #{self[j,i]}\n" - end - end - end - - f - end - end diff --git a/lib/gnuplot/array.rb b/lib/gnuplot/array.rb new file mode 100644 index 0000000..7180ac6 --- /dev/null +++ b/lib/gnuplot/array.rb @@ -0,0 +1,41 @@ +module Gnuplot + module Array + def to_gplot + if ( self[0].kind_of? Array ) then + tmp = self[0].zip( *self[1..-1] ) + tmp.collect { |a| a.join(" ") }.join("\n") + "\ne" + elsif ( self[0].kind_of? Numeric ) then + s = "" + self.length.times { |i| s << "#{self[i]}\n" } + s + else + self[0].zip( *self[1..-1] ).to_gplot + end + end + + def to_gsplot + f = "" + + if ( self[0].kind_of? Array ) then + x = self[0] + y = self[1] + d = self[2] + + x.each_with_index do |xv, i| + y.each_with_index do |yv, j| + f << [ xv, yv, d[i][j] ].join(" ") << "\n" + end + # f << "\n" + end + elsif ( self[0].kind_of? Numeric ) then + self.length.times do |i| f << "#{self[i]}\n" end + else + self[0].zip( *self[1..-1] ).to_gsplot + end + + f + end + end +end + +Array.send(:include, Gnuplot::Array) diff --git a/lib/gnuplot/dataset.rb b/lib/gnuplot/dataset.rb new file mode 100644 index 0000000..61eb063 --- /dev/null +++ b/lib/gnuplot/dataset.rb @@ -0,0 +1,78 @@ +module Gnuplot + # Container for a single dataset being displayed by gnuplot. Each object + # has a reference to the actual data being plotted as well as settings that + # control the "plot" command. The data object must support the to_gplot + # command. + # + # +data+ The data that will be plotted. The only requirement is that the + # object understands the to_gplot method. + # + # The following attributes correspond to their related string in the gnuplot + # command. See the gnuplot documentation for more information on this. + # + # title, with + # + # @todo Use the delegator to delegate to the data property. + + class DataSet + attr_accessor :title, :with, :using, :data, :linewidth, :linecolor, :matrix, :smooth, :axes, :index, :linestyle + + alias :ls :linestyle + alias :ls= :linestyle= + + def initialize (data = nil) + @data = data + @linestyle = @title = @with = @using = @linewidth = @linecolor = + @matrix = @smooth = @axes = @index = nil # avoid warnings + yield self if block_given? + end + + def notitle + @title = "notitle" + end + + def plot_args (io = "") + + # Order of these is important or gnuplot barfs on 'em + + io << ( (@data.instance_of? String) ? @data : "'-'" ) + + io << " index #{@index}" if @index + + io << " using #{@using}" if @using + + io << " axes #{@axes}" if @axes + + io << case @title + when /notitle/ then " notitle" + when nil then "" + else " title '#{@title}'" + end + + io << " matrix" if @matrix + io << " smooth #{@smooth}" if @smooth + io << " with #{@with}" if @with + io << " linecolor #{@linecolor}" if @linecolor + io << " linewidth #{@linewidth}" if @linewidth + io << " linestyle #{@linestyle.index}" if @linestyle + io + end + + def to_gplot + case @data + when nil then nil + when String then nil + else @data.to_gplot + end + end + + def to_gsplot + case @data + when nil then nil + when String then nil + else @data.to_gsplot + end + end + + end +end diff --git a/lib/gnuplot/matrix.rb b/lib/gnuplot/matrix.rb new file mode 100644 index 0000000..ec148c2 --- /dev/null +++ b/lib/gnuplot/matrix.rb @@ -0,0 +1,23 @@ +require 'matrix' + +module Gnuplot + module Matrix + def to_gplot (x = nil, y = nil) + xgrid = x || (0...self.column_size).to_a + ygrid = y || (0...self.row_size).to_a + + f = "" + ygrid.length.times do |j| + y = ygrid[j] + xgrid.length.times do |i| + if ( self[j,i] ) then + f << "#{xgrid[i]} #{y} #{self[j,i]}\n" + end + end + end + + f + end + + end +end diff --git a/lib/gnuplot/plot.rb b/lib/gnuplot/plot.rb new file mode 100644 index 0000000..d86b256 --- /dev/null +++ b/lib/gnuplot/plot.rb @@ -0,0 +1,154 @@ +module Gnuplot + # Holds command information and performs the formatting of that command + # information to a Gnuplot process. When constructing a new plot for + # gnuplot, this is the first object that must be instantiated. On this + # object set the various properties and add data sets. + + class Plot + attr_accessor :cmd, :data, :settings + + QUOTED = %w(title output xlabel x2label ylabel y2label clabel cblabel zlabel) + + def initialize (io = nil, cmd = "plot") + @cmd = cmd + @settings = [] + @arbitrary_lines = [] + @data = [] + @styles = [] + yield self if block_given? + puts "writing this to gnuplot:\n" + to_gplot + "\n" if $VERBOSE + + if io + io << to_gplot + io << store_datasets + end + end + attr_accessor :arbitrary_lines + + # Invoke the set method on the plot using the name of the invoked method + # as the set variable and any arguments that have been passed as the + # value. See the +set+ method for more details. + + def method_missing( methId, *args ) + set methId.id2name, *args + end + + + # Set a variable to the given value. +Var+ must be a gnuplot variable and + # +value+ must be the value to set it to. Automatic quoting will be + # performed if the variable requires it. + # + # This is overloaded by the +method_missing+ method so see that for more + # readable code. + + def set ( var, value = "" ) + value = "\"#{value}\"" if QUOTED.include? var unless value =~ /^'.*'$/ + @settings << [ :set, var, value ] + end + + # Unset a variable. +Var+ must be a gnuplot variable. + def unset ( var ) + @settings << [ :unset, var ] + end + + + # Return the current value of the variable. This will return the setting + # that is currently in the instance, not one that's been given to a + # gnuplot process. + + def [] ( var ) + v = @settings.rassoc( var ) + if v.nil? or v.first == :unset + nil + else + v[2] + end + end + + class Style + attr_accessor :linestyle, :linetype, :linewidth, :linecolor, + :pointtype, :pointsize, :fill, :index + + alias :ls :linestyle + alias :lt :linetype + alias :lw :linewidth + alias :lc :linecolor + alias :pt :pointtype + alias :ps :pointsize + alias :fs :fill + + alias :ls= :linestyle= + alias :lt= :linetype= + alias :lw= :linewidth= + alias :lc= :linecolor= + alias :pt= :pointtype= + alias :ps= :pointsize= + alias :fs= :fill= + + STYLES = [:ls, :lt, :lw, :lc, :pt, :ps, :fs] + + def Style.increment_index + @index ||= 0 + @index += 1 + + @index + end + + def initialize + STYLES.each do |s| + send("#{s}=", nil) + end + yield self if block_given? + + # only set the index if the user didn't do it + @index = Style::increment_index if index.nil? + end + + def to_s + str = "set style line #{index}" + STYLES.each do |s| + style = send(s) + if not style.nil? + str << " #{s} #{style}" + end + end + + str + end + end + + # Create a gnuplot linestyle + def style &blk + s = Style.new &blk + @styles << s + s + end + + def add_data ( ds ) + @data << ds + end + + + def to_gplot (io = "") + @settings.each do |setting| + io << setting.map(&:to_s).join(" ") << "\n" + end + @styles.each{|s| io << s.to_s << "\n"} + @arbitrary_lines.each{|line| io << line << "\n" } + + io + end + + def store_datasets (io = "") + if @data.size > 0 + io << @cmd << " " << @data.collect { |e| e.plot_args }.join(", ") + io << "\n" + + v = @data.collect { |ds| ds.to_gplot } + io << v.compact.join("e\n") + end + + io + end + end +end diff --git a/lib/gnuplot/splot.rb b/lib/gnuplot/splot.rb new file mode 100644 index 0000000..5b0e411 --- /dev/null +++ b/lib/gnuplot/splot.rb @@ -0,0 +1,19 @@ +module Gnuplot + # Analogous to Plot class, holds command information and performs the formatting of that command + # information to a Gnuplot process. Should be used when for drawing 3D plots. + + class SPlot < Plot + + def initialize (io = nil, cmd = "splot") + super + end + + # Currently using the implementation from parent class Plot. + # Leaving the method explicit here, though, as to allow an specific + # implementation for SPlot in the future. + def to_gplot (io = "") + super + end + + end +end diff --git a/lib/gnuplot/version.rb b/lib/gnuplot/version.rb new file mode 100644 index 0000000..8ad123b --- /dev/null +++ b/lib/gnuplot/version.rb @@ -0,0 +1,3 @@ +module Gnuplot + VERSION = "2.6.1" +end diff --git a/test/test_gnuplot.rb b/test/test_gnuplot.rb index 6b301e9..8127161 100644 --- a/test/test_gnuplot.rb +++ b/test/test_gnuplot.rb @@ -4,11 +4,11 @@ require 'test/unit' class StdDataTest < Test::Unit::TestCase - + def test_array_1d data = (0..5).to_a ds = Gnuplot::DataSet.new( data ) - + assert data == ds.data assert data.join("\n") + "\n", ds.to_gplot end @@ -20,10 +20,10 @@ def test_array_nd d1 = (0..3).to_a d2 = d1.collect { |v| 3 * v } d3 = d2.collect { |v| 4 * v } - + data = [ d1, d2, d3 ] ds = Gnuplot::DataSet.new( data ) - + assert data == ds.data assert "0 0 0\n1 3 12\n2 6 24\n3 9 36\n", ds.to_gplot end @@ -34,24 +34,24 @@ class DataSetTest < Test::Unit::TestCase def test_yield_ctor ds = Gnuplot::DataSet.new do |ds| - ds.with = "lines" + ds.with = "lines" ds.using = "1:2" ds.data = [ [0, 1, 2], [1, 2, 5] ] end - + assert "lines", ds.with assert "1:2", ds.using assert nil == ds.title assert [ [0, 1, 2], [1, 2, 5] ] == ds.data assert "'-' using 1:2 with lines", ds.plot_args - assert "0 1\n1 2\n2 5\n", ds.to_gplot + assert "0 1\n1 2\n2 5\n", ds.to_gplot end - + end class PlotTest < Test::Unit::TestCase - + def test_no_data plot = Gnuplot::Plot.new do |p| p.set "output", "'foo'" @@ -60,13 +60,13 @@ def test_no_data end assert( plot.settings == - [ [:set, "output", "'foo'"], - [:set, "terminal", "postscript enhanced"], + [ [:set, "output", "'foo'"], + [:set, "terminal", "postscript enhanced"], [:unset, "border"] ] ) - + assert( plot.to_gplot, \ - "set output 'foo'\nset terminal postscript enhanced\n" ) + "set output 'foo'\nset terminal postscript enhanced\n" ) end @@ -109,7 +109,7 @@ def test_style assert s2.index == 2, "index must be incremented" ds = Gnuplot::DataSet.new do |ds| - ds.with = "lines" + ds.with = "lines" ds.linestyle = s1 ds.data = [ [0, 1, 2], [1, 2, 5] ] end @@ -122,7 +122,7 @@ def test_style require 'rbconfig' -CONFIG = Config::MAKEFILE_CONFIG +CONFIG = RbConfig::MAKEFILE_CONFIG # This attempts to test the functions that comprise the gnuplot package. Most # of the bug reports that I get for this package have to do with finding the @@ -134,7 +134,7 @@ class GnuplotModuleTest def test_which # Put the spaces around the command to make sure that it gets stripped - # properly. + # properly. assert( CONFIG["SHELL"], Gnuplot::which(" sh " ) ) assert( CONFIG["SHELL"], Gnuplot::which( CONFIG["SHELL"] ) ) end @@ -151,10 +151,10 @@ def test_gnuplot # name (one that is in the path) then I should get the shell name as the # result of the gnuplot call. - ENV["RB_GNUPLOT"] = "sh" + ENV["RB_GNUPLOT"] = "sh" assert( CONFIG["SHELL"], Gnuplot.gnuplot(false) ) end - + end