How to draw an line arrow in canvas using fabric js? - javascript

I want to draw an line arrow in my canvas aria clicking a button
<button style="margin-left: 0" class="btn btn-info btn-sm" id="line-shape-arrows"> <i class="fa fa-long-arrow-right"></i> 矢印 </button>

I was done that using simple technique. I draw a Line than draw a Triangle after that have group Line and Triangle objects
$("#line-shape-arrows").on("click", function(event){
event.preventDefault();
var triangle = new fabric.Triangle({
width: 10,
height: 15,
fill: 'red',
left: 235,
top: 65,
angle: 90
});
var line = new fabric.Line([50, 100, 200, 100], {
left: 75,
top: 70,
stroke: 'red'
});
var objs = [line, triangle];
var alltogetherObj = new fabric.Group(objs);
canvas.add(alltogetherObj);
});

Below code is worked for me
var fromx, fromy, tox, toy;
this.canvas.on('mouse:down', function (event) {
var pointer = this.canvas.getPointer(event.e);
fromx = pointer.x;
fromy = pointer.y;
});
this.canvas.on('mouse:up', function (event) {
var pointer = this.canvas.getPointer(event.e);
tox = pointer.x;
toy = pointer.y;
//this.drawArrow(startX, startY, endX, endY);
var angle = Math.atan2(toy - fromy, tox - fromx);
var headlen = 10; // arrow head size
// bring the line end back some to account for arrow head.
tox = tox - (headlen) * Math.cos(angle);
toy = toy - (headlen) * Math.sin(angle);
// calculate the points.
var points = [
{
x: fromx, // start point
y: fromy
}, {
x: fromx - (headlen / 4) * Math.cos(angle - Math.PI / 2),
y: fromy - (headlen / 4) * Math.sin(angle - Math.PI / 2)
},{
x: tox - (headlen / 4) * Math.cos(angle - Math.PI / 2),
y: toy - (headlen / 4) * Math.sin(angle - Math.PI / 2)
}, {
x: tox - (headlen) * Math.cos(angle - Math.PI / 2),
y: toy - (headlen) * Math.sin(angle - Math.PI / 2)
},{
x: tox + (headlen) * Math.cos(angle), // tip
y: toy + (headlen) * Math.sin(angle)
}, {
x: tox - (headlen) * Math.cos(angle + Math.PI / 2),
y: toy - (headlen) * Math.sin(angle + Math.PI / 2)
}, {
x: tox - (headlen / 4) * Math.cos(angle + Math.PI / 2),
y: toy - (headlen / 4) * Math.sin(angle + Math.PI / 2)
}, {
x: fromx - (headlen / 4) * Math.cos(angle + Math.PI / 2),
y: fromy - (headlen / 4) * Math.sin(angle + Math.PI / 2)
},{
x: fromx,
y: fromy
}
];
var pline = new fabric.Polyline(points, {
fill: color, //'white',
stroke: color, //'black',
opacity: 1,
strokeWidth: 1,
originX: 'left',
originY: 'top',
selectable: true
});
this.add(pline);
this.isDown = false;
this.off('mouse:down').off('mouse:move').off('mouse:up')
this.renderAll();
});

Related

Polygon Generator

