Let's say I have a line drawn as so in HTML5 Canvas:
...
ctx.beginPath();
ctx.moveTo(x,y);
ctx.lineTo(x1,y1);
ctx.closePath();
...
I want to find out if the mouse down event happened on this line, and I have code like this:
var handleMouseDown = function(e) {
var coords = translateCoords(e.x,e.y);
...
if (ctx.isPointInPath(coords.x, coords.y) {
...
Now, this code works fine in the case of circles & rectangles, but not for lines. I have 2 questions:
My thinking is that perhaps calling closePath() on a line itself is incorrect. Question - how can I check if the mouse down event happened on this line?
How can I extend this to find if the mouse down event happened near the line?
The first step is to find the normal projection of the point onto the line. This is actually quite simple: take the distance from point 1 to the target, and point 2 to the target, and call them D1 and D2 respectively. Then calculate D1+(D2-D1)/2. This is the distance to the projected point on the line from point 1.
You can now find that point, and get the distance from that point to the target. If the distance is zero, then the target is exactly on the line. If the distance is less than 5, then the target was less than 5px away, and so on.
EDIT: A picture is worth a thousand words. Here's a diagram:
(source: adamhaskell.net)
(In hindsight, probably should have made those circles a different colour... Also, the purple line is supposed to be perpendicular to line AB. Blame my terrible aim with the blue line!)
Here is the approach taken from Wikipedia article Distance from a point to a line (Line defined by two points)
var Dx = x2 - x1;
var Dy = y2 - y1;
var d = Math.abs(Dy*x0 - Dx*y0 - x1*y2+x2*y1)/Math.sqrt(Math.pow(Dx, 2) + Math.pow(Dy, 2));
where (x0,y0) is your Point coordinates and your Line is ((x1,y1),(x2,y2))
However, this does not checks for boundaries of the line, so I had to add another check for it.
function inBox(x0, y0, rect) {
var x1 = Math.min(rect.startX, rect.startX + rect.w);
var x2 = Math.max(rect.startX, rect.startX + rect.w);
var y1 = Math.min(rect.startY, rect.startY + rect.h);
var y2 = Math.max(rect.startY, rect.startY + rect.h);
return (x1 <= x0 && x0 <= x2 && y1 <= y0 && y0 <= y2);
}
Where your Line defined as rectangle. Hope this helps.
You have two options for this. Your "simple" option is to use canvas to do it -- Read the pixel data wherever the mouse is and if it's the same color as your line then the user clicked on the line. This makes a lot of assumptions, however, like that everything on your canvas is rendered in a different solid color. This is possible, however, as a common trick is to render everything to an off-screen canvas in a different solid color. Then when the user clicks something you know exactly what it is by reading the color of that one pixel and mapping it back to the original object.
But that's not exactly what you asked for :)
You don't want to know if the user clicked on a line because they almost never will. A line is infinitely thin, so unless it's exactly horizontal or exactly vertical they will never click on it. What you want instead is to see how far the mouse is from the line. Kolink just answered that part, so I'll stop here :)
Related
I have a canvas code which render polygon shapes. I can drag a figure and move it to another place. I have move event functions which change the color of the nearest polygons and the draggable polygon. The problem is that I don't know how to make them change their color only when I touching a corner of a figure with my draggable figure. I did what I did considering only width and height of a figure so when I drag a figure near the others some of them change their colors when I even don't touch them. So I should somehow consider their corners ... It's just too complicated to me.
There are relatively much code so I did codepen where you can see what I'm talking about.
PS: only javascript's API allowed.
There are many different ways to do hit detection, depending on what you want to detect, how efficiently you want to detect it etc. The link below gives some nice solutions to finding points inside arbitrary polygons by looking at the relationship between the point in question and any y axis intercepts.
http://alienryderflex.com/polygon/
By comparing the given point to the y intercepts, the counts on either side can be used to determine if there is a hit.
This is solved using a function like the one below, providing an array of x and y coordinates of the polygons vertices, as well as the x and y of the test point.
function pointInPolygon(xs, ys, x, y) {
var j = xs.length - 1
var oddNodes = false
for (var i=0; i<xs.length; i++) {
if ((ys[i] < y && ys[j] >= y ||
ys[j] < y && ys[i] >= y)
&& (xs[i] <= x || xs[j] <= x)) {
oddNodes ^= (xs[i] + (y - ys[i]) / (ys[j] - ys[i]) * (xs[j] - xs[i]) < x)
}
j=i
}
return oddNodes
}
I have created a fiddle that shows this in action (clicking on the poly will change its colour).
https://jsfiddle.net/95k3t26q/19/
I created an array of small circles (dots) laid on a circumference of a larger circle in a loop, with each iteration I rotate the same dot with the same cx and cy by a different angle using transform=rotate(i*angle,0,0).
Now I want to connect a line between two of the dots, but since each dot has the same cx and cy, my naive way of passing in the cx and cy of the two dots as coordinates doesn't seem to work.
Strangely on JSFiddle, half of the line is shown, even though the line's x0 == x1 and y0 == y1: https://jsfiddle.net/8wn30vqn/1/
What is a good way of obtaining the coordinates of a transformed svg element so I can pass them into the line? Or is there some other way I can connect two elements with a line?
The good way would be to take transformation matrix from each of your small circles and apply it to the same point to get new point (and then connect these new points).
var svgNode = d3circle.node();
var matrix = svgNode.transform.baseVal.consolidate().matrix;
var pt = svgNode.ownerSVGElement.createSVGPoint();
pt.x = 0;
pt.y = 0;
var transformedPoint = pt.matrixTransform(matrix);
... use transformedPoint to create line
I am trying to draw flightpaths on a map using SVGs. I'm using d3 on top of Leaflet, but the frameworks used shouldn't make a difference to my problem - it's trig.
http://fiddle.jshell.net/zw8TR/26
The way I'm trying to do this is by creating a quadratic bezier curve (I'm open to other/easier ways if you know of any). What I need to calculate is 1 control point, perpendicular to the midpoint of each line. This point should always bias to a higher y value / latitude than the midpoint, to create an arc which looks like a flightpath.
In my demo above, I found it's easier to debug exactly where the control point is by adding some extra temporary points. As you can see, some of my control points are facing downwards, and none look to be properly perpendicular.
Check out my previous question on this - with diagrams!
I have a feeling the problem boils down to this line:
var theta = Math.atan2(t_area_y, t_area_x) * 180 / Math.PI;
I'm not handling negative coordinates properly. I have tried to hack in a nasty set of if gates to handle this.
I have tried to comment the fiddle nicely to explain what's going on. Once I know the point, it should be a simple case of creating a custom interpolation in d3.
This is actually easier than you think if you use a custom line generator. Instead of adding the control points to the feature, you just add them during the computation of the path. The code looks like this:
feature.attr("d", function(d) {
var s, prev;
d.geometry.coordinates.forEach(function(c) {
var proj = map.latLngToLayerPoint(new L.LatLng(c[1], c[0]));
if(s) {
var length = Math.sqrt(Math.pow(proj.x - prev.x, 2), Math.pow(proj.y - prev.y, 2)),
midx = prev.x + (proj.x - prev.x) / 2,
midy = prev.y + (proj.y - prev.y) / 2 - length * 0.2 * (Math.abs(proj.x - prev.x) / length);
s += "Q" + midx + "," + midy + " " + proj.x + "," + proj.y;
} else {
s = "M" + proj.x + "," + proj.y;
}
prev = proj;
});
return s;
});
Let's go through it step by step. The main thing is that I'm keeping track of the coordinates of the previous point to be able to compute the control point. First, s will be null and the else branch is taken -- simply move to that point (the start point) without drawing a line. For all subsequent points, the actual computation takes place.
First, we compute the distance between the two points (previous and current), length. Computing the x coordinate of the control point is straightforward, as no offset is required. The y coordinate is a bit trickier -- the first part is the same, then the offset is added. The size of the offset is 20% of the length of the path here (to make wider arcs for longer paths), adjust as necessary. This needs to be multiplied by the cosine of the angle to the x axis, but fortunately we don't need to compute the angle explicitly -- it is defined by the relation between the distance between the points and the difference in x coordinates (the arc cosine of that angle). So we can just take that relation directly and multiply by it. As you want arcs to point up (i.e. negative y offset), we're taking the absolute value of the x coordinate differences. Without that, some of the control points would be pointing down.
Complete example here.
I'm trying to learn canvas by implementing a pie chart. I've managed to parse my data, draw the slices, and calculate the center of each arc, as noted by the black circles. But now I'm trying to draw one of the slices as though it had been "slid out". Not animate it (yet), just simply draw the slice as though it had been slid out.
I thought the easiest way would be to first calculate the point at which the new corner of the slice should be (free-hand drawn with the red X), translate there, draw my slice, then translate the origin back. I thought I could calculate this easily, since I know the center of the pie chart, and the point of the center of the arc (connected with a free-hand black line on the beige slice). But after asking this question, it seems this will involve solving a system of equations, one of which is second order. That's easy with a pen and paper, dauntingly hard in JavaScript.
Is there a simpler approach? Should I take a step back and realize that doing this is really the same as doing XYZ?
I know I haven't provided any code, but I'm just looking for ideas / pseudocode. (jQuery is tagged in the off chance there's a plugin will somehow help in this endeavor)
Getting the x and y of the translation is easy enough.
// cx and cy are the coordinates of the centre of your pie
// px and py are the coordinates of the black circle on your diagram
// off is the amount (range 0-1) by which to offset the arc
// adjust off as needed.
// rx and ry will be the amount to translate by
var dx = px-cx, dy = py-cy,
angle = Math.atan2(dy,dx),
dist = Math.sqrt(dx*dx+dy*dy);
rx = Math.cos(angle)*off*dist;
ry = Math.sin(angle)*off*dist;
Plug that into the code Simon Sarris gave you and you're done. I'd suggest an off value of 0.25.
Merely translating an element on a canvas is very easy and there shouldn't be any tricky equations here. In the most basic sense it is:
ctx.save();
ctx.translate(x, y);
// Draw the things you want offset by x, y
ctx.restore();
Here's a rudimentary example of a square pie and the same pie with one of the four "slices" translated:
http://jsfiddle.net/XqwY2/
To make the pie piece "slide out" the only thing you need to calculate is how far you want it to be. In my simple example the blue block is slid out 10, -10.
If you are wondering merely how to get the X and Y you want in the first place, well, that's not quite a javascript/canvas question. For points on a line given a distance this question: Finding points on a line with a given distance seems the most clear
Edit, here you are (from comments):
// Center point of pie
var x1 = 100;
var y1 = 100;
// End of pie slice (your black dot)
var x2 = 200;
var y2 = 0;
// The distance you want
var distance = 3;
var vx = x2 - x1; // x vector
var vy = y2 - y1; // y vector
var mag = Math.sqrt(vx*vx + vy*vy); // length
vx = mag/vx;
vy = mag/vy;
// The red X location that you want:
var px = x1 + vx * ( distance);
var py = y1 + vy * ( distance);
This would give you a px,py of (104.24, 95.76) for my made-up inputs.
I am wanting to create a simple abstract pattern using the html5 canvas tag and javascript. I have worked out the guts of what I want it to do using some variables, functions and objects, but with the boundary detection that I have employed I am wanting each particular shape to go back to its starting position when it goes out of the screen (and thus loop the animation).
So with that being my question, here is my code. Also any other structure tips are appreciated as I am new to OO in Javascript.
See my progress here: http://helloauan.com/apps/test/
Cheers!
I'm not really sure if what you mean exactly is, once the big white diagnal lines are all the way off the top right corner of page, that's when you want them to start back at the bottom left ? right?
What you need to do is check if the line is beyond the width and height of the canvas, and in your case, the window itself since the canvas fills the browser window. So you need to do a series of conditionals. You check if the line x + line width is > canvas width and line.y + line height is > canvas height. If both are true then set the x and y of the line to - what it is at that time. So something like:
if( line.x + line.width > canvas.width && line.y + line.height < 0) {
line.x = -0;
line.y = canvasHeight + line.height;
}
This is how I recycle circles that come in from the right side of the screen and once they exit the left side they start over on the right.
if( d.x + d.radius < 0 ) {
d.radius = 5+(Math.random()*10);
d.x = cwidth + d.radius;
d.y = Math.floor(Math.random()*cheight);
d.vX = -5-(Math.random()*5);
}
The first thing is just psuedo, you should take a look at a thing I made to use as a starting point for things like this. The structure of your code could use some more organization, canvas gets real complex real quick.
Using the arrow keys, move the square off any one of the 4 sides and see it come in on opposite side.
http://anti-code.com/games/envy/envy.html
Fork if you want: https://github.com/jaredwilli/envy