block by larskotthoff 11406992

11406992

Full Screen

Automatic Label Placement

This example is an extension of Mike Bostock’s tutorial Lets Make a Map that implements automatic label placement using collision detection. This is a proof of concept more than anything, not polished in any way.

References

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8"/>
    <title>Automatic Label Placement</title>
    <script src="//d3js.org/d3.v3.min.js"></script>
    <script src="//d3js.org/topojson.v1.min.js"></script>

    <style>
        .subunit.SCT { fill: #ddc; }
        .subunit.WLS { fill: #cdd; }
        .subunit.NIR { fill: #cdc; }
        .subunit.ENG { fill: #dcd; }

        .subunit.IRL,
        .subunit-label.IRL {
            display: none;
        }

        .subunit-boundary {
            fill: none;
            stroke: #777;
            stroke-dasharray: 2,2;
            stroke-linejoin: round;
        }

        .subunit-boundary.IRL {
            stroke: #aaa;
        }

        .place,
        .place-label {
            fill: #444;
        }

        text {
            font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
            font-size: 24px;
            pointer-events: none;
        }
    </style>
</head>

<body>
<script>
    var width = 960,
        height = 1160;

    var projection = d3.geo.albers()
        .center([0, 55.4])
        .rotate([4.4, 0])
        .parallels([50, 60])
        .scale(1200 * 5)
        .translate([width / 2, height / 2]);

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

    var svg = d3.select("body").append("svg")
        .attr("width", width)
        .attr("height", height);

    d3.json("uk.json", function(error, uk) {

        var subunits = topojson.feature(uk, uk.objects.subunits),
        places = topojson.feature(uk, uk.objects.places);

        svg.selectAll(".subunit")
            .data(subunits.features)
            .enter().append("path")
            .attr("class", function(d) { return "subunit " + d.id; })
            .attr("d", path);

        svg.append("path")
            .datum(topojson.mesh(uk, uk.objects.subunits, function(a, b) { return a !== b && a.id !== "IRL"; }))
            .attr("d", path)
            .attr("class", "subunit-boundary");

        svg.append("path")
            .datum(topojson.mesh(uk, uk.objects.subunits, function(a, b) { return a === b && a.id === "IRL"; }))
            .attr("d", path)
            .attr("class", "subunit-boundary IRL");

        svg.append("path")
            .datum(places)
            .attr("d", path)
            .attr("class", "place");

        svg.selectAll(".place-label")
          .data(topojson.feature(uk, uk.objects.places).features)
        .enter().append("text")
          .attr("class", "place-label")
          .attr("transform", function(d) { return "translate(" + projection(d.geometry.coordinates) + ")"; })
          .attr("x", function(d) { return d.geometry.coordinates[0] > -1 ? 6 : -6; })
          .attr("dy", ".35em")
          .style("text-anchor", function(d) { return d.geometry.coordinates[0] > -1 ? "start" : "end"; })
          .text(function(d) { return d.properties.name; });

       arrangeLabels();
    });

function arrangeLabels() {
  var move = 1;
  while(move > 0) {
    move = 0;
    svg.selectAll(".place-label")
       .each(function() {
         var that = this,
             a = this.getBoundingClientRect();
         svg.selectAll(".place-label")
            .each(function() {
              if(this != that) {
                var b = this.getBoundingClientRect();
                if((Math.abs(a.left - b.left) * 2 < (a.width + b.width)) &&
                   (Math.abs(a.top - b.top) * 2 < (a.height + b.height))) {
                  // overlap, move labels
                  var dx = (Math.max(0, a.right - b.left) +
                           Math.min(0, a.left - b.right)) * 0.01,
                      dy = (Math.max(0, a.bottom - b.top) +
                           Math.min(0, a.top - b.bottom)) * 0.02,
                      tt = d3.transform(d3.select(this).attr("transform")),
                      to = d3.transform(d3.select(that).attr("transform"));
                  move += Math.abs(dx) + Math.abs(dy);
                
                  to.translate = [ to.translate[0] + dx, to.translate[1] + dy ];
                  tt.translate = [ tt.translate[0] - dx, tt.translate[1] - dy ];
                  d3.select(this).attr("transform", "translate(" + tt.translate + ")");
                  d3.select(that).attr("transform", "translate(" + to.translate + ")");
                  a = this.getBoundingClientRect();
                }
              }
            });
       });
  }
}

</script>

</body>
</html>