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
Related
I am trying to do circle approximation that is a regular polygon with N corners using d3.js.
My idea was to always have a circle in the background and use the "transform" function to slide around the circle and obtain a position (x,y), which I would pass to a polygon.
For example, if I must have the circle divided in 3 parts, it would become a triangle. With that, I would rotate within the circle 3 times starting at (0,0) and return the position of the two other points within the circumference of the circle.
My problem is that the "transform" function does not return me a x,y coordinate.
var svg = d3.select('svg');
var originX = 200;
var originY = 200;
var outerCircleRadius = 60;
var outerCircle = svg.append("circle").attr({
cx: originX,
cy: originY,
r: outerCircleRadius,
fill: "none",
stroke: "black"
});
var chairOriginX = originX + ((outerCircleRadius) * Math.sin(0));
var chairOriginY = originY - ((outerCircleRadius) * Math.cos(0));
var chairWidth = 20;
console.log(chairOriginX);
console.log(chairOriginY);
var chair = svg.append("rect").attr({
x: chairOriginX - (chairWidth / 2),
y: chairOriginY - (chairWidth / 2),
width: chairWidth,
opacity: 1,
height: 20,
fill: "none",
stroke: "blue"
});
var chairOriginX2 = originX + ((outerCircleRadius) * Math.sin(0));
var chairOriginY2 = originY - ((outerCircleRadius) * Math.cos(0));
console.log(chairOriginX2);
console.log(chairOriginY2);
var chair2 = svg.append("rect").attr({
x: chairOriginX2 - (chairWidth / 2),
y: chairOriginY2 - (chairWidth / 2),
width: chairWidth,
opacity: 1,
height: 50,
fill: "none",
stroke: "red"
});
var n_number = 5
var n_angles = 360/n_number
var angle_start=0;
var angle_next;
chair2.attr("transform", "rotate(" + (angle_start+n_angles+n_angles) + ", 200, 200)");
console.log(chair2.attr("transform", "rotate(" + (angle_start+n_angles+n_angles) + ", 200, 200)"));
var chairOriginX3 = originX + ((outerCircleRadius) * Math.sin(0));
var chairOriginY3 = originY - ((outerCircleRadius) * Math.cos(0));
console.log(chairOriginX3);
console.log(chairOriginY3);
var chair3 = svg.append("rect").attr({
x: chairOriginX3 - (chairWidth / 2),
y: chairOriginY3 - (chairWidth / 2),
width: chairWidth,
opacity: 1,
height: 50,
fill: "none",
stroke: "black"
});
var n_number = 5
var n_angles = 360/n_number
var angle_start=0;
var angle_next;
chair3.attr("transform", "rotate(" + (angle_start+n_angles+n_angles+n_angles) + ", 200, 200)");
console.log(chair3.attr("transform", "rotate(" + (angle_start+n_angles+n_angles+n_angles) + ", 200, 200)"));
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);
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! ]
I have this fiddle:
http://jsfiddle.net/WDjpx/2/
The image is not rotating correctly.
The code that i used was:
var stage = new Kinetic.Stage({
container: 'd',
width: 300,
height: 300
});
var layer = new Kinetic.Layer();
var isDragging = false;
var refRotation = null;
var rect = new Kinetic.Rect({
x: 150,
y: 150,
width: 100,
height: 100,
fill: 'green',
stroke: 'black',
strokeWidth: 4,
offset: [50, 50],
dragOnTop: true,
draggable: true,
dragBoundFunc: function (pos) {
var xd = 150 - pos.x ;
var yd = 150 - pos.y ;
var theta = Math.atan2(yd, xd);
var deg = theta * 180 / Math.PI;
if (!isDragging) {
isDragging = true;
refRotation = deg;
} else {
var rotate = deg - refRotation;
rect.setRotationDeg(deg);
}
return {
x: this.getAbsolutePosition().x,
y: this.getAbsolutePosition().y
}
}
});
layer.add(rect);
stage.add(layer);
Anyone know what is wrong with my Math???
--- EDIT ---
New feddle with what i wanted:
http://jsfiddle.net/zk9cn/
Don't know about Math.atan2, but it seems calculation of x and y is not right to me. I would deduct position from center, not center from position. http://jsfiddle.net/bighostkim/qZDcg/
var x = 150 - pos.x;
var y = 150 - pos.y;
var radian = Math.PI + Math.atan(y/x);
this.setRotation(radian);
Other thing, I also see your variables are not in use; isDragging, refRotate, and rotate.
----- Edit ----
If you want to rotate rectangle by picking up the corner, you can use the following code.
When you pick up the corner, degree calculated by center positon is already 45. That's why you cannot pick up the corner properly. By adjusting 45 makes it seem right, but when you pick up the straight line, it will go wrong again. It seems your requirement has a flaw in it unless it's intentional. http://jsfiddle.net/bighostkim/7Q5Hd/
var pos = stage.getMousePosition();
var xd = 150 - pos.x ;
var yd = 150 - pos.y ;
var theta = Math.atan2(yd, xd);
var degree = theta / (Math.PI / 180) - 45;
this.setRotationDeg(degree);
I am building a custom chart with Raphael.js which includes arcs and percentages. The text label of an arc needs to move with the arc. I have achieved this by using the onAnimation callback and getting the point of the outer arc and position the text label accordingly:
var paper = Raphael("paper", 200, 200);
var centerX = paper.width / 2;
var centerY = paper.height / 2;
// This is a dummy percentage; in real app this corresponds to actual value
var percentage = 0;
paper.customAttributes.arc = function(centerX, centerY, startAngle, endAngle, innerR, outerR) {
var radians = Math.PI / 180,
largeArc = +(endAngle - startAngle > 180),
outerX1 = centerX + outerR * Math.cos((startAngle - 90) * radians),
outerY1 = centerY + outerR * Math.sin((startAngle - 90) * radians),
outerX2 = centerX + outerR * Math.cos((endAngle - 90) * radians),
outerY2 = centerY + outerR * Math.sin((endAngle - 90) * radians),
innerX1 = centerX + innerR * Math.cos((endAngle - 90) * radians),
innerY1 = centerY + innerR * Math.sin((endAngle - 90) * radians),
innerX2 = centerX + innerR * Math.cos((startAngle - 90) * radians),
innerY2 = centerY + innerR * Math.sin((startAngle - 90) * radians);
var path = [
["M", outerX1, outerY1],
//move to the start point
["A", outerR, outerR, 0, largeArc, 1, outerX2, outerY2],
//draw the outer edge of the arc
["L", innerX1, innerY1],
//draw a line inwards to the start of the inner edge of the arc
["A", innerR, innerR, 0, largeArc, 0, innerX2, innerY2],
//draw the inner arc
["z"]
//close the path
];
return {
path: path
};
};
var arc = paper.path().attr({
'fill': "#cccccc",
'stroke': 'none',
'arc': [centerX, centerY, 0, 0, 60, 80]
});
arc.toBack();
var percentagesStart = 0;
var percentagesEnd = 0;
var percentagesDelta = 0;
var label = paper.text(
arc.attrs.path[1][6],
arc.attrs.path[1][7],
"0").toFront();
label.attr({
'font-size': 16,
'fill': "#000000",
'font-family': 'sans-serif',
'text-anchor': 'middle'
});
arc.onAnimation(function(anim) {
console.log("test");
label.attr({
x: this.attrs.path[1][6],
y: this.attrs.path[1][7],
text: (percentage++)
});
});
arc.animate({
'arc': [centerX, centerY, 0, 220, 60, 80]
}, 1000, 'easeInOut');
<script src="https://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.4/raphael-min.js"></script>
<div id="paper"></div>
However, the text label must be positioned where the arc ends, with some nice margin. The text element position must be transformed relative to the arc.
I have tried to use
label.transform("t-10,10");
And tried some other x, y values but that does not work, because at some point the x value has to be negative, and at some point the x value has to be positive, depending on the arc's angle.
Is there a way to position this correctly?