Search code examples
javascriptfabricjs

Fabricjs function to join 2 objects with line


I have a canvas where I can click to place icons and link those icons with a line, but I can't manage to get the lines to follow when moving the icon objects. I tried the following, but I can't manage to lock the ends of the lines to the icon objects.

My effort so far: JSFIDDLE

canvas.on('mouse:move', function (obj) {
var line = canvas.getItemByName('line');
var objEl = canvas.getActiveObject();
var type = objEl.get('type');
var leftEl = objEl.left;
var topEl = objEl.top;

canvas.getObjects().forEach((obj) => {
    var lineX1 = line.get('x1');
    var lineY1 = line.get('y1');
    var lineX2 = line.get('x2');
    var lineY2 = line.get('y2');

    if (lineX1 == leftEl && lineY1 == topEl) {
        line.set({
            x1: leftEl,
            y1: topEl
        });
        canvas.renderAll();
    };
});

line.set('opacity', 1);
});

enter image description here


Solution

  • With the help of this answer I was able to reference to any canvas object and lock the lines as I go.

    var canvas = new fabric.Canvas('c');
    
    fabric.Canvas.prototype.getItemByName = function(name) {
      var object = null,
          objects = this.getObjects();
    
      for (var i = 0, len = this.size(); i < len; i++) {
        if (objects[i].name && objects[i].name === name) {
          object = objects[i];
          break;
        }
      }
    
      return object;
    };
    
    var currentColor;
    var defaultIcon = {
      width: 40,
      height: 30,
      originX: 'center',
      originY: 'center',
      hasControls: false,
    };
    var iconTriangle = new fabric.Triangle(defaultIcon);
    setColor('green');
    canvas.add(iconTriangle);
    //disable icon & hide when hovering over existing icon
    canvas.on('mouse:over', function(obj) {
      iconTriangle.set('opacity', 0);
      canvas.renderAll();
    });
    //restor icon & unhide
    canvas.on('mouse:out', function(e) {
      if (document.getElementById("on").checked == true) {
        // if 'target' is null, means mouse is out of canvas
        if (e.target) {
          iconTriangle.set('opacity', 1);
        } else {
          iconTriangle.left = -100;
          iconTriangle.top = -100;
        }
        canvas.renderAll();
      };
    });
    //move pointer icon
    canvas.on('mouse:move', function(obj) {
      iconTriangle.top = obj.e.y - 100;
      iconTriangle.left = obj.e.x - 10;
      canvas.renderAll();
    });
    
    //count each by type and place new icon
    canvas.on('mouse:up', function(e) {
      if (e.target) {
        return
      }
      var red = getObjectsBy((obj) => obj.fill === 'red').length;
      var green = getObjectsBy((obj) => obj.fill === 'green').length;
      var yellow = getObjectsBy((obj) => obj.fill === 'yellow').length;
      document.getElementById("greentally").value = green;
      document.getElementById("yellowtally").value = yellow;
      document.getElementById("redtally").value = red;
      addIcon(e.e.x - 10, e.e.y - 100, currentColor);
    });
    
    function setColor(color) {
      currentColor = color;
      iconTriangle.setColor(currentColor);
      canvas.renderAll();
    }
    
    function getObjectsBy(fn) {
      return canvas.getObjects().filter(fn)
    }
    
    function addIcon(x, y, color) {
      var icon = new fabric.Triangle(defaultIcon);
      if (document.getElementById("icon").checked == true) {
        icon.setColor(color);
        icon.left = x;
        icon.top = y;
        canvas.add(icon);
      } else if (document.getElementById("link").checked == true) {
        iconTriangle.set('opacity', 0);
        icon = null;
      };
      canvas.renderAll();
    }
    
    //set icon type
    $(".switch").click(function() {
      if (document.getElementById("green").checked == true) {
        setColor('green');
      } else if (document.getElementById("yellow").checked == true) {
        setColor('yellow');
      } else if (document.getElementById("red").checked == true) {
        setColor('red');
      }
    });
    
    document.getElementById("icon").checked = true;
    
    //link icons with line
        $("input").change(function () {
    
            mode = "draw";
    
            canvas.on('mouse:up', function (o) {
                isDown = true;
    
                var icon = canvas.getItemByName('icon');
    
                if (document.getElementById("link").checked == true) {
                    if (canvas.getActiveObjects().length == 1) {
                        mode = "draw";
    
                        var pointer = canvas.getActiveObject();
                        var points = [pointer.left, pointer.top, pointer.left, pointer.top];
    
                        if (mode == "draw") {
                            line1 = new fabric.Line(points, {
                                strokeWidth: 5,
                                fill: '#39ff14',
                                stroke: '#39ff14',
                                originX: 'center',
                                originY: 'center',
                                selectable: false,
                                hasControls: false,
                                hasBorders: false,
                                evented: false,
                                targetFindTolerance: true,
                                name: 'line1',
                            });
                            canvas.add(line1);
                            line1.sendToBack();
                        };
                        canvas.renderAll();
                    } else {
                        return;
                    };
                };
    
            });
    
            if (document.getElementById("link").checked == true) {
                canvas.on('mouse:move', function (o) {
                    line1.set('opacity', 0.4);
                    if (o.target !== null) {
                        var x2 = o.target.left;
                        var y2 = o.target.top;
                        if (mode == "draw") {
                            line1.set({
                                x2: x2,
                                y2: y2
                            });
                            canvas.renderAll();
                        }
                    } else {
                        var pointer = canvas.getPointer(o.e);
    
                        if (mode == "draw") {
                            line1.set({
                                x2: pointer.x,
                                y2: pointer.y
                            });
                            canvas.renderAll();
                        }
                    }
                    line1.set('opacity', 1);
                });
    
                canvas.on('mouse:down', function (e) {
                    if (document.getElementById("link").checked == true) {
                        canvas.selection = false;
                        var line1 = canvas.getItemByName('line1');
                        var objEl = e.target;
                        var type = objEl.get('type');
                        if (type !== 'text') {
                            canvas.remove(line1);
                        }
                    };
                });
            };
            canvas.renderAll();
        });
        
            $("input").change(function () {
            if (document.getElementById("link").checked == true) {
    
                // we need this here because this is when the canvas gets initialized
        ['object:moving', 'object:scaling'].forEach(addChildMoveLine);
    
                function addChildLine(options) {
                    if (document.getElementById("link").checked == true) {
                        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], {
                            strokeWidth: 5,
                            fill: '#39ff14',
                            stroke: '#39ff14',
                            originX: 'center',
                            originY: 'center',
                            selectable: false,
                            hasControls: false,
                            hasBorders: false,
                            evented: false,
                            targetFindTolerance: true,
                            name: 'line',
                        });
                        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);
                            });
                        }
                        canvas.discardActiveObject();
                    };
                }
    
                function addChildMoveLine(event) {
                    if (document.getElementById("link").checked == true) {
                        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();
                        });
                    };
                }
    
                function addChild(o) {
                    if (document.getElementById("link").checked == true) {
                        var line = canvas.getItemByName('line');
                        var objEl = canvas.getActiveObject();
                        var type = objEl.get('type');
                        canvas.addChild = {
                            start: canvas.getActiveObject()
                        }
    
                        // for when addChild is clicked twice
                        canvas.off('object:selected', addChildLine);
                        canvas.on('object:selected', addChildLine);
                    };
                };
    
                canvas.on('mouse:down', function (e) {
                    if (document.getElementById("link").checked == true) {
                        var line = canvas.getItemByName('line');
                        canvas.selection = false;
                        if (e.target !== null) {
                            addChild();
                        } else {
                            canvas.discardActiveObject();
                        }
    
                        canvas.on('mouse:over', function () {
                            canvas.discardActiveObject();
                        });
    
                        canvas.on('mouse:out', function () {
                            canvas.forEachObject(function (obj) {
                                if (obj.get('name') !== 'cursor') {
                                    obj.set('opacity', 1);
                                }
                            });
                        });
    
                    };
                });
            };
        });
    canvas {
      border: 1px solid #ccc;
    }
    
    .tally {
      position: fixed;
      width: 50px;
      left: 255px;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.4.0/fabric.js"></script>
    
    <b>PLACE:</b>
    <input class="switch" name="iconOn" type="radio" id="icon" />
    <b>ICON</b>
    <input class="switch" name="iconOn" type="radio" id="link" />
    <b>LINK</b>
    <br>
    
    <input class="switch" checked name="iconType" type="radio" id="green" />Green
    <input class="tally" disabled id="greentally" type="text" value="0">
    <br>
    <input class="switch" name="iconType" type="radio" id="yellow" /> Yellow
    <input class="tally" disabled id="yellowtally" type="text" value="0">
    <br>
    <input class="switch" name="iconType" type="radio" id="red" /> red
    <input class="tally" disabled id="redtally" type="text" value="0">
    <canvas id="c" width="600" height="300"></canvas>