I have a code where I can draw and edit a polygon using javascript and fabric JS. I want it to work into angular and tried converting it but other fucntions does not work and the original code works fine to add and edit.
By the way I am using fabric js to draw on the canvas and I am also using angular version 12 >>
Anyone there has an idea how to solve the issue in the edit polygon function? Thank you.
I would appreciate anyone that can help and point the issue.
#original code
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<script src="https://unpkg.com/[email protected]/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>
<button type="button" onclick="clearPolygon()">Clear</button>
<canvas id="c"></canvas>
<script type="text/javascript">
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(720);
canvas.setWidth(1280);
var imageUrl = "https://images.wallpaperscraft.com/image/road_asphalt_marking_130996_1280x720.jpg";
// Define
canvas.setBackgroundImage(imageUrl, canvas.renderAll.bind(canvas), {
// Optionally add an opacity lvl to the image
backgroundImageOpacity: 0.5,
// should the image be resized to fit the container?
backgroundImageStretch: true
});
//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
console.log(points)
const polygon = new fabric.Polygon(points, {
id: new Date().getTime(),
stroke: '#0084ff',
fill: false,
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();
}
function clearPolygon(){
canvas.remove(...canvas.getObjects());
}
initCanvas();
</script>
</body>
</html>
#converted html code
<div>
<button type="button" (click)="toggleDrawPolygon()">Draw Polygon</button>
<button type="button" (click)="editPolygon()">Edit Polygon</button>
<button type="button" (click)="resizePolygon()">Resize/Move Polygon</button>
<button type="button" (click)="clearPolygon()">Clear</button>
</div>
<canvas id="c"></canvas>
#converted ts code
import { AfterViewInit, Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core';
const fabric = require("fabric").fabric;
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit, AfterViewInit {
activeLine: any;
activeShape: any;
canvas: any;
lineArray: any[] = [];
pointArray: any[] = [];
drawMode = false;
pointIndex: any;
constructor() { }
ngOnInit(): void {
}
ngAfterViewInit(): void {
this.initCanvas();
}
initCanvas() {
this.canvas = new fabric.Canvas('c');
this.canvas.setHeight(720);
this.canvas.setWidth(1280);
const imageUrl = "https://wpmedia.roomsketcher.com/content/uploads/2021/12/14164614/RoomSketcher-House-Floor-Plans-2452430-800.jpg";
this.canvas.setBackgroundImage(imageUrl, this.canvas.renderAll.bind(this.canvas), {
backgroundImageOpacity: 0.5,
backgroundImageStretch: true
});
this.canvas.on('mouse:down', (options: any) => this.onMouseDown(options));
this.canvas.on('mouse:up', () => this.onMouseUp());
this.canvas.on('mouse:move', (options: any) => this.onMouseMove(options));
this.canvas.on('object:moving', (option: any) => this.onObjectMove(option));
}
onMouseDown(options: any) {
if (this.drawMode) {
if (options.target && options.target.id === this.pointArray[0].id) {
this.generatePolygon(this.pointArray);
} else {
this.addPoint(options);
}
}
const evt = options.e;
if (evt.altKey === true) {
this.canvas.isDragging = true;
this.canvas.selection = false;
this.canvas.lastPosX = evt.clientX;
this.canvas.lastPosY = evt.clientY;
}
}
onMouseUp() {
this.canvas.isDragging = false;
this.canvas.selection = true;
}
onMouseMove(options: any) {
if (this.canvas.isDragging) {
const e = options.e;
this.canvas.viewportTransform[4] += e.clientX - this.canvas.lastPosX;
this.canvas.viewportTransform[5] += e.clientY - this.canvas.lastPosY;
this.canvas.requestRenderAll();
this.canvas.lastPosX = e.clientX;
this.canvas.lastPosY = e.clientY;
}
if (this.drawMode) {
if (this.activeLine && this.activeLine.class === 'line') {
const pointer = this.canvas.getPointer(options.e);
this.activeLine.set({
x2: pointer.x,
y2: pointer.y
});
const points = this.activeShape.get('points');
points[this.pointArray.length] = {
x: pointer.x,
y: pointer.y,
};
this.activeShape.set({
points
});
}
this.canvas.renderAll();
}
}
onObjectMove(option: any) {
const object = option.target;
object._calcDimensions();
object.setCoords();
this.canvas.renderAll();
}
toggleDrawPolygon() {
if (this.drawMode) {
this.activeLine = null;
this.activeShape = null;
this.lineArray = [];
this.pointArray = [];
this.canvas.selection = true;
this.drawMode = false;
} else {
this.canvas.selection = false;
this.drawMode = true;
}
}
addPoint(options: any) {
const pointOption = {
id: new Date().getTime(),
radius: 5,
fill: '#ffffff',
stroke: '#333333',
strokeWidth: 0.5,
left: options.e.layerX / this.canvas.getZoom(),
top: options.e.layerY / this.canvas.getZoom(),
selectable: false,
hasBorders: false,
hasControls: false,
originX: 'center',
originY: 'center',
objectCaching: false,
};
const point = new fabric.Circle(pointOption);
if (this.pointArray.length === 0) {
point.set({
fill: 'red'
});
}
const linePoints = [
options.e.layerX / this.canvas.getZoom(),
options.e.layerY / this.canvas.getZoom(),
options.e.layerX / this.canvas.getZoom(),
options.e.layerY / this.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);
// Set a custom property 'customClass' on the line object
(line as any).customClass = 'line';
if (this.activeShape) {
const pos = this.canvas.getPointer(options.e);
const points = this.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,
});
this.canvas.remove(this.activeShape);
this.canvas.add(polygon);
this.activeShape = polygon;
this.canvas.renderAll();
} else {
const polyPoint = [{
x: options.e.layerX / this.canvas.getZoom(),
y: options.e.layerY / this.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,
});
this.activeShape = polygon;
this.canvas.add(polygon);
}
this.activeLine = line;
this.pointArray.push(point);
this.lineArray.push(line);
this.canvas.add(line);
this.canvas.add(point);
}
generatePolygon(pointArray: any) {
const points = [];
for (const point of pointArray) {
points.push({
x: point.left,
y: point.top,
});
this.canvas.remove(point);
}
for (const line of this.lineArray) {
this.canvas.remove(line);
}
this.canvas.remove(this.activeShape).remove(this.activeLine);
const polygon = new fabric.Polygon(points, {
stroke: '#0084ff',
fill: 'black',
});
this.canvas.add(polygon);
this.toggleDrawPolygon();
this.editPolygon();
}
polygonPositionHandler(dim : any, finalMatrix : any, fabricObject : any) {
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()
)
);
}
actionHandler(eventData: any, transform: any, x: any, y: any) {
const polygon = transform.target;
const currentControl = polygon.controls[polygon.__corner];
const mouseLocalPosition = polygon.toLocalPoint(new fabric.Point(x, y), 'center', 'center');
const polygonBaseSize = polygon._getNonTransformedDimensions();
const size = polygon._getTransformedDimensions(0, 0);
const 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;
}
anchorWrapper(anchorIndex: any, fn: any) {
return (eventData: any, transform: any, x: any, y: any) => {
const fabricObject = transform.target;
const point = new fabric.Point(
fabricObject.points[anchorIndex].x - fabricObject.pathOffset.x,
fabricObject.points[anchorIndex].y - fabricObject.pathOffset.y
);
const absolutePoint = fabric.util.transformPoint(point, fabricObject.calcTransformMatrix());
const actionPerformed = fn(eventData, transform, x, y);
const newDim = fabricObject._setPositionDimensions({});
const polygonBaseSize = fabricObject._getNonTransformedDimensions();
const newX = (fabricObject.points[anchorIndex].x - fabricObject.pathOffset.x) / polygonBaseSize.x;
const newY = (fabricObject.points[anchorIndex].y - fabricObject.pathOffset.y) / polygonBaseSize.y;
fabricObject.setPositionByOrigin(absolutePoint, newX + 0.5, newY + 0.5);
return actionPerformed;
};
}
editPolygon() {
let activeObject = this.canvas.getActiveObject();
if (!activeObject) {
activeObject = this.canvas.getObjects()[0];
this.canvas.setActiveObject(activeObject);
}
activeObject.edit = true;
activeObject.objectCaching = false;
const lastControl = activeObject.points.length - 1;
activeObject.cornerStyle = 'circle';
activeObject.controls = activeObject.points.reduce((acc:any,index:any) => {
acc['p' + index] = new fabric.Control({
positionHandler: this.polygonPositionHandler,
actionHandler: this.anchorWrapper(index > 0 ? index - 1 : lastControl, this.actionHandler),
actionName: 'modifyPolygon',
pointIndex: index,
});
return acc;
}, {});
activeObject.hasBorders = false;
this.canvas.requestRenderAll();
}
resizePolygon() {
let activeObject = this.canvas.getActiveObject();
if (!activeObject) {
activeObject = this.canvas.getObjects()[0];
this.canvas.setActiveObject(activeObject);
}
activeObject.edit = false;
activeObject.objectCaching = false;
activeObject.controls = fabric.Object.prototype.controls;
activeObject.cornerStyle = 'rect';
activeObject.hasBorders = true;
this.canvas.requestRenderAll();
}
clearPolygon() {
this.canvas.remove(...this.canvas.getObjects());
}
}