Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Raphael JS - maintain path between two objects

I've created a plugin for Raphael JS. It basically allows you to call

paper.connect(obj1,obj2,colour)

That draws a line between the two objects and maintains the line when the objects are animated. This is what I've come up with so far. It works but its not very performant, any suggestions on what else I could do to achieve the same thing.

Raphael.fn.connect = function(obj1, obj2, colour) {
    // list of paths each object has
    obj1.connections = []
    obj2.connections = []
    // get the bounding box of each object
    var box1 = obj1.getBBox()
    var box2 = obj2.getBBox()
    // create a line/path from object 1 to object 2
    var p = this.path("M" + (box1.x + box1.width / 2) + ","
            + (box1.y + box1.height / 2) + "L" + (box2.x + box2.width / 2)
            + "," + (box2.y + box2.height / 2))
    // adjust attributes of the path
    p.attr({
        stroke : colour,
        "stroke-linecap" : "round",
        "stroke-opacity" : Math.max(obj1.attr('opacity'), obj2.attr('opacity'))
    })
    // set the start and end element for this path
    p.startElement = obj1;
    p.endElement = obj2;
    // add the path to each of the object
    obj1.connections.push(p)
    obj2.connections.push(p)
    // mark each object as being connected
    obj1.connected = true;
    obj2.connected = true;
    // listen for the Raphael frame event
    eve.on("raphael.anim.frame.*", function(obj) {
        // if the object the frame event is fired on is connected
        if (this.connected) {
            // for each connection on this object
            for ( var c in this.connections) {
                var path = this.connections[c]; // temp path
                var b1 = path.startElement.getBBox(); // get the current
                                                        // location of start
                                                        // element
                var b2 = path.endElement.getBBox();// get the current location
                                                    // of end element
                // move the path to the new locations
                path.attr({
                    path : "M " + (b1.x + b1.width / 2) + " "
                            + (b1.y + b1.height / 2) + "L "
                            + (b2.x + b2.width / 2) + " "
                            + (b2.y + b2.height / 2),
                    opacity : Math.max(path.startElement.attr('opacity'),
                            path.endElement.attr('opacity'))
                });
            }
        }
    });
}

Not convinced this is the best way at all, but this is my first time using Raphael so I just did all that from looking at the Raphael source code...

like image 864
zcourts Avatar asked Nov 13 '22 07:11

zcourts


1 Answers

In our app we have a line tool. We drop a line with 2 movable end points onto our paper.

All shapes in our app have an associated VisualModel that contains all the geometric data inside of them. These VisualModels also double as Actors. Any Actor can subscribe to any other Actor and when changes occur all interested parties respond.

A system like this allows for a line's path to change via a redraw function that gets called anytime the 2 connected objects modify their X/Y coordinates.

connecting_line.js (redraw)

redraw: function() {
    var x1 = this.shapeView1.visualModel.get('x'),
        y1 = this.shapeView1.visualModel.get('y'),
        x2 = this.shapeView2.visualModel.get('x'),
        y2 = this.shapeView2.visualModel.get('y'),
        pathData;

    pathData = 'M' + x1 + ',' + y1 + 'L' + x2 + ',' + y2;

    this.line.attr({
        path: pathData,
        fill: '#000000',
        stroke: LineConstants.COLOR,
        'stroke-width': LineConstants.THICKNESS
    });
}

We created "movable" mixin. This mixin would allow you to add movability to your shapes. This mixin would update the x/y coordinates in and in turn trigger a 'change' event that your line class would pick up.

movable.js

handleDraggging: function(delta) {
    this.shape.move(delta);
}

move: function(delta) {
    //... compute movement based on delta
    this.visualModel.set('x', xPosition);
    this.visualModel.set('y', yPosition);
}

connecting_line.js

initialize: function(shapeView1, shapeView2) {
    // ...
    this.shapeView1 = shapeView1;
    this.shapeView2 = shapeView2;

    this.listenTo(shapeView1.visualModel, 'change:x change:y', this.redraw);
    this.listenTo(shapeView2.visualModel, 'change:x change:y', this.redraw);
}

Performance for this is great. You can take a look at it in action by going to eventbrite.com, create an event, enabling reserved seating (step 2), add a new map, click 'objects' on the left and drop a line on the paper.

like image 178
Parris Avatar answered Nov 16 '22 03:11

Parris