Why when I click at the beginning or end of a Raphael path element are the coordinates not what I expect? See example (http://jsfiddle.net/gharabed/prh2J/1/)
In the example, I have a path drawn from (10,10) to (100,100). If I click at the very tip of the path near 100,100, I get a click event coordinate of something like (107,108). I can't be more than 4 or 5 pixels away from the end of the line. In fact it looks like I am only 2 (maybe 3 at the most) pixels away. It seems like the coordinates are a little off. Am I not considering something here?
function sendAlert(e) {
alert("Clicked at: " + e.x + "," + e.y + " I would expect it to be near the coordinates in the path defined (10,10) or (100,100)");
}
var paper = Raphael(document.getElementById('myid'),200,200);
var line = paper.path('M10,10L100,100');
line.attr({"stroke":"red","stroke-width":4});
line.click(sendAlert);
alert("Click as close as you can to the end of either end of the line segment.")
i agree what Vasil has said. i have made a small utility function which will gives you exact coordinates. just pass the mousedown event to this function. ( call this function in your sendalert function)
(i am using javascript).
var mainsvg = document.getElementsByTagName('svg')[0];
function getSvgCordinates(event) {
var m = mainsvg.getScreenCTM();
var p = mainsvg.createSVGPoint();
var x, y;
x = event.pageX;
y = event.pageY;
p.x = x;
p.y = y;
p = p.matrixTransform(m.inverse());
x = p.x;
y = p.y;
x = parseFloat(x.toFixed(3));
y = parseFloat(y.toFixed(3));
return {x: x, y: y};
}
Related
I try to get mouse coordinates on a canvas which has borders defined in CSS. First, I have used a solution found out on this link to get the coordinates of a mouse over a canvas :
function getPosition(event)
{
var x = new Number();
var y = new Number();
var canvas = document.getElementById("canvas");
if (event.x != undefined && event.y != undefined)
{
x = event.x;
y = event.y;
}
else // Firefox method to get the position
{
x = event.clientX + document.body.scrollLeft +
document.documentElement.scrollLeft;
y = event.clientY + document.body.scrollTop +
document.documentElement.scrollTop;
}
x -= canvas.offsetLeft;
y -= canvas.offsetTop;
}
solution 1 the result of this method. You can see that coordinates have a shift of 10 pixels, i.e the width of canvas borders.
To fix this issue of border width, I modified like this :
var computedStyle = window.getComputedStyle(canvas,null);
var topBorder = parseInt(computedStyle.getPropertyValue("border-top-width"),10);
var leftBorder = parseInt(computedStyle.getPropertyValue("border-left-width"),10);
// (x,y) coordinates
x -= canvas.offsetLeft + leftBorder;
y -= canvas.offsetTop + topBorder;
But this doesn't work very well : I have a light shifting on the corners (1 or 2 px shifting) ; you can see the result of this modification on solution 2
I also tried to do :
function getPosition(event) {
var x, y;
var rect = canvas.getBoundingClientRect();
var computedStyle = window.getComputedStyle(canvas,null);
var topBorder = parseInt(computedStyle.getPropertyValue("border-top-width"),10);
var leftBorder = parseInt(computedStyle.getPropertyValue("border-left-width"),10);
var x = event.clientX - rect.left - leftBorder;
var y = event.clientY - rect.top - topBorder;
// Display coordinates
xspan.innerHTML = x;
yspan.innerHTML = y;
}
The result is shown on solution 3 and there's still a light shift of 1, 2 pixels for the inner corners of the board.
I would like put the (0,480) mouse coordinates in the inner left bottom of canvas, (0,0) at the inner left up corner, (480,0) at the right up corner and (480,480) in the right bottom corner.
How to deal with the "10 pixel" of borders width (see CSS) ?
I make you notice that, before calling getPosition, I have added 0.5px to all canvas coordinates because, for example, a line drawn at i=1 with border-width equal to 1, must be drawn at x = 0.5.
If anyone could give me advices to get valid coordinates on this canvas taking account of the borders width.
Thanks
Borders
Do as #markE said and wrap your <canvas> element inside a div:
<div id="game-wrapper">
<canvas id="game" width="481" height="481">
</canvas>
And give the border to the game-wrapper div. Also make game-wrapper to be display: inline-block; so the border lays out correctly around the canvas.
Then your whole coordinate script could be this:
function getPosition(event) {
var x = event.layerX;
var y = event.layerY;
}
Cursor
Aside from that, i think your real problem is that the cursor's pivot point is not at the tip of the cursor, but rather a couple of pixels away from it (at least in OSX). You can observe this by changing the cursor to crosshair:
cursor: crosshair;
Coordinate system
You also asked about the valid way to do a coordinate system transformation. I've found that it's good to have some function that translates the "in-game" coordinates to actual canvas coordinates and back. In your case the 0.5px line you draw:
function Vector2(x,y) {
this.x = x;
this.y = y;
}
Vector2.prototype.toGameCoordinates() {
return new Vector2(
this.x - 0.5,
this.y - 0.5
);
}
Vector2.prototype.toCanvasCoordinates() {
return new Vector2(
this.x + 0.5,
this.y + 0.5
);
}
I need your advice, I have a html with canvas, in this html you can add circles and then link these to make a figure, I also want to insert a search where you can find the different figures and focussed on it, so I don't know if it'll be better : to overlay the figures or use different canvas for each figure.
(I'm not implement the search function yet.)
What do you think?
Here is the functions that makes de draws
//this function puts the circles on a <table>, and then the other function takes the img coordinates and link it making a figure.
function position(year, mon)
{
$('#' + year + ' .' + mon).prepend('<img class="black_point" src="./images/circle.png"/>');
}
var table = document.getElementById("tabla");
var images = table.getElementsByTagName("img");
var canvas = document.getElementById("miCanvas");
var ctx = canvas.getContext("2d");
var x,y; // Remember coordinates
canvas.width = table.offsetWidth;
canvas.height = table.offsetHeight;
function connect(image, index) { //this function link the images
var tabBcr = table.getBoundingClientRect();
var imgBcr = image.getBoundingClientRect();
x = imgBcr.left + (imgBcr.width / 2) - tabBcr.left;
y = imgBcr.top + (imgBcr.height / 2) - tabBcr.top;
index === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
}
//this function add a new canvas
function figure(){
/*$('#miCanvas').prepend('<canvas id="myCanvas">Su navegador no soporta Canvas.</canvas>');
//this is the property who overlay the figures
cxt.globalCompositeOperation="source-over";/*
//which it will be better to implement a search function?
}
// new path here
ctx.beginPath();
for(var i=0; i<images.length; i++){
connect( images[i], i); // provide index so we can sep. move/line
}
// then at the end:
ctx.fill();
Use 1 html canvas to hold all your connected circles.
This simplifies the event handling when focusing / blurring your figures.
You can test if the mouse is inside one of your circles like this:
// given a circle defined by centerX, centerY, radius
var dx = mouseX - centerX;
var dy = mouseY - centerY;
var isInside = dx*dx+dy*dy <= radius*radius;
Here's an outline of how to focus on a figure:
Create a javascript object defining each circle. If a set of objects makes up a figure, then add a group property to each circle-object representing which group that circle is a member of.
Put all your circle-objects in an array.
In your mouse event handlers, iterate through the circle-objects-array and find which circle is under the mouse. That circle's group has been focused.
Aim of my code:
Draw a small rectangle on a HTML canvas whenever a user clicks the canvas. The rectangle should have a small number representing the number of rectangles made by the user.
The user should be able to connect any two rectangles using a straight line. (Preferably by just pressing left mouse button, and taking the mouse from first rectangle to second rectangle)
Approach and my attempt
As you can see in this jsFiddle , I have been able to achieve the first part of above very well. On clicking on the canvas, a rectangle with a number inside of it is made. But I am really clueless about the second part.
How do I make the user connect any two made rectangles? I want the connection to be made only if a rectangle is there ( So I would need to store coordinates of every rectangle that has been made, that's okay as I can use an array for that ).
Basically, I just want to check if the mousedown was at one place and mouseup at the other.
How do I get these two different coordinates ( one of mousedown and other of mouseup ) , and draw a line between them?
I have given the Fiddle above but still here's my jquery:
$(function () {
var x, y;
var globalCounter = 0;
$('#mycanvas').on("click", function (event) {
x = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
x -= mycanvas.offsetLeft;
y = event.clientY + document.body.scrollTop + document.documentElement.scrollTop;
y -= mycanvas.offsetLeft;
// alert("x:"+x+"y: "+y);
drawRectangle(x, y);
});
function drawRectangle(x, y) {
var acanvas = document.getElementById("mycanvas");
var context = acanvas.getContext("2d");
context.strokeRect(x, y, 25, 25);
globalCounter++;
writeNo(x, y, globalCounter);
}
function writeNo(x, y, n) {
var acanvas = document.getElementById("mycanvas");
var context = acanvas.getContext("2d");
context.font = "bold 14px sans-serif";
context.fillText(n, x + 8, y + 12.5);
}
});
The main question is therefore: connecting the two made rectangles by mousedrag
How do I achieve this?
Thank You.
How about this: http://jsfiddle.net/4jqptynt/4/
Ok, first I did a little refactoring for your code to make things easier. Just stuff like putting the code that gets the canvas coordinates into it's own function, and caching some variables (like the canvas context) in the outer function's scope. Oh, and defining your rectangle dimensions as constants because we'll be using the same numbers in a couple of different places.
As you said, the first thing we need is to keep track of the existing rectangles using an array rects (easy enough to do within drawRectangle). Then we need a function to check if a particular pair of coordinates are within some rectangle:
function inRectangle(x, y) {
for (var i = 0, l = rects.length; i < l; i++) {
if ((x - rects[i].x) <= RECT_X && (y - rects[i].y) <= RECT_Y &&
(x - rects[i].x) >= 0 && (y - rects[i].y) >= 0) {
return i;
}
}
}
where RECT_X & RECT_Y define the sides of the rectangle. If the coordinates do exist within some rectangle then this will return the index of that rectangle within the rects array.
Then it's a case of checking whether or not a mousedown occurred within a rectangle, noting that inRectangle will only return a number if the mousedown event was within a rectangle:
$acanvas.on("mousedown", function (event) {
var coords = getCoords(event),
rect = inRectangle(coords.x, coords.y);
if (typeof rect === "number") {
dragStart = rect + 1;
} else {
drawRectangle(coords.x, coords.y);
}
});
if so, make a note of which rectangle using dragStart, if not draw a rectangle as before.
Then to complete the drag, we need to attach a handler to mouseup:
$acanvas.on("mouseup", function (event) {
if (!dragStart) { return; }
var coords = getCoords(event),
rect = inRectangle(coords.x, coords.y);
if (typeof rect === "number") {
drawConnection(dragStart - 1, rect);
}
dragStart = 0;
});
If no drag was started, then it does nothing. If it's coordinates aren't within a rectangle, then it does nothing but reset dragStart. If however, it is within a rectangle, then it draws a connecting line:
function drawConnection(rect1, rect2) {
context.strokeStyle = "black";
context.lineWidth = 1;
context.beginPath();
context.moveTo(rects[rect1].x + RECT_X/2, rects[rect1].y + RECT_Y/2);
context.lineTo(rects[rect2].x + RECT_X/2, rects[rect2].y + RECT_Y/2);
context.stroke();
context.closePath();
}
So I'm trying to implement shooting, and what I want to do is take the players x and y and use that as the base point where the projectile derives from and then it basically goes to the point pressed in the canvas. The projectile is deriving from the player, but instead of going to the cursor point it strangely ALWAYS goes right, but will go right+up or right+down depending on which direction was pressed; I've looked on-line and can't seem to find the answer.
Inside my javascript here is the shoot function:
function shoot(event){
bullets[bulletCount] = new Array(4);
bullets[bulletCount][0] = x;
bullets[bulletCount][1] = y;
bullets[bulletCount][2] = window.event.clientX;
bullets[bulletCount][3] = window.event.clientY;
bulletCount++;
}
In my javascript here is the bullet part in the update method:
for(var b = 0; b < bullets.length; b++){
if(bullets[b][0] < bullets[b][2]) bullets[b][0] += 5;
if(bullets[b][0] > bullets[b][2]) bullets[b][0] -= 5;
if(bullets[b][1] < bullets[b][3]) bullets[b][1] += 5;
if(bullets[b][1] > bullets[b][3]) bullets[b][1] -= 5;
ctx.fillRect(bullets[b][0],bullets[b][1], 8, 8);
}
and in my index.html page:
<canvas id="gameBoard" onClick="shoot(event)" width="500" height="500" tabindex="1"></canvas>
EDIT: fixed the problem, for anyone else who suffers this problem inside the shoot function change from what was originally up there to this:
function shoot(event){
var rect = canvas.getBoundingClientRect();
bullets[bulletCount] = new Array(4);
bullets[bulletCount][0] = x;
bullets[bulletCount][1] = y;
bullets[bulletCount][2] = event.clientX - rect.left;
bullets[bulletCount][3] = event.clientY - rect.top;
bulletCount++;
}
The problem lies within these two lines:
bullets[bulletCount][2] = window.event.clientX;
bullets[bulletCount][3] = window.event.clientY;
It is because that the location you save for the click is not the real location.
You see, event.clientX/Y give you the mouse coordinates relative to the top left corner of the window, but you only want the coordinates relative to the top left corner of your canvas element.
To fix this, first get to know another way to get the mouse coordinates, which is event.pageX/Y.
This will not give you the coordinates relative to the canvas, however you need this instead of event.clientX/Y because it will give you the mouse coordinates relative to the top left corner of the page, not the window, which means it doesn't matter if the user scrolls somewhere, the coordinates are always true to the page.
So, now that you're using event.pageX/Y, how to make it relative to the canvas? Well, we can just take these coordinates and subtract the top left corner coordinates of the canvas:
bullets[bulletCount][2] = window.event.pageX - canvas.offsetLeft;
bullets[bulletCount][3] = window.event.pageY - canvas.offsetTop;
Assuming that your canvas element is saved in a variable named canvas
Hope that solves it :D
var bullets = [];
var bullet_speed = 10;
call this in your init() fucntion: which could have your setInterval.
window.addEventListener('click', shoot, false);
call this in your draw function:
for (var i = 0; i < bullets.length; i++){
bullets[i].x += bullets[i].xChange;
bullets[i].y += bullets[i].yChange;
context.fillStyle ='black';
context.fillRect(bullets[i].x,bullets[i].y,4,4)
}
function shoot(event){
x = event.offsetX
y = event.offsetY
d = Math.sqrt(Math.pow(Math.abs(player.x-x),2)+Math.pow(Math.abs(player.y-y),2))
bullet = {
x : player.x,
y : player.y,
xChange : (x-player.x)/(d/bullet_speed),
yChange : (y-player.y)/(d/bullet_speed),
};
bullets.push(bullet);
}
I'm trying to hack arbor.js so I can perform on edges hover event and then display any information from it.
So let's say I can move over any edge and detect which edge's been hovered.
This is the drawing edges part of the rendering code:
particleSystem.eachEdge(function(edge, pt1, pt2){
// edge: {source:Node, target:Node, length:#, data:{}}
// pt1: {x:#, y:#} source position in screen coords
// pt2: {x:#, y:#} target position in screen coords
// find the start point
var tail = intersect_line_box(pt1, pt2, nodeBoxes[edge.source.name])
var head = intersect_line_box(tail, pt2, nodeBoxes[edge.target.name])
ctx.save()
ctx.beginPath()
ctx.lineWidth = (!isNaN(weight)) ? parseFloat(weight) : 1
ctx.strokeStyle = (color) ? color : "#cccccc"
ctx.fillStyle = null
//We save edge's line data, "Diego"
var slope = ((head.y)-(tail.y))/((head.x)-(tail.x))
a.push({x1:tail.x, y1:tail.y, x2:head.x, y2:head.y, m:slope})
ctx.moveTo(tail.x, tail.y)
ctx.lineTo(head.x, head.y)
ctx.stroke()
// Why pushing here makes x2 and y2 undefined?, "Diego"
ctx.restore()
})
This is the refered function:
var intersect_line_box = function(p1, p2, boxTuple)
{
var p3 = {x:boxTuple[0], y:boxTuple[1]},
w = boxTuple[2],
h = boxTuple[3]
var tl = {x: p3.x, y: p3.y};
var tr = {x: p3.x + w, y: p3.y};
var bl = {x: p3.x, y: p3.y + h};
var br = {x: p3.x + w, y: p3.y + h};
return intersect_line_line(p1, p2, tl, tr) ||
intersect_line_line(p1, p2, tr, br) ||
intersect_line_line(p1, p2, br, bl) ||
intersect_line_line(p1, p2, bl, tl) ||
false
}
I thought about two approaches:
First approach
First one is adding a 'hover' anonymous function inside var handler = {...}. So when $(canvas).mousemove(handler.hover) all would be managed by hover's function. I thought capturing the mouse's position and comparing it with the previous edges line coordinates.
I have a variable like:
var a = []
And then, before drawing again I clear out saved edges:
a=[]
And save the to be drawed edges:
var slope = ((head.y)-(tail.y))/((head.x)-(tail.x))
a.push({x1:tail.x, y1:tail.y, x2:head.x, y2:head.y, m:slope})
So, hover's event function is:
hover: function(e) {
var pos = $(canvas).offset();
var x = e.pageX-pos.left
var y = e.pageY-pos.top
_mouseP = arbor.Point(e.pageX-pos.left, e.pageY-pos.top)
for (var i = 0; i<a.length; i++) {
if ( (_mouseP.x-a[i].x1)) == (a[i].m* (_mouseP.y-a[i].y1)) )
console.log("This is an edge)
},
Second approach
What about going for jQuery? So something like:
$("anyDrawedOnCanvasStuff").hover(function(){}
would trigger events automatically. However I don't think this as possible, is it?
I quite completed first approach, but something weird is happening with the saved points in a array, they're all double and obtained mouse points happened to be integer. And also, they don't even get close to complete the formula's logic.