Lately, I've been playing around with some of the upcoming features of HTML5. Last week, I decided to try the HTML canvas. In order to make a realistic test, I tried to port the parse tree viewer in
NuGram IDE, one of
Nu Echo's products, to JavaScript and the HTML canvas.
To debug a speech recognition grammar with NuGram IDE, an
Eclipse plugin, the developer simply enters a test sentence and get a parse tree in the Interpretation view:
NuGram IDE is written almost entirely in Kawa Scheme. The parse tree viewer does not use any graph layout toolkit, the layout algorithm has been written from scratch in Scheme (about 200 lines of commented code). It was a good candidate for a rewrite in JavaScript. Here is what I ended up with (in Chrome):
The conversion process
I translated the whole layout algorithm by hand. I did not use (or develop) any automatic translation tool. I wanted to see how different the JavaScript code would look like compared to the original Scheme code.
I knew that JavaScript/ECMAScript is one of Scheme's very close cousins, but it was amazingly easy to convert the Scheme code into very similarly looking JavaScript code. For instance, take this procedure definition:
(define (compute-nodes-size! gc tree-node level)
(compute-node-size! gc (get-graph-node tree-node))
(set-level! tree-node level)
(for-each (lambda (child)
(compute-nodes-size! gc child (+ level 1)))
(get-node-children tree-node)))
It translates to the following JavaScript definition:
function computeNodesSize(ctx, treeNode, level) {
computeNodeSize(ctx, treeNode.graphnode);
treeNode.level = level;
treeNode.children.forEach(function(child) {
computeNodesSize(ctx, child, level + 1);
});
}
Granted, it's a very simple example. There were some pieces of code that demanded a more complex rewrite. Uses of the Scheme apply procedure is an example. I had to translate the call
(apply max nodes-y)
to
var maxY = 0;
nodesY.forEach(function(y) { if (y > maxY) { maxY = y; } });
In JavaScript, the max function is not variadic function. It accepts exactly two arguments. It would have been possible to write a function that takes a function of two arguments and turns it into a variadic version of it. But it wasn't worth it, since there was only a single usage of apply and max in the whole algorithm.
Update: I was completely mistaken here, as pointed out by Jon-Carlos Rivera in another post. the max function is variadic. I could instead have written:
Math.max.apply(this, nodesY)
My mistake was to not use apply properly. The first argument to apply must be an object, and not the list of arguments. I should have RTFM before writing such nonsense.
Canvas Support
I tried my parse tree viewer on 3 different browsers (Chrome 5.0.375.29 beta, Firefox 3.6.8, Opera 10.61) on Ubuntu 9.04.
How portable is the resulting widget? Not as much as advertised. I know, HTML5 is not a standards yet, but most of the major browsers claim to support HTML5 canvases. There were two majors aspects that differed between browsers: rendering and user experience.
On the rendering side, Firefox is the worst. Here is what I get:
Some lines appear on top of the nodes (boxes), even though they were drawn before. Opera is not bad, but the texts are not always rendered properly for some font sizes.
On the user experience side, Opera seems the fastest of the three. Chrome comes close. In Firefox, however, the canvas is not redrawn while dragging the parse tree. The parse tree is redrawn only when the button is released.
Conclusion
In conclusion, I've been fairly pleased by the overall experience. The conversion from Scheme to JavaScript was painless, and the result was nice looking. However, and even though it's only based on anecdotal evidence or my lack of experience with the HTML5 canvas, portability is clearly not there yet. My guess is that we'll have to devote a lot of our development time fixing portability issues (we're already spending too much time doing exactly that for plain HTML, right?).
Update: I put the canvas demo online here. Try it and let me know if portability can be improved, if I did things the wrong way, etc.