Question

Been away from d3.js for a few months... and I've inherited a simple US map with features that someone else started.

The features are represented by simple dots of varying sizes.

I want to add emanating concentric circles to each dot, similar to the classic Onion example by Mike Bostock: http://bl.ocks.org/mbostock/4503672 (maybe not so ominous looking though)

I've got a block set up here: http://bl.ocks.org/mbostock/4503672

(Not sure why the states aren't rendering correctly in the block, but it probably doesn't matter for this.)

In Mike's example there is only one dot, so I'm have trouble understanding how to translate what he did to what I've got (many dots).

Here's my script:

/**
 * Page initialization
 */
$(function() {
    renderMap('#map-container');
});

function renderMap(container) {
    var width = 960,
        height = 500,
        active;

    var projection = d3.geo.albersUsa()
        .scale(960)
        .translate([width / 2, height / 2]);

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

    var radius = d3.scale.sqrt()
        .domain([0, 1e7])
        .range([0, 10]);

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

    //  Remove svg, if already exist
    d3.select(container).select('svg').remove();

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

    svg.append("rect")
        .attr("width", width)
        .attr("height", height);
        //.on("click", reset);

    var g = svg.append("g");

    queue()
        .defer(d3.json, "/mbostock/raw/4090846/us.json")
        .defer(d3.json, "dots.json")
        .await(function (error, us, centroid) {
            g.append("g")
                .attr("id", "states")
                .selectAll("path")
                .data(topojson.feature(us, us.objects.states).features)
                .enter().append("path")
                .attr("d", path)
                .attr("class", "state");
                //.on('click', click);

            g.append('path')
                .attr("id", "state-borders")
                .datum(topojson.mesh(us, us.objects.states, function(a, b) { return a !== b; }))
                .attr("d", path)
                .attr("class", "mesh");

            var dots = g.append("g")
                .attr("id", "dots")
                .selectAll("path")
                .data(centroid.data)
              .enter().append("path")
                .attr("class", "dot")
                .attr("d", path2.pointRadius(function(d) { return radius(d.properties.pool); }));
        }    
    );

 }

and the key part of Mike's example for making the rings is:

setInterval(function() {
  svg.append("circle")
      .attr("class", "ring")
      .attr("transform", "translate(" + projection([100, -8]) + ")")
      .attr("r", 6)
      .style("stroke-width", 3)
      .style("stroke", "red")
    .transition()
      .ease("linear")
      .duration(6000)
      .style("stroke-opacity", 1e-6)
      .style("stroke-width", 1)
      .style("stroke", "brown")
      .attr("r", 160)
      .remove();
}, 750);

how do I get the rings positioned on the dots?

Was it helpful?

Solution

Review the differences between the two methods to learn a little bit more about how functional/declarative programming abstracts away the pain of iterative programming.

Approach with D3 idioms:

fiddle: http://jsfiddle.net/blakedietz/E66eT/1/

update: D3 Way

<!DOCTYPE html>
<html>
<head>
    <title></title>

    <style>
        body {
              background: #192887;
            }

            .graticule {
              fill: none;
              stroke: #fff;
              stroke-width: .5px;
            }

            .land {
              fill: #007421;
            }

            .dot {
              fill: #c7141a;
            }

            .ring {
              fill: none;
              stroke: #c7141a;
            }
    </style>

    <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</head>
<body>
<script>

var width = 960,
    height = 500;

var projection = d3.geo.mercator()
    .center([113, -3])
    .scale(1275)
    .translate([width / 2, height / 2])
    .clipExtent([[0, 0], [width, height]])
    .precision(.1);

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

var graticule = d3.geo.graticule()
    .step([5, 5]);

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

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


var data = [{x:-8,y:100},{x:-10,y:110},{x: -12,y:120}];

svg.selectAll("circle")
    .data(data)
    .enter()
    .append("circle")
    .attr("class","dot")
    .attr("transform",translateCircle)
    .attr("r",8);


function translateCircle(datum, index)
          {
            return "translate(" +  projection([datum.y, datum.x]) + ")";
          };

setInterval(function(){
              svg
                .selectAll("ring")
                .data(data)
                .enter()
                .append("circle")
                  .attr("class", "ring")
                  .attr("transform", translateCircle)
                  .attr("r", 6)
                  .style("stroke-width", 3)
                  .style("stroke", "red")
                .transition()
                  .ease("linear")
                  .duration(6000)
                  .style("stroke-opacity", 1e-6)
                  .style("stroke-width", 1)
                  .style("stroke", "brown")
                  .attr("r", 160)
                  .remove();
      }, 750)


d3.select(self.frameElement).style("height", height + "px");    
</script>

</body>
</html>

So I didn't create a fully d3 idiomatic approach for this solution, but it will work. If you can get this to work implicitly within a svg.selectAll("circle"/"unique selection"...), etc, that would be even more awesome. I'll work on that in the mean time. Until then there's this more explicitly iterative approach.

With Mike's example you're only appending a single element to the D.O.M. in the setInterval call. In order to expedite the binding process, I've created a projection method which operates on a set of coordinates: the translateCircle will operate on a datum within a collection of coordinates allowing access to the internal attributes of each collection element.

Within each setInterval call the forEach method iterates over the collection of coordinates and then calls the same internals within the setInterval method that was called by Mike originally.

Not So D3

    <style>
        body {
              background: #192887;
            }

            .graticule {
              fill: none;
              stroke: #fff;
              stroke-width: .5px;
            }

            .land {
              fill: #007421;
            }

            .dot {
              fill: #c7141a;
            }

            .ring {
              fill: none;
              stroke: #c7141a;
            }
    </style>

    <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</head>
<body>
<script>

var width = 960,
    height = 500;

var projection = d3.geo.mercator()
    .center([113, -3])
    .scale(1275)
    .translate([width / 2, height / 2])
    .clipExtent([[0, 0], [width, height]])
    .precision(.1);

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

var graticule = d3.geo.graticule()
    .step([5, 5]);

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

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


var data = [{x:-8,y:100},{x:-10,y:110},{x: -12,y:120}];

svg.selectAll("circle")
    .data(data)
    .enter()
    .append("circle")
    .attr("class","dot")
    .attr("transform",translateCircle)
    .attr("r",8);


function translateCircle(datum, index)
          {
            return "translate(" +  projection([datum.y, datum.x]) + ")";
          };

setInterval(function(){
          data.forEach(function(datum)
          {
              svg
                .append("circle")
                  .attr("class", "ring")
                  .attr("transform", translateCircle(datum))
                  .attr("r", 6)
                  .style("stroke-width", 3)
                  .style("stroke", "red")
                .transition()
                  .ease("linear")
                  .duration(6000)
                  .style("stroke-opacity", 1e-6)
                  .style("stroke-width", 1)
                  .style("stroke", "brown")
                  .attr("r", 160)
                  .remove();
          })
      }, 750)


d3.select(self.frameElement).style("height", height + "px");    
</script>

</body>
</html>
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top