Can someone please suggest me the right way to update the transform points and custom control points of my custom polygon (draw by mouse move + click) after zoom or pan the canvas?
Step to reproduce:
click on Draw Polygon
draw a polygon in canvas
zoom (scroll up or down) or pan (alt + mouse drag)
Here is my code: https://jsfiddle.net/ckitisak/qv6y283p/
/**
* based on:
* 1. https://codepen.io/durga598/pen/gXQjdw?editors=0010
* 2. http://fabricjs.com/custom-controls-polygon
*/
let activeLine;
let activeShape;
let canvas;
let lineArray = [];
let pointArray = [];
let drawMode = false;
function initCanvas() {
canvas = new fabric.Canvas('c');
canvas.backgroundColor = 'rgba(0,0,0,0.3)';
canvas.setHeight(500);
canvas.setWidth(500);
fabric.Object.prototype.originX = 'center';
fabric.Object.prototype.originY = 'center';
canvas.on('mouse:down', onMouseDown);
canvas.on('mouse:up', onMouseUp);
canvas.on('mouse:move', onMouseMove);
canvas.on('object:moving', onObjectMove);
canvas.on('mouse:wheel', onMouseWheel);
}
function onMouseDown(options) {
if (drawMode) {
if (options.target && options.target.id === pointArray[0].id) {
// when click on the first point
generatePolygon(pointArray);
} else {
addPoint(options);
}
}
var evt = options.e;
if (evt.altKey === true) {
this.isDragging = true;
this.selection = false;
this.lastPosX = evt.clientX;
this.lastPosY = evt.clientY;
}
}
function onMouseUp(options) {
this.isDragging = false;
this.selection = true;
}
function onMouseMove(options) {
if (this.isDragging) {
var e = options.e;
this.viewportTransform[4] += e.clientX - this.lastPosX;
this.viewportTransform[5] += e.clientY - this.lastPosY;
this.requestRenderAll();
this.lastPosX = e.clientX;
this.lastPosY = e.clientY;
}
if (drawMode) {
if (activeLine && activeLine.class === 'line') {
const pointer = canvas.getPointer(options.e);
activeLine.set({
x2: pointer.x,
y2: pointer.y
});
const points = activeShape.get('points');
points[pointArray.length] = {
x: pointer.x,
y: pointer.y,
};
activeShape.set({
points
});
}
canvas.renderAll();
}
}
function onMouseWheel(options) {
var delta = options.e.deltaY;
var pointer = canvas.getPointer(options.e);
var zoom = canvas.getZoom();
if (delta > 0) {
zoom += 0.1;
} else {
zoom -= 0.1;
}
if (zoom > 20) zoom = 20;
if (zoom < 0.1) zoom = 0.1;
canvas.zoomToPoint({ x: options.e.offsetX, y: options.e.offsetY }, zoom);
options.e.preventDefault();
options.e.stopPropagation();
}
function onObjectMove(option) {
const object = option.target;
object._calcDimensions();
object.setCoords();
canvas.renderAll();
}
function toggleDrawPolygon(event) {
if (drawMode) {
// stop draw mode
activeLine = null;
activeShape = null;
lineArray = [];
pointArray = [];
canvas.selection = true;
drawMode = false;
} else {
// start draw mode
canvas.selection = false;
drawMode = true;
}
}
function addPoint(options) {
const pointOption = {
id: new Date().getTime(),
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',
objectCaching: false,
};
const point = new fabric.Circle(pointOption);
if (pointArray.length === 0) {
// fill first point with red color
point.set({
fill: 'red'
});
}
const linePoints = [
options.e.layerX / canvas.getZoom(),
options.e.layerY / canvas.getZoom(),
options.e.layerX / canvas.getZoom(),
options.e.layerY / canvas.getZoom(),
];
const lineOption = {
strokeWidth: 2,
fill: '#999999',
stroke: '#999999',
originX: 'center',
originY: 'center',
selectable: false,
hasBorders: false,
hasControls: false,
evented: false,
objectCaching: false,
};
const line = new fabric.Line(linePoints, lineOption);
line.class = 'line';
if (activeShape) {
const pos = canvas.getPointer(options.e);
const points = activeShape.get('points');
points.push({
x: pos.x,
y: pos.y
});
const 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 {
const polyPoint = [{
x: options.e.layerX / canvas.getZoom(),
y: options.e.layerY / canvas.getZoom(),
}, ];
const 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(point);
lineArray.push(line);
canvas.add(line);
canvas.add(point);
}
function generatePolygon(pointArray) {
const points = [];
// collect points and remove them from canvas
for (const point of pointArray) {
points.push({
x: point.left,
y: point.top,
});
canvas.remove(point);
}
// remove lines from canvas
for (const line of lineArray) {
canvas.remove(line);
}
// remove selected Shape and Line
canvas.remove(activeShape).remove(activeLine);
// create polygon from collected points
const polygon = new fabric.Polygon(points, {
id: new Date().getTime(),
stroke: '#eee',
fill: '#f00',
objectCaching: false,
moveable: false,
//selectable: false
});
canvas.add(polygon);
toggleDrawPolygon();
editPolygon();
}
/**
* define a function that can locate the controls.
* this function will be used both for drawing and for interaction.
*/
function polygonPositionHandler(dim, finalMatrix, fabricObject) {
const transformPoint = {
x: fabricObject.points[this.pointIndex].x - fabricObject.pathOffset.x,
y: fabricObject.points[this.pointIndex].y - fabricObject.pathOffset.y,
};
return fabric.util.transformPoint(transformPoint, fabricObject.calcTransformMatrix());
}
/**
* define a function that will define what the control does
* this function will be called on every mouse move after a control has been
* clicked and is being dragged.
* The function receive as argument the mouse event, the current trasnform object
* and the current position in canvas coordinate
* transform.target is a reference to the current object being transformed,
*/
function actionHandler(eventData, transform, x, y) {
const polygon = transform.target;
const currentControl = polygon.controls[polygon.__corner];
const mouseLocalPosition = polygon.toLocalPoint(new fabric.Point(x, y), 'center', 'center');
const size = polygon._getTransformedDimensions(0, 0);
const finalPointPosition = {
x: (mouseLocalPosition.x * polygon.width) / size.x + polygon.pathOffset.x,
y: (mouseLocalPosition.y * polygon.height) / size.y + polygon.pathOffset.y,
};
polygon.points[currentControl.pointIndex] = finalPointPosition;
return true;
}
/**
* define a function that can keep the polygon in the same position when we change its
* width/height/top/left.
*/
function anchorWrapper(anchorIndex, fn) {
return function(eventData, transform, x, y) {
const fabricObject = transform.target;
const point = {
x: fabricObject.points[anchorIndex].x - fabricObject.pathOffset.x,
y: fabricObject.points[anchorIndex].y - fabricObject.pathOffset.y,
};
// update the transform border
fabricObject._setPositionDimensions({});
// Now newX and newY represent the point position with a range from
// -0.5 to 0.5 for X and Y.
const newX = point.x / fabricObject.width;
const newY = point.y / fabricObject.height;
// Fabric supports numeric origins for objects with a range from 0 to 1.
// This let us use the relative position as an origin to translate the old absolutePoint.
const absolutePoint = fabric.util.transformPoint(point, fabricObject.calcTransformMatrix());
fabricObject.setPositionByOrigin(absolutePoint, newX + 0.5, newY + 0.5);
// action performed
return fn(eventData, transform, x, y);
};
}
function editPolygon() {
let activeObject = canvas.getActiveObject();
if (!activeObject) {
activeObject = canvas.getObjects()[0];
canvas.setActiveObject(activeObject);
}
activeObject.edit = true;
activeObject.objectCaching = false;
const lastControl = activeObject.points.length - 1;
activeObject.cornerStyle = 'circle';
activeObject.controls = activeObject.points.reduce((acc, point, index) => {
acc['p' + index] = new fabric.Control({
positionHandler: polygonPositionHandler,
actionHandler: anchorWrapper(index > 0 ? index - 1 : lastControl, actionHandler),
actionName: 'modifyPolygon',
pointIndex: index,
});
return acc;
}, {});
activeObject.hasBorders = false;
canvas.requestRenderAll();
}
function resizePolygon() {
let activeObject = canvas.getActiveObject();
if (!activeObject) {
activeObject = canvas.getObjects()[0];
canvas.setActiveObject(activeObject);
}
activeObject.edit = false;
activeObject.objectCaching = false;
activeObject.controls = fabric.Object.prototype.controls;
activeObject.cornerStyle = 'rect';
activeObject.hasBorders = true;
canvas.requestRenderAll();
}
initCanvas();
<script src="https://unpkg.com/fabric#4.0.0-beta.12/dist/fabric.js"></script>
<button type="button" onclick="toggleDrawPolygon()">Draw Polygon</button>
<button type="button" onclick="editPolygon()">Edit Polygon</button>
<button type="button" onclick="resizePolygon()">Resize/Move Polygon</button>
<canvas id="c"></canvas>
i modified the code to take in account of the canvas zoom and panning.
The viewport transform needs to be take in account when calculating the position of the controls.
/**
* based on:
* 1. https://codepen.io/durga598/pen/gXQjdw?editors=0010
* 2. http://fabricjs.com/custom-controls-polygon
*/
let activeLine;
let activeShape;
let canvas;
let lineArray = [];
let pointArray = [];
let drawMode = false;
function initCanvas() {
canvas = new fabric.Canvas('c');
canvas.backgroundColor = 'rgba(0,0,0,0.3)';
canvas.setHeight(500);
canvas.setWidth(500);
fabric.Object.prototype.originX = 'center';
fabric.Object.prototype.originY = 'center';
canvas.on('mouse:down', onMouseDown);
canvas.on('mouse:up', onMouseUp);
canvas.on('mouse:move', onMouseMove);
canvas.on('object:moving', onObjectMove);
canvas.on('mouse:wheel', onMouseWheel);
}
function onMouseDown(options) {
if (drawMode) {
if (options.target && options.target.id === pointArray[0].id) {
// when click on the first point
generatePolygon(pointArray);
} else {
addPoint(options);
}
}
var evt = options.e;
if (evt.altKey === true) {
this.isDragging = true;
this.selection = false;
this.lastPosX = evt.clientX;
this.lastPosY = evt.clientY;
}
}
function onMouseUp(options) {
this.isDragging = false;
this.selection = true;
}
function onMouseMove(options) {
if (this.isDragging) {
var e = options.e;
this.viewportTransform[4] += e.clientX - this.lastPosX;
this.viewportTransform[5] += e.clientY - this.lastPosY;
this.requestRenderAll();
this.lastPosX = e.clientX;
this.lastPosY = e.clientY;
}
if (drawMode) {
if (activeLine && activeLine.class === 'line') {
const pointer = canvas.getPointer(options.e);
activeLine.set({
x2: pointer.x,
y2: pointer.y
});
const points = activeShape.get('points');
points[pointArray.length] = {
x: pointer.x,
y: pointer.y,
};
activeShape.set({
points
});
}
canvas.renderAll();
}
}
function onMouseWheel(options) {
var delta = options.e.deltaY;
var pointer = canvas.getPointer(options.e);
var zoom = canvas.getZoom();
if (delta > 0) {
zoom += 0.1;
} else {
zoom -= 0.1;
}
if (zoom > 20) zoom = 20;
if (zoom < 0.1) zoom = 0.1;
canvas.zoomToPoint({ x: options.e.offsetX, y: options.e.offsetY }, zoom);
options.e.preventDefault();
options.e.stopPropagation();
}
function onObjectMove(option) {
const object = option.target;
object._calcDimensions();
object.setCoords();
canvas.renderAll();
}
function toggleDrawPolygon(event) {
if (drawMode) {
// stop draw mode
activeLine = null;
activeShape = null;
lineArray = [];
pointArray = [];
canvas.selection = true;
drawMode = false;
} else {
// start draw mode
canvas.selection = false;
drawMode = true;
}
}
function addPoint(options) {
const pointOption = {
id: new Date().getTime(),
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',
objectCaching: false,
};
const point = new fabric.Circle(pointOption);
if (pointArray.length === 0) {
// fill first point with red color
point.set({
fill: 'red'
});
}
const linePoints = [
options.e.layerX / canvas.getZoom(),
options.e.layerY / canvas.getZoom(),
options.e.layerX / canvas.getZoom(),
options.e.layerY / canvas.getZoom(),
];
const lineOption = {
strokeWidth: 2,
fill: '#999999',
stroke: '#999999',
originX: 'center',
originY: 'center',
selectable: false,
hasBorders: false,
hasControls: false,
evented: false,
objectCaching: false,
};
const line = new fabric.Line(linePoints, lineOption);
line.class = 'line';
if (activeShape) {
const pos = canvas.getPointer(options.e);
const points = activeShape.get('points');
points.push({
x: pos.x,
y: pos.y
});
const 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 {
const polyPoint = [{
x: options.e.layerX / canvas.getZoom(),
y: options.e.layerY / canvas.getZoom(),
}, ];
const 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(point);
lineArray.push(line);
canvas.add(line);
canvas.add(point);
}
function generatePolygon(pointArray) {
const points = [];
// collect points and remove them from canvas
for (const point of pointArray) {
points.push({
x: point.left,
y: point.top,
});
canvas.remove(point);
}
// remove lines from canvas
for (const line of lineArray) {
canvas.remove(line);
}
// remove selected Shape and Line
canvas.remove(activeShape).remove(activeLine);
// create polygon from collected points
const polygon = new fabric.Polygon(points, {
id: new Date().getTime(),
stroke: '#eee',
fill: '#f00',
objectCaching: false,
moveable: false,
//selectable: false
});
canvas.add(polygon);
toggleDrawPolygon();
editPolygon();
}
/**
* define a function that can locate the controls.
* this function will be used both for drawing and for interaction.
*/
function polygonPositionHandler(dim, finalMatrix, fabricObject) {
var x = (fabricObject.points[this.pointIndex].x - fabricObject.pathOffset.x),
y = (fabricObject.points[this.pointIndex].y - fabricObject.pathOffset.y);
return fabric.util.transformPoint(
{ x: x, y: y },
fabric.util.multiplyTransformMatrices(
fabricObject.canvas.viewportTransform,
fabricObject.calcTransformMatrix()
)
);
}
/**
* define a function that will define what the control does
* this function will be called on every mouse move after a control has been
* clicked and is being dragged.
* The function receive as argument the mouse event, the current trasnform object
* and the current position in canvas coordinate
* transform.target is a reference to the current object being transformed,
*/
function actionHandler(eventData, transform, x, y) {
var polygon = transform.target,
currentControl = polygon.controls[polygon.__corner],
mouseLocalPosition = polygon.toLocalPoint(new fabric.Point(x, y), 'center', 'center'),
polygonBaseSize = polygon._getNonTransformedDimensions(),
size = polygon._getTransformedDimensions(0, 0),
finalPointPosition = {
x: mouseLocalPosition.x * polygonBaseSize.x / size.x + polygon.pathOffset.x,
y: mouseLocalPosition.y * polygonBaseSize.y / size.y + polygon.pathOffset.y
};
polygon.points[currentControl.pointIndex] = finalPointPosition;
return true;
}
/**
* define a function that can keep the polygon in the same position when we change its
* width/height/top/left.
*/
function anchorWrapper(anchorIndex, fn) {
return function(eventData, transform, x, y) {
var fabricObject = transform.target,
absolutePoint = fabric.util.transformPoint({
x: (fabricObject.points[anchorIndex].x - fabricObject.pathOffset.x),
y: (fabricObject.points[anchorIndex].y - fabricObject.pathOffset.y),
}, fabricObject.calcTransformMatrix()),
actionPerformed = fn(eventData, transform, x, y),
newDim = fabricObject._setPositionDimensions({}),
polygonBaseSize = fabricObject._getNonTransformedDimensions(),
newX = (fabricObject.points[anchorIndex].x - fabricObject.pathOffset.x) / polygonBaseSize.x,
newY = (fabricObject.points[anchorIndex].y - fabricObject.pathOffset.y) / polygonBaseSize.y;
fabricObject.setPositionByOrigin(absolutePoint, newX + 0.5, newY + 0.5);
return actionPerformed;
}
}
function editPolygon() {
let activeObject = canvas.getActiveObject();
if (!activeObject) {
activeObject = canvas.getObjects()[0];
canvas.setActiveObject(activeObject);
}
activeObject.edit = true;
activeObject.objectCaching = false;
const lastControl = activeObject.points.length - 1;
activeObject.cornerStyle = 'circle';
activeObject.controls = activeObject.points.reduce((acc, point, index) => {
acc['p' + index] = new fabric.Control({
positionHandler: polygonPositionHandler,
actionHandler: anchorWrapper(index > 0 ? index - 1 : lastControl, actionHandler),
actionName: 'modifyPolygon',
pointIndex: index,
});
return acc;
}, {});
activeObject.hasBorders = false;
canvas.requestRenderAll();
}
function resizePolygon() {
let activeObject = canvas.getActiveObject();
if (!activeObject) {
activeObject = canvas.getObjects()[0];
canvas.setActiveObject(activeObject);
}
activeObject.edit = false;
activeObject.objectCaching = false;
activeObject.controls = fabric.Object.prototype.controls;
activeObject.cornerStyle = 'rect';
activeObject.hasBorders = true;
canvas.requestRenderAll();
}
initCanvas();
<script src="https://unpkg.com/fabric#4.0.0-beta.12/dist/fabric.js"></script>
<button type="button" onclick="toggleDrawPolygon()">Draw Polygon</button>
<button type="button" onclick="editPolygon()">Edit Polygon</button>
<button type="button" onclick="resizePolygon()">Resize/Move Polygon</button>
<canvas id="c"></canvas>
I'm using fabric js with gestures, but i want to prevent zooming, if zoom is getting more than 4x in and less than 1x out
I've tried to call preventDefault() and stopPropogation() functions on the event, but it doesn't stop the zooming
event.e.stopPropagation();
Get the deltaY & getZoom then you can limit the zoom as below.
var canvas = new fabric.Canvas('c');
var dia1 = new fabric.Circle({
radius: 12,
originX: 'center',
originY: 'center',
fill: 'transparent',
strokeWidth: 5,
stroke: "red",
});
var dia2 = new fabric.Circle({
radius: 5,
originX: 'center',
originY: 'center',
fill: 'red',
});
var targetEl = new fabric.Group([dia1, dia2], {
originX: 'center',
originY: 'center',
});
canvas.centerObject(targetEl);
canvas.add(targetEl);
canvas.renderAll();
//mouse zoom
canvas.on('mouse:wheel', function(opt) {
var delta = opt.e.deltaY;
var pointer = canvas.getPointer(opt.e);
var zoom = canvas.getZoom();
zoom = zoom - delta / 200;
// limit zoom to 4x in
if (zoom > 4) zoom = 4;
// limit zoom to 1x out
if (zoom < 1) {
zoom = 1;
canvas.setViewportTransform([1, 0, 0, 1, 0, 0]);
}
canvas.zoomToPoint({
x: opt.e.offsetX,
y: opt.e.offsetY
}, zoom);
opt.e.preventDefault();
opt.e.stopPropagation();
});
//touch zoom
canvas.on({
'touch:gesture': function(e) {
if (e.e.touches && e.e.touches.length == 2) {
pausePanning = true;
var point = new fabric.Point(e.self.x, e.self.y);
if (e.self.state == "start") {
zoomStartScale = canvas.getZoom();
}
var delta = zoomStartScale * e.self.scale;
canvas.zoomToPoint(point, delta);
pausePanning = false;
// limit zoom to 4x in
if (delta > 4) delta = 4;
// limit zoom to 1x out
if (delta < 1) {
delta = 1;
canvas.setViewportTransform([1, 0, 0, 1, 0, 0]);
}
}
}
});
canvas {
border: 1px solid #ccc;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.4.0/fabric.js"></script>
<canvas id="c" width="600" height="300"></canvas>
I am with a task in which I need to create two very specific type of Pattern Brush using Fabric.js
A Dashed Line with a X in the end.
A simple arrowed Line.
I need these two types of brushes in the free drawing mode.
Any guidance or suggestion would be very helpful.
This is what I tried for Brush Number 1 but this doesn't solve the purpose:
var hLinePatternBrush = new fabric.PatternBrush(canvas);
hLinePatternBrush.getPatternSrc = function() {
var patternCanvas = fabric.document.createElement('canvas');
patternCanvas.width = patternCanvas.height = 10;
var ctx = patternCanvas.getContext('2d');
ctx.strokeStyle = '#ffffff';
ctx.strokeLineCap ="round";
ctx.lineWidth = 5;
ctx.beginPath();
ctx.moveTo(5, 0);
ctx.lineTo(5, 10);
ctx.closePath();
ctx.stroke();
return patternCanvas;
};
canvas.freeDrawingBrush = hLinePatternBrush;
And for Brush number 2 I have no clue whatsoever.
Here is my implementation for the problem. Might help someone in the future.
fabric.Path.prototype.selectable = false;
fabric.Triangle.prototype.selectable = false;
fabric.Text.prototype.selectable = false;
/* ------------------------ Player Path Brush --------------------- */
var vLinePatternBrush = new fabric.PencilBrush(canvas);
vLinePatternBrush.color = '#fff';
if($("#line_type").val() == 'choose_type' || $("#line_type").val() == 'player_path' )
{
vLinePatternBrush.strokeDashArray = [5, 15];
}
else
{
vLinePatternBrush.strokeDashArray = [0,0];
}
vLinePatternBrush.hasControls = false;
canvas.freeDrawingBrush = vLinePatternBrush;
//canvas.freeDrawingBrush.color = '#fff';
canvas.freeDrawingBrush.width = 4;
//Choosing the Right Brush as per Users Requirement
$("#line_type").change(function(e){
if($("#line_type").val() == 'choose_type' || $("#line_type").val() == 'player_path' )
{
canvas.freeDrawingBrush = vLinePatternBrush;
canvas.freeDrawingBrush.color = '#fff';
canvas.freeDrawingBrush.width = 4;
vLinePatternBrush.strokeDashArray = [5, 15];
}
else
{
var normalLine = new fabric['PencilBrush'](canvas);
normalLine.strokeDashArray = [0,0];
canvas.freeDrawingBrush = normalLine;
canvas.freeDrawingBrush.color = '#fff';
canvas.freeDrawingBrush.width = 4;
console.log('here');
}
});
// This is required to make sure that the objects can be selected if wrapped inside a path line.
canvas.on('object:added', function(e) {
console.log(e);
if(e.target.type == 'path' || e.target.type == 'text' || e.target.type == 'triangle')
{
console.log('Sending Object to Background');
canvas.sendToBack(e.target);
}
});
// Handling the X and the Arrow part Once the Path is Drawn
canvas.on('path:created', function(path) {
console.log(path);
if($("#line_type").val() == 'choose_type' || $("#line_type").val() == 'player_path' )
{
console.log(path.path.path[(path.path.path.length -1)]);
var x1 = path.path.path[0][1];
var y1 = path.path.path[0][2];
var x2 = path.path.path[(path.path.path.length -1)][1];
var y2 = path.path.path[(path.path.path.length -1)][2];
var angle = calcArrowAngle(x1,y1,x2,y2);
angle = angle - 90;
var text = new fabric.Text('+', {
left: path.path.path[(path.path.path.length -1)][1],
top: path.path.path[(path.path.path.length -1)][2],
fill: 'white',
originX: 'center',
originY: 'center',
flipx: true,
selectable: false,
flipy: true,
fontSize: 80,
fontFamily: 'ABeeZee',
fill: 'white',
angle: angle,
hasControls: false
});
canvas.add(text);
}
if($("#line_type").val() == 'ball_path' )
{
console.log(path.path.path[(path.path.path.length -1)]);
var x1 = path.path.path[0][1];
var y1 = path.path.path[0][2];
var x2 = path.path.path[(path.path.path.length -1)][1];
var y2 = path.path.path[(path.path.path.length -1)][2];
var angle = calcArrowAngle(x2,y2,x1,y1);
angle = angle - 90;
arrow = new fabric.Triangle({
left: (path.path.path[(path.path.path.length -1)][1] + 2),
top: (path.path.path[(path.path.path.length -1)][2] + 2),
originX: 'center',
originY: 'center',
hasBorders: false,
hasControls: false,
lockScalingX: true,
lockScalingY: true,
lockRotation: true,
pointType: 'arrow_start',
angle: angle,
width: 15,
height: 15,
fill: 'white',
hasControls: false
});
canvas.add(arrow);
}
// This is specific to my implementation for undo and redo. Once can ignore
updateModifications(true);
});
In the First Section of the code I am just creating two simple pencilBrush one with DottedLines and one a normal Line
In the second part I am getting the exact point where the Path ended (may be not the best way to do it, but it worked for me). Once I get the Point I am dropping the desired shape in that position.
CalcArrowAngle: Credits StackOverflow
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);
}
I am not saying this is the best way to do it. But it solved the problem in hand.
OUTPUT PNG:
I want to do something. Example, I will put a point when I click on the screen. After When I click somewhere else to put point there. Finally, automatic line to be drawn between these two points. How do I do this?
Thank you for your help:)
HTML
<canvas id="c" height="200" width="300"></canvas>
JS
var canvas = new fabric.Canvas('c');
var point1;
canvas.on('mouse:down', function (options) {
var x = options.e.clientX - canvas._offset.left;
var y = options.e.clientY - canvas._offset.top;
var circle = new fabric.Circle({
left: x,
top: y,
fill: 'red',
originX: 'center',
originY: 'center',
hasControls: false,
hasBorders: false,
lockMovementX: true,
lockMovementY: true,
radius: 5,
hoverCursor: 'default'
});
canvas.add(circle);
if (point1 === undefined) {
point1 = new fabric.Point(x, y)
} else {
canvas.add(new fabric.Line([point1.x, point1.y, x, y], {
stroke: 'blue',
hasControls: false,
hasBorders: false,
lockMovementX: true,
lockMovementY: true,
hoverCursor: 'default'
}))
point1 = undefined;
}
});
Fiddle - http://jsfiddle.net/zdaax418/
Solution;
HTML
<button id="end">End</button>
<button id="zoomIn">Zoom-In</button>
<button id="zoomOut">Zoom-Out</button>
<canvas id="c" width="1800" height="910" ></canvas>
JavaScript
var canvas = new fabric.Canvas('c');
var point1;
var line=null;
var canvasScale = 1;
var SCALE_FACTOR = 1.2;
var bool = true;
canvas.on('mouse:down', function (options) {
var x = options.e.clientX - canvas._offset.left;
var y = options.e.clientY - canvas._offset.top;
var circle = new fabric.Circle({
left: x,
top: y,
fill: 'red',
originX: 'center',
originY: 'center',
hasControls: false,
hasBorders: false,
lockMovementX: false,
lockMovementY: false,
radius: 5,
hoverCursor: 'default'
});
if(bool) {
circle.line1 = null;
circle.line2 = null;
canvas.add(circle);
}
point1 = new fabric.Point(x, y);
if(line){
line = new fabric.Line([line.get('x2'), line.get('y2'), x, y], {
fill: 'grey',
stroke: 'grey',
hasControls: false,
hasBorders: false,
lockMovementX: true,
lockMovementY: true,
selectable: false,
hoverCursor: 'default'
});
}else{
line = new fabric.Line([x, y, x, y], {
fill: 'grey',
stroke: 'grey',
hasControls: false,
hasBorders: false,
lockMovementX: true,
lockMovementY: true,
selectable: false,
hoverCursor: 'default'
});
}
if(bool)
canvas.add(line);
});
$("#end").click(function(){
var lines = canvas.getObjects('line');
var firstLine = lines[1];
var lastLine = lines[lines.length - 1];
line = new fabric.Line([lastLine.get('x2'), lastLine.get('y2'),firstLine.get('x1'), firstLine.get('y1')], {
fill: 'grey',
stroke: 'grey',
hasControls: false,
hasBorders: false,
lockMovementX: false,
lockMovementY: false,
selectable: false,
hoverCursor: 'default'
});
canvas.add(line);
var lines = canvas.getObjects('line');
var circles = canvas.getObjects('circle');
for(i = 1; i < circles.length - 1; i++) {
for(j = 0; j < lines.length; j++) {
if(circles[i].getLeft() == lines[j].get('x1') && circles[i].getTop() == lines[j].get('y1')) {
circles[i].line2 = lines[j];
circles[i].line1 = lines[j - 1];
}
}
}
circles[0].line1 = lines[lines.length - 1];
circles[0].line2 = lines[1];
circles[circles.length - 1].line2 = lines[lines.length - 1];
circles[circles.length - 1].line1 = lines[0];
bool = false;
});
$("#zoomIn").click(function() {
canvasScale = canvasScale * SCALE_FACTOR;
canvas.setHeight(canvas.getHeight() * SCALE_FACTOR);
canvas.setWidth(canvas.getWidth() * SCALE_FACTOR);
var circles = canvas.getObjects('circle');
for (var i in circles) {
var scaleX = circles[i].scaleX;
var scaleY = circles[i].scaleY;
var left = circles[i].left;
var top = circles[i].top;
circles[i].scaleX = scaleX * SCALE_FACTOR;
circles[i].scaleY = scaleY * SCALE_FACTOR;
circles[i].left = left * SCALE_FACTOR;
circles[i].top = top * SCALE_FACTOR;
circles[i].setCoords();
var coord = circles[i].getCenterPoint();
circles[i].line1.set({
'x2': coord.x,
'y2': coord.y
});
circles[i].line2.set({
'x1': coord.x,
'y1': coord.y
});
}
canvas.renderAll();
canvas.calcOffset();
});
$("#zoomOut").click(function() {
canvasScale = canvasScale / SCALE_FACTOR;
canvas.setHeight(canvas.getHeight() * (1 / SCALE_FACTOR));
canvas.setWidth(canvas.getWidth() * (1 / SCALE_FACTOR));
var circles = canvas.getObjects('circle');
for (var i in circles) {
var scaleX = circles[i].scaleX;
var scaleY = circles[i].scaleY;
var left = circles[i].left;
var top = circles[i].top;
circles[i].scaleX = scaleX * (1 / SCALE_FACTOR);
circles[i].scaleY = scaleY * (1 / SCALE_FACTOR);
circles[i].left = left * (1 / SCALE_FACTOR);
circles[i].top = top * (1 / SCALE_FACTOR);
circles[i].setCoords();
var coord = circles[i].getCenterPoint();
circles[i].line1.set({
'x2': coord.x,
'y2': coord.y
});
circles[i].line2.set({
'x1': coord.x,
'y1': coord.y
});
}
canvas.renderAll();
canvas.calcOffset();
});
canvas.on('object:moving', function(e) {
var p = e.target;
var coord = p.getCenterPoint();
p.line1.set({ 'x2': coord.x, 'y2': coord.y });
p.line2.set({ 'x1': coord.x, 'y1': coord.y });
canvas.renderAll();
});
I'm trying to prevent a rotated Label from being dragged off the screen, but I cannot figure out how to get MinX, MaxX, MinY, and MaxY of the object in its rotated state. getHeight & getWidth only return the values prior to the rotation.
Here is an example illustrating the problem:
var stage = new Kinetic.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight
});
var layer = new Kinetic.Layer();
var labelLeft = new Kinetic.Label({
x: 95,
y: 180,
opacity: 1.0,
listening: true,
draggable: true,
rotationDeg: -45,
text: {
text: 'Pointing Arrow',
fontFamily: 'Calibri',
fontSize: 20,
padding: 5,
fill: 'white'
},
rect: {
fill: 'blue',
pointerDirection: 'left',
pointerWidth: 20,
pointerHeight: 38,
stroke: 'black',
strokeWidth: 2
},
dragBoundFunc: function (pos) {
var newY = pos.y < 50 ? 50 : pos.y;
return {
x: pos.x,
y: newY
};
}
});
layer.add(labelLeft);
// add the layer to the stage
stage.add(layer);
http://jsfiddle.net/fSNnA/4/
In this example, I use dragBoundFunc to prevent the label from being dragged above y=50. but since the label is rotated, its actual highest point (MinY) has changed, and therefore you can drag it up and partially out of view.
What I really need is a function that will return the absolute current MinX, MaxX, MinY, and MaxY - taking into account the angle of rotation and length of text will not always be the same.
Can anyone help?
Here's how to calculate the bounding-box size of a rotated rectangle (label).
var w = label.getWidth();
var h = label.getHeight();
var rads = label.getRotation();
var c = Math.abs(Math.cos(rads));
var s = Math.abs(Math.sin(rads));
var newWidth = h * s + w * c;
var newHeight = h * c + w * s;
Then assuming you'll always rotate around the center, the left/top boundaries are:
var centerX = label.getX()+label.getWidth()/2;
var centerY = label.getY()+label.getHeight()/2;
var MinX = centerX - newWidth/2;
var MinY = centerY - newHeight/2;
[ Disclaimer: This is just off the top of my head--review it accordingly! ]