Javascript - mapping mouse clicks over a grid - javascript

I have a cube grid made of an array of arrays in javascript. The grid dimensions are stored in variables. So dim is the dimension of the single cube, spacing is the distance between each cube (dim and spacing expressed in pixels).
Now, considering that I can always have the user mouse position in the variables g.mouseX and g.mouseY why this code isn't always so precise?
var j = Math.round(g.mouseX / (dim+spacing));
var i = Math.round(g.mouseY / (dim+spacing));
// user clicked on the cell grid[j][i]
Sometimes I click a cube but it looks like he's considering the one nearby. It looks like the whole mapping of the user click is shifted of half a dim. Probably using Math.round makes everything almost wrong, but I don't see any other way to transform such a chaotic and unpredictable value as the user click to a precise coordinate in my grid.
Hope anyone can help on this!
Thanks in advance...

You want to truncate, not round. You can use Math.floor, which maps (e.g.) 3.7 to 3:
var j = Math.floor(g.mouseX / (dim+spacing));
But really, why not just bind the onclick event of each grid cell?

Related

How to make HTML Canvas clickable grid (that returns cell to match array element?)

Trying to build the Game of Life here with React JS. I have a 2-dimensional array that holds the state of the grid. Every element, which is an array, represents a row and a sub-element is either 0 or 1 (dead or alive).
I want to use HTML Canvas to draw the grid based on this array and also add a click function that returns the matching element in the array so I can write a function to change it to 0 or 1 based on an algorithm.
I have a general idea how to draw a grid, also checked this link
But i am not sure whether this is the way to do it so I can identify each cell with a click. And also stuck on how I can return the cell to match the array element. Anyone can help me with this?
I am also aware of this question on Stack Overflow, but the answers are implementations with HTML or SVG, I am looking for a canvas implementation.
First of all you have to handle the click event. Here you can find a good description of how: How do I add a simple onClick event handler to a canvas element?.
Furthermore, you just use the x and y of the clicked point to determine what tile has been clicked. Lets say you have 10 x 10 tiles with the height and width of 10 px.
If the user clicks the coordnate 67,12 you can divide x and y by ten, and round it off to know which elelemt in your 2d array that represents it.

Circular canvas corners clickable in Chrome

