How to Zoom SVG

The SVG zoom effect is easily achievable using d3js or svgjs libraries, there are many code snippets out there on how to do it properly. But what about doing this on a raw SVG file without running mentioned libraries? That’s what I was doing in setupmap, processing raw files to implement a variety of features.

But, before we start, make sure you are familiar with viewBox and viewPort, and how changing size one of them, affect the other.

This is the original image - a red dot in the center of the black background:

<?xml version="1.0"?>
<svg baseprofile="tiny" fill="black" width="300" height="300"  version="1.2" viewBox="0 0 300 300" xmlns="http://www.w3.org/2000/svg">
    <g>
        <rect width="300" height="300" />
        <circle cx="150" cy="150" r="5" fill="red" id="marker" />
    </g>
</svg>

rect

Now, let’s scale image using transform attribute:

<?xml version="1.0"?>
<svg baseprofile="tiny" fill="black" width="300" height="300"  version="1.2" viewBox="0 0 300 300" xmlns="http://www.w3.org/2000/svg">
    <g transform="scale(1.5)">
        <rect width="300" height="300" />
        <circle cx="150" cy="150" r="5" fill="red" id="marker" />
    </g>
</svg>

rect

The red dot is not in the center of ViewPort anymore, that’s because the ViewBox became “larger” or the relation of ViewPort to ViewBox coordinate system has been changed (scaled in 1.5 times).

To return the red dot to the center of the rectangle we need to shift it, according to different parameters, like viewport/viewbox size, the radius of the circle, the original location of the circle, etc. Shifting can be done using translate definition of transform attribute:

<?xml version="1.0"?>
<svg baseprofile="tiny" fill="black" width="300" height="300"  version="1.2" viewBox="0 0 300 300" xmlns="http://www.w3.org/2000/svg">
    <g transform="scale(1.5) translate(-50, -50)">
        <rect width="300" height="300" />
        <circle cx="150" cy="150" r="5" fill="red" id="marker" />
    </g>
</svg>

rect

So, it was easy, to get the zoom effect, you scale and shift using some predefined location. But, how to get the shift values? Here is a Python code snippet that is doing just this. The function arguments are self-explanatory and the output is two numbers that must be used in translate definition.

def get_shift(viewport_width, viewport_height, viewbox_width, viewbox_height, x, y, scale):
    viewbox_width /= scale
    viewbox_height /= scale
    width_coof = viewport_width / viewbox_width
    height_coof = viewport_height / viewbox_height
    x_vp = x * width_coof
    y_vp = y * height_coof
    xc = viewport_width / 2
    yc = viewport_height / 2
    sx = 1 if x_vp <= xc else -1
    sy = 1 if y_vp <= yc else -1
    shift_x = sx * abs(x_vp - xc)
    shift_y = sy * abs(y_vp - yc)
    shift_x /= width_coof
    shift_y /= height_coof

    return shift_x, shift_y

Let’s call this function on our sample image:

>> get_shift(300, 300, 300, 300, 150, 150, 1.5)
(-50.0, -50.0)

So, now we can use -50.0, -50.0 in translate definition:

 <g transform="scale(1.5) translate(-50, -50)">