How to draw sector (not arc) in fabric js? - javascript

I am trying to draw semecircle (sector) with fabricjs:
$('#button').click(function(){
fov+=10;
drawSector(fov);
});
$('#button2').click(function(){
fov-=10;
drawSector(fov);
});
var w=500,h=500;
var R = 100;
var fov = 75.0;
var lastFOV;
var ele = {
center: {x: 0.5, y:0.5},
focal: {x: 0.5, y: 0.5},
radius: 0.6,
transform: {
rotate: 0,
translate: {
x: 0,
y: 0,
},
scale: {
x: 1,
y: 1,
}
},
stops: [
{offset: '0', color: "purple",alpha:'0.75'},
{offset: '0.9', color: "transparent",opacity:'0'}
]
};
var tr_str = "rotate("+ele.transform.rotate+",0.5,0.5) translate("+ele.transform.translate.x*w+","+ele.transform.translate.y*h+") scale("+ele.transform.scale.x+","+ele.transform.scale.y+")";
var tr_matrix = fabric.parseTransformAttribute(tr_str);
var rg = {
type: 'radial',
x1: ele.center.x,
y1: ele.center.y,
r1: 0,
x2: ele.focal.x,
y2: ele.focal.y,
r2: R,
//transformMatrix: [1,0,0,2,0,0],
//gradientTransform: [1,0,0,2,0,0],
gradientTransform: tr_matrix,
colorStops: (function(){
var color_stops = {};
for(var i=0;i<ele.stops.length;i++){
color_stops[ele.stops[i].offset] = ele.stops[i].color;
}
return color_stops;
})()
};
var HideControls = {
'tl':false,
'tr':false,
'bl':false,
'br':false,
'ml':false,
'mt':false,
'mr':false,
'mb':false,
'mtr':true
};
var canvas = new fabric.Canvas('canvas1');
canvas.setWidth(w);
canvas.setHeight(h);
var x,y,my,startPoints;
var sector;
var rotationAngle = 0;
drawSector(fov);
function drawSector(fov){
$('#fov').html("FOV = "+fov);
x = Math.cos(fov*Math.PI/180.0)*R;
y = Math.sin(fov*Math.PI/180.0)*R;
my = -Math.sin(fov/2.*Math.PI/180.0)*R/2.;
startPoints = [
{x: 0, y: 0},
{x: R, y: 0},
{x: x, y: y}
];
if(sector) {
rotationAngle = sector.angle;
canvas.remove(sector);
}
sector = new fabric.Polygon(startPoints,{
left: w/2,
top: h/2,
originX:'left',
originY:'top',
centeredRotation:false,
hasBorders:false,
lockMovementX:true,
lockMovementY:true,
rotatingPointOffset:my,
width: R,
height: R
});
sector.setControlsVisibility(HideControls);
sector.setGradient('fill', rg);
rotationAngle = rotationAngle==0?-fov/2:rotationAngle-(fov-lastFOV)/2;
sector.setAngle(rotationAngle);
canvas.add(sector);
lastFOV = fov;
}
https://jsfiddle.net/2v0es4xh/35/
But when FOV is bigger than 90, rotationX/Y is changing.
There is something in Leaflet http://jieter.github.io/Leaflet-semicircle/examples/semicircle.html
It would be great if I can draw semicircle like this using fabric.
is there anyone who tried to do this?
Thank you in advance.

Try something like this: http://jsfiddle.net/qoadhrop/
Set two semi-circles on top of a main circle to give you this kind of "radius" effect
HTML:
<canvas id="c" width="300" height="300"></canvas>
JS:
var canvas = new fabric.Canvas('c');
var c1 = new fabric.Circle({
radius: 50,
left: 100,
top: 100,
stroke: '#000',
strokeWidth: 1,
fill: 'lightblue',
opacity: 0.8
});
var c2 = new fabric.Circle({
radius: 50,
left: 100,
top: 100,
startAngle: 0,
endAngle: Math.PI,
stroke: '#000',
strokeWidth: 1,
fill: 'red'
});
c2.setAngle(45);
var c3 = new fabric.Circle({
radius: 50,
left: 100,
top: 100,
flipX: true,
startAngle: 0,
endAngle: Math.PI,
stroke: '#000',
strokeWidth: 1,
fill: 'red'
});
c3.setAngle(-45);
canvas.add(c1);
canvas.add(c2);
canvas.add(c3);