I have two canvases. I have made them circular using border-radius. The 2nd is positioned inside the first one (using absolute position).
I have click events on both circles. If you click on inside canvas, the color at the point of the click is loaded in the outside canvas with opacity varying from white to the picked color and finally to black. If you click on outer canvas the exact color value at that point is loaded in the text-box at the bottom
I am unable to click in red zones (as shown in figure below) of the outer canvas when using chrome. I tried z-idex, arcs but nothing is helping me. But In Firefox everything is working fine.
Note: You can drag the picker object in the outer circle. But if you leave it in red zones, you would not be able to click it again in Chrome. Clicking in green zone will get you its control again
Code in this JSFiddle
Edit
I excluded all irrelevant code to make it easy. Now there is only a container having two canvas.
Filled simply with two distinct colors. Open following fiddle link in both chrome and firefox. Click on both cirles in different zones and see difference in chrome and firefox. I want them to behave in chrome as they do in firefox
Note I will ultimately draw an image in inner canvas.
Updated Fiddle Link
-
Your problem is because canvases currently are always rectangular, even if they don't look rectangular. Border radius makes the edges except the circle transparent, but it still doesn't stop events in Chrome on the corner areas. This is why you cannot click the bottom circle in those areas
I even tried putting it inside of a container that had a border-radius instead but the click event still goes through
With that being said, you have two options. You could either change your code to only use one canvas with the same type of layout, just drawing the background circle before the other each time. Essentially you'd draw a circle, draw your black to color to white gradient, use the xor operation to combine the two into one circle, then do the same with the rainbox gradient. You must draw the background circle first because canvas paints over the old layers every time
or
You could use javascript to only detect clicks in the circular area which takes just a little bit of math (: This solution is featured in edit below
In the future, CSS Shapes may allow canvases to be non-rectangular elements to be used, I'm actually not sure, but we don't have that capability yet at least
Edit
Alright, so after going through your code a bit it seems there are some things I should cover before I offer a solution
Setup all your finite variables outside of the functions that run every time. This means you don't put them (like radiuses, offsets, etc.) in the click function or something that runs often since they don't change
Your "radius"es are actually "diameter"s. The format of .rect goes .rect(x, y, width (diameter of circle), height (diameter of circle))
Almost always when overlaying canvases like you are you want to make them equal dimensions and starting position to prevent calculation error. In the end it makes it easier, doing all relative positioning with javascript instead of mixing it with CSS. In this case, however, since you're using border-radius instead of arc to make a circle, keep it like it is but position it using javascript ....
jQuery isn't needed for something this simple. If you're worried about any load speed I'd recommend doing it in vanilla javascript, essentially just changing the .click() functions into .onclick functions, but I left jQuery for now
You can declare multiple variables in a row without declaring var each time by using the following format:
var name1 = value1,
name2 = value2;
Variables with the same value you can declare like so:
var name1 = name2 = sameValue;
When children have position:absolute and you want it to be positioned relative to the parent, the parent can have position:relative, position:fixed, or position:absolute. I would think you'd want position:relative in this case
When you don't declare var for a variable it becomes global (unlessed chained with a comma like above). For more on that read this question
Now, onto the solution.
After talking with a friend I realized I could sort do the math calculation a lot easier than I originally thought. We can just calculate the center of the circles and use their radiuses and some if statements to make sure the clicks are in the bounds.
Here's the demo
After everything is set up correctly, you can use the following to detect whether or not it's in the bounds of each
function clickHandler(e, r) {
var ex = e.pageX,
ey = e.pageY,
// Distance from click to center
l = Math.sqrt(Math.pow(cx - ex, 2) + Math.pow(cy - ey, 2));
if(l > r) { // If the distance is greater than the radius
if(r === LARGE_RADIUS) { // Outside of the large
// Do nothing
} else { // The corner area you were having a problem with
clickHandler(e, LARGE_RADIUS);
}
} else {
if(r === LARGE_RADIUS) { // Inside the large cirle
alert('Outer canvas clicked x:' + ex + ',y:' + ey);
} else { // Inside the small circle
alert('Inner canvas clicked x:' + ex + ',y:' + ey);
}
}
}
// Just call the function with the appropriate radius on click
$(img_canvas).click(function(e) { clickHandler(e, SMALL_RADIUS); });
$(wheel_canvas).click(function(e) { clickHandler(e, LARGE_RADIUS); });
Hopefully the comments above and code make enough sense, I tried to clean it up as best as I could. If you have any questions don't hesitate to ask!

Gnuplot canvas: tics disappear on zoom

I have been working for a while on a web interface related to my work. I have some data that I fit with a model iteratively. In every iteration I create a Gnuplot figure that goes online to help me keep track of the process. It works very well, however, I have some minor problems related to the canvas terminal that I couldn't fix for a long time. I use Gnuplot 4.6.5 and several browsers. They all behave consistently.
Here is an example of such a page, with two figures:
http://stelweb.asu.cas.cz/~nemeth/xtgrid_log/xtgrid_008_r1458384bfCsdd_dat.html
I have a struggle with the x-axis tics and resolution, and I think these problems may be related:
1 - x-axis coordinate below the figure does not show decimals. I think the x coordinate is truncated by Gnuplot.
2 - Axis tics disappear when we zoom in.
3 - I have some labels defined with: "set label at x,screen 0.2 '...some label...' "
These labels disappear when zooming in, even though they are set to the screen y coordinate (0.2).
4 - Zoom resets in figure 1 when mouse is moved over to figure 2 and back to 1 again. This might be a html/java issue.
A Gnuplot script to generate these figures looks like this:
set encoding iso_8859_1
set terminal canvas solid butt size 1024,410 name figure1 mousing lw 0.7
set output "figure1.js"
set samples 50, 50
set size ratio 0.4
set xtics autofreq 200
set mxtics 4
set xr [4000:5000]
set ylab "Relative flux"
set xlab "Wavelength (\305)"
set format y "%1.1e"
load 'lines.gnp' # just a bunch of labels
plot " ... some file ... "
I wonder if there is a general fix to these inconveniences.
To problem #4:
The Gnuplot generated javascript starts like this:
function ALue310128bfCsdd_dat_7_0()
{
canvas = document.getElementById("ALue310128bfCsdd_dat_7_0");
ctx = canvas.getContext("2d");
// Reinitialize mouse tracking and zoom for this particular plot
if ((typeof(gnuplot.active_plot) == "undefined" || gnuplot.active_plot != ALue310128bfCsdd_dat_7_0) && typeof(gnuplot.mouse_update) != "undefined")
{
gnuplot.active_plot_name = "ALue310128bfCsdd_dat_7_0";
gnuplot.active_plot = ALue310128bfCsdd_dat_7_0;
canvas.onmousemove = gnuplot.mouse_update;
canvas.onmouseup = gnuplot.zoom_in;
canvas.onmousedown = gnuplot.saveclick;
canvas.onkeypress = gnuplot.do_hotkey;
if (canvas.attachEvent) {canvas.attachEvent('mouseover', ALue310128bfCsdd_dat_7_0);
}
else if (canvas.addEventListener)
{canvas.addEventListener('mouseover', ALue310128bfCsdd_dat_7_0, false);}
gnuplot.zoomed = false;
gnuplot.zoom_axis_width = 0;
gnuplot.zoom_in_progress = false;
gnuplot.polar_mode = false;
ctx.clearRect(0,0,1280,513);
}
The gnuplot.zoomed and gnuplot.zoom_in_progress variables seem like they have something to do with the reinitialization of the zoom level.
If you mean the coordinates of the mousebox: For e.g. wxt the format of these mouse coordinates is adaptable with set mouse format. That doesn't work for the canvas terminal, might be a bug.
After gnuplot created the image the axis tics become only lines, i.e. the axis tics aren't regenerated when zooming. Try e.g. to zoom in around a tic: this will become very large.
Same as 2.: The screen coordinates are recognized as such only within gnuplot. After being written to the file there are only absolute coordinates. If the label coordinates fall into the zoom area, the respective label is drawn, otherwise not.
This might be a bug and can also be observed on e.g. http://gnuplot.sourceforge.net/demo_canvas_5.0/finance.html
I think 1. and 4. could be bugs, 2. and 3. are limits of the interactivity currently provided, and probably also limitations of gnuplot as such to produce such images.
Maybe its worth for you testing gnuplot-JS (demo at http://gnuplot.respawned.com/). I haven't tried it, yet.

Making Javascript TileEngine Scrollable in Canvas

The basis for the code is from John E. Graham's blog http://johnegraham2.com/blog/2010/09/25/project-javascript-2d-tile-engine-with-html5-canvas-part-4-using-zones-for-further-optimization/
It works perfectly for drawing a screen's worth of tiles, but I cannot for the life of me figure out how to adjust it 1 row/column at a time based on pressing up, down, left, or right keys.
Here is an example with the transparency to help visualize the zones http://simplehotkey.com/Javascript/canvas.html (loading positions of 1,188 tiles but only draws a couple hundred to fill the screen) I had it loading an array with 70,000 entries and it was still quick because it's only drawing whats on the screen, but cannot figure out how to slide everything based on input...
I've come up with a couple ideas and am not sure what's the best way.
One screen worth of tiles is shown here:
tilesArray = [
0,0,0,0,0,0,0,1,2,9,6,0,0,7,0,0,1,0,
0,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,0,
0,9,9,9,9,9,9,9,9,9,9,9,9,9,9,0,9,0,
0,9,9,9,9,9,9,9,9,9,9,9,9,9,9,0,9,0,
0,9,9,9,9,9,9,0,7,2,0,0,0,0,0,1,2,0,
0,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,0,
0,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,0,
0,9,0,7,2,9,9,9,9,9,9,9,9,9,9,9,9,0,
0,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,0,
0,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
];
Where 0 is a wall tile (around perimeter), 9 a floor tile, 7 a door and a couple other random tiles.
That is exactly what is loaded to the screen, but I cannot figure out how to shift everything 1 tile in either direction based on input, up, down, left, right.
The one idea I'm leaning towards now, is just to use that array above as the basis for rendering, and somehow feeding the new values into it based on keyboard input, from another array. Maybe slicing from another, much larger array (holding all the tiles for the entire level) and using that slice to populate the array that's actually rendered???
That's replacing every tile every frame though...
for getting player input I was using:
//Key listener
document.onkeydown = function(e){
e = e?e:window.event;
console.log(e.keyCode + "Down");
switch (e.keyCode){
case 38:
//UP KEY
Game.inputReaction('up');
//Game.moveDir('up');
break;
case 40:
//DOWN KEY
//Game.inputReaction(40);
//Game.moveDir('down');
break;
case 37:
//Left Key
//Game.inputReaction(37);
break;
}
}
The other alternative is to try to adjust the tiles already on the screen and add new tiles but this engine isn't using global variables so I'm not sure how to affect the tile engine programatically based on input....like I can add another method (inputReaction(num)) and trigger some actions from my keyboard input (console.log()) but I can't access the other methods actually drawing the tiles. Or maybe I have to make a copy of the object, change it and return it? but it's pretty complex.
I think it might be easier to adjust the array values that are being fed into the "engine" (array above) rather than changing around how the engine is calculating what's being drawn. Can you confirm this?
Add a camera abstraction that you can move around on the map, then shift the drawing positions according to the camera position. When the camera moves south 10px, all tiles move north 10px, same with east and west. Since you only draw the tiles that are visible, there won't be much of a performance loss.
The renderer looks at the camera to figure out what needs to be drawn and you can expose the camera object to the outside to manipulate it. That way you only need to change the camera position to change what is shown on the screen.
I did this in a proof of concept tiling engine a year ago and I was able to smoothly scroll and scale huge tilemaps.
If you start changing the array itself, your performance will suffer and you won't be able to scroll smoothly since you can only go in steps of one tile and not one pixel.

How can this object be appended to a specific tile on an isometric map?

I have an isometric map that has selectable tiles. The map is formed by a matrix such as $('div').gameMap({map:[[{"tile":"tile_0","object":"anObjectOnTile"},{"tile":"tile_3","object":""}], [{...}]],mapsize:3}); If the tile has something within the object:"", then that tile has that object (in my case, an image of an oil derrick) on top of that tile.
There is a Place button which when clicked should place an oil derrick object on top of the currently selected tile.
However, I have run into two problems:
Knowing which tile is currently selected
Adding a single oil derrick
At first I wanted to figure a way to modify the map's matrix to insert the object's name (oilDerrick) into the tile's object parameter, but now I think the better solution would be to append the oilDerrick to the tile. I can't figure out how to append to the particular tile and not every tile.
I've got the code set up to look at and play with: http://jsfiddle.net/briz/jdhPW/13/
There's an oilDerrick object set up in the map's matrix for you to view, which can be removed by deleting the word oilDerrick in the matrix
I played with $(".tile").append($('.oilDerrick')); though I knew that would append to every tile. But I simply do not know enough about appending yet to figure out how to limit it to a single instance.
Each tile has a unique ID. I also tried this in order to add an oilDerrick to a certain tile:
$("#placeButton").click(function()
{
for (var y in config.map)
{
for (var x in config.map[y])
{
document.getElementById(obj.attr('id') + '_tile_' + x + '_' +y).appendChild(".oilDerrick");
}
}
}
But this came to no avail as well. I think I'm heading in the right direction though? Can anyone help?
I did some modifications.
There a little mess in your code. For example, your using jquery but there is a lot of direct DOM manipulation.
.style.background = ...
// should be
.css("background",...)
And the most important:
document.getElementById(...)
// should be
$("#...")
Easier, don't you think? Then, the problem is simple solved by creating a var currentTile and when the user select one tile, it save it. When click in 'place', $(currentTile).append($('.oilDerrick')); And the oil derrick move to where to selected tile.
Anyway, more details you can see in the link.

Categories