Fabricjs: How to get the area? - javascript

I need to know the area of all elements of the group. Is there a ready-made solution? Perhaps you know the area excluding the imposition?
window.canvas = new fabric.Canvas('fabriccanvas');
var circle1 = new fabric.Circle({
radius: 50,
fill: 'red',
left: 0
});
var Rect = new fabric.Rect({
left: 50,
top: 50,
height: 20,
width: 20,
fill: 'green'
});
var circle2 = new fabric.Circle({
radius: 20,
fill: 'black',
left: 70,
top: 80
});
var group = new fabric.Group([ circle1, circle2, Rect ], {});
window.canvas.add(group);

VINET,
There is no predefined functionality, you have to build your own. This is short example how to calculate area for circles and rectangles in the group (as you created above):
var groupArea = 0;
group.forEachObject(function(o){
groupArea += calculateArea(o);
});
function calculateArea(obj){
switch (obj.type){
case 'circle':
return Math.PI*obj.radius*obj.radius;
break;
case 'rect':
return obj.width*obj.height;
break;
}
}
alert(groupArea);
This post is how to calculate polygon area: Calculating Polygon Area
This post is for triangle area: Calculate triangle area and circumference from user input sides
Hopefully it will help you.

Related

Rotate a group of shapes containing text whilst keeping text centered and horizontal

This is probably just maths.
I am using Konva to dynamically generate shapes, which I'm storing as a label. So there's a label which contains a textElement and a rectangle. I want to make sure text in that rectangle is always a) Centered horizontally and vertically and b) facing the right way up.
So a rectangle could have any rotation, but I always want the text centered and facing the right way up.
The code for creation; width, height, rotation, x and y all have values pulled from a database.
var table = new Konva.Label({
x: pos_x,
y: pos_y,
width: tableWidth,
height: tableHeight,
draggable:true
});
table.add(new Konva.Rect({
width: tableWidth,
height: tableHeight,
rotation: rotation,
fill: fillColor,
stroke: strokeColor,
strokeWidth: 4
}));
table.add(new Konva.Text({
width: tableWidth,
height: tableHeight,
x: pos_x, //Defaults to zero
y: pos_y, //Default to zero
text: tableNumber,
verticalAlign: 'middle',
align: 'center',
fontSize: 30,
fontFamily: 'Calibri',
fill: 'black'
}))
tableLayer.add(table);
The problem is, if rotation is in place, text is off center, as in this image:
I do manually correct in some circumstances - for example if rotation = 45 degrees:
pos_x = -tableWidth/2;
pos_y = tableHeight/5;
but that is not a permanent solution. I want the x and y co-ordinates of the text to be at the centerpoint of the shape itself.
I've tried a few approaches (such as applying rotation to the Label itself and then negative rotation value to the text)
This code snippet illustrates a solution. It is copied & modified from my other self-answer when I was looking for a robust approach to rotation around an arbitrary point - note that I consider this a slightly different question than my original so I have not suggested this is a dup. The difference is the need to work with a more complex grouped shape and to keep some element within that group unrotated.
Not in the OP's question, but I set a background rectangle into the text by making the text a group. The purpose of this was to show that the text rectangle will extend outside the label rectangle in some points of rotation. This is not a critical issue but it is useful to see it happen.
The fundamental challenge for the coder is to understand how the shapes move when rotated since we usually want to spin them around their centre but the fundamental 2D canvas pattern that Konva (and all HTML5 canvas wrappers) follow is to rotate from the top-left corner, al least for rectangles as per shapes in the question. It 'is' possible to move the rotation point (known as the offset) but again that is a conceptual challenge for the dev and a nice trap for anyone trying to support the code later.
There's a lot of code in this answer that is here to set up something dynamic that you can use to visualise what is going on. However, the crux is in this:
// This is the important call ! Cross is the rotation point as illustrated by crosshairs.
rotateAroundPoint(shape, rotateBy, {x: cross.x(), y: cross.y()});
// The label is a special case because we need to keep the text unrotated.
if (shape.name() === 'label'){
let text = shape.find('.text')[0];
rotateAroundPoint(text, -1 * rotateBy, {x: text.getClientRect().width/2, y: text.getClientRect().height/2});
}
The rotateAroundPoint() function takes as parameters the Konva shape to rotate, the clockwise rotation angle (not radians, good ole degrees), and the x & y position of the rotation point on the canvas / parent.
I constructed a group of shapes as my label, composing it from a rectangle and a text shape. I named this 'label'. Actually I switched the text shape to be another group of rect + text to that I could show the rectangle the text sits within. You could leave out the extra group. I named this 'text'.
The first call to rotateAroundPoint() rotates the group named 'label'. So the group rotates on the canvas. Since the 'text' is a child of the 'label' group, that would leave the 'text' rotated, so the next line checks if we are working with the 'label' group, and if so we need to get hold of the 'text' shape which is what this line does:
let text = shape.find('.text')[0];
In Konva the result of a find() is a list so we take the first in the list. Now all that remains for me to do is rotate the text on the 'label' group back again by applying the negative rotation degrees to its center point. The line below achieves this.
rotateAroundPoint(text, -1 * rotateBy, {x: text.getClientRect().width/2, y: text.getClientRect().height/2});
One note worthy of mention - I used a group for my 'text' shape. A Konva group does not naturally have a width or height - it is more of a means to collect shapes together but without a 'physical' container. So to get its width and height for the centre point calculations I use the group.getClientRect() method which gives the size of the minimum bounding box that would contain all shapes in the group, and yields an object formed as {width: , height: }.
Second note - the first use of rotateAroundPoint() affects the 'label' group which has as its parent the canvas. The second use of that function affects the 'text' group which has the 'label' group as its parent. Its subtle but worth knowing.
Here is the snippet. I urge you to run it fullscreen and spin a few shapes around a few different points.
// Code to illustrate rotation of a shape around any given point. The important functions here is rotateAroundPoint() which does the rotation and movement math !
let
angle = 0, // display value of angle
startPos = {x: 80, y: 45},
shapes = [], // array of shape ghosts / tails
rotateBy = 20, // per-step angle of rotation
shapeName = $('#shapeName').val(), // what shape are we drawing
shape = null,
ghostLimit = 10,
// Set up a stage
stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight
}),
// add a layer to draw on
layer = new Konva.Layer(),
// create the rotation target point cross-hair marker
lineV = new Konva.Line({points: [0, -20, 0, 20], stroke: 'lime', strokeWidth: 1}),
lineH = new Konva.Line({points: [-20, 0, 20, 0], stroke: 'lime', strokeWidth: 1}),
circle = new Konva.Circle({x: 0, y: 0, radius: 10, fill: 'transparent', stroke: 'lime', strokeWidth: 1}),
cross = new Konva.Group({draggable: true, x: startPos.x, y: startPos.y}),
labelRect, labelText;
// Add the elements to the cross-hair group
cross.add(lineV, lineH, circle);
layer.add(cross);
// Add the layer to the stage
stage.add(layer);
$('#shapeName').on('change', function(){
shapeName = $('#shapeName').val();
shape.destroy();
shape = null;
reset();
})
// Draw whatever shape the user selected
function drawShape(){
// Add a shape to rotate
if (shape !== null){
shape.destroy();
}
switch (shapeName){
case "rectangle":
shape = new Konva.Rect({x: startPos.x, y: startPos.y, width: 120, height: 80, fill: 'magenta', stroke: 'black', strokeWidth: 4});
break;
case "hexagon":
shape = new Konva.RegularPolygon({x: startPos.x, y: startPos.y, sides: 6, radius: 40, fill: 'magenta', stroke: 'black', strokeWidth: 4});
break;
case "ellipse":
shape = new Konva.Ellipse({x: startPos.x, y: startPos.y, radiusX: 40, radiusY: 20, fill: 'magenta', stroke: 'black', strokeWidth: 4});
break;
case "circle":
shape = new Konva.Ellipse({x: startPos.x, y: startPos.y, radiusX: 40, radiusY: 40, fill: 'magenta', stroke: 'black', strokeWidth: 4});
break;
case "star":
shape = new Konva.Star({x: startPos.x, y: startPos.y, numPoints: 5, innerRadius: 20, outerRadius: 40, fill: 'magenta', stroke: 'black', strokeWidth: 4});
break;
case "label":
shape = new Konva.Group({name: 'label'});
labelRect = new Konva.Rect({x: 0, y: 0, width: 120, height: 80, fill: 'magenta', stroke: 'black', strokeWidth: 4, name: 'rect'})
shape.add(labelRect);
labelText = new Konva.Group({name: 'text'});
labelText.add(new Konva.Rect({x: 0, y: 0, width: 100, height: 40, fill: 'cyan', stroke: 'black', strokeWidth: 2}))
labelText.add(new Konva.Text({x: 0, y: 0, width: 100, height: 40, text: 'Wombat',fontSize: 20, fontFamily: 'Calibri', align: 'center', padding: 10}))
shape.add(labelText)
labelText.position({x: (labelRect.width() - labelText.getClientRect().width) /2, y: (labelRect.height() - labelText.getClientRect().height) /2})
break;
};
layer.add(shape);
cross.moveToTop();
}
// Reset the shape position etc.
function reset(){
drawShape(); // draw the current shape
// Set to starting position, etc.
shape.position(startPos)
cross.position(startPos);
angle = 0;
$('#angle').html(angle);
$('#position').html('(' + shape.x() + ', ' + shape.y() + ')');
clearTails(); // clear the tail shapes
stage.draw(); // refresh / draw the stage.
}
// Click the stage to move the rotation point
stage.on('click', function (e) {
cross.position(stage.getPointerPosition());
stage.draw();
});
// Rotate a shape around any point.
// shape is a Konva shape
// angleRadians is the angle to rotate by, in radians
// point is an object {x: posX, y: posY}
function rotateAroundPoint(shape, angleDegrees, point) {
let angleRadians = angleDegrees * Math.PI / 180; // sin + cos require radians
const x =
point.x +
(shape.x() - point.x) * Math.cos(angleRadians) -
(shape.y() - point.y) * Math.sin(angleRadians);
const y =
point.y +
(shape.x() - point.x) * Math.sin(angleRadians) +
(shape.y() - point.y) * Math.cos(angleRadians);
shape.rotation(shape.rotation() + angleDegrees); // rotate the shape in place
shape.x(x); // move the rotated shape in relation to the rotation point.
shape.y(y);
shape.moveToTop(); //
}
$('#rotate').on('click', function(){
let newShape = shape.clone();
shapes.push(newShape);
layer.add(newShape);
// This ghost / tails stuff is just for fun.
if (shapes.length >= ghostLimit){
shapes[0].destroy();
shapes = shapes.slice(1);
}
for (var i = shapes.length - 1; i >= 0; i--){
shapes[i].opacity((i + 1) * (1/(shapes.length + 2)))
};
// This is the important call ! Cross is the rotation point as illustrated by crosshairs.
rotateAroundPoint(shape, rotateBy, {x: cross.x(), y: cross.y()});
// The label is a special case because we need to keep the text unrotated.
if (shape.name() === 'label'){
let text = shape.find('.text')[0];
rotateAroundPoint(text, -1 * rotateBy, {x: text.getClientRect().width/2, y: text.getClientRect().height/2});
}
cross.moveToTop();
stage.draw();
angle = angle + 10;
$('#angle').html(angle);
$('#position').html('(' + Math.round(shape.x() * 10) / 10 + ', ' + Math.round(shape.y() * 10) / 10 + ')');
})
// Function to clear the ghost / tail shapes
function clearTails(){
for (var i = shapes.length - 1; i >= 0; i--){
shapes[i].destroy();
};
shapes = [];
}
// User cicks the reset button.
$('#reset').on('click', function(){
reset();
})
// Force first draw!
reset();
body {
margin: 10;
padding: 10;
overflow: hidden;
background-color: #f0f0f0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://unpkg.com/konva#^3/konva.min.js"></script>
<p>1. Click the rotate button to see what happens when rotating around shape origin.</p>
<p>2. Reset then click stage to move rotation point and click rotate button again - rinse & repeat</p>
<p>
<button id = 'rotate'>Rotate</button>
<button id = 'reset'>Reset</button>
<select id='shapeName'>
<option value='label' selected='selected'>Label</option>
<option value='rectangle'>Rectangle</option>
<option value='hexagon'>Polygon</option>
<option value='ellipse' >Ellipse</option>
<option value='circle' >Circle</option>
<option value='star'>Star</option>
</select>
Angle : <span id='angle'>0</span>
Position : <span id='position'></span>
</p>
<div id="container"></div>

How to resize some objects in a group in fabric.js?

I have a group of two objects, what I want is that when resizing the group one of the objects does not change size but the position does.
I have searched the documentation but I can not find references to solve this problem.
var r = 15;
var rect = new fabric.Rect({
left: 50,
top: 50,
fill: 'blue',
width: 100,
height: 100
});
var linkBtn = new fabric.Circle({
left: 50 + rect.width / 2 - r,
top: 50 - r,
strokeWidth: 1,
radius: r,
fill: '#fff',
stroke: '#666'
});
var g = new fabric.Group([rect, linkBtn]);
g.hasRotatingPoint = false;
canvas.add(g);
when I stretch the width of the group, the object "linkBtn" is deformed like an oval, it happens the same with the height. I want that when changing the size, the object "linkBtn" is always the same size, and that is always half the width of the object "rect".
Add {lockUniScaling : true} as the second parameter on this line
var g = new fabric.Group([rect, linkBtn], {lockUniScaling : true});
lockUniScaling
Prevents scaling in either X or Y direction but not in both. In other words, prevents non-proportional scaling of an object.

kineticjs group cache and layer draw is hiding kinetic arc shapes

I am creating a circle as a group of kinetic arcs. When I cache the group and subsequently call the draw function on the layer, three quarters of the circle are hidden. I think layer.draw may require an offset but really I'm only guessing. When I remove the fill, stroke or opacity from the arc or the object literal from the cache call then the full circle is displayed. http://jsfiddle.net/leydar/gm2FT/5/ Any insights gratefully received.
function createArc(n){
var arc = new Kinetic.Arc({
innerRadius: 30,
outerRadius: 50,
/* if I remove the fill, stroke or opacity
the full wheel is correctly displayed */
fill: 'blue',
stroke: 'black',
opacity: 0.3,
strokeWidth: 1,
angle: 36,
rotation: 36*n
});
return arc;
}
function init() {
var arc;
var stage = new Kinetic.Stage({
container: 'container',
width: 104,
height: 104
});
var layer = new Kinetic.Layer();
var circle = new Kinetic.Group();
for(var i=0;i<10;i++) {
arc = createArc(i);
circle.add(arc);
};
layer.add(circle);
stage.add(layer);
/* if I do not cache or do not call layer.draw()
then again the wheel is correctly displayed */
circle.cache({
x: -52,
y: -52,
width: 104,
height: 104,
drawBorder: true
});
layer.draw();
}
init();
Stephen
This is a bug of KineticJS.
You may use this workaround:
Kinetic.Arc.prototype._useBufferCanvas = function() {
return false;
};
http://jsfiddle.net/gm2FT/6/

fabric.js Group members' properties don't update?

I'm using fabric.js to create objects on a canvas. I have a couple of objects together in a Group. When the group is selected, an event is triggered, which creates and adds a circle, centered at the end of a line. When a selection is cleared, another event is triggered, which removes the circle.
What I don't understand is why the circle doesn't change position to the line's new end point, when I move the group, deselect it, and then re-select it.
<html>
<head>
<script type="text/javascript" src="path/to/fabric.1.4.5.js"></script>
</head>
<body>
<canvas id="c" width="800" height="600"></canvas>
<script type="text/javascript">
fabric.Object.prototype.originX = fabric.Object.prototype.originY = 'center';
var canvas = new fabric.Canvas('c');
var circle;
var text = new fabric.Text('hello world', {
fontSize: 30,
originX: 'left',
originY: 'top',
left: 25,
top: 30
});
var line = new fabric.Line([10,10, 100,100], {
stroke: 'green',
strokeWidth: 2
});
var group = new fabric.Group([line, text], {
});
group.on('selected', function() {
circle = new fabric.Circle({
strokeWidth: 5,
radius: 28,
fill: 'rgba(200,200,200,0.4)',
stroke: '#666',
left: line.get('x2'),
top: line.get('y2')
})
canvas.add(circle);
});
canvas.add(group);
canvas.renderAll();
canvas.on('selection:cleared', function () {
group._objects.length = 2;
canvas.remove(circle);
});
</script>
</body>
</html>
Here's the same code on jsFiddle: http://jsfiddle.net/Tqmdw/
The position of your line actually doesn't change. Have a look at the groups tutorial. http://fabricjs.com/fabric-intro-part-3/#groups. In a group items are positioned relative to the groups center and the group is handled as a single entity. That means moving the group will only update the groups top and left position. You can use these new top/left values and calculate the endpoint of the line.
group.on('selected', function() {
circle = new fabric.Circle({
strokeWidth: 5,
radius: 28,
fill: 'rgba(200,200,200,0.4)',
stroke: '#666',
left: group.left + line.get('x2'),,
top: group.top + line.get('y2'),
})
canvas.add(circle);
});
I created an updated fiddle: http://jsfiddle.net/2e9B9/5/

After adding a path to a group in fabricjs, toSVG() gives an svg image that is different from the canvas layout

I have a group of shapes that are supposed to be moved and scaled together on the canvas. For doing this I added all of these to a fabric Group. One of the shape is a fabric Path. While it adds properly on the canvas, when converting the Canvas to SVG, it changes it's position.
I have made a fiddle for it : http://jsfiddle.net/RZcY7/4/
Export svg button will push the svg data to console. click there to open it in browser. It can be seen that the position of triangle(which is a fabric Path) is different on canvas and svg.
Here's the code:
var DefaultGroupSettings = {
originX: 'center',
originY: 'center',
hasBorders: true,
hasControls: true,
hasRotatingPoint: true,
lockUniScaling: true
};
var canvas = new fabric.Canvas(document.getElementById('c'));
var rect = new fabric.Rect({
width: 100,
height: 100,
left: 50,
top: 50,
angle: 30,
fill: 'rgba(255,0,0,0.5)'
});
var circle = new fabric.Circle({
radius: 50,
left: 175,
top: 75,
fill: '#aac'
});
var group = new fabric.Group([rect, circle], DefaultGroupSettings);
canvas.add(group);
var path = new fabric.Path('M 0 0 L 200 100 L 170 200 z');
path.set({
top: 150,
left: 100
});
group.addWithUpdate(path);
canvas.renderAll();
Is it an issue to add a Path to a Group? If yes, how can I club Path and an Object together?
Thanks in advance!

Categories

Resources