Search code examples
javascriptd3.jszoomingscale

D3 How to keep element same size while transform scale / translate


This example illustrates my problem: https://bl.ocks.org/feketegy/ce9ab2efa9439f3c59c381f567522dd3

I have a couple of paths in a group element and I want to pan/zoom these elements except the blue rectangle path, which is in another group element.

The zooming and panning is done by applying transform="translate(0,0) scale(1) to the outer most group element then capturing the zoom delta and applying it to the same-size group element to keep it the same size.

This is working, but the blue rectangle position, which should remain the same size, is messed up, I would like to keep it in the same relative position to the other paths.

The rendered html structure looks like this:

<svg width="100%" height="100%">
  <g class="outer-group" transform="translate(0,0)scale(1)">
    <path d="M100,100 L140,140 L200,250 L100,250 Z" fill="#cccccc" stroke="#8191A2" stroke-width="2px"></path>
    <path d="M400,100 L450,100 L450,250 L400,250 Z" fill="#cccccc" stroke="#8191A2" stroke-width="2px"></path>

    <g class="same-size-position" transform="translate(300,250)">
      <g class="same-size" transform="scale(1)">
        <path d="M0,0 L50,0 L50,50 L0,50 Z" fill="#0000ff"></path>
      </g>
    </g>
  </g>
</svg>

I've tried to get the X/Y position of the same-size-position group and create a delta from the translate x/y of the outer-group, but that doesn't seem to work.


Solution

  • After dusting off my high school geometry books I found a solution.

    You need to get the bounding box of the element you want to keep the same size of and calculate a matrix conversion on it like so:

    const zoomDelta = 1 / d3.event.transform.k;
    
    const sameSizeElem = d3.select('.same-size');
    const bbox = sameSizeElem.node().getBBox();
    
    const cx = bbox.x + (bbox.width / 2);
    const cy = bbox.y + (bbox.height / 2);
    const zx = cx - zoomDelta * cx;
    const zy = cy - zoomDelta * cy;
    
    sameSizeElem
      .attr('transform', 'matrix(' + zoomDelta + ', 0, 0, ' + zoomDelta + ', ' + zx + ', ' + zy + ')');
    

    The matrix transformation will keep the relative position of the element which size remains the same and the other elements will pan/zoom.