user1010101
user1010101

Reputation: 1658

Connect two objects using fabric js

I currently have a canvas application where I can add objects(shapes). Here is my FIDDLE. You basically click on new simulation which will initialize the canvas, then you can add a circle or rectangle.

I am trying to add a feature called "Add child" where you can click on some object (shape) then click add child and add another object(shape) and they are both linked with a line. Something similar to this DEMO.

I figured the pesudo code for this feature would go something like as following:

function addChild(){

    get getActiveObject
    draw a line/arrow connect it with getActiveObject
    draw object connected with line
    should be able to move it / strecth it around


}

I was wondering if this is possible and how to start. Please advice.

Upvotes: 6

Views: 9326

Answers (2)

potatopeelings
potatopeelings

Reputation: 41075

Here's a new version of what you are looking for including support for multiple connections and deletion

Add Child Functionality

function addChildLine(options) {
    canvas.off('object:selected', addChildLine);

    // add the line
    var fromObject = canvas.addChild.start;
    var toObject = options.target;
    var from = fromObject.getCenterPoint();
    var to = toObject.getCenterPoint();
    var line = new fabric.Line([from.x, from.y, to.x, to.y], {
        fill: 'red',
        stroke: 'red',
        strokeWidth: 5,
        selectable: false
    });
    canvas.add(line);
    // so that the line is behind the connected shapes
    line.sendToBack();

    // add a reference to the line to each object
    fromObject.addChild = {
        // this retains the existing arrays (if there were any)
        from: (fromObject.addChild && fromObject.addChild.from) || [],
        to: (fromObject.addChild && fromObject.addChild.to)
    }
    fromObject.addChild.from.push(line);
    toObject.addChild = {
        from: (toObject.addChild && toObject.addChild.from),
        to: (toObject.addChild && toObject.addChild.to) || []
    }
    toObject.addChild.to.push(line);

    // to remove line references when the line gets removed
    line.addChildRemove = function () {
        fromObject.addChild.from.forEach(function(e, i, arr) {
            if (e === line)
                arr.splice(i, 1);
        });
        toObject.addChild.to.forEach(function (e, i, arr) {
            if (e === line)
                arr.splice(i, 1);
        });
    }

    // undefined instead of delete since we are anyway going to do this many times
    canvas.addChild = undefined;
}

function addChildMoveLine(event) {
    canvas.on(event, function (options) {
        var object = options.target;
        var objectCenter = object.getCenterPoint();
        // udpate lines (if any)
        if (object.addChild) {
            if (object.addChild.from)
                object.addChild.from.forEach(function (line) {
                    line.set({ 'x1': objectCenter.x, 'y1': objectCenter.y });
                })
            if (object.addChild.to)
                object.addChild.to.forEach(function (line) {
                    line.set({ 'x2': objectCenter.x, 'y2': objectCenter.y });
                })
        }

        canvas.renderAll();
    });
}

window.addChild = function () {
    canvas.addChild = {
        start: canvas.getActiveObject()
    }

    // for when addChild is clicked twice
    canvas.off('object:selected', addChildLine);
    canvas.on('object:selected', addChildLine);
}

Since we don't have a canvas reference unless Add Animation is clicked the handler has to be attached in the Add Animation handler

window.newAnimation = function () {
    canvas = new fabric.Canvas('canvas');

    // we need this here because this is when the canvas gets initialized
    ['object:moving', 'object:scaling'].forEach(addChildMoveLine)
}

Deleting Lines when Deleting Objects

window.deleteObject = function () {
    var object = canvas.getActiveObject();

    // remove lines (if any)
    if (object.addChild) {
        if (object.addChild.from)
            // step backwards since we are deleting
            for (var i = object.addChild.from.length - 1; i >= 0; i--) {
                var line = object.addChild.from[i];
                line.addChildRemove();
                line.remove();
            }
        if (object.addChild.to)
            for (var i = object.addChild.to.length - 1; i >= 0; i--) {
                var line = object.addChild.to[i];
                line.addChildRemove();
                line.remove();
            }
    }

    // from original code
    object.remove();
}

Fiddle - http://jsfiddle.net/xvcyzh9p/

Upvotes: 3

potatopeelings
potatopeelings

Reputation: 41075

You need to listen for the object:selected event to add the connecting line and the object:moving event to update the line coordinates.

// function for drawing a line
function makeLine(coords) {
    return new fabric.Line(coords, {
        fill: 'red',
        stroke: 'red',
        strokeWidth: 5,
        selectable: false
    });
}

var canvas; 
window.newAnimation = function(){
   canvas = new fabric.Canvas('canvas');

   var selectedElement = null;
   canvas.on('object:selected', function(options) {
       // we are doing t oadd a connection
       if (canvas.connect) {
           canvas.connect = false;
           var from = selectedElement.getCenterPoint();
           var to = options.target.getCenterPoint();
           var line = makeLine([from.x, from.y, to.x, to.y]);
           canvas.add(line);

           // these take care of moving the line ends when the object moves
           selectedElement.moveLine = function() {
               var from = selectedElement.getCenterPoint();
               line.set({ 'x1': from.x, 'y1': from.y });
           };
           options.target.moveLine = function() {
               var to = options.target.getCenterPoint();
               line.set({ 'x2': to.x, 'y2': to.y });
           };
       }
       selectedElement = options.target;
   });

   canvas.on('object:moving', function(e) {
        e.target.moveLine();
        canvas.renderAll();
  });
}

window.addChild = function() {
   canvas.connect = true;
}

The window.addChild keeps track of the whether Add Child was clicked (and should be added to the onclick for the button) so that the next object:selected can draw the line (otherwise it simply keeps track of the currently selected element)

<button onClick="addChild()">Add Child</button>

Note that for a full solution you need to be able to cancel out of an Add Child and you probably want to handle object resizing (currently the line updates when you move the object but does not when you resize the object)


Fiddle - http://jsfiddle.net/ctcdaxop/

Upvotes: 6

Related Questions