I've this simple dummy file that I'm using to do some testing. The intended result is to drag the red circle along the path. The thing is that I can't figure out how to associate both shapes.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<script src="raphael-min.js"></script>
</head>
<body>
<script type="text/javascript">
// Creates canvas 320 × 200 at 10, 50
var r = Raphael(10, 50, 320, 200);
var p = r.path("M100,100c0,50 100-50 100,0c0,50 -100-50 -100,0z").attr({stroke: "#ddd"}),
e = r.ellipse(104, 100, 4, 4).attr({stroke: "none", fill: "#f00"}),
/*var c = r.circle(100, 100, 50).attr({
fill: "hsb(.8, 1, 1)",
stroke: "none",
opacity: .5
});*/
var start = function () {
// storing original coordinates
this.ox = this.attr("cx");
this.oy = this.attr("cy");
this.attr({opacity: 1});
},
move = function (dx, dy) {
// move will be called with dx and dy
this.attr({cx: this.ox + dx, cy: this.oy + dy});
},
up = function () {
// restoring state
this.attr({opacity: 1});
};
e.drag(move, start, up);
</script>
</body>
</html>
You didn't specify exactly how you want the interaction to work, so I used what feels most natural to me.
We can assume the dot must remain on the path, so its position must be given by
p.getPointAtLength(l);
for some l. To find l we can search for the local minimum of the distance between the curve and the cursor position. We initialize the search with l0 where l0 is the value of l currently defining the location of the dot.
See the JSfiddle here for a working example:
http://jsfiddle.net/fuzic/kKLtH/
Here is the code:
var searchDl = 1;
var l = 0;
// Creates canvas 320 × 200 at 10, 50
var r = Raphael(10, 50, 320, 200);
var p = r.path("M100,100c0,50 100-50 100,0c0,50 -100-50 -100,0z").attr({stroke: "#ddd"}),
pt = p.getPointAtLength(l);
e = r.ellipse(pt.x, pt.y, 4, 4).attr({stroke: "none", fill: "#f00"}),
totLen = p.getTotalLength(),
start = function () {
// storing original coordinates
this.ox = this.attr("cx");
this.oy = this.attr("cy");
this.attr({opacity: 1});
},
move = function (dx, dy) {
var tmpPt = {
x : this.ox + dx,
y : this.oy + dy
};
l = gradSearch(l, tmpPt);
pt = p.getPointAtLength(l);
this.attr({cx: pt.x, cy: pt.y});
},
up = function () {
this.attr({opacity: 1});
},
gradSearch = function (l0, pt) {
l0 = l0 + totLen;
var l1 = l0,
dist0 = dist(p.getPointAtLength(l0 % totLen), pt),
dist1,
searchDir;
if (dist(p.getPointAtLength((l0 - searchDl) % totLen), pt) >
dist(p.getPointAtLength((l0 + searchDl) % totLen), pt)) {
searchDir = searchDl;
} else {
searchDir = -searchDl;
}
l1 += searchDir;
dist1 = dist(p.getPointAtLength(l1 % totLen), pt);
while (dist1 < dist0) {
dist0 = dist1;
l1 += searchDir;
dist1 = dist(p.getPointAtLength(l1 % totLen), pt);
}
l1 -= searchDir;
return (l1 % totLen);
},
dist = function (pt1, pt2) {
var dx = pt1.x - pt2.x;
var dy = pt1.y - pt2.y;
return Math.sqrt(dx * dx + dy * dy);
};
e.drag(move, start, up);
A circle object has an x,y coordinate for its center, and a radius. To make sure the circle remains on the line, simply find the intersection of the center of the circle and the line itself.
To do this, you will need to store the start and end coordinates of your line. Then using the equation of a line: y = mx + b, you can find the slope and y-intercept. Once you have a function for the line, you can generate new coordinates for the circle by plugging in different values of x.
Also, by plugging in the x,y coordinates of the circle into your function, you can check to see if the circle is on the line.
Related
How can i get the angle of each node with the center of the circumference in cytoscape using circle layout?
Visual example:
First step to get the angle of the node, get the center of the circumference
So i get 3 points of the 3rd first nodes to get it:
AX= cy.nodes()[0]._private.position.x
AY= cy.nodes()[0]._private.position.y
BX= cy.nodes()[1]._private.position.x
BY= cy.nodes()[1]._private.position.y
CX= cy.nodes()[2]._private.position.x
CY= cy.nodes()[2]._private.position.y
var yDelta_a = BY - AY
var xDelta_a = BX - AX;
var yDelta_b = CY - BY;
var xDelta_b = CX - BX;
var aSlope = yDelta_a / xDelta_a;
var bSlope = yDelta_b / xDelta_b;
//Get center circumference
coordCentroCircunferenciaX = (aSlope*bSlope*(AY - CY) + bSlope*(AX + BX) - aSlope*(BX+CX) )/(2* (bSlope-aSlope) );
coordCentroCircunferenciaY = -1*(coordCentroCircunferenciaX- (AX+BX)/2)/aSlope + (AY+BY)/2;
Then iterate through the nodes getting the angles:
for (i = 1;i< cy.nodes().length; i=i+1) { //starts nodes loop
let nodo=cy.nodes()[i];
array.push(nodo._private.data.name);
//get the node position(x,y)
nodox=nodo._private.position.x
nodoy=nodo._private.position.y
Now with 3 points: center circumference , node position and another point in the x axis
C = { x: coordCentroCircunferenciaX, y: coordCentroCircunferenciaY };
A = { x: nodox, y: 0 };
B = { x: nodox,y:nodoy };
Then i get the angle with this function:
function find_angle(A,B,C) {
var AB = Math.sqrt(Math.pow(B.x-A.x,2)+ Math.pow(B.y-A.y,2));
var BC = Math.sqrt(Math.pow(B.x-C.x,2)+ Math.pow(B.y-C.y,2));
var AC = Math.sqrt(Math.pow(C.x-A.x,2)+ Math.pow(C.y-A.y,2));
return Math.acos((BC*BC+AB*AB-AC*AC)/(2*BC*AB))*(180/Math.PI);
}
Finally this is the angle with the center of the circumference:
let angulo=Math.round(find_angle(A,B,C))
So with this angle i can get this effect to rotate node labels:
Wasted weeks - Need solution to edit Polygons using either Fabric.js and Konva.js - Both have no way to actually update the poly points and transformer when the poly or it's points are MOVED, FLIPPED or MIRRORED. I'll assume the array points need to be reversed and the end the starting index switch depending on the quadrant the poly has been flipped.
If anyone have a solution please post. Fabric.js code in CodePen: https://codepen.io/Rstewart/pen/LYbJwQE
/* CODE FROM POLY DEMO ON FABRIC WEBSITE - CODE FAILS WHEN FLIPPED OR MIRRORED */
function polygonPositionHandler(dim, finalMatrix, fabricObject) {
var x = (fabricObject.points[this.pointIndex].x - fabricObject.pathOffset.x),
y = (fabricObject.points[this.pointIndex].y - fabricObject.pathOffset.y);
return fabric.util.transformPoint( { x: x, y: y },
fabric.util.multiplyTransformMatrices(
fabricObject.canvas.viewportTransform,
fabricObject.calcTransformMatrix()
)
);
}
function actionHandler(eventData, transform, x, y) {
var polygon = transform.target, currentControl = polygon.controls[polygon.__corner],
mouseLocalPosition = polygon.toLocalPoint(new fabric.Point(x, y), 'center', 'center'),
polygonBaseSize = polygon._getNonTransformedDimensions(), size = polygon._getTransformedDimensions(0, 0),
finalPointPosition = {
x: mouseLocalPosition.x * polygonBaseSize.x / size.x + polygon.pathOffset.x,
y: mouseLocalPosition.y * polygonBaseSize.y / size.y + polygon.pathOffset.y
};
polygon.points[currentControl.pointIndex] = finalPointPosition; return true;
}
function anchorWrapper(anchorIndex, fn) {
return function(eventData, transform, x, y) {
var fabricObject = transform.target,
absolutePoint = fabric.util.transformPoint({
x: (fabricObject.points[anchorIndex].x - fabricObject.pathOffset.x),
y: (fabricObject.points[anchorIndex].y - fabricObject.pathOffset.y),
}, fabricObject.calcTransformMatrix()),
actionPerformed = fn(eventData, transform, x, y),
newDim = fabricObject._setPositionDimensions({}),
polygonBaseSize = fabricObject._getNonTransformedDimensions(),
newX = (fabricObject.points[anchorIndex].x - fabricObject.pathOffset.x) / polygonBaseSize.x,
newY = (fabricObject.points[anchorIndex].y - fabricObject.pathOffset.y) / polygonBaseSize.y;
fabricObject.setPositionByOrigin(absolutePoint, newX + 0.5, newY + 0.5);
return actionPerformed;
}
}
Let's say I need to put a text in the middle of the area of a triangle.
I can calculate the coordinates of the triangle's center using getBBox():
var triangle = "M0,0 L100,0 100,50 z";
var BBox = paper.path(triangle).getBBox();
var middle;
middle.x = BBox.x + BBox.width/2;
middle.y = BBox.y + BBox.height/2;
This results in the coordinates (50, 25) which are always on the long side of the triangle.
How can I make sure the calculated "middle" is inside the triangle?
The correct coordinates should be approximately: (75, 25).
The code should of course be independent of this particular example, it should work for any kind of shape.
I've done some more research in the topic, and following an advice from another list I got here:
https://en.wikipedia.org/wiki/Centroid
There is an algorithm there to calculate the centroid of an irregular polygon, which I have translated into this code:
function getCentroid(path) {
var x = new Array(11);
var y = new Array(11);
var asum = 0, cxsum = 0, cysum = 0;
var totlength = path.getTotalLength();
for (var i = 0; i < 11; i++) {
var location = path.getPointAtLength(i*totlength/10);
x[i] = location.x;
y[i] = location.y;
if (i > 0) {
asum += x[i - 1]*y[i] - x[i]*y[i - 1];
cxsum += (x[i - 1] + x[i])*(x[i - 1]*y[i] - x[i]*y[i - 1]);
cysum += (y[i - 1] + y[i])*(x[i - 1]*y[i] - x[i]*y[i - 1]);
}
}
return({x: (1/(3*asum))*cxsum, y: (1/(3*asum))*cysum});
}
It's basically an approximation of any path by 10 points (the 11th is equal to the starting point), and the function returns, for that triangle, the coordinates:
Object {x: 65.32077336966377, y: 16.33111549955705}
I've tested it with many other shapes, and it works pretty good.
Hope it helps somebody.
This snippet will calculate the center of any polygon by averaging the vertices.
var paper = Raphael(0,0, 320, 200);
var triangle = "M0,0 L100,0 100,50 z";
var tri = paper.path(triangle);
tri.attr('fill', 'blue');
var center = raphaelPathCenter( tri );
var circle = paper.circle( center.x, center.y, 5);
circle.attr("fill", "#f00");
circle.attr("stroke", "#fff");
function raphaelPathCenter( path ) {
path.getBBox(); // forces path to be traced so realPath is not null.
var vertices = parseSVGVertices( path.realPath );
var center = vertices.reduce( function(prev,cur) {
return { x: prev.x + cur.x, y: prev.y + cur.y }
}, {x:0, y:0} );
center.x /= vertices.length;
center.y /= vertices.length;
return center;
}
function parseSVGVertices( svgPath )
{
var vertices = [];
for ( var i = 0; i < svgPath.length; i ++ )
{
var vertex = svgPath[i];
if ( "ML".indexOf( vertex[0] ) > -1 ) // check SVG command
vertices.push( { x: vertex[1], y: vertex[2] } );
}
return vertices;
}
<script src="https://raw.githubusercontent.com/DmitryBaranovskiy/raphael/master/raphael-min.js"></script>
<canvas id='canvas'></canvas>
<pre id='output'></pre>
However there are a few more triangle centers to choose from.
I'm trying to animate a point to follow the potion of the mouse.
It's like an eye looking at the arrow while I'm not on the eye.
The point should move along a circle if I mouve around the mouse.
If the mouse is on the eye, the eye should follow the arrow.
That's what I'm currently trying to do.
I use snap.svg library.
I currently have a point following the mobility of the mouse but I cant make it stay in a circle.
It looks like this so far :
var s = Snap(400,400);
var c1 = s.circle(0,0,10).attr({ fill: "red" });
function OnMouseMove(evt) {
c1.attr({ cx: evt.clientX , cy: evt.clientY });
}
document.onmousemove = OnMouseMove;
Any idea community ?
Here's my visual solution, it uses Snap's built in functions:-
var s = Snap(400,400);
var circleX = 150, circleY = 150, circleRadius = 100;
var bigCircle = s.circle(circleX, circleY, circleRadius);
var L1 = s.path("M "+circleX+" "+circleY +"L 0 0").attr({stroke: "blue"});
// BigCircle default its black, lets change its attributes
bigCircle.attr({
fill: "#bada55",
stroke: "#000",
strokeWidth: 5
});
var c1 = s.circle(0,0,10).attr({ fill: "red" });
function OnMouseMove(evt) {
L1.attr({ d: "M "+circleX+" "+circleY +"L "+evt.clientX+" "+evt.clientY });
var totalLength = L1.getTotalLength();
if (totalLength < circleRadius) {
c1.attr({ cx: evt.clientX , cy: evt.clientY });
} else {
var PAL = L1.getPointAtLength(circleRadius);
c1.attr({ cx: PAL.x , cy: PAL.y });
}
}
document.onmousemove = OnMouseMove;
Update: Here's the fiddle demo. Challenge for readers: Try var bigCircle = s.ellipse(150, 150, 100, 50);.
You have to test how far away your mouse coordinates are from the centre of the circle, and stop them if they reach the edge.
Something like this should work.
function OnMouseMove(evt) {
// Get the mouse position relative to the centre of the circle (circleX,circleY)
var dx = evt.clientX - circleX;
var dy = evt.clientY - circleY;
// Calculate distance from centre of circle to mouse (Pythagoras' theorem)
var distance = Math.sqrt(dx * dx + dy *dy);
// Test against radius
if (distance > circleRadius) {
// Scale the dx,dy coords back so they are on the circumference
dx = dx * circleRadius / distance;
dy = dy * circleRadius / distance;
}
c1.attr({ cx: dx, cy: dy });
}
If this doesn't work for you, make a jsfiddle so we can see what you have so far.
Just strayling slightly, but as an extension to Alvin Ks answer (for the readers challenge!), if you can make sure the the object is a path, you could use Snap.path.intersection which would work for many other shapes. It would need an extra bit of code for multiple intersections though possibly.
Relevant amendment to Alvins code...
function OnMouseMove(evt) {
L1.attr({ d: "M "+circleX+" "+circleY +"L "+evt.clientX+" "+evt.clientY });
var intersect = Snap.path.intersection( path, L1 )
if (intersect.length == 0) {
c1.attr({ cx: evt.clientX , cy: evt.clientY });
} else {
c1.attr({ cx: intersect[0].x , cy: intersect[0].y });
}
}
jsfiddle
This is less an HTML, CANVAS question and more of a general math question. I posted it here because it's prototyped using CANVAS and is still kind of a general programming question that I thought someone could answer. Here is the basic idea: I want to draw a line 10 pixels thick, but I don't want to use the standard lineTo and set a stroke width. I want to actually draw the border of the line using beginPath and lineTo. The reason being is this is actually for AS3 project and using this method allows us to have a line stroke and fill. So rotating the canvas and things of that nature are out of the question. I just need to figure out how to calculate the proper x, y coordinates for the line.
In the code below is the coordinates for the top of a line. I basically want to take this coordinates, add 10 to the y axis for each one and that will give me the return coordinates for the bottom of the line. Of course, each segment of the line is rotated, so calculating the coordinates for the bottom of the line has proved tricky. I'm hoping someone can help.
Once you run the example code, the issue should be obvious. The line isn't drawn properly. For relatively mild rotations of line segments it seems to work, but as the angle of rotation gets more severe the x, y coordinates are calculated incorrectly.
<!doctype html>
<html>
<body>
<canvas id="canvas" width="800" height="600">
</canvas>
<script type="text/javascript">
var coords = [
{x:78,y:183},
{x:130,y:183},
{x:237,y:212},
{x:450,y:213},
{x:517,y:25},
{x:664,y:212},
{x:716,y:212}
];
var coordsBck = [];
for( i = 0; i < coords.length; i++ ) {
var c1, c2, r;
c1 = coords[i];
if( i < coords.length - 1 ) {
c2 = coords[i + 1];
r = Math.atan2((c2.y - c1.y),(c2.x - c1.x));
console.log( c1.x, c1.y, c2.x, c2.y, ( r * 180/Math.PI ));
}
else
{
r = 00;
}
var d = r * 180/Math.PI;
var cos = Math.cos( r );
var sin = Math.sin( r );
var x = cos * 0 - sin * 10;
var y = sin * 0 + cos * 10;
coordsBck.push({x: c1.x + x, y: c1.y + y});
}
while(coordsBck.length > 0 )
{
coords.push( coordsBck.pop() );
}
var ctx = document.getElementById("canvas").getContext("2d");
ctx.beginPath();
for( i = 0; i < coords.length; i++ ) {
var line = coords[i];
console.log( i, line.x, line.y );
if( i == 0 )
{
ctx.moveTo( line.x, line.y );
}
else
{
ctx.lineTo( line.x, line.y );
}
}
ctx.fill();
function t(o) {
return "x: " + o.x + ", y: " + o.y;
}
</script>
</body>
</html>
If you don't need end caps. http://jsfiddle.net/xA6kB/1/
<doctype !html>
<html>
<body>
<canvas id="canvas" width="800" height="600">
</canvas>
<script type="text/javascript">
var points =
[
{x: 78, y: 183},
{x: 130, y: 183},
{x: 237, y: 212},
{x: 450, y: 213},
{x: 517, y: 25},
{x: 664, y: 212},
{x: 716, y: 212}
];
var quads = [];
var lineThickness = 10;
// Remove the -1 to create a loop
for (var i = 0; i < points.length - 1; ++i)
{
var point = points[i];
var nextPoint = points[(i + 1) % points.length];
// Right hand normal with x positive to the right and y positive down
var normal = {x: -(nextPoint.y - point.y), y: nextPoint.x - point.x};
// Normalize normal
var normalLength = Math.sqrt(normal.x * normal.x + normal.y * normal.y);
normal.x /= normalLength;
normal.y /= normalLength;
// A quad has 4 points
quads.push({x: point.x - lineThickness / 2 * normal.x, y: point.y - lineThickness / 2 * normal.y});
quads.push({x: nextPoint.x - lineThickness / 2 * normal.x, y: nextPoint.y - lineThickness / 2 * normal.y});
quads.push({x: nextPoint.x + lineThickness / 2 * normal.x, y: nextPoint.y + lineThickness / 2 * normal.y});
quads.push({x: point.x + lineThickness / 2 * normal.x, y: point.y + lineThickness / 2 * normal.y});
}
var context = document.getElementById("canvas").getContext("2d");
context.beginPath();
for(var i = 0; i < quads.length; i += 4)
{
context.moveTo(quads[i].x, quads[i].y);
context.lineTo(quads[i + 1].x, quads[i + 1].y);
context.lineTo(quads[i + 2].x, quads[i + 2].y);
context.lineTo(quads[i + 3].x, quads[i + 3].y);
}
context.fill();
</script>
</body>
</html>
When i have such issues, i usually compute normalized vectors, and 'play' with them.
Say you draw a line from A to B, compute AB vector (ABx=Bx-Ax ; ABy=By-Ay) then i normalize it (...) to get ABN.
Then i compute ABNR, the 90 degree rotation of ABN ( ABNR.x = -ABN.y ; ABNR.y = ABN.x )
Then in your example, say A' and A'' are the points surrounding A, we have the simple A'=A+5*ABNR and A''= A-5*ABNR , and as well B'=B+5*ABNR and B''=B-5*ABNR.
The rectangle you want to draw is the A'A''B''B' rectangle.
There must be much more optimized way to do this (after all, one can draw a line with only additions), this one is simple and works, it depends on your speed rquirements. You may also optimze the code once you have your formulas working.
I ended up sorting this out after Vincent and Sirisian's answers gave me some ideas. I really appreciate the input guys! Basically, both of those answers made me realized I should be treating the segments like rectangles and that I needed some additional coordinates. I put together a jsfiddle if anyone was in interested.
http://jsfiddle.net/WesleyJohnson/sAaM9/1/