Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

D3js: finding path's bounding box (without getBBox() )?

The following code works on 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 pointed out that JSDOM doesn't support getBBox()

What D3js replacement to use to get the bounding box of #L1 > *:nth-child(2) ?

Past efforts lead me there : getBBox() based fiddle

enter image description here

like image 226
Hugolpz Avatar asked Apr 20 '15 11:04

Hugolpz


1 Answers

Path's bounding box

Digging straight into the element's path data d="..." should work. An svg line is basically a set of x,y points. Assuming absolute coordinates without translation nor big bezier curves, which is the case of my D3js-generated svg lines, I'am finding in this data the min and max values for both x and y.

To do so, I get the d="..." svg line or multilines code. For simplicity sake, I rudely removes possible relative jumps such h30 or v20 since I never saw any in my D3js output, then clean out letters (aka svg commands : M,L,H,V,C,S,Q,T,A,Z), simplify the spaces and line jumps, then split by the remaining spaces. I get a clean arrays of coordinates.

Important to note, my selector directly target a single non-translated path.

var getBBox = function(selector){
    var xmin, xmax, ymin, ymax,p;
    // clean up path
    var t = d3.select(selector).attr("d");  // get svg line's 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

Groups bounding boxes

For groups of multiple paths, you may want to traverse the svg DOM to loop upon each single path of the group in order to update xmin, ymin, xmax, ymax.

Translated elements

To handle translated elements, adapt further.

Alternatives

Other better approaches may exist. Remember to check if getBBox() and getBoundingClientRect() are available in your context, since they are native and very convenient.

like image 147
Hugolpz Avatar answered Nov 15 '22 04:11

Hugolpz