Reliable dragging and rotation too!

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.

Reliable dragging using Raphael

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?

Drawing in Javascript

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.

Persistence mechanism: use-case

In a previous post, I described working on a persistence mechanism not like ActiveRecord. It's more like a gateway. I indicated that I wish it to:

  1. return domain objects instead of returning an array of values which have then to be set as properties of domain objects;
  2. allow the selection of database fields rather than merely performing a SELECT * FROM tablename;
  3. allow the expression of relations between tables and map/create related domain objects accordingly; and
  4. allow some customization of queries

Examples of queries from a blogging system will serve well to further explain my intentions, as features of such systems can be implemented with the relations I'm thinking of:

  • A blog consists of posts or entries (I'll call them posts).
  • Each post has one author. Conversely, each author has many posts.
  • Each post may attract many comments. Conversely, each comment belongs to one post.
  • Each post may be assigned to many categories or tags (I'll call them categories). Conversely, each category has many posts.

The gateway I have chosen to call a "manager". I'll demonstrate selects only at the moment, but I should probably implement separate objects for SELECTs, INSERTs and UPDATEs, or at least maintain a division between SELECT and INSERT/UPDATE.

So here are some use cases. Each requires a PDO or PDO subclass instance, and a "manager" object. So imagine that something like the following is being done for each case:

$pdo = new PDO();
$manager = new Persistence_Manager($pdo);

I've used a "PDO" object here but in fact I'm using my own subclass of the connection object from the excellent pdoext library.

Use Case 1: Find all posts

//Make a prototype
$prototype = new Post();
//Decide which fields to fetch
$prototype->with('Post', array('id', 'title', 'post');
foreach($manager->find($prototype) as $post) {
    //display post
}

Use Case 2: Find a specific post

$prototype = new Post();
$prototype->with('Post', array('id', 'title', 'post');
//Find using primary key
$posts = $manager->findByPk($prototype, 2);
//Or find using some other condition, such as title = 'My First Post'
$posts = $manager->find($prototype, array(array('title', 'My First Post', '=')); //WHERE title='My First Post'

foreach($posts and $post) {
    //display post or posts
}

My plan here is to allow multiple conditions. The reason there's a nested array is because pdoext allows expressing conditions as arrays.

Use Case 3: Find a post and its author

$prototype = new Post();
$prototype->with('Post', array('id', 'title', 'post'));
$prototype->with('Author', array('id', 'name'));
foreach($manager->findByPk($post, 1) as $post) {
    echo "$post->title, posted by $post->author->name"; //Author object is automatically created
}

The Author object is created automatically and populated with the 'Author' fields specified in the second call to $prototype->with().

The relationship between a Post and its Author is defined in an 'Association' object which is added to either or both domain objects. One could add them manually at run-time or make them part of the domain classes:

class Post extends Persistence_Object
{
    public function __construct()
    {
        parent::__construct();
        $this->addAssociation( new PostToAuthorAssociation() );
    }
}

Use Case 4: Custom associations

It's not uncommon for pages displaying single blog entries to display links to the previous and the next post. I've seen code where these are done with separate queries. But since each post has only one previous and one next, and since fetching a couple post ids and titles is not much more overhead than fetching one complete post and its possibly long text, one could do it in a custom query. Something like:

SELECT Post.id, Post.title, Post.post, prev.id, prev.title, next.id, next.title FROM posts AS Post
LEFT JOIN posts AS prev ON (prev.id = Posts.id - 1)
LEFT JOIN post AS next ON (next.id = Posts.id + 1)
WHERE Post.id = '2'

So, assuming that such relationships are defined in an Association, the calling code for this might look like:

$prototype = new Post();
$prototype->with('Post', array('id', 'title', 'post'));
$prototype->with('prev', array('id', 'title'));
$prototype->with('next', array('id', 'title'));
foreach($manager->findByPk($post, 2) as $post) {
    echo "$post->prev->title"; //Display previous post's title
    echo "$post->next->title"; //Display next post's title
}

Use Case 5: Nested queries

One might wish to find display along with the current post a list of recent posts by the author. For now I'm going to ignore any LIMITs in the underlying query, but the syntax might be something like:

$postProto = new Post();
$postProto->with('Post', array('id', 'title', 'post'));
$authorProto = new Author();
$authorProto->with('Author', array('id', 'name'));
$authorProto->with('Post', array('id', 'title'));    //one-to-many
$postProto->with('Author', $authorProto);
foreach($manager->findByPk($postProto, 1) as $post) {
//A list of posts by this post's author
    print_r($post->author->posts);    
}

This time, we're passing in a prototype of the Author instead of a list of fields. Whatever fields are defined for the Author prototype will be used for the subsidiary query to find posts by the Author.

This post is intended to show how this persistence mechanism might be used, not the exact syntax. In other words, things might change.

Palinflow

Now, this is funny.

← Previous  1 … 12 13 14 15 16 17 Next →

About

A vanity publishing venture of David Rodger, sound production teacher and wannabe PHP developer

User