Search code examples
javascriptsvgbounding-box

SVG - Using checkIntersection() with transformed elements


TLDR;

I would like to use svg.checkIntersection() but am having difficulty getting/calculating the correct bounding box required by the function. What is the cleanest way to get the bounding box of a transformed SVG element in the initial coordinates of the SVG?


I am trying to check for overlapping text elements in my SVG generated by code. The text elements are positioned at (0, 0) and maneuvred to the correct position using transform attribute, something like this:

<svg xmlns="http://www.w3.org/2000/svg" id="svg0" viewBox="-100 -100 200 200">
  <text id="t0" transform="rotate(-30) translate(15 -5)">Text 0</text>
  <text id="t1" transform="rotate(-10) translate(15 -10)">Text 1</text>
  <!-- do they overlap? -->
</svg>

Note that the specific transforms used is not predeterminable.

So naturally one would try this:

const mySvg = document.querySelector("#svg0");
const text0 = mySvg.querySelector("#t0");
const text1 = mySvg.querySelector("#t1");
const isOverlap = mySvg.checkIntersection(text0, /* bounding box of text1 */);

The trouble is that there's no easy way to get this bounding box. W3 definition states:

The values are in the initial coordinate system for the current ‘svg’ element.

Therefore:

  • getBoundingClientRect() is inappropriate because this SVG is intended to be a component in a HTML DOM, and getBoundingClientRect() would return values in the HTML coordinate system.
  • getBBox() is not useful because it returns values in the element's local coordinate system, which is different than the SVG's initial coordinate due to the transform applied.
  • getCTM() seems potentially useful, but manually applying the matrix to get the bounding box looks like a lot hand-written mathematics. I would like to avoid this if possible.

So finally...

Given that there is no straight-forward built-in methods, what is the cleanest and/or easiest way to get an element's bounding box for use in svg.checkIntersection()?


Solution

  • I ended up using the solution provided by OSUblake here, combined with a simple rectangle intersection algorithm here.

    Basically, first compute a transform matrix from the object coordinate space to the SVG coordinate space:

    const matrix = svg.getScreenCTM().inverse().multiply(element.getScreenCTM());
    

    Then apply the matrix to all four points of the element's bounding box and compute x, y, width, and height from the minima and maxima.

    const p = svg.createSVGPoint();
    
    p.x = r.x;
    p.y = r.y;
    const pA = p.matrixTransform(matrix);
    
    p.x = r.x + r.width;
    p.y = r.y;
    const pB = p.matrixTransform(matrix);
    
    ...
    

    See original post for full implementation.

    This did end up being essentially a big math function, something I did really want to avoid. But ultimately it does the job without requiring DOM changes. Combined with the simple intersection function, it did allow me to ditch svg.checkIntersection() too, which was giving weird results in the first place (tested on Chrome 83).