I know this is old news.
It's been a while since I worked on the stageplan app. One issue I worked on a lot was how to drag items around (not to hard) and rotate them (a bit harder), and then how to drag them around in a rotated state.
I have lately discovered Raphael's drag-and drop functionality which didn't exist in previous versions. It has now allowed me to position a "hot spot" on objects, which when dragged around can be used to calculate the angle of rotation of an object.
Calculating that angle took a bit of trigonometry and then getting "resetting" the object to a new rotated state before the next click-and-drag was harder still.
Add a bit of code for grouping the main object with its hot spot, and it works!
Very cool. (At some point, I'll put up a demo here.)
My re-write of the Stageplan app is going well, despite the fact that I have little time to do it. So I'm posting a demo here. It uses Raphael 0.71. I had some difficulties with this, but have solved them, albeit somewhat inelegantly.
Click on an item and drag it onto the "stage" area. When you do, a slider control appears. It allows you to rotate the object. When multiple objects are on the stage, clicking on any one of them selects the item shows its slider control. When you click on the stage, a selected item is deselected and its slider disappears.
I've added an "amplifier" object. I should also add a DI box and a keyboard as these are regularly specified by students in their stageplans. Other items, such as the drumkit and piano, tend to be static (as they are difficult to move). The latter can probably be added to the stage as partly opaque objects.
Here's the demo.
The next step is to figure out how to save objects on the server so that stageplans can be recalled, viewed and edited. Bolt on an authentication system and it'll be done!
It's been some time since I worked on the 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.
There have been several revisions of Raphael since I last worked on the app, so when I upgraded I found that I could no longer clone image objects. The reason turned out to be that calling myImage.attr("href") no longer returns the URL of the source image. There is now no easy way to do this. Dimitry or one of the other contributors has added a "src" attribute which is easily set (for changing the image source of an object) but not retrieved. Since my cloning method relies on retrieving this value, I was stuck.
When I looked at the Raphael source code, I figured out a way of doing it, but it's clumsy:
myImage.node.getAttributeNS(myImage.svg.xlink, "href");
The app works like this: If you click on a tool, the tool is cloned as an "item". If you let go of the mouse button while it's still over the "palette", the clone is deleted. When you drag the item onto the stage, a slider for controlling its rotation appears. Clicking on the stage "deselects" the item and its slider disappears. Clicking on an item shows its slider with the slider thumb showing the previous degree of rotation. Moving the slider's thumb rotates the item. Clicking and dragging the item moves it around the stage.
I took the opportunity to refactor the code and rewrite almost all of it from scratch. There is now an object for each tool and for each item created, as well as the slider which controls the rotation of the selected object. It's not very nice OO, as there are explicit references to the "palette" and "stage" in Tool and Item methods, particularly for assigning event handlers to items. However, for a reasonably experienced PHP programmer (with a smattering of assembly code, Basic, Pascal and C on the way there), Javascript's lambda functions and closures and the consequent effects on variable scope seem quite weird anyway, so I'm not going to lose any sleep over it!
In a day or two, I'll post a demo of the work done so far.
In my last entry, I demonstrated that when a Raphael SVG object is rotated, its coordinate system is also rotated. In fact, when the object is returned to a rotational angle of 0 degrees, it jumps to the location it would have moved to had it not been rotated.
So the issue is, how can one move an object which has been 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 move to where it would be if its angle of rotation had been 0 degrees. But I now realise that this would not have worked properly as the distance the object moves when it is rotated away from 0 degrees is the same as it would have moved when it was at 0 degrees' rotation. If my memory of trigonometry serves me correctly, that would require tan, not sin or cos.
I'm sure that, with a little revision, I could work it out. But I suspect that some of those mathematical functions result in a performance hit, especially as they'd have to be performed with every mousemove event. I've no doubt that SVG or VML functions already employ such calculations, even though they're done by the Javascript engine which is most likely written in C or C++ and therefore performs at least an order of magnitude faster than the interpreted Javascript which invokes them. Or maybe such calculations only happen once, when the object is rotated and then a completely different coordinate system is maintained in memory.
Since I understand nothing about Javascript engines, I could be talking completely through my hat.
So I decided to try a simpler approach that I alluded to before: rotate the object back to 0 degrees before moving it and then re-rotate it to whatever angle it was before the move.
I could "undo" the rotation on the mousedown and "redo" the rotation on the mouseup, but the user would see the object in an unrotated state during the mousemove. I decided to so these operations for every mousemove event.
Here's the demo. It has the same "Rotate"/"Unrotate" button under the canvas (scroll down a bit if you can't see it) as before.
If you examine the Javascript, you'll notice there are only a few lines added. I don't notice any performance difference between the previous one and this, even though the Javascript engine is probably working a bit harder. WRONG! There is a noticeable performance hit. At first, I thought there wasn't because I forgot how smooth movement is without the modifications to the previous script. And to the user, it looks as if the object is moving smoothly fairly smoothly, with no blurring due to redraws even though, one assumes, redraws are occurring.
I'm not using a superfast machine either: it's an old IBM R30 running Opensuse 11.0 (Linux) with KDE4 and Firefox 3 (FF3 is required for scalable vector graphics).
So, with this key functionality in place, I can continue developing the "stageplan" application.
After some weeks of frantic marking and other administrative affairs, I finally had the chance to sit down this week and work on the Javascript-based app using Raphael.
I discovered that one can indeed access drawing objects with the JQuery Javascript library, provided one adds an "id" attribute and refers to the "original" object. So if, for example, one creates an object as follows:
var paper = Raphael("container", 640, 480);
var square = paper.rect(50, 50, 20, 20);
square.attr({
"fill":"#b34300",
"id":"square",
});
...one can only access attributes of the object like this:
square.attr("x");
...and not this (using Jquery):
$("#square").attr("x");
I think this reflects the very complicated nature of Raphael and, in fact, SVG or VML drawing. (Take any comments about VML with a grain of salt though because I haven't got any of this to work in IE yet.) At least, the structure of SVG and its child objects seems to be very 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 object being clicked and dragged. And it works a treat.
Demo
There is still the problem of rotating the object. It's probably more obvious in this example than in the previous one. Click on the "Rotate" button to rotate the square 45 degrees clockwise. When you drag the mouse straight up or down, the object moves diagonally. When "Unrotate" is clicked, it shifts position as its coordinate system is re-aligned with the canvas's. Rotating any object also rotates its coordinate system.
I don't know if this is a side-effect of Raphael or the underlying SVG or VML. Any ideas?