paul tarvydas
paul tarvydas

Reputation: 113

snap svg gluing line endpoints for drag and drop

I've got two visual objects that I can drag. I want to "glue" a line between them so that when I drag one object the line adjusts and stays stuck to the same relative point on the dragged object (think node-red, jointjs, cad/cam).

The visual objects are created using groups of elements (at 0,0). Dragging is implemented by translating the matrix for the group objects. (I want to use the matrix, as this will facilitate future zoom-in and zoom-out).

I think that I want to create a new kind of "line" which contains two elements - a start point and an end-point. The startpoint is grouped with visual object 1, the endpoint is grouped with object 2. If object 2 is dragged, the line's endpoint will be included in the matrix transformation for group 2 and will stay stuck with that object. (And similarly with object 1 and the startpoint).

I'm new to snap and js. I'm stuck on how to create such a new "line" element which contains startpoint and endpoint elements (e.g. how to extend the line prototype or "subclass" it).

Advice needed. Thanks.

Upvotes: 3

Views: 1697

Answers (1)

Dobz
Dobz

Reputation: 1213

I presume this is what you're looking for. It attaches a line between two squares on screen which you can drag.

Create a snap plugin for draggable rectangles.

Snap.plugin(function (Snap, Element, Paper, global, Fragment) {

Get the current transformation.

    function dragStart(x, y, e) {
        this.current_transform = this.transform();
    }

Update the current transformation.

    function dragMove(dx, dy, x, y, e) {
        this.transform(this.current_transform+'T'+dx+','+dy);
        this.updatePaths();
    }

Save the current transformation.

    function dragEnd(e) {
        this.current_transform = this.transform();
    }

Go through each saved path and update it's path attribute. Call prependTo - to make sure it is behind the rectangles.

    function updatePaths() {
        var key;
        for(key in this.paths) {
            this.paths[key][0].attr({"path" : this.getPathString(this.paths[key][1])});
            this.paths[key][0].prependTo(paper);
        }
    }

Get the coordinates of each element.

    function getCoordinates() {
        return [this.matrix.e + (this.node.width.baseVal.value / 2),
          this.matrix.f + (this.node.height.baseVal.value / 2)];
    }

Get the path string using both elements coordinates.

    function getPathString(obj) {
        var p1 = this.getCoordinates();
        var p2 = obj.getCoordinates();
        return "M"+p1[0]+","+p1[1]+"L"+p2[0]+","+p2[1];
    }

Add a path to two elements. Use their IDs as reference. Snap automatically creates a random ID for the elements so we don't need to.

    function addPath(obj) {
        var path = paper.path(this.getPathString(obj))
            .attr({
              fill:'none', 
              stroke:'blue',
              strokeWidth:1
            });

        path.prependTo(paper);
        this.paths[obj.id] = [path, obj];
        obj.paths[this.id] = [path, this];            
    }

Create your draggableRectangle protoype and attach all functionality.

    Paper.prototype.draggableRect = function (x, y, w, h) {

        var rect = paper.rect(0,0,w,h).attr({id: id}).transform("T"+x+","+y);
        rect.paths = {};
        rect.drag(dragMove, dragStart, dragEnd);
        rect.updatePaths = updatePaths;
        rect.getCoordinates = getCoordinates;
        rect.getPathString = getPathString;
        rect.addPath = addPath;

        return rect;
    };
});

Create the paper.

var paper = Snap("#svgout");

Create the draggable rectangles.

var rect1 = paper.draggableRect(0, 0, 40, 40);
var rect2 = paper.draggableRect(0, 0, 40, 40);

Attach them to each other.

rect1.addPath(rect2);

Here is a link to the JSFiddle

Upvotes: 3

Related Questions