D3js: finding a bounding box of a path (without getBBox ())?

The following code works for Chromium:

var node = window.d3.selectAll('#L1 > *:nth-child(2)');
var bbox = node.node().getBBox();
console.log(bbox) // {height: 44, width: 44, y: -13, x: 144}

      

but not with nodejs + jsdom:

"TypeError: Object [ PATH ] has no method 'getBBox' "

      

M. Bostock noted that JSDOM does not support getBBox()

What can D3js replace to get the bounding rectangle #L1 > *:nth-child(2)

?

Past Efforts Have Got Me There: getBBox()

Based on a Violin

enter image description here

+3


source to share


2 answers


The reason getBBox

/ getBoundingClientRect

/ getClientRect

doesn't work in NodeJS + JSDOM is because calculating these SVG (or HTML) element values ​​involves a huge amount of computation.

First, all the CSS in the elements <style>

must be parsed (which is no longer trivial). Then the rules of CSS selectors, cascading and inheritance must be applied to find out what size, position, or line width an element has. And even after you know all the values ​​of the style properties, you need to do some non-trivial math to calculate the bounding rectangles: defining various SVG transform functions, their compositions, SVG primitive bounding boxes, and Bézier curves. Browsers support all of these (they have to in order to draw an element), but JSDOM just isn't meant for all of these.



Fortunately, canvg is a JavaScript implementation of most SVGs that is used to draw an image. It supports most of the above, and while it doesn't have an interface to give you this data, thankfully it has very good (and MIT licensed) code, so hopefully you can copy and reuse it. At the moment, the code is written in one file and has CSS parsing , applying cascading rules , analyzing path data , defining SVG transforms , applying transforms and calculating a bezier bounding box curve <canvas>

... That is, almost everything you need to calculate bounding margins :) However, it does not support CSS selectors, but can reuse another library . But unfortunately as far as I can tell canvg is not ready to run in NodeJS, you probably need some tweaking.

There is canvgc , an SVG to JS compiler that contains an older version of canvg , and a one capable of running in NodeJS. So it's easier to start with this.

+2


source


Path bounding box

Digging right into the element path data d="..."

should work. The svg string is basically a collection of x,y

dots. Assuming absolute coordinates without translation, or large Bézier curves , which applies to my svg strings generated by D3js, I find in this data the min and max values x

for both y

,

For this I am getting d="..."

svg code or multi-line code. For simplicity's sake, I roughly remove possible relative jumps like h30

or v20

, since I've never seen any in my D3js output, and then clear the letters (aka svg commands

: M, L, H, V, C, S, Q, T, A , Z), simplify spaces and linear jumps, and then split into other spaces. I am getting pure arrays of coordinates.

It's important to note that my selector is directly targeting one untranslated path .

var getBBox = function(selector){
    var xmin, xmax, ymin, ymax,p;
    // clean up path
    var t = d3.select(selector).attr("d");  // get svg line code
    console.log(t)
   t = t.replace(/[a-z].*/g," ") // remove relative coords, could rather tag it for later processing to absolute!
        .replace(/[\sA-Z]+/gi," ").trim().split(" ");  // remove letters and simplify spaces.
    console.log(t)

    for(var i in t){    // set valid initial values
        if(t[i].length>1){ 
            p = t[i].split(","); 
            xmin = xmax = p[0]; ymin = ymax = p[1]; }
    }
    for(var i in t){ // update xmin,xmax,ymin,ymax
      p = t[i].split(",");
      if(!p[1]){ p[0]=xmin; p[1] = ymin;} // ignore relative jumps such h20 v-10
      xmin = Math.min(xmin, p[0]);
      xmax = Math.max(xmax, p[0]);
      ymin = Math.min(ymin, p[1]);
      ymax = Math.max(ymax, p[1]);
    } return [[xmin,ymax],[xmax,ymin]]; //  [[left, bottom], [right, top]] as for https://github.com/mbostock/d3/wiki/Geo-Paths#bounds
}
var bb = getBBox("path");

      

JSfiddle DEMO



Bounding block groups

For groups with multiple paths, you may need to traverse the SVG DOM to loop through each individual path in the group to update xmin, ymin, xmax, ymax.

Translated elements

To handle the translated elements, adapt them further.

Alternatives

There are other approaches as well. Don't forget to check if getBBox()

and getBoundingClientRect()

in your context as they are native and very handy.

+1


source







All Articles