I am working at a project with fabric js. I tried to minimize my problem, so I'm hoping that the code isn't too messed up.
I am creating some Objects which are linked with each other:
A Line, which contains a Start and an Endpoint
A Circle, which is StartPoint of 1 line and Endpoint of another line
with this combination i can create different shapes(like a polygon) and modify my move-functions for them too.
When a Circle is dragged, the related Lines are scaling and moving too. (in my code you can move the lines too and the shape is resized after that, but i didnt put it into this example, bc this short extract should be enough to show what my problem is.)
I got a little example in jsfiddle: https://jsfiddle.net/bxgox7cr/
When you look at the ends of the lines, you can clearly see a cut, so the eye soon recognize, that this is not a connected shape but rather some lines which are close to each other. Is there a way to modify the look of the lines, that the shape looks "closed"?
Here is my code, i tried to put some comments, that it is easy to read:
var canvas = new fabric.Canvas('canvas');
fabric.Object.prototype.originX = fabric.Object.prototype.originY = 'center';
document.getElementById("canvas").tabIndex = 1000;
/** ------------creating a Line Object, which contains a start and an endpoint ------------**/
fabric.LineWithPoints = fabric.util.createClass(fabric.Line, {
initialize: function(points, options) {
options || (options = {});
this.callSuper('initialize', points, options);
options &&
this.set('type', options.type),
this.set('name', options.name),
this.set('start_point', options.start_point),
this.set('end_point', options.end_point),
this.set('current_x', options.current_x),
this.set('current_y', options.current_y)
},
setStartPointAndEndPoint: function(start_point, end_point) {
this.set({
start_point: start_point,
end_point: end_point
});
},
setValues: function(new_x1, new_y1, new_x2, new_y2) {
// console.log(this);
this.set({
x1: new_x1,
x2: new_x2,
y1: new_y1,
y2: new_y2
});
this.setCoords();
}
});
/**--- modifie the circle element, adding new functions for the movement of the object-------*/
fabric.LinePoint = fabric.util.createClass(fabric.Circle, {
initialize: function(options) {
options || (options = {});
this.callSuper('initialize', options);
options &&
this.set('subtype', 'line_point'),
this.set('x', this.left),
this.set('y', this.top)
},
setPointCoordinates: function(new_left, new_top) {
this.set({
x: new_left,
y: new_top,
left: new_left,
top: new_top
});
this.setCoords();
},
move: function(new_left, new_top) {
var wall_1 = this.line1;
var wall_2 = this.line2;
this.setPointCoordinates(new_left, new_top);
wall_1.setValues(wall_1.x1, wall_1.y1, this.getLeft(), this.getTop());
wall_2.setValues(this.getLeft(), this.getTop(), wall_2.x2, wall_2.y2);
canvas.renderAll();
},
});
/**------------------- Moving Function------------------------------------------------- */
canvas.on('object:moving', function(event) {
var object = event.target;
if (object.subtype == "line_point") {
object.move(object.getLeft(), object.getTop());
}
});
/**------------------------------ create functions for the objects -----------------------*/
function newCircleObject(left, top, wall_1, wall_2) {
var circle = new fabric.LinePoint({
left: left,
top: top,
strokeWidth: 2,
radius: 15,
fill: 'grey',
stroke: 'black',
opacity: 0.1,
perPixelTargetFind: true,
subtype: 'line_point',
includeDefaultValues: false
});
circle.hasControls = false;
circle.hasBorders = false;
circle.line1 = wall_1;
circle.line2 = wall_2;
return circle;
}
function newWallObject(coords) {
var wall = new fabric.LineWithPoints(coords, {
stroke: 'black',
strokeWidth: 6,
lockScalingX: true,
lockScalingY: true,
perPixelTargetFind: true,
subtype: 'line',
type: 'line',
padding: 10,
includeDefaultValues: false
});
wall.hasControls = false;
wall.hasBorders = false;
return wall;
}
/**------------------------------ adding the shapes--------------------------------*/
var wall_1 = newWallObject([100, 100, 100, 500]);
var wall_2 = newWallObject([100, 500, 500, 500]);
var wall_3 = newWallObject([500, 500, 500, 100]);
var wall_4 = newWallObject([500, 100, 100, 100]);
var end_point_1 = newCircleObject(wall_1.x1, wall_1.y1, wall_4, wall_1);
var end_point_2 = newCircleObject(wall_2.x1, wall_2.y1, wall_1, wall_2);
var end_point_3 = newCircleObject(wall_3.x1, wall_3.y1, wall_2, wall_3);
var end_point_4 = newCircleObject(wall_4.x1, wall_4.y1, wall_3, wall_4);
wall_1.setStartPointAndEndPoint(end_point_1.name, end_point_2.name);
wall_2.setStartPointAndEndPoint(end_point_2.name, end_point_3.name);
wall_3.setStartPointAndEndPoint(end_point_3.name, end_point_4.name);
wall_4.setStartPointAndEndPoint(end_point_4.name, end_point_1.name);
canvas.add(wall_1, wall_2, wall_3, wall_4, end_point_1, end_point_2, end_point_3, end_point_4);
Add strokeLineCap: 'round',:
function newWallObject(coords) {
var wall = new fabric.LineWithPoints(coords, {
stroke: 'black',
strokeWidth: 6,
lockScalingX: true,
lockScalingY: true,
perPixelTargetFind: true,
strokeLineCap: 'round',
subtype: 'line',
type: 'line',
padding: 10,
includeDefaultValues: false
});
wall.hasControls = false;
wall.hasBorders = false;
return wall;
}
I looked up: http://fabricjs.com/docs/fabric.Object.html#strokeLineCap
Related
I've been getting familiar w/ FabricJS on a project.
I have a custom class that extends fabric.Polyline. This custom class has many custom methods. My problem is : When I serialize the whole canvas and later on load it again with loadFromJSON(), the custom methods are not there anymore...
I noticed that the object before/after serialization are not exactly the same. Indeed, we can see that after serialization the method myCustomClassMethod() is not in the object's __proto__ anymore :
I know that loadFromJSON() has a reviver() method for re-adding event listeners, but how can I re-add methods? The tutorials say that I need to add a fromObject() method to save/restore objects, I tried playing with that but no luck.
It might also be that my de-serialized object is a "regular" object, and not a fabricjs object instance. But I haven't seen anywhere how to make it a fabric object again.
MRE down below. As you can see, method works if called before canvas reloading, but not if called afterwards.
// create a wrapper around native canvas element (with id="treeCanvas")
var canvas = new fabric.Canvas('treeCanvas', {
allowTouchScrolling: true,
});
// un-comment log below to inspect objects
canvas.on('mouse:up', function (e) {
if (e.target != null) {
//console.log(e.target);
}
});
// My TreeNode custom class
var TreeNode = fabric.util.createClass(fabric.Polyline, {
initialize: function (X, Y, armsArray, options) {
options || (options = {});
this.callSuper('initialize', options);
this.X = X;
this.Y = Y;
this.armsArray = armsArray;
this.set({ width: 160, height: 50, originX: 'center', originY: 'center' });
this.set({ left: this.X, top: this.Y, fill: 'rgba(255, 255, 255, 0)', stroke: 'black', selectable: true });
this.setCoords();
this.set({ pathOffset: { x: 0, y: 25 } });
for (point of this.armsArray) {
this.points.push({ x: point[0], y: point[1] });
this.points.push({ x: 0, y: 0 });
}
},
_render: function (ctx) {
this.callSuper('_render', ctx);
},
// this is the method that disappears after serializing
myCustomClassMethod: function () {
this.set({ stroke: 'green' });
canvas.renderAll();
}
});
// Adding an instance of my custom class to canvas
var node1 = new TreeNode(100, 100, [[-80, 50], [80, 50]], []);
canvas.add(node1);
canvas.renderAll();
// node1.myCustomClassMethod(); // method works before serializing + deserializing
var stringifiedCanvas = JSON.stringify(canvas);
canvas.loadFromJSON(stringifiedCanvas, canvas.renderAll.bind(canvas));
node1.myCustomClassMethod(); // method DOES NOT WORK anymore after deserializing
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/4.3.1/fabric.min.js"></script>
<div class="container">
<canvas id="treeCanvas" width="500" height="300"></canvas>
</div>
<script src="canvas.js"></script>
I am very far from being a fabricjs expert and I do things somewhat differently but you were missing a few important parts (marked with "** CHANGE" in the code below)
Hope this is of some help!
// create a wrapper around native canvas element (with id="treeCanvas")
var canvas = new fabric.Canvas('treeCanvas', {
allowTouchScrolling: true,
});
// un-comment log below to inspect objects
canvas.on('mouse:up', function(e) {
if (e.target != null) {
//console.log(e.target);
}
});
// ** CHANGE: define Treenode on the fabric instance
fabric.TreeNode = fabric.util.createClass(fabric.Polyline, {
// ** CHANGE: define the type
type: 'treeNode',
initialize: function(X, Y, armsArray, options) {
options || (options = {});
this.callSuper('initialize', options);
this.X = X;
this.Y = Y;
this.armsArray = armsArray;
this.set({
width: 160,
height: 50,
originX: 'center',
originY: 'center'
});
this.set({
left: this.X,
top: this.Y,
fill: null,
stroke: 'black',
selectable: true
});
this.setCoords();
this.set({
pathOffset: {
x: 0,
y: 25
}
});
for (point of this.armsArray) {
this.points.push({
x: point[0],
y: point[1]
});
this.points.push({
x: 0,
y: 0
});
}
},
_render: function(ctx) {
this.callSuper('_render', ctx);
},
// ** CHANGE: export the custom method when serializing
toObject: function() {
return fabric.util.object.extend(this.callSuper('toObject'), {
myCustomClassMethod: this.myCustomClassMethod
});
},
myCustomClassMethod: function(x, y, color) {
// console.log("myCustomClassMethod")
// console.log(this.myCustomClassMethod)
this.set({
left: x,
top: y,
stroke: color,
strokeWidth: 10
});
canvas.renderAll();
}
});
fabric.TreeNode.fromObject = function(object, callback) {
// console.log(object)
};
var node1 = new fabric.TreeNode(100, 100, [
[-80, 50],
[80, 50]
], []);
canvas.add(node1);
canvas.renderAll();
// DESERIALIZE
var stringifiedCanvas = JSON.stringify(canvas);
canvas.loadFromJSON(stringifiedCanvas, canvas.renderAll.bind(canvas));
// ** CHANGE: need to get a reference to the restored object
var restored = canvas.getObjects()[0]
///console.log(restored)
restored.myCustomClassMethod(200, 50, "red");
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/4.3.1/fabric.min.js"></script>
<div class="container">
<canvas id="treeCanvas" width="500" height="300"></canvas>
</div>
<script src="canvas.js"></script>
So I'm currently working on a project that requires similar functionality to what you would expect in a floorplan designing application, and I need to be able to essentially draw a polygon (room) by connecting points generated on-click, with lines (walls).
I've found a few really great examples that resemble the functionality I'm looking for, but I'm unsure of how to approach implementing it as a functional react component.
I've seen Fabric.js and Konva.js used in a few other implementations of this kind of functionality, but I can't seem to be able to find a react example to use as a reference..
Below is an example I found on codepen using jQuery
var min = 99;
var max = 999999;
var polygonMode = true;
var pointArray = new Array();
var lineArray = new Array();
var activeLine;
var activeShape = false;
var canvas
$(window).load(function() {
prototypefabric.initCanvas();
$('#create-polygon').click(function() {
prototypefabric.polygon.drawPolygon();
});
});
var prototypefabric = new function() {
this.initCanvas = function() {
canvas = window._canvas = new fabric.Canvas('c');
canvas.setWidth($(window).width());
canvas.setHeight($(window).height() - $('#nav-bar').height());
//canvas.selection = false;
canvas.on('mouse:down', function(options) {
if (options.target && options.target.id == pointArray[0].id) {
prototypefabric.polygon.generatePolygon(pointArray);
}
if (polygonMode) {
prototypefabric.polygon.addPoint(options);
}
});
canvas.on('mouse:up', function(options) {
});
canvas.on('mouse:move', function(options) {
if (activeLine && activeLine.class == "line") {
var pointer = canvas.getPointer(options.e);
activeLine.set({
x2: pointer.x,
y2: pointer.y
});
var points = activeShape.get("points");
points[pointArray.length] = {
x: pointer.x,
y: pointer.y
}
activeShape.set({
points: points
});
canvas.renderAll();
}
canvas.renderAll();
});
};
};
prototypefabric.polygon = {
drawPolygon: function() {
polygonMode = true;
pointArray = new Array();
lineArray = new Array();
activeLine;
},
addPoint: function(options) {
var random = Math.floor(Math.random() * (max - min + 1)) + min;
var id = new Date().getTime() + random;
var circle = new fabric.Circle({
radius: 5,
fill: '#ffffff',
stroke: '#333333',
strokeWidth: 0.5,
left: (options.e.layerX / canvas.getZoom()),
top: (options.e.layerY / canvas.getZoom()),
selectable: false,
hasBorders: false,
hasControls: false,
originX: 'center',
originY: 'center',
id: id,
objectCaching: false
});
if (pointArray.length == 0) {
circle.set({
fill: 'red'
})
}
var points = [(options.e.layerX / canvas.getZoom()), (options.e.layerY / canvas.getZoom()), (options.e.layerX / canvas.getZoom()), (options.e.layerY / canvas.getZoom())];
line = new fabric.Line(points, {
strokeWidth: 2,
fill: '#999999',
stroke: '#999999',
class: 'line',
originX: 'center',
originY: 'center',
selectable: false,
hasBorders: false,
hasControls: false,
evented: false,
objectCaching: false
});
if (activeShape) {
var pos = canvas.getPointer(options.e);
var points = activeShape.get("points");
points.push({
x: pos.x,
y: pos.y
});
var polygon = new fabric.Polygon(points, {
stroke: '#333333',
strokeWidth: 1,
fill: '#cccccc',
opacity: 0.3,
selectable: false,
hasBorders: false,
hasControls: false,
evented: false,
objectCaching: false
});
canvas.remove(activeShape);
canvas.add(polygon);
activeShape = polygon;
canvas.renderAll();
} else {
var polyPoint = [{
x: (options.e.layerX / canvas.getZoom()),
y: (options.e.layerY / canvas.getZoom())
}];
var polygon = new fabric.Polygon(polyPoint, {
stroke: '#333333',
strokeWidth: 1,
fill: '#cccccc',
opacity: 0.3,
selectable: false,
hasBorders: false,
hasControls: false,
evented: false,
objectCaching: false
});
activeShape = polygon;
canvas.add(polygon);
}
activeLine = line;
pointArray.push(circle);
lineArray.push(line);
canvas.add(line);
canvas.add(circle);
canvas.selection = false;
},
generatePolygon: function(pointArray) {
var points = new Array();
$.each(pointArray, function(index, point) {
points.push({
x: point.left,
y: point.top
});
canvas.remove(point);
});
$.each(lineArray, function(index, line) {
canvas.remove(line);
});
canvas.remove(activeShape).remove(activeLine);
var polygon = new fabric.Polygon(points, {
stroke: '#333333',
strokeWidth: 0.5,
fill: 'red',
opacity: 1,
hasBorders: false,
hasControls: false
});
canvas.add(polygon);
activeLine = null;
activeShape = null;
polygonMode = false;
canvas.selection = true;
}
};
* {
font-family: "Roboto", sans-serif;
font-weight: 100;
}
body {
overflow: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/4.2.0/fabric.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css" rel="stylesheet" />
<nav class="grey darken-4" role="navigation" id="nav-bar" style="height:50px;">
<div class="nav-wrapper container">
<a id="logo-container" class="brand-logo" style="line-height: 50px;font-size: 20px;">Fabric Polygon</a>
<ul id="nav-mobile" class="right hide-on-med-and-down">
<li><a id="create-polygon" style="line-height: 50px;font-size: 20px;cursor:pointer;">Create Polygon</a></li>
</ul>
</div>
</nav>
<div class="section no-pad-bot no-pad-top">
<canvas id="c"></canvas>
</div>
And this is essentially the end functionality I'd like to achieve.
I really appreciate any help or advice!
To begin with, you should keep in mind that react is meant for structuring the visual components of your application and their interaction with the rest of your code, that is to say react is not a framework that covers everything you may need.
I'm not even sure that react is what you need, maybe you need a canvas. Maybe the canvas resides in a react component.
Nevertheless, if you decide that you would like to go at it with react then lets break down what you need, visually:
a "canvas" component which contains all your polygons or lines
a "polygon" which contains lines
a "line" component.
to begin with, before the interaction, make sure you create a state that makes sense that will be sent to the canvas, maybe an array of polygon or line objects. a line object is clearly two points, maybe a color, etc.
If you got this far with a mock of your polgyon state, its time to add a state manager (like redux) and some event listeners to update the state when events happen in the DOM (like mouse or keyboard events.)
To summarize:
Use react for visual elements.
Use redux to manage the state of the canvas.
When initializing the parent component (the "canvas"), create event listeners that fire actions that affect the state.
var canvas = new fabric.Canvas('canvas');
canvas.setWidth(500);
canvas.setHeight(500);
canvas.setBackgroundColor('#ccc');
canvas.allowTouchScrolling = true;
var line = new fabric.Path('M100,350 Q200,100 300,350', {
fill: '',
stroke: 'red',
strokeWidth: 5,
objectCaching: false
});
var circle = new fabric.Circle({
radius: 15,
fill: 'blue',
lockScalingY: true,
lockScalingX: true,
left: 300,
top: 350
});
canvas.add(line, circle);
canvas.on('object:moving', function(event) {
var path = canvas.item(0);
path.path[1][3] = event.target.left;
path.path[1][4] = event.target.top;
path.setCoords();
canvas.calcOffset();
canvas.renderAll();
});
<script src="//cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.20/fabric.js"></script>
<canvas id="canvas"></canvas>
show demo on fiddle
When I move circle there is a change path of line. After change path of line I see wrong bounding rectangles.
How I can fix that?
I can just remove line and create again, but maybe there is an easier way to do it.
You can actually recalculate the bounding box, but you have to do by yourself.
So is actually tricky, you need to remove the current pathOffset from path, and then call the internal method to recalculate the bbox.
It works, but being an internal method and a non supported feature, it could be changed in the future.
var canvas = new fabric.Canvas('canvas');
canvas.setWidth(500);
canvas.setHeight(500);
canvas.setBackgroundColor('#ccc');
canvas.allowTouchScrolling = true;
var line = new fabric.Path('M100,350 Q200,100 300,350', {
fill: '',
stroke: 'red',
strokeWidth: 5,
objectCaching: false
});
var circle = new fabric.Circle({
radius: 15,
fill: 'blue',
lockScalingY: true,
lockScalingX: true,
left: 300,
top: 350
});
canvas.add(line, circle);
canvas.on('object:moving', function(event) {
var path = canvas.item(0);
path.path[1][3] = event.target.left;
path.path[1][4] = event.target.top;
path.pathOffset = null;
path._setPositionDimensions({});
path.setCoords();
canvas.calcOffset();
canvas.renderAll();
});
<script src="//cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.20/fabric.js"></script>
<canvas id="canvas"></canvas>
WARNING: turn the volume down before you run the snippet!
I want to be able to click on the stage to add a 'module' shape. But I have found that a click on the 'module' shape itself creates another, meaning that the stage.click listener is being fired when it should not be.
How can I have a stage.click listener that does not fire incorrectly when I click on a shape ?
var width = window.innerWidth;
var height = window.innerHeight;
var rectButtonClicked = false;
var stage = new Konva.Stage({
container: 'container',
width: width,
height: height
});
var layer = new Konva.Layer();
var group = new Konva.Group({
draggable: true
});
stage.on('contentClick', function() {
createModule();
});
function createModule() {
var mouseX = stage.getPointerPosition().x;
var mouseY = stage.getPointerPosition().y;
var rect = new Konva.Rect({ //module rect
x: mouseX,
y: mouseY,
width: 100,
height: 50,
cornerRadius: 5,
fill: '#BEDBDD',
stroke: '#807C7B',
strokeWidth: 2,
draggable: true
});
group.add(rect);
var buttonRect = new Konva.Rect({ //button
x: mouseX+80,
y: mouseY+20,
width: 10,
height: 10,
cornerRadius: 1,
fill: 'blue',
stroke: '#807C7B',
strokeWidth: 1,
});
group.add(buttonRect)
var text = new Konva.Text({ //text on module
x: mouseX + 20,
y: mouseY + 20,
//fontFamily: 'Calibri',
fontSize: 16,
text: 'OSC',
fill: 'black'
});
group.add(text);
var randomFreq = getRandomInt();
var osc = new Tone.Oscillator(randomFreq, "sawtooth");
layer.add(group);
stage.add(layer);
buttonRect.on('click', function() {
rectButtonClicked = !rectButtonClicked;
if(rectButtonClicked){
osc.toMaster().start();
this.setFill('red');
} else {
osc.stop();
this.setFill('blue');
}
});
}
function getRandomInt() {
min = Math.ceil(100);
max = Math.floor(1000);
return Math.floor(Math.random() * (max - min)) + min;
}
var width = window.innerWidth;
var height = window.innerHeight;
//var drag = false;
var rectButtonClicked = false;
var stage = new Konva.Stage({
container: 'container',
width: width,
height: height
});
var layer = new Konva.Layer();
var group = new Konva.Group({
draggable: true
});
stage.on('contentClick', function() {
createModule();
});
function createModule() {
var mouseX = stage.getPointerPosition().x;
var mouseY = stage.getPointerPosition().y;
var rect = new Konva.Rect({ //module rect
x: mouseX,
y: mouseY,
width: 100,
height: 50,
cornerRadius: 5,
fill: '#BEDBDD',
stroke: '#807C7B',
strokeWidth: 2,
draggable: true
});
group.add(rect);
var buttonRect = new Konva.Rect({ //button
x: mouseX+80,
y: mouseY+20,
width: 10,
height: 10,
cornerRadius: 1,
fill: 'blue',
stroke: '#807C7B',
strokeWidth: 1,
});
group.add(buttonRect)
var text = new Konva.Text({ //text on module
x: mouseX + 20,
y: mouseY + 20,
//fontFamily: 'Calibri',
fontSize: 16,
text: 'OSC',
fill: 'black'
});
group.add(text);
var randomFreq = getRandomInt();
var osc = new Tone.Oscillator(randomFreq, "sawtooth");
layer.add(group);
stage.add(layer);
buttonRect.on('click', function() {
rectButtonClicked = !rectButtonClicked;
if(rectButtonClicked){
osc.toMaster().start();
this.setFill('red');
} else {
osc.stop();
this.setFill('blue');
}
});
}
function getRandomInt() {
min = Math.ceil(100);
max = Math.floor(1000);
return Math.floor(Math.random() * (max - min)) + min;
}
<script src="https://tonejs.github.io/build/Tone.min.js"></script>
<script src="https://cdn.rawgit.com/konvajs/konva/1.7.6/konva.min.js"></script>
<div id="container"></div>
The stage.contentClick() listener is a special case to be used when you want the stage to listen to events on the stage content. However, the cancelBubble() function does not stop events bubbling from say a click on a shape to the stage.contentClick() listener.
To get the effect that you want, which is to give the impression that a click on the stage has happened, you need to add a rect that fills the stage and listen for events on that rect instead of the stage.
Below is a working example. The red background I added deliberately so you know there is something else above the stage. To remove this take out the fill color on the clickRect.
I also fixed up your buttons so that the contents are correctly grouped and drag together. You were almost correct but you needed the group to be created within in the createModule() function. You can see that I also made the group elements dragabble = false to complete the process.
I added a couple of console writes to show when the events fire.
[Also I got quite a shock when I switched on the tone for tone].
var width = window.innerWidth;
var height = window.innerHeight;
//var drag = false;
var rectButtonClicked = false;
var stage = new Konva.Stage({
container: 'container',
width: width,
height: height
});
var layer = new Konva.Layer();
stage.add(layer);
var clickRect = new Konva.Rect({
x:0,
y:0,
width: width,
height: height,
fill: 'red',
stroke: '#807C7B',
strokeWidth: 2,
listening: 'true'
})
layer.add(clickRect);
clickRect.on('click', function() {
console.log('Stage click');
createModule();
});
function createModule() {
var group = new Konva.Group({ // move group create into createModule
draggable: true // we will make the elements not draggable - we drag the group
});
var mouseX = stage.getPointerPosition().x;
var mouseY = stage.getPointerPosition().y;
var rect = new Konva.Rect({ //module rect
x: mouseX,
y: mouseY,
width: 100,
height: 50,
cornerRadius: 5,
fill: '#BEDBDD',
stroke: '#807C7B',
strokeWidth: 2,
draggable: false // make the element not draggable - we drag the group
});
group.add(rect);
rect.on('click', function(evt){
console.log('Clicked on button');
})
var buttonRect = new Konva.Rect({ //button
x: mouseX+80,
y: mouseY+20,
width: 10,
height: 10,
cornerRadius: 1,
fill: 'blue',
stroke: '#807C7B',
strokeWidth: 1,
listening: true,
draggable: false // make the element not draggable - we drag the group
});
group.add(buttonRect)
var text = new Konva.Text({ //text on module
x: mouseX + 20,
y: mouseY + 20,
//fontFamily: 'Calibri',
fontSize: 16,
text: 'OSC',
fill: 'black',
draggable: false // make the element not draggable - we drag the group
});
group.add(text);
var randomFreq = getRandomInt();
var osc = new Tone.Oscillator(randomFreq, "sawtooth");
layer.add(group);
stage.add(layer);
buttonRect.on('click', function(evt) {
rectButtonClicked = !rectButtonClicked;
if(rectButtonClicked){
osc.toMaster().start();
this.setFill('red');
} else {
osc.stop();
this.setFill('blue');
}
});
}
function getRandomInt() {
min = Math.ceil(100);
max = Math.floor(1000);
return Math.floor(Math.random() * (max - min)) + min;
}
stage.draw(); // draw so we can see click rect.
<script src="https://tonejs.github.io/build/Tone.min.js"></script>
<script src="https://cdn.rawgit.com/konvajs/konva/1.7.6/konva.min.js"></script>
<div id="container" style="background-color: gold;"></div>
I try to add several times the same element in my paper.
Then I created it thanks to embed and then I cloned it with deep:true.
My problem is that deep:true change some properties (I don't know which) but I'm now enable to apply a translation to it.
here is my code, that work without deep:true but doesn't with. (but clone a rect and not my combined element) :
var width=400, height=1000;
var graph = new joint.dia.Graph; //define a graph
var paper = new joint.dia.Paper({ //define a paper
el: $('#myholder'),
width: width,
height: height,
gridSize: 1,
model: graph, //include paper in my graph
restrictTranslate: function(element) { //restriction of drag n drop to the vertical line (x never changes)
return {
x: element.model.attributes.position.x, //this represent the original x position of my element
y: 0,
width: 0,
height: height
};
}
});
var rect1 = new joint.shapes.basic.Rect({ //create first rect (with input and output)
position: { x: 100, y: 20 },
size: { width: 90, height: 90 },
attrs: {
rect: { fill: '#2ECC71' }
}
});
var add_block= new joint.shapes.basic.Circle({ //create add button
position: { x: 170, y: 20 },
size: { width: 20, height: 20 },
attrs: {
circle: { fill: '#2ECC71'},
text:{text:'+', "font-size":20}
}
});
rect1.embed(add_block);
graph.addCell([rect1,add_block]);
var clones=rect1.clone({deep:true});
graph.addCells(clones);
var line = V('line', { x1: 250, y1: 10, x2: 250, y2: 500, stroke: '#ddd' });
V(paper.viewport).append(line);
//Create a new block and link between two blocks
//chek if click on + button
//if click on this button, create a new block and link between them
paper.on('cell:pointerclick', function(cellView, evt, x, y) {
var elementBelow = graph.get('cells').find(function(cell) {
if (!cell.get('parent')) return false;//not interested in parent
if (cell instanceof joint.dia.Link) return false; // Not interested in links.
if (cell.getBBox().containsPoint(g.point(x, y))) {
return true;
}
return false;
});
if(elementBelow) //if we start from a block, let's go!
{
rect=rect1.clone({deep:true});
graph.addCell(rect);
rect.translate(200, 0);
}
});