We simulate push-pins marking locations on a map. We use Force layout to position the pins as close to where they belong as possible. This may not be very close in geographic terms. However, the energy minimizing placement looks sensible on maps of large regions with densely clustered locations.
xxxxxxxxxx
<html>
<head>
<title>Factories</title>
<script src="https://d3js.org/d3.v3.min.js"></script>
<style type="text/css">
.legend { position: reletave; }
#year { color:darkGray; font-size: 36px; font-family: "Helvetica Neue", Helvetica, sans-serif; position: absolute; left: 30px; top: 30px; }
path { fill: #ccc; stroke: #fff; }
svg { border: solid 1px #ccc; background: #eee; }
</style>
</head>
<body>
<div legend><span id=year>2009</span></div>
<div id="chart"></div>
<script type="text/javascript">
// Use a map projection to scale lat/lng data
var width = 960,
height = 500;
var xy = d3.geo.mercator()
.scale(500)
.translate([-600, 500]);
var path = d3.geo.path()
.projection(xy);
var fill = d3.scale
.category10();
function animate (world, factories) {
factories = factories.filter(function(o){ return o.year == 2009 });
factories = factories.map(function(f){
f.coordinates = [f.lng, f.lat];
// Set starting position
f.x = 0;
f.y = 0;
// Set destination position for the circle
f.fx = xy(f.coordinates)[0];
f.fy = xy(f.coordinates)[1];
return f;
});
// Create force layout to give sense of charge.
var force = d3.layout.force()
.nodes(factories)
.links([])
.size([width, height])
.friction(.65)
.start();
// Add SVG tag to stage.
var vis = d3.select("#chart").append("svg")
.attr("width", width)
.attr("height", height);
// Create the paths for countries
var states = vis
.append("g")
.attr("id", "states");
states
.selectAll("path")
.data(world.features)
.enter().append("path")
.attr("d", path)
.append("title")
.text(function(d) { return d.properties.name; });
// Create the marks for factories
var node = vis.selectAll("circle.node")
.data(factories)
// Get nodes that are entering stage.
.enter().append("circle")
// Set attributes on entering nodes.
.attr("opacity", 0)
.attr("class", "node")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", 8)
.attr("title", function(d) {return d.crcode;})
.style("fill", function(d, i) { return d3.rgb(fill(d.country)).darker(Math.random()); })
// .style("stroke", function(d, i) { return d3.rgb(fill(i & 3)).darker(2); })
// .style("stroke-width", 1.5)
.call(force.drag)
.transition().duration(900)
.attr('opacity', 1);
force.on("tick", function(e) {
// Push nodes toward their designated focus.
var k = .3 * e.alpha;
factories.forEach(function(o, i) {
// Push nodes towards location set above.
o.y += (o.fy - o.y) * k;
o.x += (o.fx - o.x) * k;
});
// Move the marks to new location
vis.selectAll("circle.node")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
force.resume(); // don't know when to stop
});
}
d3.json("world-countries.json", function(world) {
d3.json("factories-condensed.json", function(factories) {
animate(world, factories);
});
});
</script>
</body>
</html>
Modified http://d3js.org/d3.v3.min.js to a secure url
https://d3js.org/d3.v3.min.js