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:
- index.cgi—ruby CGI script
- index.html.erb—main HTML template
- fatal.html.erb—template for error output
- layout.js—layout library using jQuery and Raphael
- layout.css—simple stylesheet