Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

calculate centroid d3

Based on a svg world map from wikipedia i need to calculate the centroid of every country. The d3 library supports a centorid method for paths. In the provided svg file the path coordinates of every country are included. For example Germany:

How can i you use the attribute "d" of the svg data to calculate the centroid in D3 syntax? I need it to place point in to the centroid of every country.

like image 615
Oliver Avatar asked Dec 13 '25 05:12

Oliver


2 Answers

You should consider using a world map file from Mike Bostock's library, since d3 examples will be primed for those projections and files. Some world maps are here, under examples: https://github.com/mbostock/topojson

I think that would really simplify your life, and you could code something like this, using topojson:

var svg = d3.select('#yourmap').append('svg');

var g = svg.append('g')
    .style(//some styling, like stroke-width);

var projection = //your projection
    .scale(//your scale)
    .translate(//your width/2, height/2)
var path = d3.geo.path()
    .projection(projection

//read in your json file...

var world = topojson.feature(world.objects.countries).features //guessing at a potential structure of your file

g.selectAll('circle')
        .data(world)
      .enter().append('circle')
        .attr('transform', function(d) { return 'translate(' + path.centroid(d) + ')'; })
        .attr('r', //whatever you want your radius to be, in pixels);

Unless there's a particular reason you're using the Wikipedia SVG?

like image 101
merry Avatar answered Dec 15 '25 19:12

merry


You can compute the centroid based on this example, taken from this question. There is probably an easier way, but using it with d3.js could be like this:

function area(pts) {
    var area=0;
    var nPts = pts.length;
    var j=nPts-1;
    var p1; var p2;

    for (var i=0;i<nPts;j=i++) {
        p1=pts[i]; p2=pts[j];
        area+=p1.x*p2.y;
        area-=p1.y*p2.x;
    }
    area/=2;
    return area;
};

function computeCentroid(pts) {
    var nPts = pts.length;
    var x=0; var y=0;
    var f;
    var j=nPts-1;
    var p1; var p2;

    for (var i=0;i<nPts;j=i++) {
        p1=pts[i]; p2=pts[j];
        f=p1.x*p2.y-p2.x*p1.y;
        x+=(p1.x+p2.x)*f;
        y+=(p1.y+p2.y)*f;
    }

    f=area(pts)*6;
    return [x/f,y/f];
};

var path = d3.geo.path().projection(projection);

var map = chartSvg.selectAll('path.feature')
    .data(mapFiltered);
map.enter().append('path')
    .attr('class', 'feature');
map.attr('d', function(d, i){
    var pathString = path(d,i);
    var pathStringTrimmed = pathString.substring(1, pathString.length-1);
    var pathArray = pathStringTrimmed.split('L').map(function(d, i){var xy = d.split(','); return {x: ~~xy[0], y: ~~xy[1]};});
    console.log(computeCentroid(pathArray));
    return path(d,i);})

Edit:

This example was starting with a GeoJSON file with normalized paths. From your SVG, you will have to normalize the paths yourself, as the normalizedPathSegList attribute is not populated. You can do it with Inkscape, for example, by saving it to pdf and re-importing it to an SVG file. Here is a way to convert all relative paths to absolute with javascript.

BTW, an alternative to parsing the raw d string is to query the SVGPathSegList of every elements with SVGPathSegList.getItem().

But to really use d3 for loading, projecting and finding the centroid for you, it would be much easier if you start with the default GeoJSON world map or with another one you find or you make.

like image 23
Biovisualize Avatar answered Dec 15 '25 19:12

Biovisualize



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!