Plotting and charting libraries for Ruby (on Rails) abound. However, few are sophisticated enough for scientists and many are not actively maintained. Plotting in R, on the other hand, is about as sophisticated as it comes.
Can we bridge Ruby and R? Yes we can, thanks to Alex Gutteridge’s RSRuby. The next logical question: how to plot data using RSRuby in your shiny new Rails application?
Update Jul 22: recently, the code in this post has stopped working for me (unwanted X windows pop up, stack smashing errors) – so use at your discretion or not at all!
First, thanks to Peter Lane over at Ruby for Scientific Research, a relatively-new blog with some excellent introductory RSRuby articles. Thanks also to Alex for providing RSRuby.
- Install RSRuby
- Make the RSRuby gem available to Rails
- Create your Rails application
- Tell the application about R_HOME
- Make an instance of RSRuby available to all controllers
- Create a sample controller and view
- Create the view
My OS is Ubuntu 9.04 with rubygems 1.3.3, installed from source and Rails 2.3.2. This works for me:
sudo gem install rsruby -- --with-R-home=/usr/lib/R --with-R-include=/usr/share/R/include
I edited config/environment.rb to include the gem:
Rails::Initializer.run do |config| config.gem 'rsruby' # other config options here... end
Let’s call it Plotter. Usual procedure:
rails Plotter cd Plotter rake db:create
We won’t use the database (default sqlite3) in this example, but you may use it later on.
RSRuby will fail unless the environment variable R_HOME is set. To tell Plotter that R_HOME is /usr/lib/R, I put this in the file app/helpers/application_helper.rb:
module ApplicationHelper # set R_HOME if not set if ENV['R_HOME'].nil? ENV['R_HOME'] = "/usr/lib/R" end end
RSRuby works by providing an instance of an “R object”, on which you call R functions as methods. I provided the object by editing app/controllers/application_controller.rb to include a method named InitR:
class ApplicationController < ActionController::Base # Make R instance available to all def InitR @r = RSRuby.instance return @r end end
For testing purposes, I created a controller named Home with a single view named index:
./script/generate controller Home index rm public/index.html
I edited config/routes.rb so as the root of the application is the index view for the home controller:
ActionController::Routing::Routes.draw do |map| map.root :controller => "home" #default view is app/views/home/index.html.erb map.connect ':controller/:action/:id' map.connect ':controller/:action/:id.:format' end
Now we can get to work on the file app/controllers/home_controller.rb. I wrote a sample method, show_image to plot a histogram using R via RSRuby:
def show_image # next 6 lines use R to plot a histogram @r = InitR() @d = @r.rnorm(1000) @l = @r.range(-4,4,@d) @r.png "/tmp/plot.png" @r.par(:bg => "cornsilk") @r.hist(@d, :range => @l, :col => "lavender", :main => "My Plot") @r.eval_R("dev.off()") #required for png output # then read the png file and deliver it to the browser @g = File.open("/tmp/plot.png", "rb") [email protected]| @f.read} send_data @g, :type=>"image/png", :disposition=>'inline' end
Not the prettiest code, but you get the idea. Obviously the owner of the HTTP daemon (www-data on Ubuntu) must have write permission to the location of the PNG file; /tmp in this case.
All that remains is to edit app/views/home/index.html.erb so as it displays the image:
<h1>Home#index</h1> <p>Find me in app/views/home/index.html.erb</p> <%= image_tag(url_for(:controller => "home", :action => "show_image")) %>
Which just says “wrap whatever comes out of the method show_image in an IMG SRC tag”.
Fire up your application using “./script/server”, point your browser at http://localhost:3000 and you should see something like the image to the right.
One drawback of this approach is the need to write a file to disk and so have to worry about naming, cleaning up and so on. I have not found a way to make R output plots to stdout or as a base64 string. If you think this is possible, leave a comment.
OK – so now you can get serious, write some generic methods, design a database schema to store data for plotting and so on.