I'm trying to generate a polygon with circles inside (JS canvas). Here's a sample expected output:
It's basically a 4-sided polygon (square) with circles next to the vertices. Here is what I tried:
However, I don't get the expected outcome.
Note: I want this to work for any sized polygon and not just a square. Also, stopping the draw() function to execute gives me a proper square. I believe there's a problem in the draw() function. Any help is appreciated :)
function draw(x, y, ctx){
ctx.arc(x, y, 4, 0, Math.PI * 2);
ctx.fillStyle = "#283149";
ctx.fill(); // create circle
}
function createPolygon(n){
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext('2d');
ctx.reset();
var size = 60, Xcenter = 80, Ycenter = 80;
ctx.beginPath();
ctx.moveTo (Xcenter + size * Math.cos(0), Ycenter + size * Math.sin(0));
for (var i = 1; i <= n; i++) {
ctx.lineTo (Xcenter + size * Math.cos(i * 2 * Math.PI / n), Ycenter + size * Math.sin(i * 2 * Math.PI / n));
draw(Xcenter + Math.cos(i * 2 * Math.PI / n), Ycenter + Math.sin(i * 2 * Math.PI / n), ctx);
}
ctx.fillStyle = "#00818A"
ctx.fill();
}
<button onclick="createPolygon(4)">Create 4</button>
<canvas id="canvas"></canvas>
Focus on drawing the polygon first. Then you can add the circles relative to the points of the polygon.
const main = () => {
const ctx = document.getElementById('poly').getContext('2d');
Object.assign(ctx.canvas, { width: 350, height: 150 });
Object.assign(ctx, { strokeStyle: 'red', lineWidth: 1 });
drawPolygon(ctx, 60, 75, 6, 50, {
nodeSize: 5,
nodeInset: 10
});
drawPolygon(ctx, 180, 75, 4, 50, {
nodeInset: 25,
nodeSize: 10,
rotation: Math.PI / 4
});
drawPolygon(ctx, 290, 75, 3, 50, {
nodeSize: 4,
rotation: Math.PI / 3
});
};
const defaultPolygonOptions = {
nodeInset: 0,
nodeSize: 0,
rotation: 0,
};
/**
* #param {CanvasRenderingContext2D} ctx - Canvas 2D context
* #param {Number} x - origin x-position
* #param {Number} y - origin y-position
* #param {Number} n - number of points
* #param {Number} radius - radius of polygon
* #param {Object} [options] - configuration options
* #param {Number} [options.nodeInset] - insets for nodes
* #param {Number} [options.nodeSize] - size of node
* #param {Number} [options.rotation] - polygon rotation
*/
const drawPolygon = (ctx, x, y, n, radius, options = {}) => {
const opts = { ...defaultPolygonOptions, ...options };
ctx.beginPath();
ctx.moveTo(
x + radius * Math.cos(opts.rotation),
y + radius * Math.sin(opts.rotation)
);
for (let i = 1; i <= n; i += 1) {
const angle = (i * (2 * Math.PI / n)) + opts.rotation;
ctx.lineTo(
x + radius * Math.cos(angle),
y + radius * Math.sin(angle)
);
}
ctx.stroke();
if (!opts.nodeSize) return;
const dist = radius - opts.nodeInset;
for (let i = 1; i <= n; i += 1) {
const angle = (i * (2 * Math.PI / n)) + opts.rotation;
ctx.beginPath();
ctx.arc(
x + dist * Math.cos(angle),
y + dist * Math.sin(angle),
opts.nodeSize,
0,
2 * Math.PI
);
ctx.stroke();
}
};
main();
canvas { background: #FF0; }
<canvas id="poly"></canvas>
Inspired by: How to draw polygons on an HTML5 canvas?
Animation
Here is a basic animation that is FPS throttled.
const ctx = document.getElementById('poly').getContext('2d');
let state = {
magnitude: 1,
origin: { x: 0, y: 0 },
points: 4,
radius: 60,
rotation: Math.PI / 4,
nodeInset: 16,
nodeSize: 8,
nodeRotation: Math.PI / 4,
};
const main = () => {
init();
requestAnimationFrame(update);
};
const init = () => {
Object.assign(ctx.canvas, { width: 160, height: 160 });
Object.assign(ctx, { strokeStyle: 'green', lineWidth: 1 });
Object.assign(state, {
origin: {
x: ctx.canvas.width / 2,
y: ctx.canvas.height / 2
}
});
};
// https://stackoverflow.com/a/65829199/1762224
const FPS = 30;
let lastTimestamp = 0;
const update = (timestamp) => {
let inset = state.nodeInset + state.magnitude;
if (inset > state.radius) {
inset = state.radius;
state.magnitude *= -1;
} else if (inset < state.nodeSize) {
inset = state.nodeSize;
state.magnitude *= -1;
}
state.nodeInset = inset;
state.nodeRotation += (Math.PI / 36);
requestAnimationFrame(update);
if (timestamp - lastTimestamp < 1000 / FPS) return;
redraw();
lastTimestamp = timestamp;
};
const redraw = () => {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
drawPolygon(ctx, state.origin.x, state.origin.y, state.points, state.radius, {
nodeInset: state.nodeInset,
nodeSize: state.nodeSize,
nodeRotation: state.nodeRotation,
rotation: state.rotation
});
}
const defaultPolygonOptions = {
nodeInset: 0,
nodeSize: 0,
rotation: 0,
};
/**
* #param {CanvasRenderingContext2D} ctx - Canvas 2D context
* #param {Number} x - origin x-position
* #param {Number} y - origin y-position
* #param {Number} n - number of points
* #param {Number} radius - radius of polygon
* #param {Object} [options] - configuration options
* #param {Number} [options.nodeInset] - insets for nodes
* #param {Number} [options.nodeSize] - size of node
* #param {Number} [options.nodeRotation] - rotation of nodes
* #param {Number} [options.rotation] - polygon rotation
*/
const drawPolygon = (ctx, x, y, n, radius, options = {}) => {
const opts = { ...defaultPolygonOptions, ...options };
ctx.beginPath();
ctx.moveTo(
x + radius * Math.cos(opts.rotation),
y + radius * Math.sin(opts.rotation)
);
for (let i = 1; i <= n; i += 1) {
const angle = (i * (2 * Math.PI / n)) + opts.rotation;
ctx.lineTo(
x + radius * Math.cos(angle),
y + radius * Math.sin(angle)
);
}
ctx.stroke();
if (!opts.nodeSize) return;
const dist = radius - opts.nodeInset;
for (let i = 1; i <= n; i += 1) {
const angle = (i * (2 * Math.PI / n)) + opts.nodeRotation;
ctx.beginPath();
ctx.arc(
x + dist * Math.cos(angle),
y + dist * Math.sin(angle),
opts.nodeSize,
0,
2 * Math.PI
);
ctx.stroke();
}
};
main();
canvas { background: #222; }
<canvas id="poly"></canvas>
You have to close paths, so you have to use two loops
function draw(x, y, ctx) {
ctx.beginPath();
ctx.arc(x, y, 4, 0, Math.PI * 2);
ctx.fillStyle = "#283149";
ctx.fill(); // create circle
ctx.closePath()
}
function createPolygon(n) {
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext('2d');
ctx.reset();
var size = 60,
Xcenter = 80,
Ycenter = 80;
ctx.beginPath();
ctx.moveTo(Xcenter + size * Math.cos(0), Ycenter + size * Math.sin(0));
for (let i = 1; i <= n; i++) {
ctx.lineTo(Xcenter + size * Math.cos(i * 2 * Math.PI / n), Ycenter + size * Math.sin(i * 2 * Math.PI / n));
}
ctx.fillStyle = "#00818A"
ctx.fill();
ctx.closePath()
for (let i = 1; i <= n; i++) {
draw(Xcenter + Math.cos(i * 2 * Math.PI / n), Ycenter + Math.sin(i * 2 * Math.PI / n), ctx);
}
}
<button onclick="createPolygon(4)">Create 4</button>
<canvas id="canvas"></canvas>

How to identify values from oval triangle on canvas example?

I'm trying to identify the following parameters from the example for oval triangle but when i modify the line:
drawCircle(canvas.width / 3,canvas.height / 2,2.5,'red');
//want to replace with specific values but its not working
drawCircle(32 / 3,33 / 2,2.5,'red');
I want to identify the correct parameter so the demo can change the red point into other space inside the triangle
CH4= 20
C2H4= 70
C2H2= 20
Demo:
https://codepen.io/Barak/pen/WwdPxQ
I read the post from stackoverflow community and cannot see values
how to create Duval Triangle in canvas
In the post you've mentioned, markE did a great job replicating the look of a Duval triangle. The only problem is that the codepen including the drawCircle() function is just a dummy and does nothing more than placing a dot at an arbitrary position, given by it's x and y parameters.
To make this function show the correct position of actual data on the triangle - e.g. CH4=20 | C2H4=70 | C2H2=20 - there's a lot more involved.
Let's have a more in-depth look, if we were to solve this graphically on paper.
(The following is based on this paper)
The Duval triangle is basically an equilateral triangle like this, where side b=%CH4,
side a=%C2H4 and side c=%C2H2.
If we consider the following example data %CH4=25 | %C2H4=35 | %C2H2=40
we would have to go from point A to point C, 25% the length of side b and draw a line parallel to side c:
then from point C to point B, 35% the length of side a and draw a line parallel to side b:
and finally from point B to point A, 40% the length of side c and draw a line parallel to side a:
So where those three lines intersect - so the paper says - we have our target position indicating the status. This can be done programatically using plain trigonometry. Well, I'm not too sure why we need all three lines though. As th percentage for CH4 is always parallel to the triangle's base and C2H4 is always parallel to side b, we have an intersection yet and the line for C2H2 is given automatically.
Basically we just need a function which calculates the intersection between the CH4 and the C2H4 line.
I've taken markE's existing code and enhanced it by a function plotResult(), which takes three parameters for the CH4, C2H2 and C2H4 ppm values:
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
// https://www.researchgate.net/publication/4345236_A_Software_Implementation_of_the_Duval_Triangle_Method
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
var v0 = {
x: 114,
y: 366
};
var v1 = {
x: 306,
y: 30
};
var v2 = {
x: 498,
y: 366
};
var triangle = [v0, v1, v2];
// Define all your segments here
var segments = [{
points: [{
x: 114,
y: 366
}, {
x: 281,
y: 76
}, {
x: 324,
y: 150
}, {
x: 201,
y: 366
}],
fill: 'rgb(172,236,222)',
label: {
text: 'D1',
cx: 200,
cy: 290,
withLine: false,
endX: null,
endY: null
},
},
{
points: [{
x: 385,
y: 366
}, {
x: 201,
y: 366
}, {
x: 324,
y: 150
}, {
x: 356,
y: 204
}, {
x: 321,
y: 256
}],
fill: 'deepskyblue',
label: {
text: 'D2',
cx: 290,
cy: 290,
withLine: false,
endX: null,
endY: null
},
},
{
points: [{
x: 297,
y: 46
}, {
x: 392,
y: 214
}, {
x: 372,
y: 248
}, {
x: 441,
y: 366
}, {
x: 385,
y: 366
}, {
x: 321,
y: 256
}, {
x: 356,
y: 204
}, {
x: 281,
y: 76
}],
fill: 'lightCyan',
label: {
text: 'DT',
cx: 370,
cy: 290,
withLine: false,
endX: 366,
endY: 120
},
},
{
points: [{
x: 306,
y: 30
}, {
x: 312,
y: 40
}, {
x: 300,
y: 40
}],
fill: 'black',
label: {
text: 'PD',
cx: 356,
cy: 40,
withLine: true,
endX: 321,
endY: 40
},
},
{
points: [{
x: 312,
y: 40
}, {
x: 348,
y: 103
}, {
x: 337,
y: 115
}, {
x: 297,
y: 46
}, {
x: 300,
y: 40
}],
fill: 'navajoWhite',
label: {
text: 'T1',
cx: 375,
cy: 70,
withLine: true,
endX: 340,
endY: 75
},
},
{
points: [{
x: 348,
y: 103
}, {
x: 402,
y: 199
}, {
x: 392,
y: 214
}, {
x: 337,
y: 115
}],
fill: 'tan',
label: {
text: 'T2',
cx: 400,
cy: 125,
withLine: true,
endX: 366,
endY: 120
},
},
{
points: [{
x: 402,
y: 199
}, {
x: 498,
y: 366
}, {
x: 441,
y: 366
}, {
x: 372,
y: 248
}],
fill: 'peru',
label: {
text: 'T3',
cx: 425,
cy: 290,
withLine: false,
endX: null,
endY: null
},
},
];
// label styles
var labelfontsize = 12;
var labelfontface = 'verdana';
var labelpadding = 3;
// pre-create a canvas-image of the arrowhead
var arrowheadLength = 10;
var arrowheadWidth = 8;
var arrowhead = document.createElement('canvas');
premakeArrowhead();
var legendTexts = ['PD = Partial Discharge', 'T1 = Thermal fault < 300 celcius', '...'];
// start drawing
/////////////////////
// draw colored segments inside triangle
for (var i = 0; i < segments.length; i++) {
drawSegment(segments[i]);
}
// draw ticklines
ticklines(v0, v1, 9, 0, 20);
ticklines(v1, v2, 9, Math.PI * 3 / 4, 20);
ticklines(v2, v0, 9, Math.PI * 5 / 4, 20);
// molecules
moleculeLabel(v0, v1, 100, Math.PI, '% CH4');
moleculeLabel(v1, v2, 100, 0, '% C2H4');
moleculeLabel(v2, v0, 75, Math.PI / 2, '% C2H2');
// draw outer triangle
drawTriangle(triangle);
// draw legend
drawLegend(legendTexts, 10, 10, 12.86);
plotResult(25, 40, 35);
// end drawing
/////////////////////
function drawSegment(s) {
// draw and fill the segment path
ctx.beginPath();
ctx.moveTo(s.points[0].x, s.points[0].y);
for (var i = 1; i < s.points.length; i++) {
ctx.lineTo(s.points[i].x, s.points[i].y);
}
ctx.closePath();
ctx.fillStyle = s.fill;
ctx.fill();
ctx.lineWidth = 2;
ctx.strokeStyle = 'black';
ctx.stroke();
// draw segment's box label
if (s.label.withLine) {
lineBoxedLabel(s, labelfontsize, labelfontface, labelpadding);
} else {
boxedLabel(s, labelfontsize, labelfontface, labelpadding);
}
}
function moleculeLabel(start, end, offsetLength, angle, text) {
ctx.textAlign = 'center';
ctx.textBaseline = 'middle'
ctx.font = '14px verdana';
var dx = end.x - start.x;
var dy = end.y - start.y;
var x0 = parseInt(start.x + dx * 0.50);
var y0 = parseInt(start.y + dy * 0.50);
var x1 = parseInt(x0 + offsetLength * Math.cos(angle));
var y1 = parseInt(y0 + offsetLength * Math.sin(angle));
ctx.fillStyle = 'black';
ctx.fillText(text, x1, y1);
// arrow
var x0 = parseInt(start.x + dx * 0.35);
var y0 = parseInt(start.y + dy * 0.35);
var x1 = parseInt(x0 + 50 * Math.cos(angle));
var y1 = parseInt(y0 + 50 * Math.sin(angle));
var x2 = parseInt(start.x + dx * 0.65);
var y2 = parseInt(start.y + dy * 0.65);
var x3 = parseInt(x2 + 50 * Math.cos(angle));
var y3 = parseInt(y2 + 50 * Math.sin(angle));
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x3, y3);
ctx.strokeStyle = 'black';
ctx.lineWidth = 1;
ctx.stroke();
var angle = Math.atan2(dy, dx);
ctx.translate(x3, y3);
ctx.rotate(angle);
ctx.drawImage(arrowhead, -arrowheadLength, -arrowheadWidth / 2);
ctx.setTransform(1, 0, 0, 1, 0, 0);
}
function boxedLabel(s, fontsize, fontface, padding) {
var centerX = s.label.cx;
var centerY = s.label.cy;
var text = s.label.text;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle'
ctx.font = fontsize + 'px ' + fontface
var textwidth = ctx.measureText(text).width;
var textheight = fontsize * 1.286;
var leftX = centerX - textwidth / 2 - padding;
var topY = centerY - textheight / 2 - padding;
ctx.fillStyle = 'white';
ctx.fillRect(leftX, topY, textwidth + padding * 2, textheight + padding * 2);
ctx.lineWidth = 1;
ctx.strokeRect(leftX, topY, textwidth + padding * 2, textheight + padding * 2);
ctx.fillStyle = 'black';
ctx.fillText(text, centerX, centerY);
}
function lineBoxedLabel(s, fontsize, fontface, padding) {
var centerX = s.label.cx;
var centerY = s.label.cy;
var text = s.label.text;
var lineToX = s.label.endX;
var lineToY = s.label.endY;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle'
ctx.font = fontsize + 'px ' + fontface
var textwidth = ctx.measureText(text).width;
var textheight = fontsize * 1.286;
var leftX = centerX - textwidth / 2 - padding;
var topY = centerY - textheight / 2 - padding;
// the line
ctx.beginPath();
ctx.moveTo(leftX, topY + textheight / 2);
ctx.lineTo(lineToX, topY + textheight / 2);
ctx.strokeStyle = 'black';
ctx.lineWidth = 1;
ctx.stroke();
// the boxed text
ctx.fillStyle = 'white';
ctx.fillRect(leftX, topY, textwidth + padding * 2, textheight + padding * 2);
ctx.strokeRect(leftX, topY, textwidth + padding * 2, textheight + padding * 2);
ctx.fillStyle = 'black';
ctx.fillText(text, centerX, centerY);
}
function ticklines(start, end, count, angle, length) {
var dx = end.x - start.x;
var dy = end.y - start.y;
ctx.lineWidth = 1;
for (var i = 1; i < count; i++) {
var x0 = parseInt(start.x + dx * i / count);
var y0 = parseInt(start.y + dy * i / count);
var x1 = parseInt(x0 + length * Math.cos(angle));
var y1 = parseInt(y0 + length * Math.sin(angle));
ctx.beginPath();
ctx.moveTo(x0, y0);
ctx.lineTo(x1, y1);
ctx.stroke();
if (i == 2 || i == 4 || i == 6 || i == 8) {
var labelOffset = length * 3 / 4;
var x1 = parseInt(x0 - labelOffset * Math.cos(angle));
var y1 = parseInt(y0 - labelOffset * Math.sin(angle));
ctx.fillStyle = 'black';
ctx.fillText(parseInt(i * 10), x1, y1);
}
}
}
function premakeArrowhead() {
var actx = arrowhead.getContext('2d');
arrowhead.width = arrowheadLength;
arrowhead.height = arrowheadWidth;
actx.beginPath();
actx.moveTo(0, 0);
actx.lineTo(arrowheadLength, arrowheadWidth / 2);
actx.lineTo(0, arrowheadWidth);
actx.closePath();
actx.fillStyle = 'black';
actx.fill();
}
function drawTriangle(t) {
ctx.beginPath();
ctx.moveTo(t[0].x, t[0].y);
ctx.lineTo(t[1].x, t[1].y);
ctx.lineTo(t[2].x, t[2].y);
ctx.closePath();
ctx.strokeStyle = 'black';
ctx.lineWidth = 2;
ctx.stroke();
}
function drawLegend(texts, x, y, lineheight) {
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
ctx.fillStyle = 'black';
ctx.font = '12px arial';
for (var i = 0; i < texts.length; i++) {
ctx.fillText(texts[i], x, y + i * lineheight);
}
}
function plotResult(val1, val2, val3) {
let deltaX, length;
let sum = val1 + val2 + val3;
const cos60 = Math.cos(Math.PI / 3);
const sin60 = Math.sin(Math.PI / 3);
let ch4 = val1 / sum;
let c2h2 = val2 / sum;
let c2h4 = val3 / sum;
length = Math.sqrt(Math.pow((v1.x - v0.x), 2) + Math.pow((v1.y - v0.y), 2));
let ch4PointA = new Point(v0.x + (length * ch4) * cos60, v0.y - (length * ch4) * sin60);
length = Math.sqrt(Math.pow((v2.x - v1.x), 2) + Math.pow((v2.y - v1.y), 2));
let ch4PointB = new Point(v2.x - (length * ch4) * cos60, v2.y - (length * ch4) * sin60);
length = Math.sqrt(Math.pow((v1.x - v2.x), 2) + Math.pow((v1.y - v2.y), 2));
let c2h4PointA = new Point(v1.x + (length * c2h4) * cos60, v1.y + (length * c2h4) * sin60);
deltaX = (v2.x - v0.x) * c2h4;
let c2h4PointB = new Point(v0.x + deltaX, v0.y);
let point = getIntersection(ch4PointA, ch4PointB, c2h4PointA, c2h4PointB);
ctx.beginPath();
ctx.arc(point.x, point.y, 5, 0, 2 * Math.PI, false);
ctx.fillStyle = "red";
ctx.fill();
}
function getIntersection(pointA, pointB, pointC, pointD) {
let denominator, a, b, numeratorA, numeratorB;
denominator = ((pointD.y - pointC.y) * (pointB.x - pointA.x)) - ((pointD.x - pointC.x) * (pointB.y - pointA.y));
a = pointA.y - pointC.y;
b = pointA.x - pointC.x;
numeratorA = ((pointD.x - pointC.x) * a) - ((pointD.y - pointC.y) * b);
numeratorB = ((pointB.x - pointA.x) * a) - ((pointB.y - pointA.y) * b);
a = numeratorA / denominator;
b = numeratorB / denominator;
return new Point(pointA.x + (a * (pointB.x - pointA.x)), pointA.y + (a * (pointB.y - pointA.y)));
}
body {
background-color: ivory;
padding: 10px;
}
#canvas {
border: 1px solid red;
margin: 0 auto;
}
<canvas id="canvas" width=650 height=500></canvas>

