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
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.
The reason why getBBox/getBoundingClientRect/getClientRect does not work in NodeJS+JSDOM is that calculating these values of an SVG (or HTML) element involves massive amounts of computation.
First, all CSS code in <style> elements must be parsed (which is already not trivial). Then the CSS selectors, cascading and inheritance rules must be applied to know what size, position or line width an element has. And even after you know all style property values, you need to do some non-trivial maths to calculate the bounding boxes: definition of different SVG transform functions, compositions of these, bounding boxes of SVG primitives and Bezier curves. Browsers support all of these (they have to, in order to draw the element), but JSDOM is simply not meant for all of these.
But fortunately, canvg is a JavaScript implementation of most of SVG, which uses a <canvas> element to draw the image. It does support most of the above, and although it does not have an interface for giving you those data, fortunately it has very nice (and MIT licensed) code, so hopefully you can copy and reuse parts of it. As of now, the code is written in a single file, and it has CSS parsing, applying cascading rules, path data parsing, definitions of SVG transforms, applying transformations, and bezier curve bounding box calculation. That is, almost everything you need to calculate bounding boxes :) It does not, however, support CSS selectors, but it can reuse another library. But unfortunately, as far as I can tell, canvg is not ready for running in NodeJS, you probably need some tweaks.
There is, however canvgc, an SVG to JS compiler, which contains an older version of canvg, and it is capable of running in NodeJS. So it is easier to start with that.
This question is related to this question. The answer shows very nice way to extrude polygons that have holes (see the excellent live example). The main learning of the answer was that paths in three.js (r58) cannot have more than one moveTo command and it have to be in the start of the path, which means that path have to be broken by moveTos, so that moveTo start always a new path.
Extruding in three.js means that 2D paths are converted to 3D shapes using possible beveling. It is suitable for extruding texts to make 3D letters and words, but can be used also to extrude custom paths.
Now there arises two questions:
how is it possible to handle polygons that have multiple hole-polygons and multiple non-hole-polygons?
how is it possible to add a texture to generated shape as a whole?
I made an example of this as SVG in http://jsbin.com/oqomuj/1/edit:
The image is produced using this path:
<path d="
M57.11,271.77 L57.11,218.33 L41.99,218.63 L105.49,165.77 L138.41,193.18 L138.41,172.2 L152.53,172.2 L152.53,204.93 L168.99,218.63 L153.21,218.63 L153.21,271.77Z
M74.14,264.13 L105.49,264.13 L105.49,232.8 L74.14,232.8Z
M115.35,250.7 L135.96,250.7 L135.96,232.61 L115.35,232.61Z
M56.11,145.77 L56.11,92.33 L40.99,92.63 L104.49,39.77 L137.41,67.18 L137.41,46.2 L151.53,46.2 L151.53,78.93 L152.53,79.76 L155.55,77.23 L159.5,74.52 L168.65,69.81 L176.46,66.93 L188.04,64.16 L200.63,62.7 L213.65,62.7 L226.05,64.09 L234.83,66.06 L245.65,69.73 L252.87,73.27 L259.12,77.34 L262.63,80.33 L265.6,83.47 L268.01,86.76 L269.83,90.17 L271.08,93.68 L271.76,99.08 L271.04,104.64 L269.75,108.2 L267.87,111.63 L265.42,114.91 L262.44,118.01 L258.95,120.92 L255.02,123.63 L245.86,128.34 L238.06,131.22 L226.48,133.99 L213.88,135.44 L200.63,135.44 L188.04,133.99 L176.46,131.22 L168.65,128.34 L159.5,123.63 L155.55,120.92 L152.21,118.12 L152.21,145.77Z
M73.14,138.13 L104.49,138.13 L104.49,106.8 L73.14,106.8Z
M114.35,124.7 L134.96,124.7 L134.96,106.61 L114.35,106.61Z
M207.26,117.33 L210.57,117.26 L216.87,116.53 L222.66,115.15 L227.8,113.18 L233.11,110 L236.34,106.99 L238.51,103.64 L239.42,100.48 L239.42,97.67 L238.51,94.51 L236.34,91.16 L233.11,88.15 L227.8,84.97 L222.66,83 L216.87,81.62 L210.57,80.89 L203.94,80.89 L197.65,81.62 L191.86,83 L186.71,84.97 L181.41,88.15 L178.18,91.16 L176.01,94.51 L175.1,97.67 L175.1,100.48 L176.01,103.64 L178.18,106.99 L181.41,110 L186.71,113.18 L191.86,115.15 L197.65,116.53 L203.94,117.26Z
"></path>
and this path converted to individual arrays of vertices:
var lower_house_material = [{x:57.11,y:271.77},{x:57.11,y:218.33},{x:41.99,y:218.63},{x:105.49,y:165.77},{x:138.42,y:193.18},{x:138.42,y:172.2},{x:152.53,y:172.2},{x:152.53,y:204.93},{x:168.99,y:218.63},{x:153.21,y:218.63},{x:153.21,y:271.77}];
var lower_house_hole_1 = [{x:74.14,y:264.13},{x:105.49,y:264.13},{x:105.49,y:232.8},{x:74.14,y:232.8}];
var lower_house_hole_2 = [{x:115.35,y:250.7},{x:135.96,y:250.7},{x:135.96,y:232.61},{x:115.35,y:232.61}];
var upper_house_material = [{x:56.11,y:145.77},{x:56.11,y:92.33},{x:40.99,y:92.63},{x:104.49,y:39.77},{x:137.42,y:67.18},{x:137.42,y:46.2},{x:151.53,y:46.2},{x:151.53,y:78.93},{x:152.53,y:79.76},{x:155.55,y:77.23},{x:159.5,y:74.52},{x:168.65,y:69.81},{x:176.46,y:66.93},{x:188.04,y:64.16},{x:200.63,y:62.7},{x:213.65,y:62.7},{x:226.05,y:64.1},{x:234.83,y:66.06},{x:245.65,y:69.73},{x:252.87,y:73.27},{x:259.12,y:77.35},{x:262.63,y:80.33},{x:265.6,y:83.47},{x:268.01,y:86.76},{x:269.84,y:90.17},{x:271.08,y:93.68},{x:271.76,y:99.08},{x:271.04,y:104.64},{x:269.75,y:108.2},{x:267.87,y:111.63},{x:265.42,y:114.91},{x:262.44,y:118.01},{x:258.96,y:120.92},{x:255.02,y:123.63},{x:245.86,y:128.34},{x:238.06,y:131.22},{x:226.48,y:133.99},{x:213.88,y:135.45},{x:200.63,y:135.45},{x:188.04,y:133.99},{x:176.46,y:131.22},{x:168.65,y:128.34},{x:159.5,y:123.63},{x:155.55,y:120.92},{x:152.21,y:118.12},{x:152.21,y:145.77}];
var upper_house_hole_1 = [{x:73.14,y:138.13},{x:104.49,y:138.13},{x:104.49,y:106.8},{x:73.14,y:106.8}];
var upper_house_hole_2 = [{x:114.35,y:124.7},{x:134.96,y:124.7},{x:134.96,y:106.61},{x:114.35,y:106.61}];
var upper_house_hole_3 = [{x:207.26,y:117.33},{x:210.57,y:117.26},{x:216.87,y:116.53},{x:222.66,y:115.15},{x:227.8,y:113.18},{x:233.11,y:110},{x:236.34,y:106.99},{x:238.51,y:103.64},{x:239.42,y:100.48},{x:239.42,y:97.67},{x:238.51,y:94.51},{x:236.34,y:91.16},{x:233.11,y:88.15},{x:227.8,y:84.97},{x:222.66,y:83},{x:216.87,y:81.62},{x:210.57,y:80.89},{x:203.94,y:80.89},{x:197.65,y:81.62},{x:191.86,y:83},{x:186.71,y:84.97},{x:181.41,y:88.15},{x:178.18,y:91.16},{x:176.01,y:94.51},{x:175.1,y:97.67},{x:175.1,y:100.48},{x:176.01,y:103.64},{x:178.18,y:106.99},{x:181.41,y:110},{x:186.71,y:113.18},{x:191.86,y:115.15},{x:197.65,y:116.53},{x:203.94,y:117.26}];
The question is, how this like structure can be converted to 3D object in three.js so that it can be extruded using THREE.ExtrudeGeometry( shape, extrusionSettings ) and after that textured as a whole?
I can examine the path data to know what hole belongs to what polygon and handle all as separate shapes, but because I want to use one texture image across all the shapes, I think the preferred way is to handle all material-polygons as one shape, and hole-polygons as other shape and use something like:
var shape = [lower_house_material, upper_house_material];
shape.holes = [lower_house_hole_1, lower_house_hole_2, upper_house_hole_1, upper_house_hole_2, upper_house_hole_3];
var 3d_geometry = THREE.ExtrudeGeometry( shape, extrusionSettings );
So the 3d_geometry should be at the end one mesh to which I can append a texture this way:
var textureFront = new THREE.ImageUtils.loadTexture( 'textureFront.png');
var textureSide = new THREE.ImageUtils.loadTexture( 'textureSide.png');
var materialFront = new THREE.MeshBasicMaterial( { map: textureFront } );
var materialSide = new THREE.MeshBasicMaterial( { map: textureSide } );
var materialArray = [ materialFront, materialSide ];
var faceMaterial = new THREE.MeshFaceMaterial(materialArray);
var final_mesh = new THREE.Mesh(3d_geometry, faceMaterial );
And one of the textures could be something like this (256x256px):
And texture applied:
And because the mesh is extruded, there is also 3D thickness on the above, but you got the idea of texturing.
I know that y-coordinates have to be flipped but it is a trivial task and not the point of my question, but if three.js has ready-made function for clipping y, it would be helpful.
I have spent hours to examine the three.js source code, examples and documentation, but because the most frequent word there is "todo", it cannot help much. And I'm very newbie to three.js, I would think that this may be trivial task for some experienced three.js user.
UPDATE: And just to make sure, the hole polygons are always well-behaved, which means that hole polygons are always fully inside material-polygons and there are no duplicate vertices or self-intersections either in material-polygons or hole-polygons and all material-polygons have CW winding order and holes CCW.
UPDATE: Merging geometries was not a solution for texturing the whole extruded polygon set by one texture: http://jsfiddle.net/C5dga. The texture is repeated on all individual shapes, so merging geometries in this case has no real meaning. The solution could be possibly found on merging shapes before they are extruded, but not found solution for this yet.
You can merge geometries as in the following snippet, resulting in just a single mesh. From your prior questions, you already know how to texture a single geometry.
var geometry1 = new THREE.ExtrudeGeometry( shape1, extrusionSettings );
var geometry2 = new THREE.ExtrudeGeometry( shape2, extrusionSettings );
geometry1.merge( geometry2 );
. . .
var mesh = new THREE.Mesh( geometry1, material );
scene.add( mesh );
Fiddle: http://jsfiddle.net/pHn2B/88/
Fiddle: http://jsfiddle.net/C5dga/13/ (with texture)
EDIT: As an alternative to creating separate geometries and using the merge utility, you can create a single geometry using the following pattern, instead:
var geometry1 = new THREE.ExtrudeGeometry( [ shape1, shape2 ], extrusionSettings );
EDIT: updated to three.js r.70
I have a KML file with placemarkers, each of which has polygon coordinates. I'd like to change the fill color of the polygons on the fly, depending on values I read in from a json file. My understanding is that Google caches kml files, so you can't easily change the fill color of polygons on the fly.
So I'm trying to convert my kml polygons into svg paths so that I can use Raphael to place my polygons on my Google map. Then I can change fill colors with javascript.
But how do I convert polygon coordinates to svg paths, does anyone know?
A sample set of coordinates would be:
<Polygon><outerBoundaryIs><LinearRing><coordinates>-80.098181,40.42127 -80.096479,40.421262 -80.096464,40.421409 -80.096448,40.421551 -80.096444,40.421583 -80.096434,40.421666 -80.096406,40.421931 -80.096389,40.422087 -80.096353,40.422423 -80.09583,40.426101 -80.095525,40.428234 -80.095315,40.429714 -80.095276,40.429989 -80.092585,40.428593 -80.092273,40.428431 -80.09069,40.430519 -80.090384,40.430924 -80.08989,40.430618 -80.089699,40.4305 -80.089499,40.430359 -80.088738,40.429886 -80.088418,40.429688 -80.088254,40.429585 -80.087931,40.429384 -80.087086,40.428859 -80.086867,40.428722 -80.086658,40.428592 -80.086493,40.42849 -80.08617,40.428282 -80.086177,40.428265 -80.08621,40.428188 -80.0864,40.427742 -80.086397,40.42749 -80.086394,40.427125 -80.08631,40.426427 -80.086335,40.425887 -80.086235,40.425409 -80.085776,40.425327 -80.085442,40.42527 -80.084993,40.424585 -80.085076,40.42448 -80.085542,40.423842 -80.085679,40.423125 -80.085659,40.423011 -80.085626,40.422827 -80.085191,40.421758 -80.08467,40.420859 -80.084258,40.420336 -80.083828,40.4201 -80.083078,40.420005 -80.082504,40.420072 -80.081444,40.420196 -80.080888,40.420181 -80.080775,40.420178 -80.080604,40.420173 -80.080122,40.420161 -80.079753,40.420151 -80.07947,40.420144 -80.079287,40.420139 -80.078239,40.420296 -80.077661,40.420418 -80.076213,40.420726 -80.075673,40.420766 -80.075298,40.420719 -80.075127,40.420625 -80.074909,40.420307 -80.075028,40.419779 -80.07539,40.419028 -80.07583,40.41836 -80.076065,40.418108 -80.076528,40.417616 -80.077217,40.417124 -80.077503,40.417002 -80.077725,40.416907 -80.078391,40.416622 -80.078614,40.416528 -80.078657,40.41651 -80.078693,40.416491 -80.078755,40.416457 -80.079174,40.416233 -80.079205,40.416217 -80.079292,40.416128 -80.079439,40.415977 -80.079456,40.41596 -80.079614,40.415473 -80.079604,40.415377 -80.079584,40.415179 -80.07958,40.415168 -80.079539,40.415042 -80.079499,40.414915 -80.079407,40.414785 -80.079742,40.414965 -80.08086,40.415568 -80.081458,40.415889 -80.081535,40.41593 -80.082292,40.416343 -80.08238,40.416392 -80.082597,40.41651 -80.082624,40.416525 -80.082787,40.416614 -80.083508,40.417007 -80.083934,40.417239 -80.084422,40.417505 -80.084622,40.417432 -80.084852,40.417348 -80.085329,40.417179 -80.085419,40.417141 -80.085948,40.41695 -80.086252,40.417184 -80.088463,40.418885 -80.088964,40.418631 -80.089036,40.418595 -80.089145,40.418539 -80.089173,40.418525 -80.089346,40.418438 -80.089376,40.418457 -80.089398,40.418471 -80.089964,40.418827 -80.090299,40.419055 -80.091306,40.419739 -80.091642,40.419967 -80.091803,40.420088 -80.091966,40.420043 -80.092445,40.419915 -80.092561,40.419885 -80.096792,40.418748 -80.098853,40.41815 -80.098868,40.418242 -80.09943,40.421273 -80.098181,40.42127</coordinates></LinearRing></outerBoundaryIs></Polygon>
Disclaimer: I'm a bit rusty on geodetics, but think your #1 problem here is the coordinate system - lat, lng are projection-dependent data points, whereas your screen is a flat pixel one. What you need to do is convert these to northing and easting points first.
There is a projection conversion library out there called proj.4 - use this to convert your coordinates. If needed, there is a javascript port of it that you can easily adapt for your use at https://trac.osgeo.org/proj/
Generic conversion process would go like this (LAT, LNG are actual coordinates.)
var source = new Proj4js.Proj('WGS84');
var dest = new Proj4js.Proj('GOOGLE');
var p = new Proj4js.Point( LATITUDE, LONGITUDE ); // replace with actual coords!
var pdest = Proj4js.transform(source, dest, p);
At this point, pdest.x and pdest.y will contain your SVG-compatible coordinates.
After your coordinates are converted to pixel units, simply plot them - and mind the range and units. Northing and easting coordinates will be in 800,000 range, so you will likely be applying some transformations, such as translation and scaling.
End result should look like this (being rusty, I may have messed up and flipped lat/lng around, etc.)
Here's a working jsfiddle with a conversion and plot: http://jsfiddle.net/LPzKV/1/