5

In my data I have values associated with countries. I have created scaled circles for each country and would now like to position them at the centre of each country using cx and cy.

I have generated a map using topoJSON that has country-code 'ids' and I have matching country-codes in my data (cd).

{"type": "Polygon",
"id": 604,
"arcs": [
  [133, -473, -448, -378, -374, -413]
 ]
},

Using D3's path.centroid(feature), how can I find the centroid of each topoJSON path?

g.selectAll("circle")
    .data(cd)
    .enter()
    .append("circle")
    .attr("class", "bubble")
    .attr("cx", 50)
    .attr("cy", 50)
    .attr("r", function(d) {
      return r(+d.Value)
    })

g.selectAll("path")
  .data(topojson.object(topology, topology.objects.countries)
    .geometries)
  .enter()
  .append("path")
  .attr("d", path)

Full code here Plunker

user3821345
  • 648
  • 1
  • 11
  • 33

2 Answers2

6

One way to do it would be like this:

  // bind the map data
  var paths = g.selectAll("path")
    .data(topojson.object(topology, topology.objects.countries)
      .geometries)
    .enter()
    .append("path")
    .attr("d", path);

  g.selectAll("circle")
    .data(cd)
    .enter()
    .append("circle")
    .attr("class", "bubble")
    .attr("r", function(d){
      return r(+d.Value);
    })
    // using the map data
    // position a circle for matches in cd array
    .attr("transform", function(d) {
      for (var i = 0; i < paths.data().length; i++){
        var p = paths.data()[i];
        if (p.id === d["country-code"]){
          var t = path.centroid(p);
          return "translate(" + t + ")";
        }
      }
    });

Updated plunker

For Comments

In the situation you describe, I always stash the x/y position in my data array:

g.selectAll("circle")
    .data(cd)
    .enter()
    .append("circle")
    .attr("class", "bubble")
    .attr("r", function(d){
      return r(+d.Value);
    })
    .attr("cx", function(d) {
      for (var i = 0; i < paths.data().length; i++){
        var p = paths.data()[i];
        if (p.id === d["country-code"]){
          var t = path.centroid(p);
          d.x = t[0];
          d.y = t[1];
          return d.x;
        }
      }
    })
    .attr("cy", function(d){
      return d.y;
    })

The objects in your cd array will now have additional properties of x/y pixel positions.

Updated plunker two.

Mark
  • 106,305
  • 20
  • 172
  • 230
  • Thanks, using translate works great! How can I do this using cx and cy positioning? (I'd like to use these dimensions to position my div tooltips and text labels also) – user3821345 Mar 09 '16 at 19:46
2

I would compute the GeoJSON equivalent of the TopoJSON features, and use d3.geo.centroid to calculate the geographic center of each feature. From an example I wrote some time ago (drawing each country as a square with proportional area, centered on each country’s centroid):

var geojson = topojson.feature(data, data.objects.countries).features;

// Compute the projected centroid, area and length of the side
// of the squares.
geojson.forEach(function(d) {
  d.centroid = projection(d3.geo.centroid(d));
  // more calculations...
});

The full example is available at http://bl.ocks.org/pnavarrc/14ed098d4072be2715db

Pablo Navarro
  • 8,244
  • 2
  • 43
  • 52
  • Thanks for the example! Although I'm still having trouble converting to GeoJSON https://plnkr.co/edit/f5Ps9afPdqdSCk3zUoiv?p=info getting error that feature isn't a function. Am I referencing the wrong object? https://github.com/mbostock/topojson/wiki/API-Reference – user3821345 Mar 09 '16 at 18:35