The previous code by A. Lau doesn't work. So, this is my simple implementation. You can draw any sectors and rotate them. Try live example at https://jsfiddle.net/irwinwelsh/rn9emuqd/
var canvas = new fabric.Canvas('c');
function arc(left,top,radius,angle, rotate) {
var coeff = Math.PI/180;
var xshift = Math.sqrt(2) * radius * ( Math.cos(45 * coeff) - Math.cos((45 + rotate) * coeff));
var yshift = Math.sqrt(2) * radius * ( Math.sin(45 * coeff) - Math.sin((45 + rotate) * coeff));
left = left + xshift;
top = top + yshift;
var width = 2 * radius * Math.sin((angle/2) * coeff);
var height = radius * Math.cos((angle/2) * coeff);
var triangle = new fabric.Triangle({
width: width,
height: height,
left: left + width/2,
top: top,
fill: 'rgba(0,200,0,0.3)',
angle: 180,
});
var circle = new fabric.Circle({
radius: radius,
fill: 'rgba(0,200,0,0.3)',
left: left - radius,
top: top - radius,
startAngle: (270 - angle/2) * coeff,
endAngle: (270 + angle/2) * coeff,
angle: 0,
});
var group = new fabric.Group([circle,triangle],{
angle : rotate,
});
return group;
}
var sector = arc(100,200,200,30, 30);
canvas.add(sector);

Related

Modify path with anchor points on HTML5 canvas

