3

I have an svg image of a map that i have to scale up to make it zoom in. Javascript has a function to scale up SVG images. However the svg scale function uses the upper left corner as center when zooming.

So to counter this the usual trick is to shift the svg 50% to the left and 50% up so that the center moves to the upper left corner. Then scale the image and again move the image back.

Edit: I think this article might say what i need to do but i do not know enought about matrices: http://www.cs.rit.edu/~icss571/clipTrans/2DTransBack.html#BACK3.0

Edit3: Here is some more information about the problem: http://commons.oreilly.com/wiki/index.php/SVG_Essentials/Transforming_the_Coordinate_System#svgess-CHP-5-FIG-9

The logic behind it looks like this.

var bbox = mapGroup.getBBox(); // the element of the svg image i want to scale up/down.
// finding center of element
var cx = bbox.x + (bbox.width/2); // x is the offset of the element horizontally
var cy = bbox.y + (bbox.height/2);  // y is the offset of the element vertically

// Shift the image so that the middle is in the upper left corner taking into account the amout of scaling;
mapGroup.attr('transform', 'translate(-' +(cx-(scale*cx)) + ', -' + (cy-(scale*cy)) + ')');
// scale the image
mapScaleGroup.attr('transform', 'scale('+ scale +')');
// Shift the image back again
mapGroup.attr('transform', 'translate(' + (cx-(scale*cx)) + ', ' + (cy-(scale*cy)) + ')');

This Works perfectly when i only want to hit the center of the image, however i have added a drag functionality that causes the offset to change. This causes problems as you can see here: http://nho-municipality-map.divshot.io/ Zoom to see how it should be or drag the map and then zoom to see the mistake.

So I think the key to the problem lies ether in this part: cx = bbox.x + (bbox.width/2) or this part (cx-(scale*cx))

How do i take the custom offset into account?

Edit2: Here is all the information i get from the bbox.

  • Quick preliminary question: If you replace cx-(scale*cx) by cx and cy-(scale*cy) by cy throughout (i.e., both before and after scaling), does the code do what you expect? – Andrew D. Hwang Jul 05 '15 at 15:22
  • 1
    No, then the position jumps all over the place :-/ – Ole Henrik Skogstrøm Jul 05 '15 at 17:07
  • I see. :S Second question (since I'm not fluent in SVG): What do cx and cy represent? Are they coordinates of the center of the image with the origin $(0, 0)$ at upper left? That is, are bbox.x and bbox.y equal to $(0, 0)$ before the user does any dragging, and are they not $(0, 0)$ after the user drags the map? Or does bbox refer to an SVG element the user has clicked? (Or is their meaning something else...?) – Andrew D. Hwang Jul 05 '15 at 17:26
  • bbox.x and bbox.y is [0,0] before the image is dragged. the cx value that i calculate above seems to be the center of the image/svg yes :) – Ole Henrik Skogstrøm Jul 05 '15 at 17:42

1 Answers1

3

This doesn't directly answer the question, just explains the underlying mathematical theory.

Let $(x, y)$ denote Cartesian coordinates in the plane, $(x_{0}, y_{0})$ the center of expansion, and $\alpha > 0$ the scale factor.

Define $S(x, y) = (\alpha x, \alpha y)$ to be scaling (centered at the origin), and let $T(x, y) = (x + x_{0}, y + y_{0})$ be translation by $(x_{0}, y_{0})$. The inverse transformation of $T$ is $T^{-1}(x, y) = (x - x_{0}, y - y_{0})$.

Scaling centered at $(x_{0}, y_{0})$ can be viewed in either of two ways:

First, as the composition $TST^{-1}$. (Composition is read right to left, so this means "do $T^{-1}$, then $S$, then $T$".) Geometrically, translate the plane so that $(x_{0}, y_{0})$ is sent to the origin $(0, 0)$, then scale by $\alpha$, then translate the origin back to $(x_{0}, y_{0})$. This is the sequence of operations suggested by my first comment, i.e., by

var bbox = mapGroup.getBBox(); // the element of the svg image i want to scale up/down. // finding center of element var cx = bbox.x + (bbox.width/2); // x is the offset of the element horizontally var cy = bbox.y + (bbox.height/2); // y is the offset of the element vertically

// Shift the image so that the middle is in the upper left corner taking into account the amout of scaling; mapGroup.attr('transform', 'translate(-' + cx + ', -' + cy + ')'); // scale the image mapScaleGroup.attr('transform', 'scale('+ scale +')'); // Shift the image back again mapGroup.attr('transform', 'translate(' + cx + ', ' + cy + ')');

Second, by working out formulas for the composition: \begin{align*} T^{-1}(x, y) &= (x - x_{0}, y - y_{0}), \\ ST^{-1}(x, y) &= \bigl(\alpha(x - x_{0}), \alpha(y - y_{0})\bigr), \\ TST^{-1}(x, y) &= \bigl(x_{0} + \alpha(x - x_{0}), y_{0} + \alpha(y - y_{0})\bigr) = \bigl(\alpha x + (x_{0} - \alpha x_{0}), \alpha y + (y_{0} - \alpha y_{0})\bigr). \end{align*} This is the formula referenced in the web link. The following (your original code with the first translate removed) should accomplish this:

var bbox = mapGroup.getBBox(); // the element of the svg image i want to scale up/down. // finding center of element var cx = bbox.x + (bbox.width/2); // x is the offset of the element horizontally var cy = bbox.y + (bbox.height/2); // y is the offset of the element vertically

// Shift the image so that the middle is in the upper left corner taking into account the amout of scaling; // scale the image mapScaleGroup.attr('transform', 'scale('+ scale +')'); // Shift the image back again mapGroup.attr('transform', 'translate(' + (cx-(scale * cx)) + ', ' + (cy-(scale * cy)) + ')');

Given that the first doesn't work, the second shouldn't, either, but the two should fail to work in the same way.

If both pieces of code do fail in the same way, my guess would be that the problem is in the definitions of cx and cy. With either block of code above, cx and cy should be the SVG coordinates of the center of expansion.

If they don't fail in the same way, there's something fundamental I don't understand about how SVG handles affine transformations.

  • 1
    Just read thoroughly through this now and it makes sense. I removed the first translate and the map behaves exactly the same. I also think that i define cx and cy incorrectly, and i believe it has something to do with the offset being wrong. I'll test it a bit and get back to you. Thank you! – Ole Henrik Skogstrøm Jul 06 '15 at 07:26
  • 1
    ok, so there is definatly something wrong with the definition of cx and cy. but how do i figure out what they should be? I'll try to add more information to the question above. – Ole Henrik Skogstrøm Jul 06 '15 at 08:03
  • Chapter 2 of the O'Reilly book looks relevant. If I'm reading Chapter 2 correctly, transformations don't change anything internal to the SVG element, they only change the viewBox (which determines what portion of the SVG maps to the canvas). Perhaps you want the center of the viewBox instead of the mapGroup...? – Andrew D. Hwang Jul 06 '15 at 11:19
  • That sounds reasonable, I'll try to find out how to get the center of the viewbox – Ole Henrik Skogstrøm Jul 06 '15 at 12:08