center of Raphael triangle - javascript

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.

Related

Circle Layout node angles cytoscape

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:

Fabric.js moving the points of a polygon shape fails when flipped

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

Paper.js: fastest way to draw many iterated shapes over loop

jsfiddle here: http://jsfiddle.net/yw0w18m3/2/
I'm using paper.js to make a background image that looks somthing like this:
Basically, I'm creating a couple thousand triangles over a loop and rotating them on every other iteration.
function Tri(x, y, rotate) {
var tri = new Path([
new Point((x - 42), (y - 48)),
new Point((x - 42), y),
new Point(x, (y - 24)),
new Point((x - 42), (y - 48))
]);
tri.fillColor = {
hue: Math.random() * 360,
saturation: 0,
brightness: ( (( Math.random() ) * .95) + .3 )
};
if(rotate) { tri.rotate(180); }
}
for (var i = 0; i < 2000; i++) {
rotate = false;
if( i % 2 ) {
rotate = true;
}
new Tri(x, y, rotate);
x = x + 42;
if( x > (winWidth + 42) ) {
x = 0 ;
y = y + 24;
}
}
There seems to be a brief 1-2 second pause/freeze though while the shapes are being drawn. Is there a more efficient way to draw all the shapes first (or push to an array) then add that to the canvas all at once?
I based my code off of the example here: http://paperjs.org/examples/candy-crash/ (click "source" in the upper right corner).
Any help is much appreciated.
Thanks!
I would end up creating two triangles, one rotated, so they don't have to be built from new points each time. Then choose the correct triangle based on the rotation variable and clone it, as opposed to create points and a triangle from scratch each time. Finally, just change the position of the cloned triangle.
Last, I would correct the maxTri so it doesn't do more than it needs to. The paren should follow the 48, not the 24. You're doing an order of magnitude more triangles than needed.
Here's a link to the sketch.paperjs.org solution I created based on your code. I find sketch easier to use than jsfiddle for paper examples.
proto1 = new Path([
new Point(0, -24),
new Point(0, 24),
new Point(42, 0)
]);
proto1.closed = true;
proto2 = proto1.clone();
proto2.rotate(180);
function putTriangle(pos, rotate) {
var tri = (rotate ? proto2 : proto1).clone();
tri.position = pos;
tri.position = tri.position.subtract([21, 0])
tri.fillColor = {
hue: Math.random() * 360,
saturation: 0,
brightness: Math.random() * 0.5 + 0.5
}
}
var tris = [],
x = 42,
y = 24,
rotate,
winWidth = paper.view.size.width,
winHeight = paper.view.size.height,
rows = (winHeight + 48) / 24,
cols = (winWidth + 42) / 42,
numTri = rows * cols,
numTriOrig = (winWidth + 42) / 42 * (winHeight + 48 / 24);
//console.log(numTri, numTriOrig);
x = 0;
y = 0;
for (var row = 0; row < rows; row++) {
rowrotate = row % 2;
for (var col = 0; col <= cols; col++) {
rotate = rowrotate ^ col % 2;
putTriangle([x,y], rotate);
x += 42;
}
x = 0;
y = y + 24;
}
Two thoughts:
I see you use rotate to transform you triangles into place. This is an expensive operation. You could replace the rotate with a less geometric & more arithmetic calculation of the triangles orientation.
Also, I see is that the fill color is being changed with each triangle and state changes (like fill) are modestly expensive. You could group all the similarly colored triangles and draw them in a single batch.

How to detect if a user has drawn a circle on a touch device using canvas and javascript?

I am creating a Tangram puzzle game using Javascript. And I need to detect when a user has drawn a circle (or circle like shape) with their finger. I have been able to gather hundreds (if not thousands) of x and y points with:
var touchX = event.targetTouches[0].pageX - canvas.offsetLeft;
var touchY = event.targetTouches[0].pageY - canvas.offsetTop;
I then push each x and y coordinate into an array:
touchMoveX.push(touchX);
touchMoveY.push(touchY);
I then loop through each array and create two points:
for(var i = 0; i < touchMoveX.length; i++)
{
for(var l=0; l < touchMoveY.length; l++)
{
var xPosition = touchMoveX[i];
var yPosition = touchMoveY[l];
var v1x = touchMoveX[i];
var v2x = touchMoveX[i + 1];
var v1y = touchMoveY[l];
var v2y = touchMoveY[l + 1];
Then using those two points, I use the following formula to figure out the angle between these two points in degrees:
var v1 = {x: v1x, y: v1y}, v2 = {x: v2x, y: v2y},
angleRad = Math.acos( (v1.x * v2.x + v1.y * v2.y) /
(Math.sqrt(v1.x*v1.x + v1.y*v1.y) * Math.sqrt(v2.x*v2.x + v2.y*v2.y) ) ),
angleDeg = angleRad * 180 / Math.PI;
I then sum up all of the angles and see if they are around 360 degrees.
But the above code I have described isn't working very well. Does someone out there have a better way to do this? Thank you very much.
yeah compute the average of all points (giving you a cheaply approximated center) then check if more than a certain percent of points are within a certain threshold. You can tune those values to adjust the precision until it feels right.
edit: Didn't consider that the circle could have multiple sizes, but you could just add another step computing the average of all distances. Adjusted the example for that.
var totalAmount = touchMoveX.length;
// sum up all coordinates and divide them by total length
// the average is a cheap approximation of the center.
var averageX = touchMoveX.reduce( function ( previous, current) {
return previous + current;
} ) / totalAmount ;
var averageY = touchMoveY.reduce( function ( previous, current) {
return previous + current;
} ) / totalAmount ;
// compute distance to approximated center from each point
var distances = touchMoveX.map ( function ( x, index ) {
var y = touchMoveY[index];
return Math.sqrt( Math.pow(x - averageX, 2) + Math.pow(y - averageY, 2) );
} );
// average of those distance is
var averageDistance = distances.reduce ( function ( previous, current ) {
return previous + current;
} ) / distances.length;
var min = averageDistance * 0.8;
var max = averageDistance * 1.2;
// filter out the ones not inside the min and max boundaries
var inRange = distances.filter ( function ( d ) {
return d > min && d < max;
} ).length;
var minPercentInRange = 80;
var percentInRange = inRange.length / totalAmount * 100;
// by the % of points within those boundaries we can guess if it's circle
if( percentInRange > minPercentInRange ) {
//it's probably a circle
}

Calculate x, y coordinates of rotated line segments to draw on a Canvas

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/

Categories

Resources