JavaScript FabricJs put image to canvas view

I want to create a circle and an arrow form using Fabric.js, so that for instance if one clicks on the arrow button, one should be able to draw an arrow (with the current selected width and height), same for the circle. The regarding html Looks like this:
<div class="btn-group-toggle toolbar-left-btns" data-toggle="buttons">
<h4 class="h4-color-white" id="current-shape-name" style="display:none;"></h4>
<label class="btn btn-secondary btn-md btn-md btn-tool-margin-left" title="Create an arrow object" id="drawing-arrow-shape">
<input type="radio" name="drawing-shape">
<i class="fas fa-arrow-right"></i>
</label>
<label class="btn btn-secondary btn-md btn-tool-margin-left" title="Create a circle object" id="drawing-circle-shape">
<input type="radio" name="drawing-shape">
<i class="far fa-circle"></i>
</label>
</div>
Basically a selection if one uses the arrow or circle form. I won't post the full script since ist really Long, currently I am able to select a Color and a brush width and paint on my canvas. That works, so Keep that in mind. I am just not "able" to create a circle and an arrow. In the following part of my script I am selecting of the if its an arrow or circle (the selection works) and if selected to "draw" that arrow or circle in my canvas, it draws Right after selecting a mix between an arrow and a cube, any idea what I am missing. Nor do I get an error message.
const canvasRenderer = {
render() {
this.initCanvas();
},
elements: {
...
canvas: '',
isDown: null,
circle: null,
circleOrigX: null,
circleOrigY: null,
startX: null,
startY: null,
id: {
...
drawingArrowShape: $('#drawing-arrow-shape'),
drawingCircleShape: $('#drawing-circle-shape'),
imgInput: $('#img-input'),
},
tags: {
html: $('html')
}
},
propToolRadio() {
this.elements.id.drawingArrowShape.prop('checked', false);
this.elements.id.drawingCircleShape.prop('checked', false);
this.elements.id.drawingArrowShape.parent().removeClass('active');
this.elements.id.drawingCircleShape.parent().removeClass('active');
},
recOnArrow(canvas) {
this.elements.id.drawingArrowShape.change((e) => {
canvas.isDrawingMode = false;
this.elements.id.currentShapeName.innerHTML = 'Arrow';
canvas.off('mouse:down', this.onCircleMouseDown(e));
canvas.off('mouse:move', this.onCircleMouseMove(e));
canvas.off('mouse:up', this.onCircleMouseUp(e));
canvas.on('mouse:down', this.onArrowMouseDown(e));
canvas.on('mouse:move', this.onArrowMouseMove(e));
canvas.on('mouse:up', this.onArrowMouseUp(e));
this.recCanvas(canvas);
});
},
recOnCircle(canvas) {
this.elements.id.drawingCircleShape.change((e) => {
canvas.isDrawingMode = false;
this.elements.id.currentShapeName.innerHTML = 'Circle';
canvas.on('mouse:down', this.onCircleMouseDown(e));
canvas.on('mouse:move', this.onCircleMouseMove(e));
canvas.on('mouse:up', this.onCircleMouseUp(e));
canvas.off('mouse:down', this.onArrowMouseDown(e));
canvas.off('mouse:move', this.onArrowMouseMove(e));
canvas.off('mouse:up', this.onArrowMouseUp(e));
this.recCanvas(canvas);
});
},
onArrowMouseDown(o) {
let pointer = this.elements.canvas.getPointer(o.e);
this.elements.startX = pointer.x;
this.elements.startY = pointer.y;
this.recCanvas(this.elements.canvas);
},
onArrowMouseUp(o) {
let pointer = this.elements.canvas.getPointer(o.e);
let endX = pointer.x;
let endY = pointer.y;
this.showArrow(
this.elements.startX,
this.elements.startY,
endX,
endY,
this.elements.canvas
);
this.recCanvas(this.elements.canvas);
},
onArrowMouseMove(e) {
},
onCircleMouseDown(o) {
this.elements.isDown = true;
let pointer = this.elements.canvas.getPointer(o.e);
this.elements.circleOrigX = pointer.x;
this.elements.circleOrigY = pointer.y;
if (!this.elements.circle) {
this.elements.circle = new fabric.Circle({
left: this.elements.circleOrigX,
top: this.elements.circleOrigY,
originX: 'center',
originY: 'center',
radius: 0,
fill: '',
stroke: this.elements.id.drawingColorEl.value,
strokeWidth: parseInt(this.elements.id.drawingLineWidthEl.value, 10) || 1,
selectable: true
});
this.elements.canvas.add(this.elements.circle);
}
this.recCanvas(this.elements.canvas);
},
onCircleMouseMove(o) {
console.log("onCircleMouseMove");
if (!this.elements.isDown) return;
let pointer = this.elements.canvas.getPointer(o.e);
this.elements.circle.set({
radius: Math.sqrt(
Math.pow(
(this.elements.circleOrigX - pointer.x), 2
) + Math.pow((this.elements.circleOrigY - pointer.y), 2)
)
});
this.elements.canvas.renderAll();
},
onCircleMouseUp(o) {
console.log("onCircleMouseUp");
this.elements.isDown = false;
this.elements.circle = null;
},
showArrow(fromx, fromy, tox, toy, canvas) {
console.log("showArrow");
let angle = Math.atan2(toy - fromy, tox - fromx);
let headlen = 15; // arrow head size
// bring the line end back some to account for arrow head.
tox = tox - (headlen) * Math.cos(angle);
toy = toy - (headlen) * Math.sin(angle);
// calculate the points.
let points = [{
x: fromx, // start point
y: fromy
}, {
x: fromx - (headlen / 4) * Math.cos(angle - Math.PI / 2),
y: fromy - (headlen / 4) * Math.sin(angle - Math.PI / 2)
}, {
x: tox - (headlen / 4) * Math.cos(angle - Math.PI / 2),
y: toy - (headlen / 4) * Math.sin(angle - Math.PI / 2)
}, {
x: tox - (headlen) * Math.cos(angle - Math.PI / 2),
y: toy - (headlen) * Math.sin(angle - Math.PI / 2)
}, {
x: tox + (headlen) * Math.cos(angle), // tip
y: toy + (headlen) * Math.sin(angle)
}, {
x: tox - (headlen) * Math.cos(angle + Math.PI / 2),
y: toy - (headlen) * Math.sin(angle + Math.PI / 2)
}, {
x: tox - (headlen / 4) * Math.cos(angle + Math.PI / 2),
y: toy - (headlen / 4) * Math.sin(angle + Math.PI / 2)
}, {
x: fromx - (headlen / 4) * Math.cos(angle + Math.PI / 2),
y: fromy - (headlen / 4) * Math.sin(angle + Math.PI / 2)
}, {
x: fromx,
y: fromy
}];
let pline = new fabric.Polyline(points, {
fill: 'black',
stroke: this.elements.id.drawingColorEl.value,
opacity: 1,
// strokeWidth: 2,
strokeWidth: parseInt(this.elements.id.drawingLineWidthEl.value, 10) || 1,
originX: 'left',
originY: 'top',
selectable: true
});
canvas.add(pline);
canvas.renderAll();
},
/**
* TODO on mdified, moving, rotating, scaling method to call only once
*/
recOnEvent(canvas) {
let isObjectMoving = false;
canvas.on('path:created', () => {
this.recCanvas(canvas)
});
canvas.on('object.added', () => {
this.recCanvas(canvas)
});
canvas.on('object:modified', () => {
isObjectMoving = true;
});
canvas.on('object:moving', () => {
isObjectMoving = true;
});
canvas.on('object:removed', () => {
this.recCanvas(canvas)
});
canvas.on('object:rotating', () => {
isObjectMoving = true;
});
canvas.on('object:scaling', () => {
isObjectMoving = true;
});
this.elements.id.canvasContainer.click(() => {
this.recCanvas(canvas);
});
canvas.on('mouse:up', () => {
if (isObjectMoving) {
isObjectMoving = false;
this.recCanvas(canvas); // fire this if finished
}
});
},
};
$(document).ready(() => {
canvasRenderer.render();
});

