TZschu
TZschu

Reputation: 25

Create a responsive D3 map with svg filter dropshadow

I am very new to D3 map, and I have been trying to create a responsive D3 map with drop shadow effect using svg filter.

The example code is adapted from blocks by Mike Bostock (drop shadow) & Darren Jaworski (responsive projection). You can view the example code on Codepen. Following is my JS code:

var width = parseInt(d3.select('#mapContainer').style('width'))
  ,height = width/960*600;

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

var defs = svg.select("defs");


d3.json("https://d3js.org/us-10m.v1.json", function(error, us) {
  if (error) throw error;
  var path = d3.geoPath();

  var projection = d3.geoIdentity()
  .scale(width)
  .translate([width / 2, height / 2])
  .fitSize([width, height],topojson.feature(us, us.objects.states));

  defs.append("path")
      .attr("id", "nation")
      .attr("d", path(topojson.feature(us, us.objects.nation)));

  svg.append("use")
      .attr("xlink:href", "#nation")
      .attr("fill-opacity", 0.2)
      .attr("filter", "url(#blur)");

  svg.append("use")
      .attr("xlink:href", "#nation")
      .attr("fill", "#fff");

  svg.append("path")
      .attr("fill", "none")
      .attr("stroke", "#777")
      .attr("stroke-width", 0.70)
      .attr("d", path(topojson.mesh(us, us.objects.states, function(a, b) { return a !== b; })));

 // resize event
 d3.select(window)
            .on("resize", resize);


 // function to resize map 
  function resize() {
      // adjust things when the window size changes
      width = parseInt(d3.select('#mapContainer').style('width'));
      //console.log(parseInt(d3.select('#mapContainer').style('width')));
      height = width/960*600;
      svg.attr('width', width).attr('height', height);
      // update projection
      projection
          .scale(width)
          .translate([width / 2, height / 2])
          .fitSize([width, height],topojson.feature(us, us.objects.states));

      // resize the map
      svg.selectAll('path').attr('d', path);
      svg.select('.land').attr('d', path);
      svg.select('.state').attr('d', path);
  }
});

In the example, the resize function doesn't seem to work. On window resize, the map would disappear, so would the svg filter appended to it.

Could someone enlighten me on what went wrong? And if someone could provide a fix?

Thank you very much for your help!


I updated the codepen on Responsive US D3 Map with Drop Shadow with @rioV8's great answer.

Upvotes: 0

Views: 737

Answers (1)

rioV8
rioV8

Reputation: 28868

  • Have you ever considered to connect your projection and path generator?

    var path = d3.geoPath(projection);
    
  • Also set the path in the resize method. Label the 2 path elements

    svg.select('#nation').attr('d', path(topojson.feature(us, us.objects.nation)));
    svg.select('#nation2').attr('d', path(topojson.mesh(us, us.objects.states, function(a, b) { return a !== b; })));
    
  • the onload in the body has to go

     <body>
    
  • there are no tags with class land or state

    //svg.select('.land').attr('d', path);
    //svg.select('.state').attr('d', path);
    
  • why call projection size and translate if you call fitSize

Here is the script code

var width = parseInt(d3.select('#mapContainer').style('width')) - 50
  ,height = width/960*600;

var svg = d3.select("svg")
  .attr("width", width + 50)
  .attr("height",height);

var defs = svg.select("defs");

d3.json("https://d3js.org/us-10m.v1.json", function(error, us) {
  if (error) throw error;

  var topoFeatureStates = topojson.feature(us, us.objects.states);
  var projection = d3.geoIdentity()
      .fitSize([width, height], topoFeatureStates);

  var path = d3.geoPath(projection);

  defs.append("path")
      .datum(topojson.feature(us, us.objects.nation))
      .attr("id", "nation")
      .attr("d", path);

  svg.append("use")
      .attr("xlink:href", "#nation")
      .attr("fill-opacity", 0.2)
      .attr("filter", "url(#blur)");

  svg.append("use")
      .attr("xlink:href", "#nation")
      .attr("fill", "#fff");

  svg.append("path")
      .datum(topojson.mesh(us, us.objects.states, function(a, b) { return a !== b; }))
      .attr("fill", "none")
      .attr("stroke", "#777")
      .attr("stroke-width", 0.70)
      .attr("d", path);


 // function to resize map 
  function resize() {
      // adjust things when the window size changes
      width = parseInt(d3.select('#mapContainer').style('width')) - 50;
      //console.log(parseInt(d3.select('#mapContainer').style('width')));
      height = width/960*600;
      svg.attr('width', width + 50).attr('height', height);
      // update projection
      projection
          .fitSize([width, height], topoFeatureStates);

      // resize the map
      svg.selectAll('path').attr('d', path);
  }

  // resize event
  d3.select(window).on("resize", resize);
});

Edit

Wondering why the path in the resize did not I work had a second look at the responsive example. It attaches the result of topojson as datum to the path.

Modified the code to do the same.

Upvotes: 1

Related Questions