December 2008

Implementing a ‘ruler’ with Raphael and jQuery

Investigating a click-and-drag ruler to measure dimensions on-screen.

I used jQuery for event handling and such, and Raphael for drawing the lines. Since it’s just two straight lines, I probably could have done it just as easily with html elements (e.g. setting two borders on a positioned div…) That would probably end up being more robust concerning cross-browser compatibility. Oh well. See the demo here.

There’s nothing innovative here, just something I need to implement for a project. It turns out to be a bit involved to handle all parts of the interaction. The basic idea is:

  • on mousedown: register the starting point, and create the lines and labels (Ruler.start())
  • on mousemove: update the end point and redraw the lines and labels (Ruler.move())
  • on mouseup: hide the lines and labels (Ruler.finish())
var Ruler = function (elem) {
  elem = $(elem);
  this.elem = elem[0];
  this.raph = new Raphael(elem[0],elem.width(),elem.height());

  var b = this;
  elem.mousedown(function(e) { b.start(e); });
  elem.mouseup(function(e) { b.finish(e); });
  elem.mousemove(function(e) { b.move(e); });
};

The full implementation came to about 125 lines, tested in Firefox 3, and IE 7. I’ll cover some of the tricky issues here.

Bias

The ruler feels more natural if it follows the direction the mouse is moved—if I drag the mouse left or right, then it draws the horizontal segment first, followed by the vertical; if I drag the mouse up or down initially, it draws vertical then horizontal.

The code waits for at least a 10-pixel movement from the starting point to determine the user intent, and then fixes that as the bias for the ruler. This affects the ordering of the line segments, as well as the placement of the text labels.

  bias: function(left,top) {
    var dx = Math.abs(this.dx);
    var dy = Math.abs(this.dy)
    if ( (dx+dy) > 10 )
      this.dir = (dx > dy) ? 'h':'v';
  },

The lines

The two line segments are drawn as a single Raphael path element. Instead of re-creating the path for each movement, it updates the points in the path instead. Raphael makes this easy:

  draw_lines: function(left,top) {
    var p = this.path.path;
    if ( this.dir == 'h' )
      p[1].arg = [left, this.start_at.top];
    else
      p[1].arg = [this.start_at.left, top];
    p[2].arg = [left, top];
    this.path.redraw();
  },

The text labels

The labels showing the horizontal and vertical distance are drawn with two dynamically-created divs, positioned absolutely. I want these to be treated as passive annotations on the page, but since they are actual DOM elements, they are treated just like any other text on the page—the get selected when the mouse goes over them while the mouse button is down (i.e. while dragging the ruler).

So, when the ruler drag is started and it is short, the mouse is over the text labels, and the browser selects the text as it is supposed to. Not a nice effect. I found this solution for making the text non-selectable in a cross-browser manner, but with limited success.

  draw_labels: function(left,top) {
    var hl, ht, vl, vt;
    if ( this.dir == 'h' ) {
      // horz label
      hl = (this.start_at.left + left)/2;
      ht = this.start_at.top;
      // vert label
      vl = left + 2;
      vt = (this.start_at.top + top)/2;
    } else {
      // horz label
      hl = (this.start_at.left + left)/2;
      ht = top;
      // vert label
      vl = this.start_at.left + 2;
      vt = (this.start_at.top + top)/2;
    }
    $(this.hlabel).text(this.dx).
      css({left: (this.offset.left + hl)+"px",
           top: (this.offset.top + ht)+"px"});
    $(this.vlabel).text(this.dy).
      css({left: (this.offset.left + vl)+"px",
           top: (this.offset.top + vt)+"px"});
  },

mouseup outside of the div

If the mouse leaves the #paper div, and then the user releases the mouse, the Ruler.finish() method never gets called. This has a very simple solution: in Ruler.start(), don’t do anything if the ruler is already in progress (the previous mousedown), so the user just has to click again inside the div to finish it.

Am I making this too hard? Feel free to point me to easier/cleaner/better solutions…

Uncategorized

Comments (1)

Permalink

Pedigree navigation mockup

Now that I have a gedcom parser to work with, I loaded up a mockup pedigree view I’ve been working on with some real data

pedigree mockup

Next steps:

  • navigation to children, siblings
  • draw connecting lines
  • popup for details on a person

At some point I’ll abandon this code to start working on the ‘real’ implementation, but it’s a good vehicle for prototyping for now. All done in Javascript (~560 lines), with a bit of help from the dojo toolkit.

Uncategorized

Comments (0)

Permalink

Ruby GEDCOM::Parser module

Getting ready to do some work on yology, I needed a good way to get at some sample date. Numerous GEDCOM files are readily available, but I only need a subset of the data for the mockups I’m planning.

I took a look at the existing ruby GEDCOM::Parser, but wasn’t satisfied. So I forked it and hacked away. See the full writeup for details.

Uncategorized

Comments (1)

Permalink

Tk-GraphViz 1.01

I just upload Tk-GraphViz 1.01 to CPAN. Alternately, download it here

This is a very minor update with some additional polygonal node shapes. This module could use some serious updating to get in sync with the latest graphviz—the first version went on CPAN back in 2003.

Uncategorized

Comments (0)

Permalink

Follow me on twitter

http://twitter.com/jslade

I plan to use that twitter profile for tech-related stuff

Uncategorized

Comments (0)

Permalink

git socks proxy with ssh2

Rufus Cable at ThreeBytesFull posted a helpful writeup on using a SOCKS proxy for git access from behind a corporate firewall.

In my corporate environment, we don’t use OpenSSH, but rather Reflection for Secure IT, which is ssh2. The only change I had to make from his instructions was in the socks-ssh wrapper script—using the SocksServer config option instead of ProxyCommand:


#!/bin/sh
# Filename: ~/bin/socks-ssh
# This script opens an SSH connection through a SOCKS server
ssh -o 'SocksServer http://myproxy.mycompany.com/' $@

Uncategorized

Comments (0)

Permalink