In the unlikely event that I have actually redesigned this blog and various tags I've created for each post appear in a sidebar or other peripheral area, and that you have navigated here via the
raphael tag, I hope you will not be disappointed that this post has nothing to do with any Renaissance painter. Except, that is, for the fact that the Javascript library to which I refer was probably named after one.
Raphael is a Javascript library for creating and manipulating vector graphics by
Dmitry Baranovskiy, a software developer for
Atlassian. My use of it is all the more miraculous as I haven't looked at Javascript for about 8 years (version 1.1, I think).
As you may know, I teach sound production at a TAFE institute (that's "Technical and Further Education" for those not aware of vocational training in Australia). My students regularly record performances by music performance students in the Performing Arts department of which we are part. The institute also runs degree courses. There's one in popular music. Students in that course have been providing stage plans for their performances so that my students can plan the recording: microphones, mixer channel and track allocation, and so on. Certain instruments and elements are more or less fixed: grand piano and drums, mainly, but usually instrument amplifiers as well. . I'm trying to get TAFE performance students to do the same.
When I read about Raphael, I immediately thought of a web application in which performance students could draw their stage plans on-line. They could make changes and my students could view the plans.
So I created a mockup with an area for a "palette" of tools and a "stage", and graphics for a microphone, an amplifier and a keyboard&emdash;the most likely mobile objects in a performance. With a library like
Jquery, the Javscript for this is fairly simple:
$(document).ready( function() {
var paper = Raphael("container", 640, 480);
var palette = paper.rect(0,0,640,60);
palette.attr({fill:"#eee"});
var stage = paper.rect(0, 60, 640, 420);
stage.attr({"stroke-width": "0", "fill": "#b38439"});
var mic = paper.image("microphone2.png", 50, 50, 15, 47);
var amp = paper.image("amp.png", 45, 5, 53, 45);
var keys = paper.image("keyboard.png", 95, 5, 137, 45);
}
Event handlers can be attached to objects as follows:
mic[0].onclick = function(e)
{
/* ... click-handling code here ... */
}
Oddly, DOM 0 handlers work while DOM 1 handlers (addEventListener) do not... or at least I haven't managed to make them work yet.
The idea is that when the user clicks on one of the objects in the palette, it clones itself and the clone can then be dragged onto the stage, and moved around or rotated. Creating the clone and implementing dragging is not too difficult. Raphael has a translate() function, but I've been unable to make that work with a mousemove event, so I directly manipulated the x, y coordinates of the object instead.
So far so good.
Rotation is quite easy:
mic.rotate(90)
...rotates the object 90 degrees (clockwise).
But once it's rotated, moving the object is a problem. It appears that the entire coordinate system (or reference) for the object rotates with it. So if the user clicks on the object and drags upwards, the object moves to the right instead!
If one were only dealing with right angles, this would be less of a problem. But I wish to have students rotate the objects to indicate their orientation on the stage. So any degree of rotation is necessary and consequently, the object moves off unpredictably (well, it's
entirely predictable but unsettling!).
Trigonometry to the rescue.
I decided to work out the mouse movement between consecutive mousemove events and then work out the corresponding changes (delta values) in x and y coordinates in the object's coordinate system. As I don't know MathML and I'm not sure of how many browsers support it, here's the Javascript for that:
var _clone = function(prototype)
{
var theItem = paper.image(
prototype.attr("href"),
prototype.attr("x"),
prototype.attr("y"),
prototype.attr("width"),
prototype.attr("height")
);
theItem.attr({"angle": 0, "gx": 0, "gy": 0});
current = theItem;
selected = theItem;
theItem[0].onmousedown = function(e)
{
theItem.attr({"gx": e.pageX, "gy": e.pageY});
current = theItem;
selected = theItem;
}
theItem[0].onmousemove = function(e)
{
if(current == theItem) {
//get mouse delta x,y
var delta = {"x": e.pageX - theItem.attr("gx"), "y":e.pageY - theItem.attr("gy")};
theItem.attr({"gx": e.pageX, "gy": e.pageY});
//get distance and angle of mouse movement
var r = Math.sqrt(Math.pow(delta.x,2) + Math.pow(delta.y, 2));
var a = theItem.attr("angle")*Math.PI/180 - Math.asin(delta.y/r);
theItem.attr("x", Math.cos(a)*r + theItem.attr("x"));
theItem.attr("y", Math.sin(a)*r - theItem.attr("y"));
}
}
theItem[0].onmouseup = function(e)
{
current = null;
}
return theItem;
}
I'm pretty sure I have the trigonometry right (though it's been 20 years since I did anything like this). But the object still
jumps around unpredictably.
The only way I see around this is to return the object to 0 degrees for the move and rotate it to whatever degree it was rotated to originally. In my next post, we'll see whether or not this was successful.
A colleague informed me that the demos don't work on IE7. I just tried them on IE6.
IE6's box model is totally screwed, so that may account for it.
Go get a proper browser people!
I've been re-reading Chapter 14 of The Javascript Anthology. It notes the problem of synching mouse movement to JS object movement; that is, how one can move the mouse so fast that it moves off the object being moved. Therefore, the object stops issuing mousemove events and any handlers associated with it will not be invoked. Therefore, the object stops moving. This is evident in the demos above.
The chapter suggests adding mousemove and mouseup event handlers to the document. I would probably want to add them to the Raphael canvas (or the div element containing it). When I get time to implement it, we'll see if it improves the object's behaviour.
...complex if the Firefox DOM Inspector tool provides any indication!Anyway, I tried the tricks that I alluded to previously: that is, attaching mousemove and mouseup handlers to the "canvas" rather than the objec...
...rotated away from its original angle (whatever shape and orientation it had when it was created). I previously considered using trigonometric calculations to adjust both x and y coordinates to make the object m...
... application that would allow performance students to draw stage plans for their performances. As I mentioned previously, I'm using Raphael, a Javascript drawing library which uses SVG or VML....