JavaScript FabricJs put image to canvas view - javascript

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();
});

Related

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>

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

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();
});

fabric.js arrow snapped to grid

I've adapted some code to snap a line to a grid using Fabric.js, as in the code below. What I want is for it to instead be an arrow. I can't seem to get the arrowhead (triangle) to move properly. I kept breaking the code by trying to add a triangle as the arrowhead, so I removed all code attempting to add it. Please help me add the arrowhead, thank you!!
var canvas = new fabric.Canvas('c', { selection: false });
var grid = 50;
for (var i = 0; i < (600 / grid); i++) {
canvas.add(new fabric.Line([ i * grid, 0, i * grid, 600], { stroke: '#ccc', selectable: false }));
canvas.add(new fabric.Line([ 0, i * grid, 600, i * grid], { stroke: '#ccc', selectable: false }))
}
var line, isDown;
canvas.on('mouse:down', function(o){
canvas.remove(line);
isDown = true;
var pointer = canvas.getPointer(o.e);
var points = [ Math.round(pointer.x / grid) * grid, Math.round(pointer.y / grid) * grid, pointer.x, pointer.y ];
line = new fabric.Line(points, {
strokeWidth: 5,
fill: 'red',
stroke: 'red',
originX: 'center',
originY: 'center'
});
canvas.add(line);
});
canvas.on('mouse:move', function(o){
if (!isDown) return;
var pointer = canvas.getPointer(o.e);
line.set({ x2: pointer.x, y2: pointer.y });
canvas.renderAll();
});
canvas.on('mouse:up', function(o){
var pointer = canvas.getPointer(o.e);
isDown = false;
line.set({ x2: Math.round(pointer.x/ grid) * grid, y2: Math.round(pointer.y/ grid) * grid });
canvas.renderAll();
});
<script src="https://rawgithub.com/kangax/fabric.js/master/dist/fabric.js"></script>
<canvas id="c" width="500" height="500" style="border:1px solid #ccc"></canvas>
Got 'em! For me, the trick was defining the center of the triangle following the method in this fiddle: http://jsfiddle.net/ug2gskj1/
left: line.get('x1') + deltaX;
top: line.get('y1') + deltaY;
where
centerX = (line.x1 + line.x2) / 2;
centerY = (line.y1 + line.y2) / 2;
deltaX = line.left - centerX;
deltaY = line.top - centerY;
window.onload = function() {
var canvas = new fabric.Canvas('c', {
selection: false
});
var grid = 50;
for (var i = 0; i < (600 / grid); i++) {
canvas.add(new fabric.Line([i * grid, 0, i * grid, 600], {
stroke: '#ccc',
selectable: false
}));
canvas.add(new fabric.Line([0, i * grid, 600, i * grid], {
stroke: '#ccc',
selectable: false
}))
}
var line, triangle, isDown;
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 + 90);
}
canvas.on('mouse:down', function(o) {
canvas.remove(line, triangle);
isDown = true;
var pointer = canvas.getPointer(o.e);
var points = [Math.round(pointer.x / grid) * grid, Math.round(pointer.y / grid) * grid, pointer.x, pointer.y];
line = new fabric.Line(points, {
strokeWidth: 5,
fill: 'red',
stroke: 'red',
originX: 'center',
originY: 'center'
});
centerX = (line.x1 + line.x2) / 2;
centerY = (line.y1 + line.y2) / 2;
deltaX = line.left - centerX;
deltaY = line.top - centerY;
triangle = new fabric.Triangle({
left: line.get('x1') + deltaX,
top: line.get('y1') + deltaY,
originX: 'center',
originY: 'center',
hasBorders: false,
hasControls: false,
lockScalingX: true,
lockScalingY: true,
lockRotation: true,
pointType: 'arrow_start',
angle: -45,
width: 20,
height: 20,
fill: 'red'
});
canvas.add(line, triangle);
});
canvas.on('mouse:move', function(o) {
//function angle(x1,y1,x2,y2){angle=Math.atan((y2-y1)/(x2-x1))*180/Math.PI+90; return angle;}
if (!isDown) return;
var pointer = canvas.getPointer(o.e);
line.set({
x2: pointer.x,
y2: pointer.y
});
triangle.set({
'left': pointer.x + deltaX,
'top': pointer.y + deltaY,
'angle': calcArrowAngle(line.x1, line.y1, line.x2, line.y2)
});
canvas.renderAll();
});
canvas.on('mouse:up', function(o) {
var pointer = canvas.getPointer(o.e);
isDown = false;
snappedxCoordinate = Math.round(pointer.x / grid) * grid;
snappedyCoordinate = Math.round(pointer.y / grid) * grid;
snappedxCoordinateArrowhead = Math.round((pointer.x + deltaX) / grid) * grid;
snappedyCoordinateArrowhead = Math.round((pointer.y + deltaY) / grid) * grid;
line.set({
x2: snappedxCoordinate,
y2: snappedyCoordinate
});
triangle.set({
'left': snappedxCoordinateArrowhead,
'top': snappedyCoordinateArrowhead,
'angle': calcArrowAngle(line.x1, line.y1, line.x2, line.y2)
});
canvas.renderAll();
});
}
<script src="https://rawgithub.com/kangax/fabric.js/master/dist/fabric.js"></script>
<canvas id="c" width="500" height="500" style="border:1px solid #ccc"></canvas>

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.

