Framer motion and transform matrix - javascript

I am making app with framer motion and I need to drag svg inside another svg but my problem is that viewbox size is not equal to window size so when I drag element my 1px movement of mouse on the screen is like 100+ px. I know in JavaScript we can calculate x and y with screenX, sceenY and CTM (current transform matrix). Is possible to make somehow framer motion drag function to calculate that?
<svg viewBox="0 0 40 20">
<motion.circle drag cx="5" cy="5" r="0.5"strokeWidth="0.1"/>
</svg>
P.S. I cannot change viewbox size and its 100% of width and height of screen and probably we need to transform current matrix with useTransform or useMotionValue hooks from framer motion but I am not sure how to do it.
this is current state of the app where you can see the problem when you try to drag player object. https://waterpolo.klaktech.com

I was having issues understanding properly the question, but I think this is what you were looking for.
There's a couple of catches:
I am capturing the with and height of each of the boxes to know if they are overlapping at the first useEffect.
The problem is that when the items change position the REF isn't updated. which means that we need and onDrag hook to update the X and Y positions of the boxes.
The second catch is that the X and Y from there are exposed from the mouse, which means that it's not the one of the origin of the object, so the overlap might not happen till the mouse actually enters the other object
I set this up so you can have a look at the code
https://codesandbox.io/s/paths-forked-0iqtdj?file=/src/Example.tsx

Related

zoom in on a particular area in a line chart (using d3 and svelte)

I am trying to zoom in on parts of a line chart using viewBox. I managed to get it working, however, not sure how to deal with the fact that everything scales accordingly (line path also becomes larger).
What I am looking for is for the line to stay constant while zoom is happening. I researched that there is also a way with translation and scale, but not sure how to implement it.
Also, not sure how to make this behavior more automatic. For example, I am zooming in on a range between two years: 2010 and 2011. Currently, I specify the y value manually. But ideally, this would be specified in using yScale function.
Any help would be appriciated!
Here is my repl: https://svelte.dev/repl/be0df7e353334108b3d432d5c82a6cd3?version=3.38.2
One way to accomplish this is by scaling the viewBox of the <svg> element.
You can do this by binding the x, y, width and height of the viewBox.
<svg ... viewBox="{x} {y} {width} {height}">
You can also tween the viewbox changes:
By using Svelte's tweened() store for x, y, width, height. ie <svg viewBox="{$x} {$y} {$width} {$height}">
Or by using SVG's <animate> element. ie <animate attributeName="viewBox" values="..." dur="2s"/>

zooming an image results in issues with pageX/pageY to SVG conversions

