How can I detect when the user clicks inside the red bubble?
It should not be like a square field. The mouse must be really inside the circle:
Here's the code:
<canvas id="canvas" width="1000" height="500"></canvas>
<script>
var canvas = document.getElementById("canvas")
var ctx = canvas.getContext("2d")
var w = canvas.width
var h = canvas.height
var bubble = {
x: w / 2,
y: h / 2,
r: 30,
}
window.onmousedown = function(e) {
x = e.pageX - canvas.getBoundingClientRect().left
y = e.pageY - canvas.getBoundingClientRect().top
if (MOUSE IS INSIDE BUBBLE) {
alert("HELLO!")
}
}
ctx.beginPath()
ctx.fillStyle = "red"
ctx.arc(bubble.x, bubble.y, bubble.r, 0, Math.PI*2, false)
ctx.fill()
ctx.closePath()
</script>
A circle, is the geometric position of all the points whose distance from a central point is equal to some number "R".
You want to find the points whose distance is less than or equal to that "R", our radius.
The distance equation in 2d euclidean space is d(p1,p2) = root((p1.x-p2.x)^2 + (p1.y-p2.y)^2).
Check if the distance between your p and the center of the circle is less than the radius.
Let's say I have a circle with radius r and center at position (x0,y0) and a point (x1,y1) and I want to check if that point is in the circle or not.
I'd need to check if d((x0,y0),(x1,y1)) < r which translates to:
Math.sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0)) < r
In JavaScript.
Now you know all these values (x0,y0) being bubble.x and bubble.y and (x1,y1) being x and y.
To test if a point is within a circle, you want to determine if the distance between the given point and the center of the circle is smaller than the radius of the circle.
Instead of using the point-distance formula, which involves the use of a (slow) square root, you can compare the non-square-rooted (or still-squared) distance between the points. If that distance is less than the radius squared, then you're in!
// x,y is the point to test
// cx, cy is circle center, and radius is circle radius
function pointInCircle(x, y, cx, cy, radius) {
var distancesquared = (x - cx) * (x - cx) + (y - cy) * (y - cy);
return distancesquared <= radius * radius;
}
(Not using your code because I want to keep the function general for onlookers who come to this question later)
This is slightly more complicated to comprehend, but its also faster, and if you intend on ever checking point-in-circle in a drawing/animation/object moving loop, then you'll want to do it the fastest way possible.
Related JS perf test:
http://jsperf.com/no-square-root
Just calculate the distance between the mouse pointer and the center of your circle, then decide whether it's inside:
var dx = x - bubble.x,
dy = y - bubble.y,
dist = Math.sqrt(dx * dx + dy * dy);
if (dist < bubble.r) {
alert('hello');
}
Demo
As mentioned in the comments, to eliminate Math.sqrt() you can use:
var distsq = dx * dx + dy * dy,
rsq = bubble.r * bubble.r;
if (distsq < rsq) {
alert('HELLO');
}
An alternative (not always useful meaning it will only work for the last path (re)defined, but I bring it up as an option):
x = e.pageX - canvas.getBoundingClientRect().left
y = e.pageY - canvas.getBoundingClientRect().top
if (ctx.isPointInPath(x, y)) {
alert("HELLO!")
}
Path can btw. be any shape.
For more details:
http://www.w3.org/TR/2dcontext/#dom-context-2d-ispointinpath
Related
I'm building a p5js donut chart, but I'm struggling to show the data labels in the middle. I think I have managed to get the boundaries right for it, but how would match the angle that I'm in? Or is there a way of matching just through the colours?
https://i.stack.imgur.com/enTBo.png
I have started by trying to match the boundaries of the chart to the pointer, which I managed to do using mouseX and mouseY. Any suggestions, please?
if(mouseX >= width / 2 - width * 0.2 && mouseY >= height / 2 - width * 0.2
&& mouseX <= width / 2 + width * 0.2 && mouseY <= height / 2 + width * 0.2)
{
//console.log("YAY!!! I'm inside the pie chart!!!");
}
else
{
textSize(14);
text('Hover over to see the labels', width / 2, height / 2);
}
};
[1]: https://i.stack.imgur.com/enTBo.png
While you could theoretically use the get() function to check the color of the pixel under the mouse cursor and correlate that with one of the entries in your dataset, I think you would be much better off doing the math to determine which segment the mouse is currently over. And conveniently p5.js provides helper functions that make it very easy.
In the example you showed you are only checking if the mouse cursor is in a rectangular region. But in reality you want to check if the mouse cursor is within a circle. To do this you can use the dist(x1, y1, x2, y2) function. Once you've established that the mouse cursor is over your pie chart, you'll want to determine which segment it is over. This can be done by finding the angle between a line draw from the center of the chart to the right (or whichever direction is where you started drawing the wedges), and a line drawn from the center of the chart to the mouse cursor. This can be accomplished using the angleBetween() function of p5.Vector.
Here's a working example:
const colors = ['red', 'green', 'blue'];
const thickness = 40;
let segments = {
foo: 34,
bar: 55,
baz: 89
};
let radius = 80, centerX, centerY;
function setup() {
createCanvas(windowWidth, windowHeight);
noFill();
strokeWeight(thickness);
strokeCap(SQUARE);
ellipseMode(RADIUS);
textAlign(CENTER, CENTER);
textSize(20);
centerX = width / 2;
centerY = height / 2;
}
function draw() {
background(200);
let keys = Object.keys(segments);
let total = keys.map(k => segments[k]).reduce((v, s) => v + s, 0);
let start = 0;
// Check the mouse distance and angle
let mouseDist = dist(centerX, centerY, mouseX, mouseY);
// Find the angle between a vector pointing to the right, and the vector
// pointing from the center of the window to the current mouse position.
let mouseAngle =
createVector(1, 0).angleBetween(
createVector(mouseX - centerX, mouseY - centerY)
);
// Counter clockwise angles will be negative 0 to PI, switch them to be from
// PI to TWO_PI
if (mouseAngle < 0) {
mouseAngle += TWO_PI;
}
for (let i = 0; i < keys.length; i++) {
stroke(colors[i]);
let angle = segments[keys[i]] / total * TWO_PI;
arc(centerX, centerY, radius, radius, start, start + angle);
// Check mouse pos
if (mouseDist > radius - thickness / 2 &&
mouseDist < radius + thickness / 2) {
if (mouseAngle > start && mouseAngle < start + angle) {
// If the mouse is the correct distance from the center to be hovering over
// our "donut" and the angle to the mouse cursor is in the range for the
// current slice, display the slice information
push();
noStroke();
fill(colors[i]);
text(`${keys[i]}: ${segments[keys[i]]}`, centerX, centerY);
pop();
}
}
start += angle;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.js"></script>
I think I know the source of the problem was that #thenewbie experienced: it is the p5 library being used. I was using the p5.min.js and experiencing the same problem. Once I started using the full p5.js library, the issue was resolved and #Paul's script worked.
Here is a link I came across while researching this which put me onto the solution:
https://github.com/processing/p5.js/issues/3973
Thanks Paul for the clear explanations and code above.
I have the xy coordinates from before and during a drag event, this.x and this.y```` are the current coordinates,this.lastXandthis.lastY``` are the origin.
What I need to do is given a radian of the source element, determine which mouse coordinate to use, IE if the angle is 0 then the x coordinates is used to give a "distance" if the degrees are 90 then the y coordinates are used
if the radian is 0.785398 then both x and y would need to be used.
I have the following code for one axis, but this only flips the y coordinates
let leftPosition;
if (this.walls[this.dragItem.wall].angle < Math.PI / 2) {
leftPosition = Math.round((-(this.y - this.lastY) / this.scale + this.dragItem.origin.left));
} else {
leftPosition = Math.round(((this.y - this.lastY) / this.scale + this.dragItem.origin.left));
}
I have an example here https://engine.owuk.co.uk
what I need to do is have the radian dictate what x or y coordinate is used to control the drag of the item by calculating the leftPosition, I have been loosing my mind trying to get this to work :(
The Math.sin and Math.cos is what you need, here is an example
<canvas id="c" width=300 height=150></canvas>
<script>
const ctx = document.getElementById('c').getContext('2d');
function drawShape(size, angle, numPoints, color) {
ctx.beginPath();
for (j = 0; j < numPoints; j++) {
a = angle * Math.PI / 180
x = size * Math.sin(a)
y = size * Math.cos(a)
ctx.lineTo(x, y);
angle += 360 / numPoints
}
ctx.fillStyle = color;
ctx.fill();
}
ctx.translate(80, 80);
drawShape(55, 0, 7, "green");
drawShape(45, 0, 5, "red");
drawShape(35, 0, 3, "blue");
ctx.translate(160, 0);
drawShape(55, 15, 7, "green");
drawShape(45, 35, 5, "red");
drawShape(35, 25, 3, "blue");
</script>
Here is a theoretical answer to your problem.
In the simplest way, you have an object within a segment that has to move relative to the position of the mouse, but constrained by the segment's vector.
Here is a visual representation:
So with the mouse at the red arrow, the blue circle needs to move to the light blue.
(the shortest distance between a line and a point)
How do we do that?
Let's add everything we can to that image:
The segment and the mouse form a triangle and we can calculate the length of all sides of that triangle.
The distance between two points is an easy Pythagorean calculation:
https://ncalculators.com/geometry/length-between-two-points-calculator.htm
Then we need the height of the triangle where the base is our segment:
https://tutors.com/math-tutors/geometry-help/how-to-find-the-height-of-a-triangle
That will give us the distance from our mouse to the segment, and we do know the angle by adding the angle of the segment + 90 degrees (or PI/2 in radians) that is all that we need to calculate the position of our light blue circle.
Of course, we will need to also add some min/max math to not exceed the boundaries of the segment, but if you made it this far that should be easy pickings.
I was able to make the solution to my issue
let position;
const sin = Math.sin(this.walls[this.dragItem.wall].angle);
const cos = Math.cos(this.walls[this.dragItem.wall].angle);
position = Math.round(((this.x - this.lastX) / this.scale * cos + (this.y - this.lastY) / this.scale * sin) + this.dragItem.origin.left);
I have a program of a ball that follows the cursor on the screen, on a html canvas.
I also have a circle on the screen, I need to make this ball stay within the circle.
here is the current conditional keeping the ball within the canvas.
if(ball.x < 0 || ball.x > canvas.width)
ball.dx = -ball.dx;
if(ball.y < 0 || ball.y > canvas.height)
ball.dy = -ball.dy;
ball.x, ball.y are the x,y coordinates of the "ball" object instance. The ball.dy and ball.dx is the direction of the ball, and the "-" reverses the direction of the ball when it hits the outside of the canvas.
here is the circle code for the arc.
context.arc(canvas.width / 2, canvas.height / 2, 60, 0, 2*Math.PI, false);
format:arc(x,y,radius, start, end, false)
how can I utilize the center point of the circle by not allowing it to move outside of it?
thank you.
I could be completely wrong on this, but can't you just use the Pythagorean Theorem? (a*a)+(b*b)=(c*c)
The center of your canvas is 0,0
The radius of your arc is 60
Ball position is 29,-29
function isInsideCircle(ball,radius){
var a=Math.pow(ball.x,2);
var b=Math.pow(ball.y,2);
var c=Math.sqrt(a+b);
return c<radius;
}
With ball at 29,-29 = distance of ~41 from center (inside the circle)
With ball at 50,50 = distance of ~71 from center (outside circle)
You will have to calculate the x and the y coordinates of the bound each time. The bound basically runs around the entire circle. This point is the intersection of the line between the center of the circle and the current x and y mouse co-ordinates and the circle. The limiting condition is that the length of this line does not exceed the radius of the circle. So if you are ever in a state where the distance between the center of the circle and the current x and y is greater than the radius of the circle, you will have to limit the x and y coordinate to be on the circumference of the circle.
The equation for a circle is:
x^2 + y^2 = r^2
Where r is the radius of the circle.
The equation for a line is:
y = mx + c
Where m is the slope and c is an offset. In our case we're going to assume that the origin is the center of the circle, so we don't have a c which means the equation is:
y = mx
What about the value of m? m is our slope which we can calculate from the rise over the run:
rise = ball.x - yCircleCenter
run = ball.y - xCircleCenter
Which means the equation for our line is:
y = (rise / run) * x
You can substitute values of x and y into the circle equation to figure out the x and y that lie on the circumference of the circle, which gives you:
x_on_circle = (r * run) / sqrt(rise^2 + run^2)
y_on_circle = (r * rise) / sqrt(rise^2 + run^2)
Assuming this was a function called limit, you can do something like this:
var distance = Math.sqrt(((ball.x - xCircleCenter) ^ 2) + ((ball.y - yCircleCenter) ^ 2));
if(distance > radius) {
var limitedCoordinates = limit(ball.x, ball.y);
ball.x = limitedCoordinates.x;
ball.y = limitedCoordinates.y
}
I am writing a multitouch jigsaw puzzle using html5 canvas in which you can rotate the pieces around a point. Each piece has their own canvas the size of their bounding box. When the rotation occurs, the canvas size must change, which I am able to calculate and is working. What I can't figure out, is how to find the new x,y offsets if I am to have this appear to be rotating around the pivot (first touch point).
Here is an image to better explain what I'm trying to achieve. Note the pivot point is not always the center, otherwise I could just halve the difference between the new bounds and the old.
So I know the original x, y, width, height, rotation angle, new bounds(rotatedWidth, rotatedHeight), and the pivot X,Y relating to original object. What I can't figure out how to get is the x/y offset for the new bounds (to make it appear that the object rotated around the pivot point)
Thanks in advance!
First we need to find the distance from pivot point to the corner.
Then calculate the angle between pivot and corner
Then calculate the absolute angle based on previous angle + new angle.
And finally calculate the new corner.
Snapshot from demo below showing a line from pivot to corner.
The red dot is calculated while the rectangle is rotated using
translations.
Here is an example using an absolute angle, but you can easily convert this into converting the difference between old and new angle for example. I kept the angles as degrees rather than radians for simplicity.
The demo first uses canvas' internal translation and rotation to rotate the rectangle. Then we use pure math to get to the same point as evidence that we have calculated the correct new x and y point for corner.
/// find distance from pivot to corner
diffX = rect[0] - mx; /// mx/my = center of rectangle (in demo of canvas)
diffY = rect[1] - my;
dist = Math.sqrt(diffX * diffX + diffY * diffY); /// Pythagoras
/// find angle from pivot to corner
ca = Math.atan2(diffY, diffX) * 180 / Math.PI; /// convert to degrees for demo
/// get new angle based on old + current delta angle
na = ((ca + angle) % 360) * Math.PI / 180; /// convert to radians for function
/// get new x and y and round it off to integer
x = (mx + dist * Math.cos(na) + 0.5)|0;
y = (my + dist * Math.sin(na) + 0.5)|0;
Initially you can verify the printed x and y by seeing that the they are exact the same value as the initial corner defined for the rectangle (50, 100).
UPDATE
It seems as I missed the word in: offset for the new bounds... sorry about that, but what you can do instead is to calculate the distance to each corner instead.
That will give you the outer limits of the bound and you just "mix and match" the corner base on those distance values using min and max.
New Live demo here
The new parts consist of a function that will give you the x and y of a corner:
///mx, my = pivot, cx, cy = corner, angle in degrees
function getPoint(mx, my, cx, cy, angle) {
var x, y, dist, diffX, diffY, ca, na;
/// get distance from center to point
diffX = cx - mx;
diffY = cy - my;
dist = Math.sqrt(diffX * diffX + diffY * diffY);
/// find angle from pivot to corner
ca = Math.atan2(diffY, diffX) * 180 / Math.PI;
/// get new angle based on old + current delta angle
na = ((ca + angle) % 360) * Math.PI / 180;
/// get new x and y and round it off to integer
x = (mx + dist * Math.cos(na) + 0.5)|0;
y = (my + dist * Math.sin(na) + 0.5)|0;
return {x:x, y:y};
}
Now it's just to run the function for each corner and then do a min/max to find the bounds:
/// offsets
c2 = getPoint(mx, my, rect[0], rect[1], angle);
c2 = getPoint(mx, my, rect[0] + rect[2], rect[1], angle);
c3 = getPoint(mx, my, rect[0] + rect[2], rect[1] + rect[3], angle);
c4 = getPoint(mx, my, rect[0], rect[1] + rect[3], angle);
/// bounds
bx1 = Math.min(c1.x, c2.x, c3.x, c4.x);
by1 = Math.min(c1.y, c2.y, c3.y, c4.y);
bx2 = Math.max(c1.x, c2.x, c3.x, c4.x);
by2 = Math.max(c1.y, c2.y, c3.y, c4.y);
to rotate around the centre point of the canvas you can use this function:
function rotate(context, rotation, canvasWidth, canvasHeight) {
// Move registration point to the center of the canvas
context.translate(canvasWidth / 2, canvasHeight/ 2);
// Rotate 1 degree
context.rotate((rotation * Math.PI) / 180);
// Move registration point back to the top left corner of canvas
context.translate(-canvasWidth / 2, -canvasHeight/ 2);
}
Here is the way that worked best for me. First I calculate what is the new width and height of that image, then I translate it by half of that amount, then I apply the rotation and finally I go back by the original width and height amount to re center the image.
var canvas = document.getElementById("canvas")
const ctx = canvas.getContext('2d')
drawRectangle(30,30,40,40,30,"black")
function drawRectangle(x,y,width,height, angle,color) {
drawRotated(x,y,width,height,angle,ctx =>{
ctx.fillStyle = color
ctx.fillRect(0,0,width,height)
})
}
function drawRotated(x,y,width,height, angle,callback)
{
angle = angle * Math.PI / 180
const newWidth = Math.abs(width * Math.cos(angle)) + Math.abs(height * Math.sin(angle));
const newHeight = Math.abs(width * Math.sin(angle)) + Math.abs(height * Math.cos(angle));
var surface = document.createElement('canvas')
var sctx = surface.getContext('2d')
surface.width = newWidth
surface.height = newHeight
// START debug magenta square
sctx.fillStyle = "magenta"
sctx.fillRect(0,0,newWidth,newHeight)
// END
sctx.translate(newWidth/2,newHeight/2)
sctx.rotate(angle)
sctx.translate(-width/2,-height/2)
callback(sctx)
ctx.drawImage(surface,x-newWidth/2,y-newHeight/2)
}
#canvas{
border:1px solid black
}
<canvas id="canvas">
</canvas>
I have created an ellipse on my canvas and now I need to draw three lines stemming from the origin. As an example let's say the first line is 90 degrees (vertical) so the point is (0, 10). I need the other two lines to be x pixels away from the point in both directions.
I'm sure I didn't describe this well enough but basically what I am trying to do is from a point on a known ellipse, find another point x distance away that lies on the ellipse.
I have tried looking for an arc of an ellipse but nothing seems to fit what I am looking for.
For an ellipse:
x = a cos(t)
y = b sin(t)
So:
x/a= cos(t)
t = acos(x/a)
y = b sin(acos(x/a))
Plug in your values of a, b, and x and you get y.
See https://www.mathopenref.com/coordparamellipse.html
Rather crudely:
var a=120;
var b=70;
var c=document.getElementById("myCanvas");
var cxt=c.getContext("2d");
var xCentre=c.width / 2;
var yCentre=c.height / 2;
// draw axes
cxt.strokeStyle='blue';
cxt.beginPath();
cxt.moveTo(0, yCentre);
cxt.lineTo(xCentre*2, yCentre);
cxt.stroke();
cxt.beginPath();
cxt.moveTo(xCentre, 0);
cxt.lineTo(xCentre, yCentre*2);
cxt.stroke();
// draw ellipse
cxt.strokeStyle='black';
cxt.beginPath();
for (var i = 0 * Math.PI; i < 2 * Math.PI; i += 0.01 ) {
xPos = xCentre - (a * Math.cos(i));
yPos = yCentre + (b * Math.sin(i));
if (i == 0) {
cxt.moveTo(xPos, yPos);
} else {
cxt.lineTo(xPos, yPos);
}
}
cxt.lineWidth = 2;
cxt.strokeStyle = "#232323";
cxt.stroke();
cxt.closePath();
// draw lines with x=+/- 40
var deltaX=40;
var y1=b*Math.sin(Math.acos(deltaX/a));
cxt.strokeStyle='red';
cxt.beginPath();
cxt.moveTo(xCentre+deltaX, yCentre-y1);
cxt.lineTo(xCentre, yCentre);
cxt.lineTo(xCentre-deltaX, yCentre-y1);
cxt.stroke();
<html>
<head><title>Ellipse</title></head>
<body>
<canvas id="myCanvas" style="position: absolute;" width="400" height="200"></canvas>
</body>
</html>
(Using https://www.scienceprimer.com/draw-oval-html5-canvas as a basis as I've never used HTML canvas before.)
Andrew Morton's answer is adequate, but you can it with one square root instead of a sin and an acos.
Suppose you have an ellipse centered at the origin, with a radius along the X-axis of a and a radius along the Y-axis of b. The equation of this ellipse is
x2/a2 + y2/b2 = 1.
Solving this for y gives
y = ± b sqrt(1 - x2/a2)
You can choose whichever sign is appropriate. Based on your post, you want the positive square root.
Translating to Javascript:
function yForEllipse(a, b, x) {
return b * Math.sqrt(1 - x*x / a * a);
}