Ruby Ploticus

19 June 2006

In my recent post on EvaluatingRuby I mentioned that a colleague had put together a web app with some fancy numerical graphs. Someone emailed to ask how he did that. I added my short answer, ploticus, to the original bliki entry, but that led to the question of how he interfaced ruby with ploticus?

I actually ran into a similar problem myself recently as I wanted to use ploticus to graph some data for a personal project. The solution I came up with was actually very similar, if rather less refined, than the one my colleague used. As a result I thought I'd share it.

First a caveat - this is literally something I knocked up one evening. It isn't intended to be robust, performant or otherwise enterprisey. It's just for some data I use for me, myself, and I.

A sophisticated way to drive a C library like ploticus is to bind directly to the C API. Ruby makes this easy, so I'm told, but it's too much work for me (particularly if I want to be done before cocktail-hour). So my approach is to build a ploticus script and pipe it into ploticus. Ploticus can run by taking a script from standard input that controls what it does, so all I have to do is run ploticus within ruby and pipe commands into it. Roughly like this:

  def generate script, outfile
    IO.popen("ploticus -png -o #{outfile} -stdin", 'w'){|p| p << script}
  end

To build up the script, I like to get objects that can work in my terms, and produce the necessary ploticus stuff. If you have anything that uses the prefabs, then putting together something is easy. I wanted to do clustered bar graphs, like this, which requires a ploticus script.

I built what I needed in three levels. At the lowest level is PloticusScripter, a class that builds up ploticus script commands. Here it is:

class PloticusScripter
  def initialize
    @procs = []
  end
  def proc name
    result =  PloticusProc.new name
    yield result
    @procs << result
    return result
  end
  def script
    result = ""
    @procs.each do |p|
      result << p.script_output << "\n\n"
    end
    return result    
  end
end
class PloticusProc
  def initialize name
    @name = name
    @lines = []
  end
  def script_output
    return (["#proc " + @name] + @lines).join("\n")
  end
  def method_missing name, *args, &proc
    line = name.to_s + ": "
    line.tr!('_', '.')
    args.each {|a| line << a.to_s << " "}
    @lines << line
  end
end

As you see the scripter is just a list of proc commands (well they could be anything that responds to script_output - but I didn't need anything else yet). I can instantiate the scripter, call proc repeatedly to define my ploticus procs, then when I'm done call script to get the entire script for piping into ploticus.

The next level is something to build clustered bar graphs:

class PloticusClusterBar 
  attr_accessor :rows, :column_names
  def initialize
    @rows = []
  end
  def add_row label, data
    @rows << [label] + data
  end
  def getdata scripter
    scripter.proc("getdata") do |p|
      p.data generate_data
    end
  end
  def colors
    %w[red yellow blue green  orange]
  end
  def clusters scripter
    column_names.size.times do |i|
      scripter.proc("bars") do |p|
        p.lenfield i + 2
        p.cluster i+1 , "/", column_names.size
        p.color colors[i]
        p.hidezerobars 'yes'
        p.horizontalbars 'yes'
        p.legendlabel column_names[i]
      end    
    end
  end

  def generate_data
    result = []
    rows.each {|r| result << r.join(" ")}
    result << "\n"
    return result.join("\n")    
  end  
end

This allows me to build a graph with simple calls to add_row to add data rows. This makes it much more easy for me to build up the data for the graph.

To make a particular graph, I'll write a third class on top of that:

#produces similar to  ploticus example in ploticus/gallery/students.htm

class StudentGrapher
  def initialize
    @ps = PloticusScripter.new
    @pcb = PloticusClusterBar.new
  end
  def run
    load_data
    @pcb.getdata @ps
    areadef
    @pcb.clusters @ps    
  end
  def load_data
    @pcb.column_names = ['Exam A', 'Exam B', 'Exam C', 'Exam D']
    @pcb.add_row '01001', [44, 45, 71, 89]
    @pcb.add_row '01002', [56, 44, 54, 36]
    @pcb.add_row '01003', [46, 63, 28, 87]
    @pcb.add_row '01004', [42, 28, 39, 49]
    @pcb.add_row '01005', [52, 74, 84, 66]    
  end
  def areadef
    @ps.proc("areadef") do |p|
      p.title "Example Student Data"
      p.yrange 0, 6
      p.xrange 0, 100
      p.xaxis_stubs "inc 10"
      p.yaxis_stubs "datafield=1"
      p.rectangle 1, 1, 6, 6
    end
  end
  def generate outfile
    IO.popen("ploticus -png -o #{outfile} -stdin", 'w'){|p| p << script}
  end
  def script
    return @ps.script
  end

end


def run
  output = 'fooStudents.png'
  File.delete output if File.exists? output
  s = StudentGrapher.new
  s.run
  s.generate output
end

It's a very simple example, but it's a good illustration of what I call the Gateway pattern. The PloticusClusterBar class is the gateway with the perfect interface for what I want to do. I make it transform between that convenient interface and what the real output needs to be. The PloticusScripter class is another level of gateway. Even for a simple thing like this I find a design of composed objects like this a good way to go. Which may only say how my brain's got twisted over the years.