I am trying to move a SVG circle that sits in the center of an HTML image. If you mouse down on the image and drag the circle it works great.
However, if I zoom the image (tap on + button in the codepen), pageX and pageY to SVG co-ordinate translation messes up.
How should I be handling this correctly ? (My complete code handles both touch and mouse events for SVG - I've simplified it to just mouse for this example)
My codepen: http://codepen.io/pliablepixels/pen/EZxyRN
Here is how I am getting co-ordinates (please see codepen for a runnable example):
// map to SVG view so I can move the circle
function recompute(ax,ay)
{
// alert ("Here");
var svg=document.getElementById('zsvg');
var pt = svg.createSVGPoint();
pt.x = ax;
pt.y = ay;
var svgP = pt.matrixTransform(svg.getScreenCTM().inverse());
$scope.cx = Math.round(svgP.x);
$scope.cy = Math.round(svgP.y);
}
function moveit(event)
{
if (!isDrag) return;
var status = "Dragging with X="+event.pageX+" Y="+event.pageY;
$timeout (function(){$scope.status = status; recompute(event.pageX, event.pageY)});
}
The relevant SVG:
<svg id = "zsvg" class="zonelayer" viewBox="0 0 400 200" width="400" height="300" >
<circle id="c1" ng-attr-cx="{{cx}}" ng-attr-cy="{{cy}}" r="20" stroke="blue" fill="purple" />
</svg>
Firstly, you would normally use clientX and clientY rather than pageX and pageY.
Secondly, the Ionic(?) zoomTo() function you are using is applying a 3D transform to the container div. Ie.
style="transform: translate3d(-791.5px, -173px, 0px) scale(2);"
I don't expect getScreenCTM() handles 3D transforms. Even ones that are effectively 2D because they do nothing in the Z axis.
You'll need to either:
do the zoom a different way. IOW handle it yourself so you can do it in a getScreenCTM()-friendly way. Or multiply the zoom factor in directly. or
find a way of getting the details of the transform that Ionic has applied and updating your transformed mouse coords appropriately.
Update:
The definition of getScreenCTM() has changed in SVG 2. In SVG 1.1 it was fairly loosely defined. In SVG 2 the definition has been updated to explicitly define how the result is calculated. Although it does not specify how 3D transforms on ancestor elements should be handled.
I have experimented a little on Chrome and Firefox. It appears that Chrome has implemented the SVG2 definition, but it has a bug. It is not returning the correct transform matrix. However Firefox has not yet been updated. It is not including any transforms on ancestor elements.
I now believe the bug in Chrome is the reason your sample is not working there. However if you want to be cross-browser, my advice to handle zoom yourself - and adjust the transform (or coords) - still holds.

Calculate "rotated" position of children elements within a div (jquery)

I'm hoping someone can help me out with this, I'm using a jquery plugin (jquery rotate) to rotate some elements inside a container, however a new crazy requirement came up and now I also need to rotate the container div, the problem is the children elements can be dragged and dropped inside this container and when it is rotated dragging stops working and it starts acting weird (I guess it makes sense as the coordinates system is being altered by the rotation on the container so the dragging behavior just goes crazy) so the only way that I can see to keep dragging and dropping working is to "simulate" the parent rotation by somehow calculate what the position of the children elements would be if the parent is rotated (without actually rotating the parent container) and position each child element on its corresponding final position, this way the coordinates system does not get "broken" and the drag and drop of the children remains working as expected.
To clarify what I'm trying to achieve lets suppose the container is 400x400px and that it has a children at top=0 and left=0, I would like to perform a "fake" 180 degrees rotation on the container that would send the child element to top=400px and left=400px (which is where the child element would visually end up being if the parent is rotated), I'm hoping there is a library I can use to perform this math (math is not my thing though) on each children and somehow "calculate" or "determine" the position they should have within the container if it is rotated 180 degrees and then position each children where they should be (the container rotation will always be a 180 degrees rotation which means it will be either at a "normal" angle of 0 degrees or a "flipped" angle of 180 degrees).
Any help would be greatly appreciated.

Understanding coordinate scaling (and viewBox) in a PyQt/SVG/JavaScript project

I am working on a PyQt application that has an SVG viewer based on QWebKit. Initially the SVG file is generated by an external application (LilyPond), but we want to edit the SVG in the viewer using JavaScript.
Basically it's working pretty well, but we experience a strange rounding/conversion/rastering issue we don't have a clue about: When dragging an object, onmousemove outputs only rasterized values for clientX/clientY or pageX/pageY. That is we can see that the event fires a number of times, and (say) clientX produces a series of values like 100 100 100 100 101 101 101 101 102 102 102 102 etc., the number of repetition being dependent of the zoom factor of the view.
I assume these values are not screen pixels but numbers in a unit of the SVG document and this is the source of our issue.
The svg element of the file looks like this:
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.2" width="210.00mm" height="297.00mm" viewBox="0 0 119.5016 169.0094">(remember, this is generated from the external program).
I assume that the viewBox attribute is used to ensure that SVG coordinates are consistent with the coordinate system of the originating application (LilyPond thinks in "staff spaces", and obviously a translation of 1 in the SVG file does exactly the same as translating by 1 staff space in LilyPond. If I change the "global staff size" in LilyPond the viewBox attribute will get different values.)
So what I would basically need is that clientX(etc.) gives me floating point results instead of rounded integers.
Is there a way to make JavaScript do that?
Or would I have to do some calculations - somehow translating clientX (etc.) coordinates to screen pixels and then back again (taking zooming and/or hardware into account)?
I'm at a loss when trying to understand the different scaling options at the different stages...
EDIT
Here's some more code...
As #uli_1973 mentioned, mouse position is sustained by the clientX/-Y property:
svgPoint.x = event.clientX;
svgPoint.y = event.clientY;
To get the mouse position translated to the coordinates of the SVG we use this line of code:
svgPoint = svgPoint.matrixTransform(svg.getScreenCTM().inverse());
I guess the main question is, could we get a higher resolution out of this somehow? Currently the smallest possible difference in mouse movement translates into 0.15 in SVG coordinates (mm).
EDIT2
The whole file can be seen at
https://github.com/PeterBjuhr/frescobaldi/blob/svg-edit/frescobaldi_app/svgview/editsvg.js
The implementation of the dragging can be found in the three event handlers at the bottom of the file.
Initially (onmousedown) the constructor function DraggableObject(elem, event) (currently starting at line 219) is called. onmousemove the event object is passed to the updatePositions(e) method in line 319.
Both use the function mousePos() at the top of the file. And this function is where we pinpoint the problem.
When we call event.clientX or any of screenX or pageX we get output of the wrong resolution.
A more detailed discussion of all our observations and thoughts may be found at https://github.com/PeterBjuhr/frescobaldi/issues/21

Understanding rotation and calculating the top left point in KineticJS

I am working on a page where I can view images. I want to create a rotation tool. I've done that, but, it's not working consistently. When I set up the centre point to rotate by, the image jumps slightly, and it gets worse each time. I was experimenting, and, I have code to add a wedge to the top left corner of my top level group ( so, at 0,0 ). If I rotate the image by 45 degrees and drag it so that half of it is off the left edge of my canvas, then I call getAbsolutePosition on the wedge and on the group, I get these values:
layer.getAbsolutePosition()
Object {x: 104.66479545850302, y: 279.2748571151325}
wedge.getAbsolutePosition()
Object {x: 180.2684127179338, y: -73.48773356791764}
I think this means my y position is actually the bottom of the image, which is off screen.
What I want to do, is calculate the absolute position of the middle of my image, when the mouse moves over it, regardless of it's rotation. I have some code that works out points with rotation, which seems like it works at first, almost, but it just gets more and more broken the more I use the tool. I feel like there's something about how Kinetic is tracking these things and what it's reporting, that I am missing. Any hints would be most appreciated. Tutorials I can read are even better ( yes, I've read everything linked from the KineticJS site and searched the web ).
In a nutshell, the question is, if I have an image inside a group, and it's rotated, how do I work out the centre point of the image, taking the rotation in to account, and how do I set the offset so it will rotate from that point, and stay in the same place ?
Thanks
As you've discovered about KinetiJS:
rotation is easy
dragging is easy
dragging+rotation is difficult
After you drag your image you must reset its rotation point (offsetX/offsetY).
KineticJS makes dragging+rotation more difficult than it has to be.
Resetting the offset points of your image will cause KineticJS to automatically move your image (Noooo!!).
That's what's causing your jumping.
The solution to the "jumping" problem:
When you reset the image's rotation point (offsetX/OffsetY) you must also reset the image's X/Y position.
This code resets both XY and Offsets for an image after dragging:
A Demo: http://jsfiddle.net/m1erickson/m9Nw7/
// calc new position and offset
var pos=rect.getPosition();
var size=rect.getSize();
var offset=rect.getOffset();
var newX=pos.x-offset.x+size.width/2;
var newY=pos.y-offset.y+size.height/2;
// reset both position and offset
rect.setPosition([newX,newY]);
rect.setOffset(size.width/2,size.height/2);

Categories