Rubyvis - Example: Becker’s Barley

The Trellis display by Becker et al. helped establish small multiples as a “powerful mechanism for understanding interactions in studies of how a response depends on explanatory variables”. Here we reproduce a trellis of Barley yields from the 1930s, complete with main-effects ordering to facilitate comparison. These examples demonstrate Protovis’ support for data transformation via the nest operator.
Notice anything unusual about one of the sites? This anomaly led Becker et al. to suspect a major error with the data that went undetected for six decades.

require 'rubyvis'
load(File.dirname(__FILE__)+"/barley_data.rb")
# Nest yields data by site then year. */

# Compute yield medians by site and by variety. */
median=lambda {|data| 
       pv.median(data, lambda {|d| d[:yield]}) 
       }

site = pv.nest($barley).
       key(lambda {|d| d[:site]}).
       rollup(median)

variety = pv.nest($barley).key(lambda {|d| d[:variety]}).rollup(median);


barley = pv.nest($barley)
    .key(lambda {|d| d[:site]})
    .sort_keys(lambda {|a, b| site[b] - site[a]})
    .key(lambda {|d| d[:year]})
    .sort_values(lambda {|a, b| variety[b[:variety]] - variety[a[:variety]]})
    .entries()

# Sizing and scales. */
w = 332
h = 132
x = pv.Scale.linear(10, 90).range(0, w)
c = pv.Colors.category10()

# The root panel. */
vis = Rubyvis::Panel.new
    .width(w)
    .height(h * pv.keys(site).size)
    .top(15.5)
    .left(0.5)
    .right(0.5)
    .bottom(25);

# A panel per site-year. */
cell = vis.add(pv.Panel)
    .data(barley)
    .height(h)
    .left(90)
    .top(lambda { self.index * h})
    .stroke_style(pv.color("#999"));

# Title bar. */
cell.add(pv.Bar)
    .height(14)
    .fill_style("bisque")
  .anchor("center").add(pv.Label)
  .text(lambda{|site| site.key});

# A dot showing the yield. */
dot = cell.add(pv.Panel)
.data(lambda {|site| site.values})
    .top(23)
  .add(pv.Dot)
  .data(lambda {|year| year.values})
  .left(lambda {|d| x.scale(d[:yield])})
    .top(lambda {self.index * 11})
    .shape_size(10)
    .line_width(2)
    .stroke_style(lambda {|d| c.scale(d[:year])})

# A label showing the variety. */
dot.anchor("left").add(pv.Label)
  .visible(lambda { self.parent.index!=0})
  .left(-2)
  .text(lambda {|d| d[:variety]})

# X-ticks. */
vis.add(pv.Rule)
    .data(x.ticks())
    .left(lambda {|d|  90+x.scale(d)})
    .bottom(-5)
    .height(5)
    .stroke_style("#999")
  .anchor("bottom").add(pv.Label);

# A legend showing the year. */
vis.add(pv.Dot)
    .mark_extend(dot)
    .data([{:year=>1931}, {:year=>1932}])
    .left(lambda {|d| 260 + self.index * 40})
    .top(-8)
  .anchor("right").add(pv.Label)
  .text(lambda {|d| d[:year]})

vis.render()

puts vis.to_svg