how to add random circles with no overlap inside a fabricjs canvas - javascript

I am trying to come up with an algorithm of adding random circles with no overlap inside fabricj canvas;
The bellow snippet is what i have tried.
const canvas = new fabric.Canvas('gameCanvas', {selection: false});
fabric.Object.prototype.originX = fabric.Object.prototype.originY = 'center';
let rectangle;
document.addEventListener('DOMContentLoaded', function(){
rectangle = new fabric.Rect({
width: 250,
height: 100,
fill: 'blue',
opacity: 0.2,
left: 200,
top: 140
});
canvas.add(rectangle);
});
document.getElementById('addCirclesBtn').addEventListener('click', function(){
drawCircles();
});
function drawCircles()
{
for(let i = 1; i <=20; i++)
{
let x= Math.random() * (rectangle.width - rectangle.left) + rectangle.left;
let y = Math.random() * (rectangle.height - rectangle.top) + rectangle.top;
let circle = new fabric.Circle({
left: x,
top: y,
strokeWidth: 2,
radius: 10,
fill: 'white',
stroke: '#666',
selectable: false,
hoverCursor: 'default',
hasControls: false,
hasBorders: false
});
canvas.add(circle);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.0.0/fabric.min.js"></script>
<canvas id="gameCanvas" width="500" height="300" style="border: 2px solid green;"></canvas>
<button id="addCirclesBtn">ADD CIRCLES</button>

This is what i came up with and the main logic is in the function drawCircles() which checks is circles are overlapping before adding inside the rectangle. It is to be noted that the program add 100 circles inside the rectangle
const canvas = new fabric.Canvas('gameCanvas', {selection: false});
fabric.Object.prototype.originX = fabric.Object.prototype.originY = 'center';
let rectangle;
const radius = 10;
let min_x;
let min_y;
let max_x;
let max_y;
let circles;
document.addEventListener('DOMContentLoaded', function(){
drawRectangle();
min_x = rectangle.left - rectangle.width / 2 ;
//min_x = 250 - 200/2;
max_x = rectangle.left + rectangle.width / 2;
//max_x = 250 + 200/2;
min_y = rectangle.top - rectangle.height / 2 ;
//min_y = 150 - 100/2;
max_y = rectangle.top + rectangle.height / 2;
//max_y = 150 + 100/2;
});
function drawRectangle()
{
rectangle = new fabric.Rect({
width: 200,
height: 100,
fill: 'blue',
opacity: 0.2,
left: 250,
top: 150
});
canvas.add(rectangle);
}
document.getElementById('addCirclesBtn').addEventListener('click', function(){
canvas.clear();
circles = [];
drawRectangle();
drawCircles();
});
function drawCircles()
{
let anti_crash_count = 0
while(circles.length < 100)
{
let circle = new fabric.Circle({
left: getRandomNumber(min_x + radius, max_x - radius),
top: getRandomNumber(min_y + radius, max_y - radius),
strokeWidth: 2,
radius: radius,
fill: 'white',
stroke: '#666',
selectable: false,
hoverCursor: 'default',
hasControls: false,
hasBorders: false,
originX:'center',
originY:'center'
});
let isOverlapping = false;
for(let j = 0; j < circles.length; j++)
{
let previous_circle = circles[j];
let distance = Math.hypot(circle.left - previous_circle.left, circle.top - previous_circle.top);
if(distance < (circle.radius + previous_circle.radius))
{
isOverlapping = true;
break;
}
}
if(!isOverlapping)
{
canvas.add(circle);
circles.push(circle);
}
anti_crash_count ++;
if(anti_crash_count > 1000)
{
break;
}
}
}
function getRandomNumber(min, max)
{
return Math.floor((Math.random() * (max - min)) + min);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.0.0/fabric.min.js"></script>
<canvas id="gameCanvas" width="500" height="300" style="border: 2px solid green;"></canvas>
<button id="addCirclesBtn">ADD CIRCLES</button>
I would appreciate any idea on how to improve it if necessary.

Related

I want to get shape info on which I am hovering as I am doing in my second code eg with konva. I want to achive same to html canva without looping

This is the result I want to achieve but without looping through all the points. Currently I have to loop through all the point and then need to find the circle which I am hovering so I would like to have a single function instead of that like we have in konva.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
#canvasOne {
position: absolute;
top: 0;
left: 0;
background-color: black;
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<canvas id="canvasOne"></canvas>
<script>
const canvas = document.getElementById("canvasOne");
const context = canvas.getContext("2d");
const circleArray = [];
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
class Circle {
constructor(xpoint, ypoint, radius, color, idOfCircle) {
this.xpoint = xpoint;
this.ypoint = ypoint;
this.radius = radius;
this.color = color;
this.circleId = idOfCircle;
}
draw(context) {
context.beginPath();
context.arc(
this.xpoint,
this.ypoint,
this.radius,
0,
Math.PI * 2,
false
);
context.fillStyle = this.color;
context.fill();
context.closePath();
}
changeColor(newColor) {
this.color = newColor;
this.draw(context);
}
clickCircle(xmouse, ymouse) {
const distance = Math.sqrt(
(xmouse - this.xpoint) * (xmouse - this.xpoint) +
(ymouse - this.ypoint) * (ymouse - this.ypoint)
);
if (distance < this.radius) {
this.changeColor("#56f");
return true;
} else {
this.changeColor("#f56");
return false;
}
}
}
for (var i = 0; i < 30; i++) {
var radius = 20;
var xPos = 120;
var yPos = 100;
var id = 1;
for (var i = 0; i < 300; i++) {
circleArray.push(
new Circle(xPos, yPos, radius, "#f56", id)
);
circleArray[i].draw(context);
xPos += 70;
if ((i + 1) % 25 === 0) {
yPos += 70;
var xPos = 120;
}
id = id + 1;
}
}
canvas.addEventListener("mousemove", (e) => {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.top;
const y = e.clientY - rect.left;
for (let i = 0; i < circleArray.length; i++) {
const distance = Math.sqrt(
(x - circleArray[i].xpoint) *
(x - circleArray[i].xpoint) +
(y - circleArray[i].ypoint) *
(y - circleArray[i].ypoint)
);
if (distance < circleArray[i].radius) {
circleArray[i]?.clickCircle(x, y);
console.log("circleArray[i]: ", circleArray[i]);
} else {
circleArray[i]?.clickCircle(x, y);
}
}
});
</script>
</body>
</html>
I want to achieve somewhat like this in html canvas without looping and using only one function.
Any help is appreciated.
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/konva#8.3.5/konva.min.js"></script>
<meta charset="utf-8" />
<title>Konva Circle Demo</title>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
background-color: #f0f0f0;
}
</style>
</head>
<body>
<div id="container"></div>
<script>
var width = window.innerWidth;
var height = window.innerHeight;
var stage = new Konva.Stage({
container: "container",
width: width,
height: height,
});
var layer = new Konva.Layer();
var circle = new Konva.Circle({
x: stage.width() / 2,
y: stage.height() / 2,
radius: 70,
fill: "red",
stroke: "black",
strokeWidth: 4,
id: "circle",
});
layer.add(circle);
var rect1 = new Konva.Rect({
x: 20,
y: 20,
width: 100,
height: 50,
fill: "green",
stroke: "black",
strokeWidth: 4,
});
// add the shape to the layer
layer.add(rect1);
var rect2 = new Konva.Rect({
x: 150,
y: 40,
width: 100,
height: 50,
fill: "red",
shadowBlur: 10,
cornerRadius: 10,
});
layer.add(rect2);
var rect3 = new Konva.Rect({
x: 50,
y: 120,
width: 100,
height: 100,
fill: "blue",
cornerRadius: [0, 10, 20, 30],
});
layer.add(rect3);
// add the shape to the layer
layer.add(circle);
// add the layer to the stage
stage.add(layer);
document
.getElementById("container")
.addEventListener("mousemove", (e) => {
var shape = stage.getIntersection({
x: e.clientX,
y: e.clientY,
});
console.log("shape: ", shape.attrs?.id);
});
</script>
</body>
</html>

Adding objects to a rotated Group

When I add an object to a group that is rotated in FabricJS it doesn't respect the angle of the other objects.
Steps to reproduce:
Select an object
Click add to group
Select group
Click Rotate group
Click add to group
The group now contains objects that have different angles.
var canvas = new fabric.Canvas("canvas", {
width: 400,
height: 400
});
canvas.add(new fabric.Triangle({
top: 100,
left: 100,
width: 50,
height: 50,
fill: '#f55'
}));
$("#rotate").on("click", function() {
var currentObject = canvas.getActiveObject();
var currentAngle = currentObject.angle;
var adjustedAngle = currentAngle + 90;
if (adjustedAngle >= 360) {
adjustedAngle = 0;
}
currentObject.setAngle(adjustedAngle);
canvas.renderAll();
});
$("#add").on('click', function() {
if (canvas.getActiveObject()) {
var activeObject = canvas.getActiveObject();
var object;
console.log(activeObject.type);
if (activeObject.type == "triangle") {
object = fabric.util.object.clone(activeObject);
object.top = activeObject.top;
object.left = activeObject.left + 25;
object.fill = "#0000FF";
var tempAngle = activeObject.angle;
activeObject.angle = 0;
object.angle = 0;
var group = new fabric.Group([activeObject, object], {
angle: tempAngle
});
canvas.add(group);
canvas.setActiveObject(group);
canvas.remove(activeObject);
} else {
var activeGroup = activeObject;
var lastAddedObject = activeGroup.getObjects()[activeGroup.size() - 1];
object = fabric.util.object.clone(lastAddedObject);
object.set('top', lastAddedObject.top + activeGroup.top + activeGroup.height / 2);
object.set('left', lastAddedObject.left + activeGroup.left + activeGroup.width / 2 + 25);
console.log(lastAddedObject.angle);
activeGroup.addWithUpdate(object);
canvas.renderAll();
}
}
});
canvas {
border: 1px solid red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.6.0-rc.1/fabric.js"></script>
<button id="rotate">Rotate</button>
<button id="add">Add to Group</button>
<canvas id="canvas" width=400 height=400></canvas>
I linked the latest version where adding items to rotated group works.
I do not really understand what effect you are trying to achieve.
If group has angle of 30, when you add an object with angle of 0, it will not be rotated.
if you want an object to jump in a rotated group and be rotated as the other, you have to add some calculations. In those calculations you have to consider that everytime you use addWithUpdate the group is destroyed and created again and that reset the angle to 0.
var canvas = new fabric.Canvas("canvas", {
width: 400,
height: 400
});
canvas.add(new fabric.Triangle({
top: 100,
left: 100,
width: 50,
height: 50,
fill: '#f55'
}));
$("#rotate").on("click", function() {
var currentObject = canvas.getActiveObject();
var currentAngle = currentObject.angle;
var adjustedAngle = currentAngle + 90;
if (adjustedAngle >= 360) {
adjustedAngle = 0;
}
currentObject.setAngle(adjustedAngle);
canvas.renderAll();
});
$("#add").on('click', function() {
if (canvas.getActiveObject()) {
var activeObject = canvas.getActiveObject();
var object;
console.log(activeObject.type);
if (activeObject.type == "triangle") {
object = fabric.util.object.clone(activeObject);
object.top = activeObject.top;
object.left = activeObject.left + 25;
object.fill = "#0000FF";
var tempAngle = activeObject.angle;
activeObject.angle = 0;
object.angle = 0;
var group = new fabric.Group([activeObject, object], {
});
canvas.add(group);
canvas.setActiveObject(group);
canvas.remove(activeObject);
} else {
var activeGroup = activeObject;
var lastAddedObject = activeGroup.getObjects()[activeGroup.size() - 1];
object = fabric.util.object.clone(lastAddedObject);
object.set('top', lastAddedObject.top + activeGroup.top + activeGroup.height / 2);
object.set('left', lastAddedObject.left + activeGroup.left + activeGroup.width / 2 + 25);
//object.set('angle', activeObject.angle);
activeGroup.addWithUpdate(object);
canvas.renderAll();
}
}
});
canvas {
border: 1px solid red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<script src="http://www.deltalink.it/andreab/fabric/fabric.js"></script>
<button id="rotate">Rotate</button>
<button id="add">Add to Group</button>
<canvas id="canvas" width=400 height=400></canvas>

fabric.js arrow snapped to grid

I've adapted some code to snap a line to a grid using Fabric.js, as in the code below. What I want is for it to instead be an arrow. I can't seem to get the arrowhead (triangle) to move properly. I kept breaking the code by trying to add a triangle as the arrowhead, so I removed all code attempting to add it. Please help me add the arrowhead, thank you!!
var canvas = new fabric.Canvas('c', { selection: false });
var grid = 50;
for (var i = 0; i < (600 / grid); i++) {
canvas.add(new fabric.Line([ i * grid, 0, i * grid, 600], { stroke: '#ccc', selectable: false }));
canvas.add(new fabric.Line([ 0, i * grid, 600, i * grid], { stroke: '#ccc', selectable: false }))
}
var line, isDown;
canvas.on('mouse:down', function(o){
canvas.remove(line);
isDown = true;
var pointer = canvas.getPointer(o.e);
var points = [ Math.round(pointer.x / grid) * grid, Math.round(pointer.y / grid) * grid, pointer.x, pointer.y ];
line = new fabric.Line(points, {
strokeWidth: 5,
fill: 'red',
stroke: 'red',
originX: 'center',
originY: 'center'
});
canvas.add(line);
});
canvas.on('mouse:move', function(o){
if (!isDown) return;
var pointer = canvas.getPointer(o.e);
line.set({ x2: pointer.x, y2: pointer.y });
canvas.renderAll();
});
canvas.on('mouse:up', function(o){
var pointer = canvas.getPointer(o.e);
isDown = false;
line.set({ x2: Math.round(pointer.x/ grid) * grid, y2: Math.round(pointer.y/ grid) * grid });
canvas.renderAll();
});
<script src="https://rawgithub.com/kangax/fabric.js/master/dist/fabric.js"></script>
<canvas id="c" width="500" height="500" style="border:1px solid #ccc"></canvas>
Got 'em! For me, the trick was defining the center of the triangle following the method in this fiddle: http://jsfiddle.net/ug2gskj1/
left: line.get('x1') + deltaX;
top: line.get('y1') + deltaY;
where
centerX = (line.x1 + line.x2) / 2;
centerY = (line.y1 + line.y2) / 2;
deltaX = line.left - centerX;
deltaY = line.top - centerY;
window.onload = function() {
var canvas = new fabric.Canvas('c', {
selection: false
});
var grid = 50;
for (var i = 0; i < (600 / grid); i++) {
canvas.add(new fabric.Line([i * grid, 0, i * grid, 600], {
stroke: '#ccc',
selectable: false
}));
canvas.add(new fabric.Line([0, i * grid, 600, i * grid], {
stroke: '#ccc',
selectable: false
}))
}
var line, triangle, isDown;
function calcArrowAngle(x1, y1, x2, y2) {
var angle = 0,
x, y;
x = (x2 - x1);
y = (y2 - y1);
if (x === 0) {
angle = (y === 0) ? 0 : (y > 0) ? Math.PI / 2 : Math.PI * 3 / 2;
} else if (y === 0) {
angle = (x > 0) ? 0 : Math.PI;
} else {
angle = (x < 0) ? Math.atan(y / x) + Math.PI : (y < 0) ? Math.atan(y / x) + (2 * Math.PI) : Math.atan(y / x);
}
return (angle * 180 / Math.PI + 90);
}
canvas.on('mouse:down', function(o) {
canvas.remove(line, triangle);
isDown = true;
var pointer = canvas.getPointer(o.e);
var points = [Math.round(pointer.x / grid) * grid, Math.round(pointer.y / grid) * grid, pointer.x, pointer.y];
line = new fabric.Line(points, {
strokeWidth: 5,
fill: 'red',
stroke: 'red',
originX: 'center',
originY: 'center'
});
centerX = (line.x1 + line.x2) / 2;
centerY = (line.y1 + line.y2) / 2;
deltaX = line.left - centerX;
deltaY = line.top - centerY;
triangle = new fabric.Triangle({
left: line.get('x1') + deltaX,
top: line.get('y1') + deltaY,
originX: 'center',
originY: 'center',
hasBorders: false,
hasControls: false,
lockScalingX: true,
lockScalingY: true,
lockRotation: true,
pointType: 'arrow_start',
angle: -45,
width: 20,
height: 20,
fill: 'red'
});
canvas.add(line, triangle);
});
canvas.on('mouse:move', function(o) {
//function angle(x1,y1,x2,y2){angle=Math.atan((y2-y1)/(x2-x1))*180/Math.PI+90; return angle;}
if (!isDown) return;
var pointer = canvas.getPointer(o.e);
line.set({
x2: pointer.x,
y2: pointer.y
});
triangle.set({
'left': pointer.x + deltaX,
'top': pointer.y + deltaY,
'angle': calcArrowAngle(line.x1, line.y1, line.x2, line.y2)
});
canvas.renderAll();
});
canvas.on('mouse:up', function(o) {
var pointer = canvas.getPointer(o.e);
isDown = false;
snappedxCoordinate = Math.round(pointer.x / grid) * grid;
snappedyCoordinate = Math.round(pointer.y / grid) * grid;
snappedxCoordinateArrowhead = Math.round((pointer.x + deltaX) / grid) * grid;
snappedyCoordinateArrowhead = Math.round((pointer.y + deltaY) / grid) * grid;
line.set({
x2: snappedxCoordinate,
y2: snappedyCoordinate
});
triangle.set({
'left': snappedxCoordinateArrowhead,
'top': snappedyCoordinateArrowhead,
'angle': calcArrowAngle(line.x1, line.y1, line.x2, line.y2)
});
canvas.renderAll();
});
}
<script src="https://rawgithub.com/kangax/fabric.js/master/dist/fabric.js"></script>
<canvas id="c" width="500" height="500" style="border:1px solid #ccc"></canvas>

How to restrict resizing a circle/rectangle to some radius/width/height in fabricjs?

I am using Fabricjs and trying to restrict a rectangle so that it cannot be resized after it reaches a minimum radius in case of circle and minimum width/height in case of rectangle :
This is what I have done so far : JSFIDDLE .
Code :
`
var canvas = new fabric.Canvas("canvas2");
var circle, isDown, origX, origY;
circle = new fabric.Circle({
left: 20,
top: 30,
originX: 'left',
originY: 'top',
radius: 30,
angle: 0,
fill: '',
stroke:'red',
strokeWidth:3,
});
canvas.add(circle);
function checkscale(e) {
var obj = e.target;
obj.setCoords();
var b = obj.getBoundingRect();
if(obj.toJSON().objects[0].radius == undefined) {
//rectangle case
if(obj.width < 27) {
obj.width = 27;
}
if(obj.height < 27) {
obj.height = 27;
}
return;
} else {
//circle case
if(obj.width < 48) {
obj.width = 48;
}
if(obj.height < 48) {
obj.height = 48;
}
return;
}
if (!(b.left >= minX && maxX >= b.left + b.width && maxY >= b.top + b.height && b.top >= minY)) {
obj.left = obj.lastLeft;
obj.top = obj.lastTop;
obj.scaleX= obj.lastScaleX
obj.scaleY= obj.lastScaleY
} else {
obj.lastLeft = obj.left;
obj.lastTop = obj.top;
obj.lastScaleX = obj.scaleX
obj.lastScaleY = obj.scaleY
}
}
canvas.observe("object:scaling", checkscale);
`
Can anybody do this?
You do not need to mess up with radius, toJson and width and height.
You are scaling a fabricJs object, so scaleX and scaleY is all you need.
Consider upgrading to fabricJs 1.6
var canvas = new fabric.Canvas("canvas2");
var circle, isDown, origX, origY;
circle = new fabric.Circle({
left: 20,
top: 30,
originX: 'left',
originY: 'top',
radius: 30,
angle: 0,
fill: '',
stroke:'red',
strokeWidth:3,
lockScalingFlip: true
});
canvas.add(circle);
function checkscale(e) {
var obj = e.target;
//var dim = obj._getTransformedDimensions();
if (obj.getWidth() < 50) {
obj.scaleX = obj.lastScaleX;
} else {
obj.lastScaleX = obj.scaleX;
}
if (obj.getHeight() < 50) {
obj.scaleY = obj.lastScaleY;
} else {
obj.lastScaleY = obj.scaleY;
}
}
canvas.observe("object:scaling", checkscale);
<script src="http://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.5.0/fabric.min.js" ></script>
<canvas id="canvas2" width=500 height=500 style="height:500px;width:500px;"></canvas>

Kinetic.js – creating a grid

I'm new to Kintetic.js and I'm trying to do a grid. The width is 800px and the height is 400px. And I want squares (20x20) to cover that area. Every square has a 1px border. So something like this:
var box = new Kinetic.Rect({
width: 20,
height: 20,
fill: 'transparent',
stroke: 'rgba(0, 0, 0, 0.02)'
});
And to fill the canvas, I have a crappy for-loop like this:
for (var i = 0; i <= this.field.getWidth(); i = i + 20) {
for (var i2 = 0; i2 <= this.field.getHeight(); i2 = i2 + 20) {
var cbox = box.clone({x: i, y: i2});
this.grid.add(cbox);
}
}
this.grid is a Kinetic.Layer. The first problem with this code is that it is very slow and I get like a 500ms delay before the grid shows up. But the worst thing is that if I put an mouseover and mouseout event on the cbox to change the fill color, that renders really really slow. This is how I do it:
cbox.on('mouseover', function () {
this.setFill('black');
self.grid.draw();
});
cbox.on('mouseout', function () {
this.setFill('transparent');
self.grid.draw();
});
So my question is how can I improve the code and performance of this?
How about to make grid with lines and use one rect for cursor highlighting?
Here i wrote the example for you:
http://jsfiddle.net/e_aksenov/R72Xu/30/
var CELL_SIZE = 35,
w = 4,
h = 5,
W = w * CELL_SIZE,
H = h * CELL_SIZE;
var make_grid = function(layer) {
var back = new Kinetic.Rect({
x: 0,
y: 0,
width: W,
height: H,
fill: "yellow"
});
layer.add(back);
for (i = 0; i < w + 1; i++) {
var I = i * CELL_SIZE;
var l = new Kinetic.Line({
stroke: "black",
points: [I, 0, I, H]
});
layer.add(l);
}
for (j = 0; j < h + 1; j++) {
var J = j * CELL_SIZE;
var l2 = new Kinetic.Line({
stroke: "black",
points: [0, J, W, J]
});
layer.add(l2);
}
return back; //to attach mouseover listener
};
var cursor_bind = function(layer, grid_rect, rect) {
grid_rect.on('mouseover', function(e) {
var rx = Math.floor(e.x / CELL_SIZE);
var ry = Math.floor(e.y / CELL_SIZE);
rect.setPosition(rx * CELL_SIZE, ry * CELL_SIZE);
layer.draw();
});
};
var stage = new Kinetic.Stage({
container: "kinetic",
width: 800,
height: 600,
draggable: true
});
var layer = new Kinetic.Layer();
var rect = new Kinetic.Rect({
x: 0,
y: 0,
width: CELL_SIZE,
height: CELL_SIZE,
fill: "#00D2FF",
stroke: "black",
strokeWidth: 4
});
var gr = make_grid(layer);
cursor_bind(layer, gr, rect);
// add the shape to the layer
layer.add(rect);
// add the layer to the stage
stage.add(layer);​

Categories

Resources