Link to home
Start Free TrialLog in
Avatar of HLRosenberger
HLRosenbergerFlag for United States of America

asked on

SVG, D3, scaling, translation

I have an SVG image loaded from file.  I then mouse click within the image and create circles at the point clicked, based on event.clientX and event.clientY.  Once the image is scaled or translated, those x and y coordinate no longer are correct.  I was able to compensate by adding the translation values to the x, y  coordinates.  But the same issue exists when scaling.  What the best/proper way to deal with this?  Is there a way to reset the x,y origin of the "canvas" so as to fix this problem?  Should I not be using event.clientX  and event.clientY, and instead use some corresponding D3 event?
Avatar of Member_2_248744
Member_2_248744
Flag of United States of America image

greetings  HLRosenberger, , you get an event location in a 2 dimension X and Y position, from the mouse reference point on the SVG (not the window position X and Y). Some of the placement coordinates for an SVG element (like Text or Rectangle) are in reference to the upper left of the element, so if the rectangle is 20px  X  20px ( <rect width="20" height="20" ) then you must subtract 10px (half the dimension) from the mouse reference point, to "Center" the Rect on the touch point from the SVG X and Y reference.

 a circle( ) in SVG is center-point reference with the cx and cy -
    <circle cx="50" cy="50"
so no change is needed in the SVG X and Y for the "Centered" placement on the touch point.

I said all of the above to suggest that in order to deal with translation and scaling, you the programmer will have to deal with the Mathematics needed for procedures and operations in any changes you do in your javascript to the elements or the SVG image.

For instance, you can have certain created elements (like your = create circles at the point clicked) associated with variables (or arrays-objects of X and Y) that will record the scale or translate  X and Y changes for that element (in and array for many elements) to be used (offset the old X and Y points) for any operations that need to factor in the position used in the operation.

By the way, you say this is an SVG "IMAGE", and then you say -
    "x,y origin of the 'canvas' "
The SVG and Canvas are not the same, and have different properties and javascript methods, and in my view, need to be thought about - in programming - differently, ,  to have a clear head on using the correct operations.
Avatar of HLRosenberger

ASKER

Slick812 -

I hear what you are saying. Need to let it sink in a bit.   As I said, I am able to deal with a translation (by itself) by using the code below, where translation[0] is x amount and translation[1] is y amount.   I also have a scale value.    I now need to factor in scaling.  When I scale my SVG, I also translate it so that it stays center in the screen.  

I've tried multiplying both event.clientX and event.clientX by the scaling factor, then subtracting the translation values.  That did not work.  I also tried multiplying  event.clientX, event.clientX,  translation[0] and translation[1] by the scaling factor, and that also does not work.

  var x = event.clientX - translation[0];
  var y = event.clientY - translation[1];
ASKER CERTIFIED SOLUTION
Avatar of Robert Schutt
Robert Schutt
Flag of Netherlands image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
I should clarify - I'm not resizing the browser window.  I'm using a D3 zoom handle. See code below:



 // Setup Zoom handler
var zoom = d3.behavior.zoom().x(xScale).y(yScale).scaleExtent([0.5, 5]).on("zoom.canvas", zoomHandler);


   function zoomHandler() {

            var tbound = -height * scale,
            bbound = height * scale,
            lbound = -width * scale,
            rbound = width * scale;

            // limit translation to thresholds
            translation = d3.event ? d3.event.translate : [0, 0];
            translation = [Math.max(Math.min(translation[0], rbound), lbound), Math.max(Math.min(translation[1], bbound), tbound)];

            // Make sure Source Event is NOT null!
            if (d3.event.sourceEvent != null) {

                // If this is a mouse move event, we are only doing translation - NO scaling.
                if (d3.event.sourceEvent.type == "mousemove") {

                    d3.select(".panCanvas, .panCanvas .bg").attr("transform", "translate(" + translation + ")" + " scale(" + scale + ")");

                }

                // If this is a mouse wheel event.  We are doing translation and scaling. The translation keeps the
                // image in the center of the client area as it scales.
                else if (d3.event.sourceEvent.type == "mousewheel") {

                    // Change the scaling by .1
                    scale = d3.event.scale + .1;
                    d3.select(".panCanvas, .panCanvas .bg").attr("transform", "translate(" + translation + ")" + " scale(" + scale + ")");

                }

            }
   
        }
if I use d3.mouse and it's x, y coordinates, my circle is NOT drawn at the correct spot.  

x = p[0];
y = p[1];

if I use event.clientX and event.clientY, my circle is drawn at the correct spot.

x = event.clientX;
y = event.clientY;

And once I translate my SVG, I can still get the circle to draw at the correct spot by substracting out the x, y translation offset.    

x = event.clientX - translation[0];
y = event.clientY - translation[1];

These last two lines of code are how I do it now.  Now, I also have a scaling factor.  What I'm try to so it factor in the scaling.
That's a pity, it seemed to me that it should work in any situation...

I'm not sure how to integrate that zoomHandler into my example code. It's not being called and anyway there are too many unknown variables to run it. That would only be helpful to me if it comes with context (html, other js needed). Ideal is usually something like a jsFiddle so anybody can start working on a solution straight away without having to puzzle together a page to test the javascript function.

The manual calculation should also be possible, I've done something similar in other environments. In this example code an important part of the scaling is that the x/y ratio is kept at all times. This means that for relatively narrow screens, the visible part of the canvas needs to be offset vertically (after scaling) and for wide screens there is a horizontal offset. So if you wanted to transform a set of mouse event coordinates, that offset would need be subtracted as well.

To get a grip on what's happening you could draw a few lines in key positions (similar to the dots) and/or look at the coordinates of the corner points using something similar like in my example code to show the coordinates (maybe extend to 'original' and 'mouse' event coordinates) on mouse move.
ah, I made some progress.   let me explain.  I load my SVG from file - no transformation.   I then dynamically draw a circle where the mouse is clicked.   No problem.  I save off the x and y coordinates where the circle was drawn.  I stop my code, and hardcode these x and y coordinates behind the mouse click.  I run the code again, and both transform and scale the SVG.   I then click the mouse and a circle is drawn at the hard coded coordinates.  That's good because the circle appears at the exact same spot, even with the SVG translated and scaled.   So, now the trick is to map between the un-transformed SVG and the transformed SVG .
I'm AFK now but your description sounds ok. Still though I fear not enough for me to reproduce the problem 100%.
Is there any way you can share more of your html? I've gotten your posted code to work but I don't know what you're looking at so can't progress further. For starters, what is your panCanvas? I've used an svg element so far but it seems to me at the very basics we're not on the same page.
I will.   However, I'm be off work until Tuesday.   So, I'll get it to ya then...
Thanks for all your help.  I've made progress!