Fiddle!!
I have set this fiddle up to show what parameters are going into the setViewBox() function. The thing is, the way it is working makes no sense to me.
Why does setViewBox(0, 0, 625, 625) result in a larger box / further zoomed in than setViewBox(0, 0, 1250, 1250)?
Also, why does setViewBox(-100, 0, 625, 625); adjust the image to the right (I would expect it to go left because of the negative x value)?
This is what the docs say about setViewBox(x, y, w, h):
parameters:
x - new x position, default is 0
y - new y position, default is 0
w - new width of the canvas
h - new height of the canvas
Also, I am trying to figure out the relationship between the first(x) / third(w) and the second(y) / fourth(h) parameters such that the box zooms in and out from the middle, instead of expanding from the top left corner, if anyone has any suggestions.
The answer is that the parameters passed to setViewBox are basically telling the size of the window you're looking through at the object. If the window is smaller, you see less of the object, or the object zoomed in. If you move the window left, you'll see the object move right, relative to the window. The key is to think of it as a window you're looking through, not the size of the object itself.
this is an example showing how to calculate the setViewBox values, you have to include jquery (to get SVG cocntainer X and Y ) :
var original_width = 777;
var original_height = 667;
var zoom_width = map_width*100/original_width/100;
var zoom_height = map_height*100/original_height/100;
if(zoom_width<=zoom_height)
zoom = zoom_width;
else
zoom = zoom_height;
rsr.setViewBox($("#"+map_name).offset().left, $("#"+map_name).offset().top, (map_width/zoom), (map_height/zoom));
Related
I receive X,Y coordinates from an API call. They look like this:
x: "-0.0120956897735595703"
y: "0.147876381874084473"
I've set the display on each of these minimap images to be absolute. I'm setting the "left" and "top" properties to be the X and Y coordinates like so: style={{left: player.x, top: player.y}} but it seems like the numbers are too small to do anything. Right now all the images are displayed in the top left corner because the x and y coordinates are too small.
My question is, what type of coordinates are these in my API call and how do I convert them to CSS friendly values for "Top and "Left" so that they are accurate displayed on my minimap?
Code of my component for reference:
const Minimap = (props) => {
const { players } = props.data.data
return (
<div style={mapStyle} className="realMinimapContainer">
{players.map(player => {
const heroName = localizedList[player.hero_id].replace('npc_dota_hero_', '');
return (
<img
style={{left: player.x, top: player.y}}
className="mapIcon" src={
player.hero_id === 126 || player.hero_id === 128 ?
newHeroes[player.hero_id] :
`http://cdn.dota2.com/apps/dota2/images/heroes/${heroName}_icon.png`
}></img>
)
})}
</div>
)
}
After following the advice of #FelipeMateusMalara below, it seems like I got close, but the images are quite a bit off.
Here you can see where the images are, I drew a red arrow showing where they should be:
It looks like the coordinates you received from the API call are normalized coordinates, so they are between -1 to 1, which the negative number of the x-coordinate indicates. The coordinate origin of the API coordinate system seems to be in the middle while your display coordinate system has its origin in the top left corner. You have to multiply the coordinates with the size of your display and shift them accordingly to your display origin:
player.x = (API.x + 1) * display.width
player.y = (API.y + 1) * display.height
You could just multiply these numbers by 100 and add 50 to each, this would result in the correct position of them in the map, and the result of the x expression you would set the left css directive, and the result of the y expression you would set the bottom css directive, both as percentage.
Why's that?
It's because your origin for both of these directives is the 50% mark and not the 0, so, you just have to transform the numbers given in percentages (multiplying by 100) and add the 50% missing
There is no way to tell from the information you've provided. I can guess that these numbers are deltas and not an offset (that you could just multiple by the width/height - since one of the numbers you've shown in negative). I'd suggest that you look in the debugger (what tools are you using?) and see if there is not another x/y coordinate provided. Or possible You could just keep updating a global variable with your delta coordinates however I doubt that is the direction you want to go.
I want to move the object in my case its a plane along the shown curve on page scroll step by step taking into consideration the amount of scroll value.firstly the object moves in straight line and then after a point it changes its direction and move in that direction.How to calculate those co-ordinates?
There are two ways you could get this one. I'll try to explain both in detail.
Scenario 1: Simple path like in the question.
Scenario 2: Arbitrary complex path.
Scenario 1:
In this case you can use a simple formula. Let's go with y = -x^2. This will yield a parabola, which has a similar shape as the path in the question. Here are the steps for what to do next (we assume your scrolling element is the body tag and I assume you have jquery):
Get the "y" value of the body using the following code:
var y = $("body").scrollTop();
Plug this value into the formula. I will give 3 examples where y is 0, 100 and 225 respectively.
//y=0
0 = -x^2
-x = sqrt(0)
x = +/- 0
So if we scroll and we are at the top of the page, then x will be zero.
//y=100
100 = -x^2
-x = sqrt(100)
x = +/- 10
The equation yieldsx as either positive of negative x but we only want positive so be sure to Math.abs() the result.
//y=225
225= -x^2
-x = sqrt(225)
x = +/- 15
From this you can see that the further we scroll down the more the object moves to the right.
Now set the "left" css of your object to the calculated value. This should be enough for this method.
Scenario 2
For more complex paths (or even random paths) you should rather put the x-values into an array ahead of time. Lets say you generate the array randomly and you end up with the following x-values:
var xvals = [0, 0.5, 1, 0.5, 0];
We use normalized x-values so that we can later calculate the position independent from screen size. This particular series of values will cause the object to zig-zag across the screen from left to right, then back to left.
The next step is to determine where our scroll position is at relative to the total scroll possibility. Lets say our page is 1000px in height. So if the scoll position is at zero then x = 0. If scroll = 500 then x = screenWidth. If scroll = 250 then x = 0.5 * screenWidth etc.
In the example I won't multiply with screen width for the sake of simplicity. But given the x value this should be simple.
The first thing you might want to get ready now is a lerping function. There is plenty of example code and so on so I trust that part to you. Basically it is a function that looks like this:
function lerp(from, to, prog);
Where from and to are any values imaginable and prog is a value between 0 and 1. If from is 100 and to is 200, a prog value of 0.5 will yield a return of 150.
So from here we proceed:
Get the scroll value as a normalized value
// scrollval = 200
var totalScroll = 1000;
var normScroll = scrollval/totalScroll; // answer is 0.2
Before we get to lerp we first need to get the x-values to lerp from and to. To do this we have to do a sort of lerping to get the correct index for the xvals array:
// normScroll = 0.2
var len = xvals.length; // 5
var indexMax = Math.ceil((len-1) * normScroll); // index = 1
var indexMin = Math.floor((len-1) * normScroll); // index = 0
Now we know the 2 x values to lerp between. They are xvals[0] which is 0, and xvals[1] which is 0.5;
But this is still not enough information. We also need the exact lerping "prog" value:
// We continue from the x indeces
scrollRange.x = totalScroll / len * indexMin; // 0
scrollRange.y = totalScroll / len * indexMax; // 250
var lerpingvalue = (scrollVal - scrollRange.x) / (scrollRange.y - scrollRange.x);// 0.8
Now we finally have everything we need. Now we know we need a value between xvals[0] and xvals[1] and that this value lies at 80% between these two values.
So finally we lerp:
var finalX = lerp(xvals[0], xvals[1], lerpingvalue);// 0.4
Now we know that the x coordinate is at 0.4 of the total screen size.
Trigger these calculations on each scroll event and you should be on your way.
I hope this was clear enough. If you try and try and can't get results and can show how hard you tried then I'll be happy to write a complete index.html sample for you to work from. Good luck!
EDIT: I made a mistake with the lerpingval calculation. Fixed now.
I'm performing drawing operations on canvas. The best way to calculate cursor position relative to canvase top left corner is, in my opinion, usage of .getBoundingClientRect():
HTMLCanvasElement.prototype.relativeCoords = function(event) {
var x,y;
//This is the current screen rectangle of canvas
var rect = this.getBoundingClientRect();
//Recalculate mouse offsets to relative offsets
x = event.clientX - rect.x;
y = event.clientY - rect.y;
//Debug
console.log("x(",x,") = event.clientX(",event.clientX,") - rect.x(",rect.x,")");
//Return as array
return [x,y];
}
I see nothing wrong with this code and it works in firefox. Test it.
In google chrome however, my debug line prints this:
x(NaN) = event.clientX(166) - rect.x(undefined)
What am I doing wrong? Is this not according to the specifications?
Edit: seems my code follows W3C:
From the specs:
getBoundingClientRect()
The getBoundingClientRect() method, when invoked, must return the
result of the following algorithm:
Let list be the result of invoking getClientRects() on the same element this method was invoked on.
If the list is empty return a DOMRect object whose x, y, width and height members are zero.
Otherwise, return a DOMRect object describing the smallest rectangle that includes the first rectangle in list and all of the
remaining rectangles of which the height or width is not zero.
DOMRect
interface DOMRect : DOMRectReadOnly {
inherit attribute unrestricted double x;
inherit attribute unrestricted double y;
inherit attribute unrestricted double width;
inherit attribute unrestricted double height;
};
The object returned by getBoundingClientRect() may have x and y properties in some browsers, but not all. It always has left, top, right, and bottom properties.
I recommend using the MDN docs instead of any W3C specs when you want to know what browsers actually implement. See the MDN docs for getBoundingClientRect() for more accurate information on this function.
So all you need to do is change your rect.x and rect.y to rect.left and rect.top.
I'm trying to get an image to flow horizontally in a sinusoidal fashion, and repeat seamlessly when it gets to the end of its own width in relation to its canvas size.
So far, I've got the image repeating and waving, but there is a significant jump when the x axis needs to be reset.
I think the problem is here:
if (x > canvasX) {
console.log('reset!!');
x = canvasX-imgW;
y = sineY(x);
}
//draw aditional image
if (x > (canvasX-imgW)) {
var ax = x-imgW+dx;
y = sineY(ax);
ctx.drawImage(img,ax,y,imgW,imgH);
}
Ultimately, what happens is that the sineY of the reset x value is about 19 degrees off of what it should be at the end of its regular period where the x value is highest. However, I can't really figure out how to adjust the bounds to make the movement seamless through the multiple periods.
Here's the fiddle: http://jsfiddle.net/3L7Dp/
The period variable needs to be normalized based on the total distance x will move.
In this case x will go image.width so period must be:
var period = x / imgW; //period must be a value in the range [0.0, 1.0]
This should give an usable value for cycling the image.
Modified fiddle
Hope this helps!
One way is to declare an offset by which x will be adjusted, such as var xOffset = 0. When calculating the sine, use x + xOffset. Every time you do x -= imgW, update the offset based on the current offset and the image width, so that the sin at the new position will equal the sin at the current position.
Doing this will allow you to have any period, even one unrelated to the width of your image.
I made my own version of your page with many simplifications, you can see it in this JsFiddle. The sine wave is seamless. My implementation also supports images much narrower than the canvas--they will be repeated all the way across, always filling the canvas (try img.width = 100 in my JsFiddle to see what I mean). In my function, since I based the period on a certain number of x-pixels, my xOffset recalculation is simplified and I can simply use modulus to calculate the new offset after subtracting from x.
Some style considerations I would like to suggest are:
Use more consistent variable names (such as context vs. ctx--if both are truly needed, give them prefixes such as baseContext, canvasContext so that context is consistent throughout the code).
Name variables closer to what they represent (for example, canvasX is not a good variable name for canvas.Width.
Don't be afraid of slightly longer variable names. imgW is less clear than imageWidth. W doesn't always mean width.
Put spaces after commas and the word function, and around operators.
Using parameter x in your sineY function is confusing as x is already declared outside.
Parameterizing your animation function is fine, but just as good is to wrap the entire script in a SEAF (self-executing anonymous function), as that properly gives all the variables a scope (keeping them out of global scope), thus simplifying your code by not having to pass around the variables.
When using the .toImage() method in KineticJS, I always seem to get a much larger image than is really necessary, with the piece I need taking up only the top left corner of the data image. My stage is scaled based on window size and a pre-defined initial size (on window resize, resize stage function called which sets the scale and the size of the container). Should I be setting some sort of crop when I use toImage() to compensate for this? Looking at the image, it seems that the overall image is about twice the size it needs to be, and the piece I need is about half the size I need, when the scale is at around 0.5 (the stage is about half size because I use Chrome and leave the developer bar open at the bottom for debugging).
Here's what I'm using now:
desc.toImage({
width: sideW / cvsObj.scale,
height: sideH / cvsObj.scale,
callback: function(img) {
desc.hide();
sideImg.transitionTo({x : sideW / 2, width : 0, duration : 0.25, callback : function() {
// add image to emulate content
var params = {name : 'descimg', width : sideW, height : sideH, image : img, x : sideW / 2, y : 0};
var image = new Kinetic.Image(params);
side.add(image);
image.setWidth(1);
sideImg.hide();
image.transitionTo({x : 0, width : sideW, duration : 0.25, callback : function() {
side.add(desc);
desc.show();
image.hide();
cvsObj.page.draw();
}});
}});
}
});
There have been improvements to KineticJs over time and functions work in a 'better' way nowadays. Try the new KineticJS 4.3:
http://d3lp1msu2r81bx.cloudfront.net/kjs/js/lib/kinetic-v4.3.0.min.js
I posted this question two and a half months ago, and received no replies until today. I've kept up-to-date with KineticJS - even adding my own bug reports and code suggestions. However, recently I revisited this particular section of code and came up with something a bit better - the image I'm getting back is now properly cropped and can be inserted as is into my canvas layers. Here's the code:
// grp = kinetic group, iw = image width, ih = image height, rimg = returned image array for a deferred function, cvsobj.scale is a global stage scale variable (i scale the stage to fit the window)
getImage : function(grp, iw, ih, rimg) {
var dfr = $.Deferred();
var gp = grp.getAbsolutePosition();
grp.toImage({
width: iw * cvsObj.scale,
height: ih * cvsObj.scale,
x : gp.x,
y : gp.y,
callback: function(img) {
rimg.push(img);
dfr.resolve(rimg);
}
});
return dfr.promise();
}
This is an extended prototype function - part of a sub-section functionality for converting an entire layer or section of a layer into an image specifically for manipulating in an animation sequence. Hopefully this helps someone else.