Javascript - Using functions within objects

I am trying to use the move methods in each of the candy objects all at once, but I had a problem: it was using the same deltaX and deltaY for all of them, no matter what I set them each to individually. I have been trying to find out a way to make it so that it would be individual for each object, but I haven't found a way to yet. I was wondering if you guys had a solution.
The Candy function:
Candy = function(img, location, canvas) {
self = {}
self.image = new Image()
self.image.src = img
self.location = {x: location.x, y: location.y}
self.canvas = canvas
self.draw = function() {
self.canvas.drawImage(this.image, this.location.x, this.location.y, 132.4, 132.4)
}
self.move = function(FPS, seconds, location) {
var object = this
object.frames = FPS * seconds
object.deltaX = (location.x - this.location.x) / frames
object.deltaY = (location.y - this.location.y) / frames
object.counter = 0
var i = setInterval(function() {
object.location.x += object.deltaX
object.location.y += object.deltaY
object.counter++
draw()
if(object.counter >= object.frames)
clearInterval(i)
}, 1000 / FPS)
}
self.image.onload = function() {
Candy.list.push(self)
Candy.queue.splice(0, 1)
if(Candy.queue.length == 0)
draw()
else
Candy(Candy.queue[0].img, Candy.queue[0].location, Candy.queue[0].canvas)
}
}
Candy.list = []
Candy.queue = []
Where I initiate the movement:
for(var i = 0; i < Candy.list.length; i++) {
var candy = Candy.list[i]
var x = i < 4 ? width / 5 : 7 * (width / 5)
var y = candy.location.y
candy.move(30, 3, {x: x, y: y})
}
Where I initialize the candy objects:
Candy.queue.push({img: "client/img/candy.png", location: {x: 2 * (width / 5), y: height / 10}, canvas: canvasContext})
Candy.queue.push({img: "client/img/candy2.png", location: {x: 2 * (width / 5), y: 3 * (height / 10)}, canvas: canvasContext})
Candy.queue.push({img: "client/img/candy3.png", location: {x: 2 * (width / 5), y: 5 * (height / 10)}, canvas: canvasContext})
Candy.queue.push({img: "client/img/candy4.png", location: {x: 2 * (width / 5), y: 7 * (height / 10)}, canvas: canvasContext})
Candy.queue.push({img: "client/img/candy2.png", location: {x: width / 2, y: 1 * (height / 10)}, canvas: canvasContext})
Candy.queue.push({img: "client/img/candy4.png", location: {x: width / 2, y: 3 * (height / 10)}, canvas: canvasContext})
Candy.queue.push({img: "client/img/candy5.jpg", location: {x: width / 2, y: 5 * (height / 10)}, canvas: canvasContext})
Candy.queue.push({img: "client/img/candy.png", location: {x: width / 2, y: 7 * (height / 10)}, canvas: canvasContext})
Candy(Candy.queue[0].img, Candy.queue[0].location, Candy.queue[0].canvas)
The draw function:
function draw() {
colorRect(0, 0, canvas.width, canvas.height, 'white');
colorText("Player 1", 0.02, 0.05, "black", "40px Comic Sans");
colorText("Player 2", 0.88, 0.05, "black", "40px Comic Sans");
if(!gameStarted) {
if(player1.ready)
colorText("Ready", 0.02, 0.09, "green", "20px Comic Sans");
else
colorText("Not Ready", 0.02, 0.09, "red", "20px Comic Sans");
if(player2.ready)
colorText("Ready", 0.88, 0.09, "green", "20px Comic Sans");
else
colorText("Not Ready", 0.88, 0.09, "red", "20px Comic Sans");
if(player1.ready && player2.ready)
colorText("Press a button to start the game!", 0.32, 0.5, "black", "40px Comic Sans")
}else{
for(var i = 0; i < Candy.list.length; i++) {
Candy.list[i].draw()
}
if(decision1 != null) {
var color
if(decision1 == "Share")
color = 'green'
else
color = 'red'
colorText(decision1, 0.02, 0.15, color, "40px Comic Sans");
}
if(decision2 != null) {
var color
if(decision2 == "Share")
color = 'green'
else
color = 'red'
colorText(decision2, 0.02, 0.15, color, "40px Comic Sans");
}
}
}
The problem is you are never creating a Candy object. This can be easily seen by running console.log(Candy.list[0] instanceof Candy) (this will print false).
To create a Candy object you must use the new keyword.
I have edited your code to insert new Candy objects into the queue. First off, instead of creating a new object like you did with {}, your constructor must use the this keyword to refer to itself:
Candy = function(img, location, canvas) {
var self = this;
self.image = new Image()
self.image.src = img
self.location = {x: location.x, y: location.y}
self.canvas = canvas
self.draw = function() {
self.canvas.drawImage(self.image, self.location.x, self.location.y, 132.4, 132.4)
}
self.move = function(FPS, seconds, location) {
self.frames = FPS * seconds
self.deltaX = (location.x - self.location.x) / self.frames
self.deltaY = (location.y - self.location.y) / self.frames
self.counter = 0
var i = setInterval(function() {
self.location.x += self.deltaX
self.location.y += self.deltaY
self.counter++
draw()
if(self.counter >= self.frames)
clearInterval(i)
}, 1000 / FPS)
}
self.image.onload = function() {
Candy.list.push(self)
Candy.queue.splice(Candy.queue.indexOf(self), 1)
if(Candy.queue.length === 0)
draw()
}
}
Candy.list = []
Candy.queue = []
Initialize code - now with the new keyword:
Candy.queue.push(new Candy("client/img/candy.png", {x: 2 * (width / 5), y: height / 10}, canvasContext))
Candy.queue.push(new Candy("client/img/candy2.png", {x: 2 * (width / 5), y: 3 * (height / 10)}, canvasContext))
Candy.queue.push(new Candy("client/img/candy3.png", {x: 2 * (width / 5), y: 5 * (height / 10)}, canvasContext))
Candy.queue.push(new Candy("client/img/candy4.png", {x: 2 * (width / 5), y: 7 * (height / 10)}, canvasContext))
Candy.queue.push(new Candy("client/img/candy2.png", {x: width / 2, y: 1 * (height / 10)}, canvasContext))
Candy.queue.push(new Candy("client/img/candy4.png", {x: width / 2, y: 3 * (height / 10)}, canvasContext))
Candy.queue.push(new Candy("client/img/candy5.jpg", {x: width / 2, y: 5 * (height / 10)}, canvasContext))
Candy.queue.push(new Candy("client/img/candy.png", {x: width / 2, y: 7 * (height / 10)}, canvasContext))
The movement code doesn't need changing.
I think the problem is var object = this;
You are manipulating the same object reference on each call.
You maybe could try something like
var object = JSON.parse(JSON.stringify(this);
to clone the object.
Edit:
Or better
var object = Object.assign({}, this);
to keep the functions.

Arrows in fabricjs

I'm trying to create an arrow shape using fabricjs. Thus far my best approach has been to add a line and a triangle and combine them into a composite group. The problem however is, when I resize the arrow, the arrow head gets stretched and its not a nice effect.
What I'm asking is, how would you go about creating an arrow object on fabricjs that can be resized lengthwise only without stretching the arrow head.
http://jsfiddle.net/skela/j45czqge/
<html>
<head>
<script src='http://fabricjs.com/build/files/text,gestures,easing,parser,freedrawing,interaction,serialization,image_filters,gradient,pattern,shadow,node.js'></script> <meta charset="utf-8">
<style>
html,body
{
height: 100%; min-height:100%;
width: 100%; min-width:100%;
background-color:transparent;
margin:0;
}
button
{
height:44px;
margin:0;
}
</style>
</head>
<body>
<span id="dev">
<button id="draw_mode" onclick="toggleDraw()">Draw</button>
<button onclick="addRect()">Add Rect</button>
<button onclick="addCircle()">Add Circle</button>
<button onclick="addTriangle()">Add Triangle</button>
<button onclick="addLine()">Add Line</button>
<button onclick="addArrow()">Add Arrow</button>
<button onclick="clearCanvas()">Clear</button>
<button onclick="saveCanvas()">Save</button>
<button onclick="loadCanvas()">Load</button>
</span>
<span id="selected" style="visibility:hidden;">
<button onclick="removeSelected()">Remove</button>
</span>
<canvas id="c" style="border:1px solid #aaa;"></canvas>
<script>
fabric.Object.prototype.toObject = (function (toObject)
{
return function ()
{
return fabric.util.object.extend(toObject.call(this),
{
id:this.id,
});
};
})(fabric.Object.prototype.toObject);
fabric.LineArrow = fabric.util.createClass(fabric.Line, {
type: 'lineArrow',
initialize: function(element, options) {
options || (options = {});
this.callSuper('initialize', element, options);
},
toObject: function() {
return fabric.util.object.extend(this.callSuper('toObject'));
},
_render: function(ctx){
this.callSuper('_render', ctx);
// do not render if width/height are zeros or object is not visible
if (this.width === 0 || this.height === 0 || !this.visible) return;
ctx.save();
var xDiff = this.x2 - this.x1;
var yDiff = this.y2 - this.y1;
var angle = Math.atan2(yDiff, xDiff);
ctx.translate((this.x2 - this.x1) / 2, (this.y2 - this.y1) / 2);
ctx.rotate(angle);
ctx.beginPath();
//move 10px in front of line to start the arrow so it does not have the square line end showing in front (0,0)
ctx.moveTo(10,0);
ctx.lineTo(-20, 15);
ctx.lineTo(-20, -15);
ctx.closePath();
ctx.fillStyle = this.stroke;
ctx.fill();
ctx.restore();
}
});
fabric.LineArrow.fromObject = function (object, callback) {
callback && callback(new fabric.LineArrow([object.x1, object.y1, object.x2, object.y2],object));
};
fabric.LineArrow.async = true;
var canvas = new fabric.Canvas('c');
canvas.isDrawingMode = false;
canvas.freeDrawingBrush.width = 5;
setColor('red');
var sendToApp = function(_key, _val)
{
var iframe = document.createElement("IFRAME");
iframe.setAttribute("src", _key + ":##drawings##" + _val);
document.documentElement.appendChild(iframe);
iframe.parentNode.removeChild(iframe);
iframe = null;
};
canvas.on('object:selected',function(options)
{
if (options.target)
{
//console.log('an object was selected! ', options.target.type);
var sel = document.getElementById("selected");
sel.style.visibility = "visible";
sendToApp("object:selected","");
}
});
canvas.on('selection:cleared',function(options)
{
//console.log('selection cleared');
var sel = document.getElementById("selected");
sel.style.visibility = "hidden";
sendToApp("selection:cleared","");
});
canvas.on('object:modified',function(options)
{
if (options.target)
{
//console.log('an object was modified! ', options.target.type);
sendToApp("object:modified","");
}
});
canvas.on('object:added',function(options)
{
if (options.target)
{
if (typeof options.target.id == 'undefined')
{
options.target.id = 1337;
}
//console.log('an object was added! ', options.target.type);
sendToApp("object:added","");
}
});
canvas.on('object:removed',function(options)
{
if (options.target)
{
//console.log('an object was removed! ', options.target.type);
sendToApp("object:removed","");
}
});
window.addEventListener('resize', resizeCanvas, false);
function resizeCanvas()
{
canvas.setHeight(window.innerHeight);
canvas.setWidth(window.innerWidth);
canvas.renderAll();
}
function color()
{
return canvas.freeDrawingBrush.color;
}
function setColor(color)
{
canvas.freeDrawingBrush.color = color;
}
function toggleDraw()
{
setDrawingMode(!canvas.isDrawingMode);
}
function setDrawingMode(isDrawingMode)
{
canvas.isDrawingMode = isDrawingMode;
var btn = document.getElementById("draw_mode");
btn.innerHTML = canvas.isDrawingMode ? "Drawing" : "Draw";
sendToApp("mode",canvas.isDrawingMode ? "drawing" : "draw");
}
function setLineControls(line)
{
line.setControlVisible("tr",false);
line.setControlVisible("tl",false);
line.setControlVisible("br",false);
line.setControlVisible("bl",false);
line.setControlVisible("ml",false);
line.setControlVisible("mr",false);
}
function createLine(points)
{
var line = new fabric.Line(points,
{
strokeWidth: 5,
stroke: color(),
originX: 'center',
originY: 'center',
lockScalingX:true,
//lockScalingY:false,
});
setLineControls(line);
return line;
}
function createArrowHead(points)
{
var headLength = 15,
x1 = points[0],
y1 = points[1],
x2 = points[2],
y2 = points[3],
dx = x2 - x1,
dy = y2 - y1,
angle = Math.atan2(dy, dx);
angle *= 180 / Math.PI;
angle += 90;
var triangle = new fabric.Triangle({
angle: angle,
fill: color(),
top: y2,
left: x2,
height: headLength,
width: headLength,
originX: 'center',
originY: 'center',
// lockScalingX:false,
// lockScalingY:true,
});
return triangle;
}
function addRect()
{
canvas.add(new fabric.Rect({left:100,top:100,fill:color(),width:50,height:50}));
}
function addCircle()
{
canvas.add(new fabric.Circle({left:150,top:150,fill:color(),radius:50/2}));
}
function addTriangle()
{
canvas.add(new fabric.Triangle({left:200,top:200,fill:color(),height:50,width:46}));
}
function addLine()
{
var line = createLine([100,100,100,200]);
canvas.add(line);
}
function addArrow()
{
var pts = [100,100,100,200];
var triangle = createArrowHead(pts);
var line = createLine(pts);
var grp = new fabric.Group([triangle,line]);
setLineControls(grp);
canvas.add(grp);
// var arrow = new fabric.LineArrow(pts,{left:100,top:100,fill:color()});
// setLineControls(arrow);
// canvas.add(arrow);
}
function removeSelected()
{
var grp = canvas.getActiveGroup();
var obj = canvas.getActiveObject();
if (obj!=null)
{
canvas.remove(obj);
}
if (grp!=null)
{
grp.forEachObject(function(o){ canvas.remove(o) });
canvas.discardActiveGroup().renderAll();
}
}
function clearCanvas()
{
canvas.clear();
}
function saveCanvas()
{
var js = JSON.stringify(canvas);
return js;
}
function loadCanvas()
{
var js = '{"objects":[{"type":"circle","originX":"left","originY":"top","left":150,"top":150,"width":50,"height":50,"fill":"red","stroke":null,"strokeWidth":1,"strokeDashArray":null,"strokeLineCap":"butt","strokeLineJoin":"miter","strokeMiterLimit":10,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"clipTo":null,"backgroundColor":"","fillRule":"nonzero","globalCompositeOperation":"source-over","id":1234,"radius":25,"startAngle":0,"endAngle":6.283185307179586}],"background":""}';
canvas.loadFromJSON(js);
}
resizeCanvas();
</script>
</body>
</html>
I had the same problem and ended up doing math to calculate the points that would make up an arrow shape around a line and using a polygon object instead.
The core of it looks like:
var angle = Math.atan2(toy - fromy, tox - fromx);
var headlen = 15; // arrow head size
// bring the line end back some to account for arrow head.
tox = tox - (headlen) * Math.cos(angle);
toy = toy - (headlen) * Math.sin(angle);
// calculate the points.
var points = [
{
x: fromx, // start point
y: fromy
}, {
x: fromx - (headlen / 4) * Math.cos(angle - Math.PI / 2),
y: fromy - (headlen / 4) * Math.sin(angle - Math.PI / 2)
},{
x: tox - (headlen / 4) * Math.cos(angle - Math.PI / 2),
y: toy - (headlen / 4) * Math.sin(angle - Math.PI / 2)
}, {
x: tox - (headlen) * Math.cos(angle - Math.PI / 2),
y: toy - (headlen) * Math.sin(angle - Math.PI / 2)
},{
x: tox + (headlen) * Math.cos(angle), // tip
y: toy + (headlen) * Math.sin(angle)
}, {
x: tox - (headlen) * Math.cos(angle + Math.PI / 2),
y: toy - (headlen) * Math.sin(angle + Math.PI / 2)
}, {
x: tox - (headlen / 4) * Math.cos(angle + Math.PI / 2),
y: toy - (headlen / 4) * Math.sin(angle + Math.PI / 2)
}, {
x: fromx - (headlen / 4) * Math.cos(angle + Math.PI / 2),
y: fromy - (headlen / 4) * Math.sin(angle + Math.PI / 2)
},{
x: fromx,
y: fromy
}
];
Then create a polygon from the points.
https://jsfiddle.net/6e17oxc3/
What you can do is calculate the new size after the object is stretched and draw another object on the same exact area and removing the previous one.
var obj = canvas.getActiveObject();
var width = obj.getWidth();
var height = obj.getHeight;
var top = obj.getTop();
Now if you have only one object that is stretched, you can simply use the data above to draw another nicely looking object on the canvas.
If you have multiples then you need to get the data for all of them and draw them one by one.

Categories

Resources