Reputation: 1658
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
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
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