kineticjs performance lag

I am working on a radial control similar to the HTML5 wheel of fortune example. I've modified the original here with an example of some additional functionality I require: http://jsfiddle.net/fEm9P/ When you click on the inner kinetic wedges they will shrink and expand within the larger wedges. Unfortunately when I rotate the wheel it lags behind the pointer. It's not too bad here but it's really noticeable on a mobile.
I know this is due to the fact that I'm not caching the wheel. When I do cache the wheel (uncomment lines 239-249) the inner wedges no longer respond to mouse/touch but the response on rotation is perfect. I have also tried adding the inner wedges to a separate layer and caching the main wheel only. I then rotate the inner wheel with the outer one. Doing it this way is a little better but still not viable on mobile.
Any suggestions would be greatly appreciated.
Stephen
//constants
var MAX_ANGULAR_VELOCITY = 360 * 5;
var NUM_WEDGES = 25;
var WHEEL_RADIUS = 410;
var ANGULAR_FRICTION = 0.2;
// globals
var angularVelocity = 360;
var lastRotation = 0;
var controlled = false;
var target, activeWedge, stage, layer, wheel,
pointer, pointerTween, startRotation, startX, startY;
var currentVolume, action;
function purifyColor(color) {
var randIndex = Math.round(Math.random() * 3);
color[randIndex] = 0;
return color;
}
function getRandomColor() {
var r = 100 + Math.round(Math.random() * 55);
var g = 100 + Math.round(Math.random() * 55);
var b = 100 + Math.round(Math.random() * 55);
var color = [r, g, b];
color = purifyColor(color);
color = purifyColor(color);
return color;
}
function bind() {
wheel.on('mousedown', function(evt) {
var mousePos = stage.getPointerPosition();
angularVelocity = 0;
controlled = true;
target = evt.targetNode;
startRotation = this.rotation();
startX = mousePos.x;
startY = mousePos.y;
});
// add listeners to container
document.body.addEventListener('mouseup', function() {
controlled = false;
action = null;
if(angularVelocity > MAX_ANGULAR_VELOCITY) {
angularVelocity = MAX_ANGULAR_VELOCITY;
}
else if(angularVelocity < -1 * MAX_ANGULAR_VELOCITY) {
angularVelocity = -1 * MAX_ANGULAR_VELOCITY;
}
angularVelocities = [];
}, false);
document.body.addEventListener('mousemove', function(evt) {
var mousePos = stage.getPointerPosition();
var x1, y1;
if(action == 'increase') {
x1 = (mousePos.x-(stage.getWidth() / 2));
y1 = (mousePos.y-WHEEL_RADIUS+20);
var r = Math.sqrt(x1 * x1 + y1 * y1);
if (r>500){
r=500;
} else if (r<100){
r=100;
};
currentVolume.setRadius(r);
layer.draw();
} else {
if(controlled && mousePos && target) {
x1 = mousePos.x - wheel.x();
y1 = mousePos.y - wheel.y();
var x2 = startX - wheel.x();
var y2 = startY - wheel.y();
var angle1 = Math.atan(y1 / x1) * 180 / Math.PI;
var angle2 = Math.atan(y2 / x2) * 180 / Math.PI;
var angleDiff = angle2 - angle1;
if ((x1 < 0 && x2 >=0) || (x2 < 0 && x1 >=0)) {
angleDiff += 180;
}
wheel.setRotation(startRotation - angleDiff);
}
};
}, false);
}
function getRandomReward() {
var mainDigit = Math.round(Math.random() * 9);
return mainDigit + '\n0\n0';
}
function addWedge(n) {
var s = getRandomColor();
var reward = getRandomReward();
var r = s[0];
var g = s[1];
var b = s[2];
var angle = 360 / NUM_WEDGES;
var endColor = 'rgb(' + r + ',' + g + ',' + b + ')';
r += 100;
g += 100;
b += 100;
var startColor = 'rgb(' + r + ',' + g + ',' + b + ')';
var wedge = new Kinetic.Group({
rotation: n * 360 / NUM_WEDGES,
});
var wedgeBackground = new Kinetic.Wedge({
radius: WHEEL_RADIUS,
angle: angle,
fillRadialGradientStartRadius: 0,
fillRadialGradientEndRadius: WHEEL_RADIUS,
fillRadialGradientColorStops: [0, startColor, 1, endColor],
fill: '#64e9f8',
fillPriority: 'radial-gradient',
stroke: '#ccc',
strokeWidth: 2,
rotation: (90 + angle/2) * -1
});
wedge.add(wedgeBackground);
var text = new Kinetic.Text({
text: reward,
fontFamily: 'Calibri',
fontSize: 50,
fill: 'white',
align: 'center',
stroke: 'yellow',
strokeWidth: 1,
listening: false
});
text.offsetX(text.width()/2);
text.offsetY(WHEEL_RADIUS - 15);
wedge.add(text);
volume = createVolumeControl(angle, endColor);
wedge.add(volume);
wheel.add(wedge);
}
var activeWedge;
function createVolumeControl(angle, colour){
var volume = new Kinetic.Wedge({
radius: 100,
angle: angle,
fill: colour,
stroke: '#000000',
rotation: (90 + angle/2) * -1
});
volume.on("mousedown touchstart", function() {
currentVolume = this;
action='increase';
});
return volume;
}
function animate(frame) {
// wheel
var angularVelocityChange = angularVelocity * frame.timeDiff * (1 - ANGULAR_FRICTION) / 1000;
angularVelocity -= angularVelocityChange;
if(controlled) {
angularVelocity = ((wheel.getRotation() - lastRotation) * 1000 / frame.timeDiff);
}
else {
wheel.rotate(frame.timeDiff * angularVelocity / 1000);
}
lastRotation = wheel.getRotation();
// pointer
var intersectedWedge = layer.getIntersection({x: stage.width()/2, y: 50});
if (intersectedWedge && (!activeWedge || activeWedge._id !== intersectedWedge._id)) {
pointerTween.reset();
pointerTween.play();
activeWedge = intersectedWedge;
}
}
function init() {
stage = new Kinetic.Stage({
container: 'container',
width: 578,
height: 500
});
layer = new Kinetic.Layer();
wheel = new Kinetic.Group({
x: stage.getWidth() / 2,
y: WHEEL_RADIUS + 20
});
for(var n = 0; n < NUM_WEDGES; n++) {
addWedge(n);
}
pointer = new Kinetic.Wedge({
fillRadialGradientStartPoint: 0,
fillRadialGradientStartRadius: 0,
fillRadialGradientEndPoint: 0,
fillRadialGradientEndRadius: 30,
fillRadialGradientColorStops: [0, 'white', 1, 'red'],
stroke: 'white',
strokeWidth: 2,
lineJoin: 'round',
angle: 30,
radius: 30,
x: stage.getWidth() / 2,
y: 20,
rotation: -105,
shadowColor: 'black',
shadowOffset: {x:3,y:3},
shadowBlur: 2,
shadowOpacity: 0.5
});
// add components to the stage
layer.add(wheel);
layer.add(pointer);
stage.add(layer);
pointerTween = new Kinetic.Tween({
node: pointer,
duration: 0.1,
easing: Kinetic.Easings.EaseInOut,
y: 30
});
pointerTween.finish();
var radiusPlus2 = WHEEL_RADIUS + 2;
wheel.cache({
x: -1* radiusPlus2,
y: -1* radiusPlus2,
width: radiusPlus2 * 2,
height: radiusPlus2 * 2
}).offset({
x: radiusPlus2,
y: radiusPlus2
});
layer.draw();
// bind events
bind();
var anim = new Kinetic.Animation(animate, layer);
//document.getElementById('debug').appendChild(layer.hitCanvas._canvas);
// wait one second and then spin the wheel
setTimeout(function() {
anim.start();
}, 1000);
}
init();
I made a couple of changes to the script which greatly improved the response time. The first was replacing layer.draw() with layer.batchDraw(). As the draw function was being called on each touchmove event it was making the interaction clunky. BatchDraw on the other hand will stack up draw requests internally "limit the number of redraws per second based on the maximum number of frames per second" (http://www.html5canvastutorials.com/kineticjs/html5-canvas-kineticjs-batch-draw).
The jumping around of the canvas I seeing originally when I cached/cleared the wheel was due to the fact that I wasn't resetting the offset on the wheel when I cleared the cache.
http://jsfiddle.net/leydar/a7tkA/5
wheel.clearCache().offset({
x: 0,
y: 0
});
I hope this is of benefit to someone else. It's still not perfectly responsive but it's at least going in the right direction.
Stephen

Categories

Resources