Layout test — experimenting with Raphael

I’ve been looking for a light-weight, cross-platform Javascript library for doing 2D vector graphics. I specifically wanted vector graphics with a full DOM for handling events and such, vs raster graphics (e.g. canvas). I think I found what I’m looking for in Raphael.js.

As a proof of concept, I wrote a little demo app presented here. It uses jQuery + Raphael for the interface, which is just a simple chip floorplan viewer. The boundaries of the various sections of the chip are drawn as rectilinear polygons (layout.js):

LT.prototype._drawBlock = function(g,block) {
  if ( block.path ) return;

  var linewidth = 1;
  if ( this.scale < 1.0 ) linewidth /= this.scale;
   var p = g.path({stroke:'white', 'stroke-width':linewidth+"px"});
   block.path = p;

  var boundary = block.boundary.split(':');
  for ( var i=0, n=boundary.length; i<n; i++ ) {
    var pt = boundary[i].split(',');
    if ( i == 0 ) p.moveTo(pt[0],pt[1]);
    else p.lineTo(pt[0],pt[1]);
  }
  p.andClose();

  var m = block.m;
  if ( m ) p.matrix(m[0],m[1],m[2],m[3],m[4],m[5]);

  p[0].onmouseover = function() { p.attr({fill:'red'}); };
  p[0].onmouseout = function() { p.attr({fill:''}); };

};

All the block boundaries are given in chip coordinates, so the whole view has to be scaled to fit in the canvas. All the polygons are drawn as a group, and then the group is translated and scaled to fit (layout.js):

LT.prototype.draw = function() {
  this.g = this.paper.group();

  // Calculate dimensions
  this.bbox.w = this.bbox.x2 - this.bbox.x1;
  this.bbox.h = this.bbox.y2 - this.bbox.y1;
  this.elem_w = this.jq.width();
  this.elem_h = this.jq.height();

  this.scale_w = this.bbox.w / this.elem_w;
  this.scale_h = this.bbox.h / this.elem_h;
  this.scale = this.scale_w > this.scale_h ? this.scale_w : this.scale_h;
  this.scale = (1 / this.scale) * 0.90;

  // Draw block outlines
  for ( var i=0, n=this.blocks.length; i<n; i++ ) {
    var block = this.blocks[i];
    this._drawBlock(this.g,block);
  }

  // normalize all drawing to 0,0
  this.g.translate(-this.bbox.x1,-this.bbox.y1);

  // Scale to fit
  this.g.scale ( this.scale, this.scale );

  // center all drawing in the paper
  this.g.translate((this.elem_w - this.bbox.w)*.5,
           (this.elem_h - this.bbox.h)*.5);
};

The polygons are given onmouseover / onmouseout handlers to fill them red when hovered, but that is the extent of the interactivity in this example.

The data for the block boundaries is pulled from mysql on the server side. It is all driven by a CGI script written in ruby. I chose this as a simpler alternative to Rails or Merb or Camping—showing the full stack of operations (database queries, template formatting, etc) without much of the hidden ‘magic’ of those frameworks (index.cgi):

class LayoutTest
  def main
    init
    parse_opts
    connect_to_gallery
    read_blocks_info
    read_layout_info
    output_layout
  rescue => e
    fatals_to_browser e
  end

  protected

  def init
    @cgi = CGI.new('html3')
    print @cgi.header
  end

  def parse_opts
    @block = @cgi['block']
    @block = 'pcore' if @block.empty?
    @parent = @cgi['parent']
  end

  def read_layout_info
    @instance = {}
    @layout = {}

    instances = query("select * from layout_instance " +
                      "where `cell` = '#{@block}'")
    instances.each do |inst|
      @instance[inst['instance']] = inst
    end

    chillins = @children.map{|c| "'#{c}'"}.join(',')
    layouts = query("select * from layout_info " +
                    "where `cell` in ('#{@block}',#{chillins})")
    layouts.each do |lo|
      @layout[lo['cell']] = lo
    end

  end

  def query sql
    rows = []
    sth = @dbi.execute(sql)
    sth.fetch_hash do |row|
      rows << row.dup
    end
    sth.finish
    rows
  end
end

The HTML output is generated using ERB. It loads the necessary javascript libraries (jQuery, Raphael, and my own layout code), then generates the necessary calls to LT.block() to define each block to be shown. The raw block boundary data has to be transformed (translated + rotated / mirrored) to account for placement relative to the containing block—that’s all handled in layout.js). The template (index.html.erb):

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <title>layout test: <%= @block %></title>
    <link rel="stylesheet" href="layout.css" type="text/css" charset="utf-8" media="screen,projection"></link>
  </head>
  <body>
    <h1>layout test: <%= @block %></h1>
<% if @parent %>
<a href="?block=<%= @parent %>"><%= @parent %></a>
<% end %>
<span id="info"></span>
<div id="layout">
</div>
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript" src="raphael.js"></script>
<script type="text/javascript" src="layout.js"></script>
<script type="text/javascript">
var layout = new LT("layout");
<%= layout_block %>
<% @children.each do |child| %>
<%= layout_child child %>
<% end %>
layout.draw();
</script>
  </body>
</html>

The full code is available here. Not very useful without a database to feed the data, but should serve as a reasonable example: