I am using Fabric.js and I have the following code for drawing a hexagon:
makeObject(originPoint, newPoint, canvasObject, properties) {
let width = Math.abs(newPoint.x - originPoint.x);
let height = 0;
if(this.shouldKeepProportion){
height=width;
}else{
height=Math.abs(newPoint.y - originPoint.y);
}
width = (this.minWidth!=null && width<this.minWidth ? this.minWidth: width);
height = (this.minHeight!=null && height<this.minHeight ? this.minHeight : height);
let sweep=Math.PI*2/6;
let points=[];
//generate points for 6 angles
for(let i=0;i<6;i++){
let x=width*Math.cos(i*sweep);
let y=height*Math.sin(i*sweep);
points.push({x:x/2,y:y/1.75});
}
properties = {
objectType: 'octagon',
left: originPoint.x,
top: originPoint.y,
originX: 'left',
originY: 'top',
fill: 'rgba(0, 0, 0, 1.0)',
width: width,
height: height,
originX: originPoint.x > newPoint.x ? 'right' : 'left',
originY: originPoint.y > newPoint.y ? 'bottom' : 'top',
flipX: originPoint.x > newPoint.x,
stroke: 'rgba(0, 0, 0, 1.0)',
strokeWidth: 1,
strokeLineJoin: 'round',
...properties
};
return new fabric.fabric.Polygon(points, properties);
}
What I want to get is a regular octagon, same as the hexagon. If i try to change the number of corners/angles, I am getting the following type of octagon:
What I actually need is this:
PLEASE NOTE: I do not need it rotated or flipped or something like that, I need it drawn like on the picture.
Thank you for the help!
EDIT: Working JSFiddle: https://jsfiddle.net/42fb716n/
This should do it, using the Polygon() function instead of Line() so it's like a single object, not a group of objects, and so it works in your drawing App as you have different properties for single and group objects:
var canvas = new fabric.Canvas('canvas');
var size = 150;
var side = Math.round((size * Math.sqrt(2)) / (2 + Math.sqrt(2)));
var octagon = new fabric.Polygon([
{x:-side / 2, y:size / 2},
{x:side / 2, y:size / 2},
{x:size / 2, y:side / 2},
{x:size / 2, y:-side / 2},
{x:side / 2, y:-size / 2},
{x:-side / 2, y:-size / 2},
{x:-size / 2, y:-side / 2},
{x:-size / 2, y:side / 2}], {
stroke: 'red',
left: 10,
top: 10,
strokeWidth: 2,
strokeLineJoin: 'bevil'
},false);
canvas.add(octagon);
<script src="//cdnjs.cloudflare.com/ajax/libs/fabric.js/1.4.0/fabric.min.js"></script>
<canvas id="canvas" width=200 height=200></canvas>
Let me know if you need anything else. Happy to help!
Related
I am working on a canvas application which needs to be replicated in IOS and Android. Hence the goal is whatever is created using the web must be replicated in the other platforms and vise versa.
In the web I am using Fabric JS as my canvas library and I am using the build in JSON method to save and recreate the canvas, where as the IOS developer is using a point based system where he is saving the x , y cordinates for all the objects and lines drawn in the canvas. The IOS developer is diving the x cordinate with the width and the y cordinate with the height of the canvas to make the object and lines positions propotinate in all the devices.
I was able to convert my web based canvas to their desired point based system. But now the challenge is to convert their point based system into a fabric based web canvas. Dealing with the objects would be easy to handle but I need some assitence as to how I can recreate the path's from the Comma Seperated values in the Database.
Here is an example of a Path saved in the database using the IOS app.
[
"{0.88156813, 0.67090207}",
"{0.86200052, 0.67090207}",
"{0.83264917, 0.67908657}",
"{0.81308156, 0.67908657}",
"{0.77883828, 0.68727112}",
"{0.75437874, 0.68727112}",
"{0.72013545, 0.68727112}",
"{0.69567597, 0.68727112}",
"{0.67121649, 0.69545561}",
"{0.65164888, 0.69545561}",
"{0.63208127, 0.70364016}",
"{0.61251366, 0.70364016}",
"{0.59294611, 0.72000921}",
"{0.58316231, 0.7281937}",
"{0.58316231, 0.73637825}"
]
How do I go about to recreate the path from the given data where the first value is x cordinate and the second value is the y cordinate.
Any guidence in this matter would be very helpful.
Regards
Something like this, I've took the width and hight of the canvas as max value =1, in that way pair x,y is a percent from width or height.
If you have coordinate with minus value in interval [-1, 1] use:
points.push({
x : (width\2 * coordinates[i].x),
y : (height\2 * coordinates[i].y)
});
and at line drawing add offset:
canvas.add(new fabric.Line([x1, y1, x2, y2], {
left: width\2,
top: height\2,
stroke: 'red'
}));
Response when considered possible path values are between [0,1]:
var coordinates = [
{x : 0.2, y: 0.2},
{x : 0.88156813, y: 0.67090207},
{x: 0.86200052, y: 0.67090207},
{x: 0.83264917, y: 0.67908657},
{x: 0.81308156, y: 0.67908657},
{x: 0.77883828, y: 0.68727112},
{x: 0.75437874, y: 0.68727112},
{x: 0.72013545, y: 0.68727112},
{x: 0.69567597, y: 0.68727112},
{x: 0.67121649, y: 0.69545561},
{x: 0.65164888, y: 0.69545561},
{x: 0.63208127, y: 0.70364016},
{x: 0.61251366, y: 0.70364016},
{x: 0.59294611, y: 0.72000921},
{x: 0.58316231, y: 0.7281937},
{x: 0.58316231, y: 0.73637825}
];
var canvas = new fabric.Canvas('c');
var width = canvas.width;//0.00 to 1.00
var height = canvas.height;//0.00 to 1.00
var points = [];
//define your points from 0 to width and from 0 to height
for(i =0; i < coordinates.length; i++)
{
points.push({
x : (width * coordinates[i].x),
y : (height * coordinates[i].y)
});
}
//drow lines
for(i =0; i < points.length - 1; i++)
{
//alert(points[i].x);
canvas.add(new fabric.Line([points[i].x, points[i].y, points[i+1].x, points[i+1].y], {
stroke: 'blue'
}));
}
canvas.add(new fabric.Text('x=0.00', { left: 20, top: 5, fontSize: 10 }));
canvas.add(new fabric.Text('y=1.00', { left: 360, top: 5, fontSize: 10 }));
canvas.add(new fabric.Text('y=0.00', { left: 5, top: 20, fontSize: 10 }));
canvas.add(new fabric.Text('y=1.00', { left: 5, top: 380, fontSize: 10 }));
canvas {
border-width: 1px;
border-style: solid;
border-color: #000;
}
<script src="http://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.5.0/fabric.min.js"></script>
<canvas id="c" width="400" height="400"></canvas>
I am writing code to maintain same strokeWidth across all the objects in Fabricjs.
I am able to successfully maintain strokeWidth for normal Objects , let it be rectangle or ellipse(I wrote code to maintain both).
But when I use group , I am unable to maintain the strokeWidth.
Here's the fiddle.
Steps to reproduce the issue ?
1)Open above fiddle link
2)resize the circle , check the size of border (thickness of border remains the same on re-sizing)
3)Now resize the rectangle and text(group) , expected result is thickness of border must be same throughout but that doesn't happens.
Code :
`
var gCanvas = new fabric.Canvas('canvDraw');
gCanvas.setWidth(700);
gCanvas.setHeight(700);
var text = new fabric.Text('test', { fontSize: 30, top: 100, left : 100, fill : 'red'});
// add some shapes - these are examples, others should work too
var myRectangle = new fabric.Rect({
left: 100,
top: 100,
fill: '#999999',
width: 120,
height: 120,
strokeWidth: 3,
fill: '',
stroke: '#000000'
});
var group = new fabric.Group([ myRectangle, text ], {borderColor: 'black', cornerColor: 'green', lockScalingFlip : true});
gCanvas.add(group);
var myEllipse = new fabric.Ellipse({
top: 250,
left: 100,
rx: 75,
ry: 50,
fill: '',
stroke: '#000000',
strokeWidth: 3
});
gCanvas.add(myEllipse);
gCanvas.observe('object:scaling', function (e) {
e.target.resizeToScale();
});
fabric.Object.prototype.resizeToScale = function () {
// resizes an object that has been scaled (e.g. by manipulating the handles), setting scale to 1 and recalculating bounding box where necessary
switch (this.type) {
case "ellipse":
this.rx *= this.scaleX;
this.ry *= this.scaleY;
this.width = this.rx * 2;
this.height = this.ry * 2;
this.scaleX = 1;
this.scaleY = 1;
break;
case "rect":
this.width *= this.scaleX;
this.height *= this.scaleY;
this.scaleX = 1;
this.scaleY = 1;
default:
break;
}
}
`
this approach will not work for all kind of shapes. when facing text or polygons you cannot recalculate everything.
you can do something like that:
fabric.Object.prototype.resizeToScale = function () {
if (this.type !=='group') {
this.strokeWidth = this._origStrokeWidth / Math.max(this.scaleX, this.scaleY);
}
else {
this._objects.forEach( function(obj){
console.log(obj);
obj.strokeWidth = obj._origStrokeWidth / Math.max(obj.group.scaleX, obj.group.scaleY);
});
}
}
if you prefer the effect of your solution (border is always uniform) put this line of code in the default case of your switch construct to handle all the types for wich you cannot find a resizing solution.
edit i added group handling.
The point is to make smaller strokewidth when the group / object scale.
Because when you will have polygons and polylines and paths, changing width / height /radius will not help.
http://jsfiddle.net/y344vs5s/4/
I have two canvas objects (Rectungle and Triangle) in group.
Then i need to delete them, then return one of objects to is past position.
Here is two objects and group
var rect1 = new fabric.Rect(
{
id: 1, left: 10, top: 10, width: 100, height: 50, angle: 45, fill: 'red'
});
var tria1 = new fabric.Triangle({
id: 2, left: 200, top: 200, width: 100, height: 50, angle: 20, fill: 'yellow'
});
var objsGroup = new fabric.Group([rect1, tria1], {left: 100, top: 100});
And the whole fiddle here. I separate all steps by alerts https://jsfiddle.net/5js60oec/
UPDATE
Have the next problem. Lets assume that existing object group making some move and rotating before deleting. Then i have to restore it to point before rotating and moving. Thats, i think the main problem.
Actions
objsGroup.top = 200;
objsGroup.left = 200;
canvas.renderAll();
alert('4');
history.push(canvas.getActiveGroup());
objsGroup.setAngle(45);
canvas.renderAll();
Here the full fiddler: https://jsfiddle.net/nppaLetn/1/
For example I am deleting the last added path in my canvas like below
var lastItemIndex = (fabricCanvas.getObjects().length - 1);
var item = fabricCanvas.item(lastItemIndex);
if(item.get('type') === 'path') {
fabricCanvas.remove(item);
fabricCanvas.renderAll();
}
This may help you to accomplish your probs!!! I think so .
I just want to know what is the best way to use multiple canvas in a single page. These canvas can be overlapped on each other.
I tried to search this issue on different form, but wasn't able to find any helpful material. This is what we actually want to do(in the following image). There are 5 canvases, and we want all of them to be fully functional. We can add images, text and draw different things on selected canvas.
We are currently using fabricjs.
If that`s not possible, what is the best solution for achieving something like that ?
Thanks in advance!
Simply use CSS for that.
<div class="wrapper">
<canvas id="background_layer" class="canvas-layer" width="100" height="100"></canvas>
<canvas id="other_layer" class="canvas-layer" width="100" height="100"></canvas>
</div>
<style>
.wrapper { position: relative }
.canvas-layer {
position: absolute; left: 0; top: 0;
}
</style>
I am not sure what you are trying to achieve but you can refer to this Fiddle http://jsfiddle.net/PromInc/ZxYCP/
var img01URL = 'https://www.google.com/images/srpr/logo4w.png';
var img02URL = 'http://fabricjs.com/lib/pug.jpg';
var canvas = new fabric.Canvas('c');
// Note the use of the `originX` and `originY` properties, which we set
// to 'left' and 'top', respectively. This makes the math in the `clipTo`
// functions a little bit more straight-forward.
var clipRect1 = new fabric.Rect({
originX: 'left',
originY: 'top',
left: 180,
top: 10,
width: 200,
height: 200,
fill: '#DDD', /* use transparent for no fill */
strokeWidth: 0,
selectable: false
});
// We give these `Rect` objects a name property so the `clipTo` functions can
// find the one by which they want to be clipped.
clipRect1.set({
clipFor: 'pug'
});
canvas.add(clipRect1);
var clipRect2 = new fabric.Rect({
originX: 'left',
originY: 'top',
left: 10,
top: 10,
width: 150,
height: 150,
fill: '#DDD', /* use transparent for no fill */
strokeWidth: 0,
selectable: false
});
// We give these `Rect` objects a name property so the `clipTo` functions can
// find the one by which they want to be clipped.
clipRect2.set({
clipFor: 'logo'
});
canvas.add(clipRect2);
function findByClipName(name) {
return _(canvas.getObjects()).where({
clipFor: name
}).first()
}
// Since the `angle` property of the Image object is stored
// in degrees, we'll use this to convert it to radians.
function degToRad(degrees) {
return degrees * (Math.PI / 180);
}
var clipByName = function (ctx) {
this.setCoords();
var clipRect = findByClipName(this.clipName);
var scaleXTo1 = (1 / this.scaleX);
var scaleYTo1 = (1 / this.scaleY);
ctx.save();
var ctxLeft = -( this.width / 2 ) + clipRect.strokeWidth;
var ctxTop = -( this.height / 2 ) + clipRect.strokeWidth;
var ctxWidth = clipRect.width - clipRect.strokeWidth;
var ctxHeight = clipRect.height - clipRect.strokeWidth;
ctx.translate( ctxLeft, ctxTop );
ctx.rotate(degToRad(this.angle * -1));
ctx.scale(scaleXTo1, scaleYTo1);
ctx.beginPath();
ctx.rect(
clipRect.left - this.oCoords.tl.x,
clipRect.top - this.oCoords.tl.y,
clipRect.width,
clipRect.height
);
ctx.closePath();
ctx.restore();
}
var pugImg = new Image();
pugImg.onload = function (img) {
var pug = new fabric.Image(pugImg, {
angle: 45,
width: 500,
height: 500,
left: 230,
top: 50,
scaleX: 0.3,
scaleY: 0.3,
clipName: 'pug',
clipTo: function(ctx) {
return _.bind(clipByName, pug)(ctx)
}
});
canvas.add(pug);
};
pugImg.src = img02URL;
var logoImg = new Image();
logoImg.onload = function (img) {
var logo = new fabric.Image(logoImg, {
angle: 0,
width: 550,
height: 190,
left: 50,
top: 50,
scaleX: 0.25,
scaleY: 0.25,
clipName: 'logo',
clipTo: function(ctx) {
return _.bind(clipByName, logo)(ctx)
}
});
canvas.add(logo);
};
logoImg.src = img01URL;
I hope this might help.
I'm making an interactive diagram website and I'm trying to easily draw arrows in kineticJS relative to the stagesize, i've got the following:
http://jsfiddle.net/K5Zhg/17/
But as you can see the rotated arrow is not matching the intended start and ending point. I tried offset, but that messes up the correct (not rotated) arrow. Also don't know why jsfiddle show's my arrows this messy (missing bottom line), on my own machine it seems to be working fine (using v5.1.0), see also here http://tomzooi.com/dump/ip/ (using kineticjs and binding bootstrap to it, working nice so far)
code:
var stage = new Kinetic.Stage({
container: 'container',
width: 1140,
height: 500
});
var layer = new Kinetic.Layer();
var border = new Kinetic.Rect( {
x: 0,
y: 0,
width: stage.getWidth(),
height: stage.getHeight(),
stroke: ' red',
strokeWidth: 1
});
layer.add(border);
var dot = new Kinetic.Circle({
x: 0.1*stage.getWidth(),
y: 0.1*stage.getHeight(),
radius: 10,
fill: 'red'
});
var dot2 = new Kinetic.Circle({
x: 0.5*stage.getWidth(),
y: 0.1*stage.getHeight(),
radius: 10,
fill: 'red'
});
var dot3 = new Kinetic.Circle({
x: 0.5*stage.getWidth(),
y: 0.5*stage.getHeight(),
radius: 10,
fill: 'red'
});
layer.add(dot);
layer.add(dot2);
layer.add(dot3);
var arrow1 = arrow(0.1,0.1,0.5,0.1,10);
var arrow2 = arrow(0.1,0.2,0.5,0.5,10);
layer.add(arrow1);
layer.add(arrow2);
// add the layer to the stage
stage.add(layer);
function arrow(psx, psy, pex, pey, pw) {
var w = stage.getWidth();
var h = stage.getHeight();
var sx = psx*w;
var ex = pex*w;
var sy = psy*h;
var ey = pey*h;
var pr = (Math.atan2(ey-sy, ex-sx)/(Math.PI/180));
var pl = Math.sqrt(Math.pow((ex-sx),2)+Math.pow((ey-sy),2));
ex = sx+pl;
ey = sy;
var poly = new Kinetic.Line({
points: [sx,sy+pw, sx,sy-pw, ex-3*pw,ey-pw, ex-3*pw,ey-2*pw, ex,ey, ex-3*pw,ey+2*pw, ex-3*pw, sy+pw],
fill: '#EDECEB',
stroke: '#AFACA9',
strokeWidth: 2,
closed: true,
rotation: pr,
shadowColor: 'black',
shadowBlur: 10,
shadowOffset: {x:2,y:2},
shadowOpacity: 0.5
});
return poly;
}
Here is a solution: http://jsfiddle.net/K5Zhg/20/
The problem was that when you rotate the arrow shape, it rotates around x=0 and y=0. However your point was drawn on x=sx and y=sy (in your example case x=60 and y=50).
To fix this draw the points around x=0 and y=0 (and translating the other points in the array using the correct variables) and then setting the x and y property of the Kinetic.Line to sx and sy in order set the position back to its intended location. I.e.
function arrow(psx, psy, pex, pey, pw) {
var w = stage.getWidth();
var h = stage.getHeight();
var sx = psx*w;
var ex = pex*w;
var sy = psy*h;
var ey = pey*h;
// console.log(sx);
var pr = (Math.atan2(ey-sy, ex-sx)/(Math.PI/180));
var pl = Math.sqrt(Math.pow((ex-sx),2)+Math.pow((ey-sy),2));
ex = sx+pl;
ey = sy;
var poly = new Kinetic.Line({
points: [0,0+pw,
0,0-pw, ex-sx-3*pw,ey-sy-pw, ex-sx-3*pw,ey-sy-2*pw, ex-sx,ey-sy, ex-sx-3*pw,ey-sy+2*pw, ex-sx-3*pw, 0+pw],
fill: 'blue',
stroke: 'black',
strokeWidth: 2,
closed: true,
rotation: pr,
x: sx,
y: sy,
shadowColor: 'black',
shadowBlur: 10,
shadowOffset: {x:2,y:2},
shadowOpacity: 0.5
});
return poly;
}
I also changed the y value to 0.1 instead of the 0.2 such that the start of the second arrow connects with the red dot.
Oh and I updated the Fiddle to uses v5.0.1.