Is there someway to get the visible area of the panel when zooming. I
have a force directed graph and I am interested in obtaining all the
elements that are in the visible area after a zoom event.
Any suggestions?
Thanks
You can access the current transformation matrix of a panel via the transform parameter. So, in the following example:
var vis = new pv.Panel()
.width(200)
.height(200);
var panel = vis.add(pv.Panel)
.event("mousewheel", pv.Behavior.zoom(1))
.fillStyle('#ccc');
var dot = panel.add(pv.Dot)
.data([[25,25],[25,75],[75,25],[75,75]])
.top(function(d) d[0])
.left(function(d) d[1])
.size(30)
.fillStyle('#999');
vis.render();
If you load this example, then zoom around a bit, you can access the current transformation matrix like this:
var t = panel.transform(),
tk = t.k, // scale factor, applied before x/y
tx = t.x, // x-offset
ty = t.y; // y-offset
You should be able to determine whether a child mark (e.g., in this example, dot) is in the visible area by applying the transformation matrix to its top and left parameters and then checking to see whether they're within the panel's original bounding box (0,0,200,200). For the dot above, you could check like this:
function(d) {
var t = panel.transform(),
// assuming the current dot instance is accessible as "this"
x = (this.left() + t.x) * t.k, // apply transform to dot x position
y = (this.top() + t.y) * t.k; // apply transform to dot y position
// check bounding box. Note that this is a little simplistic -
// I'm just checking dot center, not edges
return x > panel.left() && x < (panel.left() + panel.width()) &&
y > panel.top() && y < (panel.top() + panel.height());
}
Related
I am currently developing a game, which requires a map consisting of various tile images. I managed to make them display correctly (see second image) but I am now unsure of how to calculate the clicked tile from the mouse position.
Are there any existing libraries for this purpose?
Please also note, that the tile images aren't drawn perfectly "corner-facing-camera", they are slightly rotated clockwise.
Isometric Transformations
Define a projection
Isometric display is the same as standard display, the only thing that has changed is the direction of the x and y axis. Normally the x axis is defined as (1,0) one unit across and zero down and the y axis is (0,1) zero units across and one down. For isometric (strictly speaking your image is a dimetric projection) you will have something like x axis (0.5,1) and y axis (-1,0.5)
The Matrix
From this you can create a rendering matrix with 6 values Two each for both axes and two for the origin, which I will ignore for now (the origin) and just use the 4 for the axis and assume that the origin is always at 0,0
var dimetricMatrix = [0.5,1.0,-1,0.5]; // x and y axis
Matrix transformation
From that you can get a point on the display that matches a given isometric coordinate. Lets say the blocks are 200 by 200 pixels and that you address each block by the block x and y. Thus the block in the bottom of your image is at x = 2 and y = 1 (the first top block is x = 0, y = 0)
Using the matrix we can get the pixel location of the block
var blockW = 200;
var blockH = 200;
var locX = 2;
var locY = 1;
function getLoc(x,y){
var xx,yy; // intermediate results
var m = dimetricMatrix; // short cut to make code readable
x *= blockW; // scale up
y *= blockH;
// now move along the projection x axis
xx = x * m[0];
yy = x * m[1];
// then add the distance along the y axis
xx += y * m[2];
yy += y * m[3];
return {x : xx, y : yy};
}
Befoer I move on you can see that I have scaled the x and y by the block size. We can simplify the above code and include the scale 200,200 in the matrix
var xAxis = [0.5, 1.0];
var yAxis = [-1, 0.5];
var blockW = 200;
var blockH = 200;
// now create the matrix and scale the x and y axis
var dimetricMatrix = [
xAxis[0] * blockW,
xAxis[1] * blockW,
yAxis[0] * blockH,
yAxis[1] * blockH,
]; // x and y axis
The matrix holds the scale in the x and y axis so that the two numbers for x axis tell us the direction and length of a transformed unit.
Simplify function
And redo the getLoc function for speed and efficiency
function transformPoint(point,matrix,result){
if(result === undefined){
result = {};
}
// now move along the projection x axis
result.x = point.x * matrix[0] + point.y * matrix[2];
result.y = point.x * matrix[1] + point.y * matrix[3];
return result;
}
So pass a point and get a transformed point back. The result argument allows you to pass an existing point and that saves having to allocate a new point if you are doing it often.
var point = {x : 2, y : 1};
var screen = transformPoint(point,dimetricMatrix);
// result is the screen location of the block
// next time
screen = transformPoint(point,dimetricMatrix,screen); // pass the screen obj
// to avoid those too
// GC hits that kill
// game frame rates
Inverting the Matrix
All that is handy but you need the reverse of what we just did. Luckily the way matrices work allows us to reverse the process by inverting the matrix.
function invertMatrix(matrix){
var m = matrix; // shortcut to make code readable
var rm = [0,0,0,0]; // resulting matrix
// get the cross product of the x and y axis. It is the area of the rectangle made by the
// two axis
var cross = m[0] * m[3] - m[1] * m[2]; // I call it the cross but most will call
// it the determinate (I think that cross
// product is more suited to geometry while
// determinate is for maths geeks)
rm[0] = m[3] / cross; // invert both axis and unscale (if cross is 1 then nothing)
rm[1] = -m[1] / cross;
rm[2] = -m[2] / cross;
rm[3] = m[0] / cross;
return rm;
}
Now we can invert our matrix
var dimetricMatrixInv = invertMatrix(dimetricMatrix); // get the invers
And now that we have the inverse matrix we can use the transform function to convert from a screen location to a block location
var screen = {x : 100, y : 200};
var blockLoc = transformPoint(screen, dimetricMatrixInv );
// result is the location of the block
The Matrix for rendering
For a bit of magic the transformation matrix dimetricMatrix can also be used by the 2D canvas, but you need to add the origin.
var m = dimetricMatrix;
ctx.setTransform(m[0], m[1], m[2], m[3], 0, 0); // assume origin at 0,0
Now you can draw a box around the block with
ctx.strokeRect(2,1,1,1); // 3rd by 2nd block 1 by 1 block wide.
The origin
I have left out the origin in all the above, I will leave that up to you to find as there is a trillion pages online about matrices as all 2D and 3D rendering use them and getting a good deep knowledge of them is important if you wish to get into computer visualization.
I am building an app using HTML5 in which a grid is drawn. I have some shapes on it that you can move.
What I'm trying to do is to snap objects to some points defined when you hover them while moving a shape.
What I tried is to save anchors points inside an array and when the shape is dropped, I draw the shape on the closest anchor point.
Easeljs is my main js lib so I want to keep it but if needed I can use an other one with easeljs.
Thanks in advance for your help!
This is pretty straightforward:
Loop over each point, and get the distance to the mouse
If the item is closer than the others, set the object to its position
Otherwise snap to the mouse instead
Here is a quick sample with the latest EaselJS: http://jsfiddle.net/lannymcnie/qk1gs3xt/
The distance check looks like this:
// Determine the distance from the mouse position to the point
var diffX = Math.abs(event.stageX - p.x);
var diffY = Math.abs(event.stageY - p.y);
var d = Math.sqrt(diffX*diffX + diffY*diffY);
// If the current point is closeEnough and the closest (so far)
// Then choose it to snap to.
var closest = (d<snapDistance && (dist == null || d < dist));
if (closest) {
neighbour = p;
}
And the snap is super simple:
// If there is a close neighbour, snap to it.
if (neighbour) {
s.x = neighbour.x;
s.y = neighbour.y;
// Otherwise snap to the mouse
} else {
s.x = event.stageX;
s.y = event.stageY;
}
Hope that helps!
I am creating a rectangle element which I want to later use as a tooltip on an SVG circle element. Right now, I am trying to think of some logic to set the height and width of the rectangle element which should change according to the lines of text it contains.. I am simply creating a Raphael text element and translating it on the rectangle so that it appears as if the rectangle 'really' contains the text. (This is because I am not supposed to use any third-party tooltip plugin, neither can I create a and modify it using jQuery. Neither can I use gRaphael. I am supposed to use only Raphael rectangle element as the tooltip.)
Here's what I am doing -
var tipText = "Asoo ok m ml palksksk feesf k\nWeek:X-VAL\nRank:Y-VAL";
//splitting tipText for "\n"
var tipText_seperate = tipText.split("\n");
var tipText_seperate_len = tipText_seperate.length;
var line_len = [];
for(var i=0;i<tipText_seperate_len;i++){
line_len[i] = tipText_seperate[i].length;
}
var a = Math.max.apply(Math, line_len);//getting the length of largest line
//setting the width and height of the rectangle
var box = {};
box.width = (a*5)+5;
box.height = tipText_seperate_len*25;
var c = {};
c.x = 10;
c.y = 10;
c.r = paper.rect(c.x,c.y,box.width,box.height,5).attr({fill:'#000000', opacity:0.7});
c.t = paper.text(c.x,c.y,tipText).attr({fill:'#FFFFFF'});
c.t.transform("t"+box.width/2+","+box.height/2);
Now the size of rectangle gets adjusted for some lines of text, while for some it doesn't. In that case I have to change the value of box.width which does not seem correct. Is there any efficient and logically correct way to achieve this? Please Help...
The trick is to create the text element and use getBBox to get the bounding box of the text element, which provides the dimensions as well as x and y values.
Here's an example and demo.
// Create Raphael and circle set
var Paper = new Raphael('canvas', 300, 300),
circles = Paper.set();
// Add circles to canvas, setting the tooltip text as a
// data attribute
circles.push(
Paper.circle(100, 150, 25).data('tooltip', 'Here is some text'),
Paper.circle(200, 150, 25).data('tooltip', 'And here is \nsome longer text')
);
// Some base styles
circles.attr({
fill: 'red',
stroke: 0
});
// Positioning of the tooltip box
var margin = 10,
padding = 5;
// Hover functions
circles.hover(
// On hover, create and show tooltip
function() {
// If the tooltip already exists on the element, simply
// show it. If it doesn't then we need to create it.
if (this.tooltip && this.tooltip.box) {
this.tooltip.show();
this.tooltip.box.show();
} else {
// Get the x and y positions.
// We get the 'true y' by deducting the radius
var x = this.attr('cx'),
y = this.attr('cy') - this.attr('r');
// Create the tooltip text, attaching it to the
// circle itself
this.tooltip = Paper.text(x, y, this.data('tooltip'));
// Calculate the bounding box of our text element
var bounds = this.tooltip.getBBox();
// Shift the text element in to correct position
this.tooltip.attr({
// At this point `y` is equal to the top of the
// circle arc. When creating a text element, the
// `x` and `y` values are center points by default,
// so by deducting half the height we can fake
// a bottom align. Finally deducting our `margin`
// value creates the space between the circle and
// the tooltip.
y: y - (bounds.height / 2) - margin,
fill: '#fff'
});
// Create the tooltip box, again attaching it to the
// circle element.
//
// The `x`, `y` and dimensions are dynamically calculated
// using the text element's bounding box and margin/padding
// values.
//
// The `y` value again needs some special treatment,
// creating the fake bottom align by deducting half the
// text element's height. We then adjust the `y` further
// by deducting the sum of the `padding` and `margin`.
// The `margin` value is needed to create space between
// the circle and the tooltip, and the `padding` value
// shifts the box a little higher to create the illusion of
// padding.
//
// Try adjusting the `margin` and `padding` values.
this.tooltip.box = Paper.rect(bounds.x - padding, bounds.y - (bounds.height / 2) - (padding + margin), bounds.width + (padding * 2), bounds.height + (padding * 2));
// Style the box and put it behind text element
this.tooltip.box.attr({
fill: '#000',
stroke: 0
}).toBack();
}
},
// On hover out, hide previously created tooltip
function() {
// Hide the tooltip elements
this.tooltip.box.hide();
this.tooltip.hide();
}
);
I am trying to implement line sweep algorithm by using PURE Javascript (no other frameworks) that basically scans through the screen from left to right and looks all the elements (including overlapped elements) that share the same x coordinate.
For example
I have 6 div elements with black border, and they all layout randomly on the screen. for illustration purposes I am using a vertical dotted blue line to scan through across the plane from left to right. The goal is that report all the elements that the line passed over . For the example above, how do we report that Div A, Div E, Div D and also the hyperlink D within Div D by using JavaScript ?
You can get the position of elements with the getBoundingClientRect method. Then loop over them and check whether they match your scanning:
var all = document.body.getElementsByTagName("*");
var x = /* blue line */;
var match = [];
for (var i=0; i<all.length; i++) {
var rect = all[i].getBoundingClientRect();
if (rect.left < x && rect.right > x)
match.push(all[i]);
});
Shorter, functional way:
var match = Array.prototype.filter.call(document.body.querySelectorAll("*"), function(el) {
var rect = el.getBoundingClientRect();
return rect.left < x && rect.right > x;
});
If you need a quick-access function for often use, you would store all elements (with their coordinates) in a sorted data structure, a segment tree, where you can search for them.
Also, when it is guaranteed that child nodes of DOM elements do not exceed their parents boundaries, you can easily use the DOM itself as a search tree:
var x = /* the blue line */;
var match = function find(el, set) {
var rect = el.getBoundingClientRect();
if (rect.left < x && rect.right > x) {
set.push(el);
for (var i=0; i<el.children.length; i++)
find(el.children[i]);
}
return set;
}(document.body, []);
Gather all of the DOM elements
Find their positions and stash them in an array (so you can use the data again without going through the DOM)
Loop through the array and solve for x
I'm making a game in html5 canvas. I'm using jquery so I can get the click event and the clicks x,y coordinates. I have an array of unit objects and a tiled terrain (also an array). The unit objects have bounding box information, their position, and their type.
What is the most effecient way to map this click event to one of the units?
Loop through the unit objects and determine which was clicked like so:
// 'e' is the DOM event object
// 'c' is the canvas element
// 'units' is the array of unit objects
// (assuming each unit has x/y/width/height props)
var y = e.pageY,
x = e.pageX,
cOffset = $(c).offset(),
clickedUnit;
// Adjust x/y values so we get click position relative to canvas element
x = x - cOffset.top;
y = y - cOffset.left;
// Note, if the canvas element has borders/outlines/margins then you
// will need to take these into account too.
for (var i = -1, l = units.length, unit; ++i < l;) {
unit = units[i];
if (
y > unit.y && y < unit.y + unit.height &&
x > unit.x && x < unit.x + unit.width
) {
// Escape upon finding first matching unit
clickedUnit = unit;
break;
}
}
// Do something with `clickedUnit`
Note, this won't handle complex intersecting objects or z-index issues etc... just a starting point really.