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

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

Related

Framer motion and transform matrix

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

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"/>

aframe - how enforce max/min camera tilt

I have six planes set up as cube with textures to display a 360-degree jpg set. I positioned the planes 1000 away and made them 2000 (plus a little because the photos have a tiny bit of overlap) in height and width.
The a-camera is positioned at origin, within this cube, with wasd controls set to false, so the camera is limited to rotating in place. (I am coding on a laptop, using mouse drag to move the camera.)
I also have a sphere (invisible), placed in between the camera and the planes, and have added an event listener to it. This seemed simpler than putting event listeners on each of the six planes.
My current problem is wanting to enforce minimum and maximum tilt limits. Following is the function "handleTilt" for this purpose. The minimum tilt allowed depends on the size of the fov.
function handleTilt() {
console.log("handleTilt called");
var sceneEl = document.querySelector("a-scene");
var elCamera = sceneEl.querySelector("#rotationCam");
var camRotation = elCamera.getAttribute('rotation');
var xTilt = camRotation['x'];
var fov = elCamera.getAttribute('fov');
var minTilt = -65 + fov/2;
camRotation['x'] = xTilt > minTilt ? xTilt : minTilt;
// enforce maximum (straight up)
if (camRotation['x'] > 90) {
camRotation['x'] = 90;
}
console.log(camRotation);
}
The event handler is set up in this line:
<a-entity geometry="primitive:sphere" id="clickSphere"
radius="50" position="0 0 0" mousemove="handleTilt()">
When I do this, a console.log call on #clickSphere shows the event handler exists. But it is never invoked when I run the program and move the mouse to drag the camera to different angles.
As an alternative, I made the #clickSphere listen for onClick as follows:
<a-entity geometry="primitive:sphere" id="clickSphere"
radius="50" position="0 0 0" onclick="handleTilt()">
The only change is "mousemove" to "onclick". Now, the "handleClick()" function executes with each click, and if the camera was rotated to a value less than the minimum, it is put back to the minumum.
One bizarre thing, though, after clicking and adjusting the rotation a few times, the program goes into a state where I can't rotate the camera down below the minimum any more. It is as if the mousemove listener had become engaged, even though the only listener coded is the onclick. I can't for the life of me figure out why this kicks in.
Would it be possible to get some advice as to what I might be doing wrong, or a plan for troubleshooting? I'm new to aframe and JavaScript.
An alternative plan for enforcing the min and max camera tilts in real time would also be an acceptable solution.
I just pushed out this piece on the docs for ya: https://aframe.io/docs/0.6.0/components/look-controls.html#customizing-look-controls
While A-Frame’s look-controls component is primarily meant for VR with sensible defaults to work across platforms, many developers want to use A-Frame for non-VR use cases (e.g., desktop, touchscreen). We might want to modify the mouse and touch behaviors.
The best way to configure the behavior is to copy and customize the current look-controls component code. This allows us to configure the controls how we want (e.g., limit the pitch on touch, reverse one axis). If we were to include every possible configuration into the core component, we would be left maintaining a wide array of flags.
The component lives within a Browserify/Webpack context so you’ll need to replace the require statements with A-Frame globals (e.g., AFRAME.registerComponent, window.THREE, AFRAME.constants.DEFAULT_CAMERA_HEIGHT), and get rid of the module.exports.
Can modify https://github.com/aframevr/aframe/blob/master/src/components/look-controls.js to hack in your min/max just for mouse/touch.

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.

Get Line co-ordinates in Javascript

I am drawing lines using Canvas (HTML 5), since lines/shapes are not stored as objects in Canvas, I cannot attach unique events to it (eg onmouseclick)
I wish to attach a onmouseover event to a line, is it possible by getting to know if the mouse if over a particular line (using its 2 X and 2 Y co-ordinates) in Canvas using Javascript. Would this work for different line widths (eg: 2,5 pixels)
Want to avoid using SVG as the entire project is built on Canvas
Please advise.
You would need to use math formulas to calculate the area of the line and whether a certain point intersects with it.
Here's a basic example:
Find mouse coordinates relative to position of the canvas (How to find mouse pos on element)
Calculate whether mouse x/y is inside some rectangle (Point in rectangle formula)
Done.
There is a function isPointInPath(x,y). It will return true if a point is on the current path.
You will have to call that for every line you want to check and the best way to do that is at the same time as you draw.
The best way is using some canvas frameworks. Look at "LibCanvas :: Creating Lines" (dont forget to dblClick at canvas)

Categories