I have a graph rendered within the HTML5 canvas. The working is good till this point. Now I need to implement pinch zoom on the graph for touch devices. The logic is as the two finger stretches apart the graph zooms in and as the finger moves together the graph zooms out. In this case we need to constantly update the axis value. The problem here is how do we get the individual X and Y axis value of both the fingers and then calculate the amount of zoom to be done. As for example, for zooming using mouse we can get the start X and Y value on mouse down and on mouse up we get the end X and Y axis value. Using this start and end value of X and Y axis the graph can be zoomed accordingly. The canvas should not zoom in/out. The zoom in can be infinite but the zoom out will be till the default plotting of the graph. Any idea or help would be really appreciable. I am not getting the proper calculation.
I have implemented it in the following way. Any suggestions are welcome.
First I have taken the screen co-ordinates of the two fingers touch on start and converted it to its corresponding view co-ordinates by calculating its distance from the topmost and leftmost position. After that I have calculated the scale and new co-ordinates for X-axis in the following way.
Let
d1 and d2 be the dataspace coordinates of the initial/starting touches.
newx1 and newx2 are the x positions of the new touches.
screenW is the current screen width (i.e. width of plot in screen space).
Then
scale = (d2 - d1) / (newx2 - newx1)
If we use newd1, newd2 to denote the new datarange min and max values that we're trying to compute:
newd1 = d1 - newx1 * scale
newd2 = newd1 + screenW * scale
Similarly, we can do the calculation for new datarange min and max values of Y-axis.
Variant A:
Take a JavaScript library like Hammer.js which abstracts away all then event handling and gives you an event in case of a pinch. This should look like this:
var element = document.getElementById('test_el');
var hammertime = Hammer(element).on("pinchout", function(event) {
console.log("Zoom out!!");
// event.scale should contain the scaling factor for the zoom
});
Variant B:
Read about the touch events and how to identify if it is a multitouch. Figure out when it is a pinch and if how far the fingers have moved. There is nice write up here.
Related
I am working on this "simple" problem for hours with no success, although I tried many ways to solve it using all kind of solutions suggested in SO.
My problem is as follows:
I have a point on a canvas, which when I click on it my app does something, after identifying the point by comparing the mouse click coordinates to the stored position of the point.
After zooming into the point, using the mouse wheel, I click on the point again but the mouse coordinates no longer fits the stored position of the point.
I need to either transform the mouse coordinates to it's coordinates before the zoom, so I will be able to compare to the stored position, or to transform the stored position to the new canvas so it can be compare to the coordinates of the mouse. Any of the solution is fine by me.
I know the following data:
The "scale" value,
The size of the canvas (top, left, width, height),
The new origin of the canvas (top, left)
I would like a solution using java script.
Finally figured it out and it is quite simple, once I understood the concept.
Save the new canvas origin after doing the zoom (in JS it is calling ctx.translate() and ctx.scale(), where ctx is the canvas context.
When need to calculate the mouse position in the old coordinate system, one has to add back the moved origin of the canvas, and multiply by the scale factor.
seat.x = (-new_org.x + pos.x) / scale;
seat.y = (-new_org.y + pos.y) / scale;
where pos is the calculated mouse pointer
pos.x = event.clientX - .getBoundingClientRect().left;
pos.y = event.clientY - .getBoundingClientRect().top;
I'm trying to calculate the coordinates of a zoomed in image based on the pageX and pageY coordinates given to me by a PanResponder. I figured I would just have to add the offset of the X and Y to the coordinates given back to me but I seem to have misjudge what would have to happen.
The coordinates of the device screen regardless of scale seem to correlate to the same location on the zoomed element. So if I place something in the top left of the device screen it goes to that location on the zoomed in element.
I get my offset by measuring the zoomed element with RCTUIManager and it seems to give back the correct values for the X and Y offset based on other calculations I have tested.
This is the most recent iteration of the code I have attempted to use:
let {measureData} = this.state;
let offsetX = measureData.x;
let offsetY = measureData.y
let x = (e.nativeEvent.pageX) - offsetX;
let y = (e.nativeEvent.pageY) - offsetY;
I'm subtracting because the values I receive are negative, I also will allow them to pan this later so I had hoped it would work correctly if they moved it so the value was positive to subtract it.
measureData contains the values from the RCTUIManager which gets updated every time a zoom event occurs.
How I update measureData
setMeasureData = () => {
RCTUIManager.measure(ReactNative.findNodeHandle(this.refs['wrap']), (x, y, width, height, pageX, pageY) => {
this.setState({
measureData: {x, y, width, height, pageX, pageY}
})
});
}
This is called in a callback function after the zoom is changed.
The elements I'm trying to add are added using another component I feed the X Y values into, this component uses Animated.ValueXY() with the calculated coordinates to place the image in the correct starting location for them to be able to drag it after.
The current calculation makes anything placed go down and right of the touch event. I'm not sure where my logic or calculations are wrong with this. I'm hoping someone may have some insight into where my calculation or thought process is wrong.
Ok so my logic was correct and wrong all at the same time. The problem was when they zoom in the X Y values stay the same so I am looking at "less" pixels within the viewport meaning I had to divide the X Y coordinates from the native event by the scale then add the offset in order for me to get the correct x and y values.
I'm implementing some basic annotation draw features, such as arrows. Now I'm a little bit stuck with ellipse.
The methods to draw an ellipse usually address using it's two diameters and eventually a rotation:
However I want to display the ellipse between the point user clicked and the one he's hovering, therefore I need a function that calculates diameters and rotation based on two points:
How would I do that? Can it be achieved with sufficient performance (as it renders during mouse-hovering)?
the steps you shoul follow:
get the angle of the line (from this post: get angle of a line from horizon)
rotate the canvas or at least the part you currently drawing (live demo here: http://www.html5canvastutorials.com/advanced/html5-canvas-transform-rotate-tutorial)
draw an ellipse in canvas (http://www.scienceprimer.com/draw-oval-html5-canvas)
the resulted ellipse will be transformed as described
It can be done in the same way that it is normally done, just using different math to calculate the shape. Without writing the entire code for you, you can start by having an event trigger when the user clicks the mouse button down. The function will copy the users x and y position based on the screen. Then there is a second function which will handle mouse movement. This function will keep track of the x and y coords of the mouse while it is in motion. The final function will be a mouse up event, when a user lifts their finger from the mouse button (assuming this is when the event should be finished). Using the initial and final position of the x and y coordinates, you can calculate the length of the line the user created. That line is the long diameter of the ellipse. Half this number for the large radius. Then use whatever ratio you are using to calculate the smaller radius from the larger one. Then create an ellipse based on these numbers.
For the math: Suppose your first point is x1,y1 and the end point is x2,y2
I'm also assuming that we have a line going from bottom-left to top-right
Distance between two points = sqrt((x2-x1)^2 + (y2-y1)^2) ---> (we will call this d1)
half of this is the length of the large radius ---> (we will call this r1)
Midpoint formula = ((x1+x2)/2 , (y1+y2)/2) ---> axis of rotation (we will call it (m1, m2))
distance from midpoint to end is just the radius
radius is now the hypotenuse of constructed plane, y2-m2 is height of right triangle.
Find the angles between midpoint and one end of larger radius - sin((y2-m2)/r1).
Angle of smaller radius is this angle + pi/4 radians.
calculate length of smaller radius based on ratio.
I am using Panzoom to zoom and pan on a canvas where I have some points.
These points are clickable, and it works fine when not having zoomed the canvas (via Panzoom).
The zoom factor is 1 when unzoomed, 2 with 200% zoom etc..
I have made this function to calculate the coordinates when having panned - so you can pan around and click it, and the coordinates will always be relative. It's when zooming it's not working..
function getCanvasCoords(x,y){
var matrix = $panzoom.panzoom("getMatrix");
var calc_x = x-matrix[4];
var calc_y = y-matrix[5];
return {x:calc_x,y:calc_y};
}
Try a working sample here: http://jsfiddle.net/hugef0c7/
Click on the yellow square to get the clicked coordinates. Then zoom in and click on it again; now the coordinates are different because of the zooming.
Is there any way I can calculate the clicked coordinate when zoomed?
I have tried things like multiplying/dividing the clicked point with the zoom factor, but that doesn't help much..
First step is to correct the offset of the coordinates in order to make them relative to canvas. You can do this by simply subtracting the absolute position of canvas to the client coordinates in this function:
$(document).mouseup(function(e) {
// get canvas rectangle with absolute position of element
var rect = myCanvas.getBoundingClientRect();
// subtract position from the global coordinates
var coords = getCanvasCoords(e.clientX - rect.left,
e.clientY - rect.top);
...
Now all you need to do is to inverse the zoom factor (1 / factor) to get back to the intended coordinate, as well as using the scale values in the matrix (a and d, or 0 and 3 in index terms):
function getCanvasCoords(x,y){
var matrix = $panzoom.panzoom("getMatrix");
return {
x: x * (1 / matrix[0]), // multiply with inverse zoom factor
y: y * (1 / matrix[3])
};
}
That should do the job.
Modified fiddle
Hope this helps!
I've searched for the answer to this and have tried many proposed solutions, but none seem to work. I've been struggling with this forever so any insight is greatly appreciated.
I have 3 shapes (vectors I suppose) on a JS canvas, each with an orientation represented as degrees off of 0 and a width. I need to drag one of these shapes "straight out" from its orientation. This is difficult to explain in words so please view the graphic I created:
The middle (diagonal) shape is at 45 degrees. It's origin is the red dot, (x1,y1). The user drags the shape and their mouse lies at the green dot, (x2,y2). Since the shape's origin is in the lower left, I need to position the shape at the position of the lighter blue shape as if the user has dragged straight outward from the shape's origin.
I don't think it matters, but the library I'm using to do this is KineticJS. Here's the code and some information I have available which may help solve the problem. This code positions the shape on top of the mouse, which isn't what I want:
var rotationDeg = this.model.get("DisplayOri"), // rotation in degrees
rotationRadians = rotationDeg * Math.PI / 180, // rotation in rads
unchanged = this.content.getAbsolutePosition(), // {x,y} of the shape before any dragging
dragBoundFunc = function (changed) {
// called on a mouseMove event, so changed is always different and is the x,y of mouse on stage
var delta = {
x: changed.x - unchanged.x,
y: changed.y - unchanged.y
};
return changed; // go to the mouse position
};
[edit] I should mention that the obvious of "return delta" doesn't work.
It sounds like you want to constrain the movement of the object.
Determine the vector representing the constraint axis : that is, we only want motion to occur along this line. It appears from your drawing that this is in the direction of the short line from the red dot out to the left. That vector has a direction of -1/m where m is the slope of the line we are moving.
Constrain the movement. The movement is represented by the mouse move delta - but we only want the portion of that movement in the direction of the constraint axis. This is done with a dot product of the two vectors.
So in pseudo code
m = (line.y2 - line.y1)/(line.x2 - line.x1)
constraintSlope = -1/m
contraintVector = {1, constraintSlope} //unit vector in that direction
userMove = {x2-x1, y2-y1} //vector of mouse move direction
projection = userMove.x * constraintVector.x + userMove.y * constraintVector.y
translation = projection * constraintVector //scaled vector