My problem is how to make a UI where the user can modify a complex path by dragging anchor points that are on the path. Note on - for example the control points for Bezier curves in this example in the Konvajs documentation are off the path itself, whereas I prefer anchor points on a complex path.
I will be trying to modify a curve created as in this question.
Here is a snippet that draws a sample path. Can anyone help me please? Scroll the snippet window down slightly then click on the rectangle to see the path animation.
var stage = new Konva.Stage({
container: 'container',
width: 500,
height: 500
});
var layer = new Konva.Layer();
var rect = new Konva.Rect({
x: stage.getWidth() / 2,
y: stage.getHeight() / 2,
stroke: '#555',
strokeWidth: 3,
fill: '#ddd',
width: 50,
height: 50,
shadowColor: 'black',
shadowBlur: 10,
shadowOffset: [10, 10],
shadowOpacity: 0.2,
cornerRadius: 10
});
var path = new Konva.Path({
x: stage.getWidth() / 2,
y: stage.getHeight() / 2,
data: 'M30 10 C-20 -50 -20 -70 50 -80 C150 -100 50 -150 -100 -80',
stroke: '#00D2FF',
strokeWidth: 5
});
var pathLen = path.getLength() + 10;
path.dashOffset(pathLen);
path.dash([pathLen]);
var anim = new Konva.Animation(function (frame) {
var dashLen = pathLen - frame.time / 5;
if (dashLen < 0) {
var tween = new Konva.Tween({
node: path,
duration: 1,
strokeWidth: 10,
easing: Konva.Easings.ElasticEaseOut
});
tween.play();
anim.stop();
} else {
path.dashOffset(dashLen);
}
}, layer);
rect.on('mousedown', function() {
anim.start();
})
layer.add(path);
layer.add(rect);
stage.add(layer);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/konva/2.5.1/konva.js"></script>
<div id='container' style="display: inline-block; width: 500px, height: 500px; background-color: silver; overflow: hidden; position: relative;"></div>
<div id='img'></div>
Also, can anyone suggest why part of the curve is drawn before the animation runs?
My solution without anchor on control point:
var stage = new Konva.Stage({
container: "container",
width: 500,
height: 500,
draggable: true
});
var layer = new Konva.Layer();
var curveLayer = new Konva.Layer();
var k = 0;
var rect = new Konva.Rect({
x: stage.getWidth() / 2,
y: stage.getHeight() / 2,
stroke: "#555",
strokeWidth: 3,
fill: "#ddd",
width: 50,
height: 50,
shadowColor: "black",
shadowBlur: 10,
shadowOffset: [10, 10],
shadowOpacity: 0.2,
cornerRadius: 10
});
var path = buildPath("M25 25 L-75 -200 C-75 -200 -120 -300 -200 -200");
var pathLen = path.getLength() + 1000;
path.dashOffset(pathLen);
path.dash([pathLen]);
var anim = new Konva.Animation(function(frame) {
var dashLen = pathLen - frame.time * 10;
if (dashLen < 0) {
var tween = new Konva.Tween({
node: path,
duration: 1,
strokeWidth: 10,
easing: Konva.Easings.ElasticEaseOut
});
var circle = buildAnchorPoint(-75, -200);
var circle1 = buildAnchorPoint(-200, -200);
//var circle2 = buildAnchorPoint(-120, -242.5);
layer.draw();
circle.on("dragmove", function() {
var point = stage.getPointerPosition();
var pointx = point.x - stage.x();
var pointy = point.y - stage.y();
console.log(
"X: " +
(pointx - stage.getWidth() / 2) +
" Y: " +
(pointy - stage.getHeight() / 2)
);
path.data(
"M25 25 L" +
(pointx - stage.getWidth() / 2) +
" " +
(pointy - stage.getHeight() / 2) +
" C" +
(pointx - stage.getWidth() / 2) +
" " +
(pointy - stage.getHeight() / 2) +
"-120 -300" +
(circle1.getX() - stage.getWidth() / 2) +
" " +
(circle1.getY() - stage.getHeight() / 2)
);
curveLayer.draw();
layer.draw();
});
circle1.on("dragmove", function() {
var point = stage.getPointerPosition();
var pointx = point.x - stage.x();
var pointy = point.y - stage.y();
console.log(
"X: " +
(point.x - stage.getWidth() / 2) +
" Y: " +
(point.y - stage.getHeight() / 2)
);
path.data(
"M25 25 L" +
(circle.getX() - stage.getWidth() / 2) +
" " +
(circle.getY() - stage.getHeight() / 2) +
"C" +
(circle.getX() - stage.getWidth() / 2) +
" " +
(circle.getY() - stage.getHeight() / 2) +
" -120 -300 " +
(pointx - stage.getWidth() / 2) +
" " +
(pointy - stage.getHeight() / 2)
);
curveLayer.draw();
layer.draw();
});
tween.play();
anim.stop();
} else {
path.dashOffset(dashLen);
}
}, curveLayer);
rect.on("mousedown", function() {
if (k == 0) {
anim.start();
k++;
}
});
layer.add(rect);
stage.add(curveLayer);
stage.add(layer);
function buildPath(data) {
var path = new Konva.Path({
x: stage.getWidth() / 2,
y: stage.getHeight() / 2,
data: data,
stroke: "#00D2FF",
strokeWidth: 5,
lineJoin: "round",
lineCap: "round"
});
curveLayer.add(path);
return path;
}
function buildAnchorPoint(x, y) {
var circle = new Konva.Circle({
x: stage.getWidth() / 2 + x,
y: stage.getHeight() / 2 + y,
stroke: "#c1fbff",
strokeWidth: 1,
fill: "#00D2FF",
radius: 5,
cornerRadius: 10,
draggable: true
});
layer.add(circle);
return circle;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/konva/2.5.1/konva.js"></script>
<div id='container' style="display: inline-block; width: 500px, height: 500px; background-color: silver; overflow: hidden; position: relative;"></div>
<div id='img'></div>
Can someone help with anchor on control point?
Also, I'd like to clean code a bit, because I will have many paths witch anchors, that's why I need to make handlers not like in my code, help please!

Fabric.js - rotate one object the other rotate too

I have an application using HTML5 canvas via Fabric.js. And I would like to do arrow with text. Then moving arrow the text is in correct position, but is problem with rotation:
incorrect text position:
text position have to be:
My code is:
var canvas = new fabric.Canvas('c');
var line = new fabric.Line([10, 50, 200, 50], {
stroke: 'black',
strokeWidth: 3,
strokeDashArray: [10, 5]
});
var line2 = new fabric.Line([199, 50, 180, 60], {
stroke: 'black',
strokeWidth: 3
});
var line3 = new fabric.Line([199, 50, 180, 40], {
stroke: 'black',
strokeWidth: 3
});
var strele = new fabric.Group([line, line2, line3], {});
tekst = new fabric.IText('<<extend>>', {
left: 50,
top: 20,
fontFamily: 'Arial',
fill: '#333',
fontSize: 20
});
canvas.add(strele, tekst);
function onMoving(options) {
if (options.target === strele) {
tekst.left = strele.left + ((strele.width * strele.scaleX) / 2) - (tekst.width * tekst.scaleX) / 2;
tekst.top = strele.top - strele.height * strele.scaleY;
} else if (options.target === tekst) {
strele.left = tekst.left - ((strele.width * strele.scaleX) / 2 - (tekst.width * tekst.scaleX) / 2);
strele.top = tekst.top + strele.height * strele.scaleY;
}
canvas.forEachObject(function(o) {
o.setCoords()
});
}
function onRotating(options) {
if (options.target === strele) {
tekst.angle = strele.angle;
tekst.left = strele.left((strele.width * strele.scaleX) / 2) - (tekst.width * tekst.scaleX) / 2;
tekst.top = strele.top - strele.height * strele.scaleY;
}
}
canvas.on('object:moving', onMoving);
canvas.on('object:rotating', onRotating);
My code in fiddle
How can I fix this?
Here is an approach that closely resembles your image.
To simplify the calculations I changed the objects origin(X & Y) to center, the rest is just some math and conditions for the angles. My approach was to keep the text readable and not overlap the line.
var canvas = new fabric.Canvas('c');
var line = new fabric.Line([160, 100, 350, 100], {stroke: 'black', strokeWidth: 3, strokeDashArray: [10, 5]});
var line2 = new fabric.Line([350, 100, 330, 110], {stroke: 'black', strokeWidth: 3});
var line3 = new fabric.Line([350, 100, 330, 90], {stroke: 'black', strokeWidth: 3});
var strele = new fabric.Group([line, line2, line3], {originX: "center", originY: "center"});
var tekst = new fabric.IText('<<extend>>', {left: 160, top: 70, fontFamily: 'Arial',fill: 'red', fontSize: 20, selectable: false,
originX: "center", originY: "center"
});
canvas.add(strele, tekst);
canvas.setActiveObject(canvas.item(0));
canvas.on('object:moving', onMoving);
canvas.on('object:rotating', onRotating);
function onMoving() {
tekst.left = strele.left + 20 * Math.sin(strele.angle * Math.PI /180);
tekst.top = strele.top - 20 * Math.cos(strele.angle * Math.PI /180);
}
function onRotating() {
var ang = strele.angle % 360;
if (ang < 270) {
if (ang > 180) {
ang = strele.angle % 180;
} else if (ang > 90) {
ang = 180+strele.angle;
}
}
tekst.angle = ang
onMoving()
}
function rotate() {
strele.angle += 22.2;
strele.setCoords()
onRotating()
canvas.renderAll();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.3.4/fabric.min.js"></script>
<button id="rotate" onclick="rotate()">rotate</button>
<canvas id="c" width="600" height="600"></canvas>

how to create Duval Pentagon in canvas

Hey I have create a Duval Pentagon based on the post of creating duval triangle in canvas: how to create Duval Triangle in canvas.
My final result should be:
my current situation, is that I can created all the segments inside the pentagon,but I didn't figure out a way of placing labels of the gases around each corner(point) of my duval pentagon,any advice or help is welcome.
The creating of the duval pentagon process is like this:
1.creating outer pentagon.
2.creating all the segments in a loop.
3.creating the legend for the pentagon.
Update:
I have tried to build again the the moleculeLabel according to #markE guidance lines. so far no so good tried, I am sure doing something wrong. :(
function moleculeLabel(V,P,text){
var dx=V.x-P.x;
var dy=V.y-P.y;
var rAngle=Math.atan2(dy,dx);
var padding=15; // == how far outside the pentagon you want to go
var outsideX=P.x+(P.radius+padding)*Math.cos(rAngle);
var outsideY=P.y+(P.radius+padding)*Math.sin(rAngle);
ctx.textAlign='center';
ctx.textBaseline='middle';
ctx.fillStyle='black';
ctx.fillText(text,outsideX,outsideY);
}`
$(function() {
//offset :
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var points = [{
x: 397,
y: 149
}, {
x: 318,
y: 346
}, {
x: 112,
y: 347
}, {
x: 44,
y: 147
}, {
x: 221,
y: 27
}, {
x: 397,
y: 149
}];
var cx = 0;
var cy = 0;
for (var i = 0; i < points.lenght; i++) {
cx = cx + points[i].x;
cy = cy + points[i].y;
}
cx = cx / points.lenght;
cy = cy / points.lenght;
var centerPoint = {
x: cy,
y: cy
};
// Define all your segments here
var segments = [{
points: [{
x: 61,
y: 191
}, {
x: 112,
y: 347
}, {
x: 190,
y: 223
}, {
x: 214,
y: 211
}, {
x: 217,
y: 198
}, {
x: 61,
y: 192
}],
fill: 'rgb(172,236,222)',
label: {
text: 'T1',
cx: 130,
cy: 250,
withLine: false,
endX: null,
endY: null
},
}, {
points: [{
x: 61,
y: 191
}, {
x: 217,
y: 198
}, {
x: 220,
y: 26
}, {
x: 44,
y: 149
}],
fill: 'deepskyblue',
label: {
text: 'S',
cx: 140,
cy: 150,
withLine: false,
endX: null,
endY: null
},
}, {
points: [{
x: 220,
y: 26
}, {
x: 217,
y: 198
}, {
x: 239,
y: 135
}, {
x: 365,
y: 230
}, {
x: 397,
y: 149
}, {
x: 221,
y: 27
}],
fill: 'lightCyan',
label: {
text: 'D1',
cx: 270,
cy: 110,
withLine: false,
endX: null,
endY: null
},
}, {
points: [{
x: 214,
y: 211
}, {
x: 239,
y: 135
}, {
x: 365,
y: 231
}, {
x: 320,
y: 336
}, ],
fill: 'navajoWhite',
label: {
text: 'D2',
cx: 270,
cy: 210,
withLine: false,
endX: null,
endY: null
},
}, {
points: [{
x: 190,
y: 223
}, {
x: 214,
y: 211
}, {
x: 320,
y: 336
}, {
x: 318,
y: 346
}, {
x: 223,
y: 346
}],
fill: 'tan',
label: {
text: 'T3',
cx: 250,
cy: 310,
withLine: false,
endX: null,
endY: null
},
}, {
points: [{
x: 112,
y: 347
}, {
x: 190,
y: 223
}, {
x: 223,
y: 346
}],
fill: 'peru',
label: {
text: 'T2',
cx: 175,
cy: 300,
withLine: false,
endX: null,
endY: null
},
}, {
points: [{
x: 210,
y: 105
}, {
x: 219,
y: 105
}, {
x: 219,
y: 68
}, {
x: 210,
y: 67
}],
fill: 'red',
label: {
text: 'PD',
cx: 170,
cy: 87,
withLine: true,
endX: 215,
endY: 88
},
}];
// label styles
var labelfontsize = 12;
var labelfontface = 'verdana';
var labelpadding = 3;
var legendTexts = ['PD = Partial Discharge', 'T1 = Thermal fault < 300 celcius', '...'];
// start drawing
/////////////////////
// draw pentagon
drawPentagon(points);
// draw colored segments inside pentagon
for (var i = 0; i < segments.length; i++) {
drawSegment(segments[i]);
}
moleculeLabel(points[0], centerPoint, 'CH4');
moleculeLabel(points[1], centerPoint, 'CH2');
moleculeLabel(points[2], centerPoint, 'H2');
// draw legend
drawLegend(legendTexts, 10, 10, 12.86);
// 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 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 moleculeLabel(V, P, text) {
var dx = V.x - P.x;
var dy = V.y - P.y;
var rAngle = Math.atan2(dy, dx);
var padding = 15; // == how far outside the pentagon you want to go
var outsideX = P.x + (P.radius + padding) * Math.cos(rAngle);
var outsideY = P.y + (P.radius + padding) * Math.sin(rAngle);
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = 'black';
ctx.fillText(text, outsideX, outsideY);
}
/**
* draw basic pentagon.
**/
function drawPentagon(points) {
ctx.beginPath();
for (var i = 0; i < points.length; i++) {
ctx.lineTo(points[i].x, points[i].y);
}
ctx.strokeStyle = 'black';
ctx.lineWidth = 1;
ctx.stroke();
ctx.closePath();
}
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);
}
}
})
body {
background-color: ivory;
padding: 10px;
}
#canvas {
border: 1px solid red;
margin: 0 auto;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="canvas" width=650 height=500></canvas>
For each desired molecule label:
Given a pentagon shaped like this: {x:,y:,radius:} and a molecule vertex {x:,y:}
Use Math.atan2 to find the angle between the polygon centerpoint and the molecule vertex.
var dx=V.x-P.x;
var cy=V.y-P.y;
var rAngle=Math.atan2(dy,dx);
Use basic trigonometry to calculate a point outside the polygon along the line between the centerpoint and vertex.
var padding=15; // == how far outside the pentagon you want to go
var outsideX=P.x+(P.radius+padding)*Math.cos(rAngle);
var outsideY=P.y+(P.radius+padding)*Math.sin(rAngle);
Use fillText to draw the desired label at [outsideX,outsideY]
context.textAlign='center';
context.textBaseline='middle';
context.fillStyle='black';
context.fillText('CH4',outsideX,outsideY);
Here's example code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var points = [{x:397,y:149},{x:318,y:346},{x:112,y:347},{x:44,y:147},{x: 221,y:27},{x:397,y:149}];
// calc the polygon center mass
var cx,cy;
var totx=0;
var toty=0;
for(var i=0;i<points.length-1;i++){ // don't include the "closing" point in points[]
totx+=points[i].x;
toty+=points[i].y;
}
cx=totx/(points.length-1);
cy=toty/(points.length-1);
draw();
function draw(){
// draw the polygon
ctx.beginPath();
ctx.moveTo(points[0].x,points[0].y);
for(var i=1;i<points.length;i++){
ctx.lineTo(points[i].x,points[i].y);
}
ctx.stroke();
// draw the labels at their radial extension points
ctx.textAlign='center';
ctx.textBaseline='middle';
var padding=20;
for(var i=0;i<points.length-1;i++){
var p=points[i];
var dx=p.x-cx;
var dy=p.y-cy;
var dist=Math.sqrt(dx*dx+dy*dy);
var rAngle=Math.atan2(dy,dx);
var labelX=cx+(dist+padding)*Math.cos(rAngle);
var labelY=cy+(dist+padding)*Math.sin(rAngle);
ctx.fillText('label#'+i,labelX,labelY)
// demo only, for points[0] draw the
//centerpoint and the radial extension point
if(i==0){
ctx.beginPath();
ctx.moveTo(cx,cy);
ctx.lineTo(labelX,labelY);
ctx.moveTo(cx,cy);
ctx.arc(cx,cy,5,0,Math.PI*2);
ctx.moveTo(labelX,labelY);
ctx.arc(labelX,labelY,3,0,Math.PI*2);
ctx.strokeStyle='red';
ctx.stroke();
ctx.fillStyle='red';
ctx.fill();
ctx.strokeStyle='black';
ctx.fillStyle='black';
}
}
}
body{ background-color: ivory; }
#canvas{border:1px solid red; margin:0 auto; }
<canvas id="canvas" width=500 height=500></canvas>
Final answer, many thanks to #markE.
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var points = [{x:397,y:149},{x:318,y:346},{x:112,y:347},{x:44,y:147},{x: 221,y:27},{x:397,y:149}];
// calc the polygon center mass
var cx,cy;
var totx=0;
var toty=0;
for(var i=0;i<points.length-1;i++){ // don't include the "closing" point in points[]
totx+=points[i].x;
toty+=points[i].y;
}
cx=totx/(points.length-1);
cy=toty/(points.length-1);
draw();
function draw(){
// draw the polygon
ctx.beginPath();
ctx.moveTo(points[0].x,points[0].y);
for(var i=1;i<points.length;i++){
ctx.lineTo(points[i].x,points[i].y);
}
ctx.stroke();
// draw the labels at their radial extension points
ctx.textAlign='center';
ctx.textBaseline='middle';
var padding=20;
for(var i=0;i<points.length-1;i++){
var p=points[i];
var dx=p.x-cx;
var dy=p.y-cy;
var dist=Math.sqrt(dx*dx+dy*dy);
var rAngle=Math.atan2(dy,dx);
var labelX=cx+(dist+padding)*Math.cos(rAngle);
var labelY=cy+(dist+padding)*Math.sin(rAngle);
ctx.fillText('label#'+i,labelX,labelY)
// demo only, for points[0] draw the
//centerpoint and the radial extension point
if(i==0){
ctx.beginPath();
ctx.moveTo(cx,cy);
ctx.lineTo(labelX,labelY);
ctx.moveTo(cx,cy);
ctx.arc(cx,cy,5,0,Math.PI*2);
ctx.moveTo(labelX,labelY);
ctx.arc(labelX,labelY,3,0,Math.PI*2);
ctx.strokeStyle='red';
ctx.stroke();
ctx.fillStyle='red';
ctx.fill();
ctx.strokeStyle='black';
ctx.fillStyle='black';
}
}
}
body{ background-color: ivory; }
#canvas{border:1px solid red; margin:0 auto; }
<canvas id="canvas" width=500 height=500></canvas>

KineticJS: Curved line between two draggable shapes

I am using KineticJS in my project. I need to connect two shapes using a curved line. One of the shapes can be dragged. I am able to put the curved line between shapes. The problem arises when user starts dragging the shapes. The requirement is that it should be properly curved (please refer to screen shots), irrespective of distance between them and their position with respect to each other. I am doing this:
var utils = {
_getCenter: function(x1, y1, x2, y2) {
return {
x: (x1 + x2) / 2,
y: (y1 + y2) / 2
}
},
// Converts from degrees to radians.
_radians: function(degrees) {
return degrees * Math.PI / 180;
},
// Converts from radians to degrees.
_degrees: function(radians) {
return radians * 180 / Math.PI;
}
};
function amplitude(point) {
var rad_90 = utils._radians(90);
var rad_45 = utils._radians(45);
var rad_60 = utils._radians(60);
console.log(rad_90);
return {
x: point.x * Math.cos(rad_60),
y: point.y * Math.sin(rad_60)
};
}
var width = window.innerWidth;
var height = window.innerHeight;
var stage = new Kinetic.Stage({
container: 'container',
width: width,
height: height
});
var layer = new Kinetic.Layer();
var circle = new Kinetic.Circle({
x: stage.getWidth() / 2,
y: stage.getHeight() / 2,
radius: 20,
fill: 'red',
stroke: 'black',
strokeWidth: 2
});
var attachedCircle = new Kinetic.Circle({
x: stage.getWidth() / 4,
y: stage.getHeight() / 4,
radius: 20,
fill: 'red',
stroke: 'black',
strokeWidth: 2,
draggable: true
});
var center = amplitude(utils._getCenter(circle.getX(), circle.getY(), attachedCircle.getX(), attachedCircle.getY()));
var line = new Kinetic.Line({
points: [circle.getX(), circle.getY(), center.x, center.y, attachedCircle.getX(), attachedCircle.getY()],
fill: 'black',
stroke: 'green',
strokeWidth: 3,
/*
* line segments with a length of 33px
* with a gap of 10px
*/
dash: [33, 10],
id: 'line',
tension: 0.5
});
attachedCircle.on('dragmove', function(e) {
var targetCircle = e.target;
var tempCenter = amplitude(utils._getCenter(circle.getX(), circle.getY(), targetCircle.getX(), targetCircle.getY()));
console.log(tempCenter);
line.setPoints([circle.getX(), circle.getY(), tempCenter.x, tempCenter.y, targetCircle.getX(), targetCircle.getY()]);
});
// add the shape to the layer
layer.add(line);
layer.add(attachedCircle);
layer.add(circle);
// add the layer to the stage
stage.add(layer);
I don't know what I am missing. I have created the plunkr for this.
To define you amplitude function you need to use two input points:
function amplidure2(p1, p2) {
var alpha = Math.atan((p1.x - p2.x) / (p1.y - p2.y)) + Math.PI / 2;
if (p1.y < p2.y) {
alpha += Math.PI;
}
var center = utils._getCenter(p1.x, p1.y, p2.x, p2.y);
var r = 50;
return {
x: center.x + r * Math.sin(alpha),
y: center.y + r * Math.cos(alpha)
}
}
DEMO

Kinetic.js: Get MinX, MaxX, MinY, and MaxY of rotated Kinetic.Label

I'm trying to prevent a rotated Label from being dragged off the screen, but I cannot figure out how to get MinX, MaxX, MinY, and MaxY of the object in its rotated state. getHeight & getWidth only return the values prior to the rotation.
Here is an example illustrating the problem:
var stage = new Kinetic.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight
});
var layer = new Kinetic.Layer();
var labelLeft = new Kinetic.Label({
x: 95,
y: 180,
opacity: 1.0,
listening: true,
draggable: true,
rotationDeg: -45,
text: {
text: 'Pointing Arrow',
fontFamily: 'Calibri',
fontSize: 20,
padding: 5,
fill: 'white'
},
rect: {
fill: 'blue',
pointerDirection: 'left',
pointerWidth: 20,
pointerHeight: 38,
stroke: 'black',
strokeWidth: 2
},
dragBoundFunc: function (pos) {
var newY = pos.y < 50 ? 50 : pos.y;
return {
x: pos.x,
y: newY
};
}
});
layer.add(labelLeft);
// add the layer to the stage
stage.add(layer);
http://jsfiddle.net/fSNnA/4/
In this example, I use dragBoundFunc to prevent the label from being dragged above y=50. but since the label is rotated, its actual highest point (MinY) has changed, and therefore you can drag it up and partially out of view.
What I really need is a function that will return the absolute current MinX, MaxX, MinY, and MaxY - taking into account the angle of rotation and length of text will not always be the same.
Can anyone help?
Here's how to calculate the bounding-box size of a rotated rectangle (label).
var w = label.getWidth();
var h = label.getHeight();
var rads = label.getRotation();
var c = Math.abs(Math.cos(rads));
var s = Math.abs(Math.sin(rads));
var newWidth = h * s + w * c;
var newHeight = h * c + w * s;
Then assuming you'll always rotate around the center, the left/top boundaries are:
var centerX = label.getX()+label.getWidth()/2;
var centerY = label.getY()+label.getHeight()/2;
var MinX = centerX - newWidth/2;
var MinY = centerY - newHeight/2;
[ Disclaimer: This is just off the top of my head--review it accordingly! ]

Categories