rainbowunicorn
rainbowunicorn

Reputation: 115

Resize circles on map upon click-to-zoom?

new to D3 so I appreciate your patience! I've made a map where circles appended to city coordinates are sized upon a value in a .csv. I've also added in a click-to-zoom function from one of Mike Bostock's examples.

enter image description here

However, the circles don't resize when you zoom in. I'm not sure how to go about having them re-size or redraw upon zoom in and zoom out?

enter image description here

Any tips, examples, or suggestions are welcome! Here's my code:

<!DOCTYPE html>
<html>

<head>
  <script src="https://d3js.org/d3.v4.min.js"></script>
  <meta charset='utf-8'>
  <meta name="viewport" content='width=device-width, initial-scale=1.0'>
  <title></title>
  <style>
    .tooltip {
      line-height: 1;
      font-weight: bold;
      padding: 12px;
      background: rgba(0, 0, 0, 0.8);
      color: #fff;
      border-radius: 2px;
    }
  </style>
  <script>
    function draw(geo_data) {

      var margin = 20,
        width = 1500 - margin,
        height = 500 - margin;

      var centered;

      var svg = d3.select('#map')
        .append('svg')
        .attr('width', width + margin)
        .attr('height', height + margin)
        .append('g')
        .attr('class', 'map');

      var formatComma = d3.format(",")

      var projection = d3.geoAlbersUsa();

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

      var map = svg.selectAll('path')
        .data(geo_data.features)
        .enter()
        .append('path')
        .attr('d', path)
        .attr('fill', 'rgba(253, 227, 167, 0.8)')
        .attr('stroke', 'black')
        .attr('stroke-width', 0.4)
        .on('mouseover', function(d) {
          return d3.select(this).attr('fill', 'rgba(149, 165, 166, 0.8)')
        })
        .on('mouseout', function() {
          return d3.select(this).attr('fill', 'rgba(253, 227, 167, 0.8)')
        })
        // Why is this not working???
        .on('click', function() {
          return d3.select(this).attr('fill', 'rgba(149, 165, 166, 0.8)')
        })
        // Calls click-to-zoom function defined below.
        .on('click', clicked);

      var tooltip = svg.append('g')
        .attr('class', tooltip);

      tooltip.append('text')
        .attr('x', 15)
        .attr('class', 'tooltip')
        .attr('dy', '1.2em')
        .style('font-size', '1.5em')
        .attr('font-weight', 'bold');

      // Click-to-zoom function taken from Mike Bostock's code: https://bl.ocks.org/mbostock/2206590

      function clicked(d) {
        var x, y, k;

        if (d && centered !== d) {
          var centroid = path.centroid(d);
          x = centroid[0];
          y = centroid[1];
          k = 4;
          centered = d;
        } else {
          x = width / 2;
          y = height / 2;
          k = 1;
          centered = null;
        }

        map.selectAll('path')
          .classed('active', centered && function(d) {
            return d === centered;
          });

        map.transition()
          .duration(750)
          .attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')scale(' + k + ')translate(' + -x + ',' + -y + ')')
          .style('stroke-width', 1 / k + 'px');
      }

      d3.csv('top100cities.csv', function(error, data) {

        // Converts strings in csv to integers so they can be used.

        data.forEach(function(d) {
          return d.guns = +d.guns;
        })

        // This returns the max number of guns.

        var guns = data.map(function(d) {
          return d.guns;
        });

        var guns_extent = d3.extent(data, function(d) {
          return d.guns;
        });

        var radius = d3.scaleSqrt()
          .domain(guns_extent)
          .range([0, 50]);

        svg.append('g')
          .attr('class', 'bubble')
          .selectAll('circle')
          .data(data)
          .enter()
          .append('circle')
          .attr('cx', function(d) {
            return projection([d.lon, d.lat])[0];
          })
          .attr('cy', function(d) {
            return projection([d.lon, d.lat])[1];
          })
          .attr('r', function(d) {
            return radius(d.guns);
          })
          .attr('fill', 'rgba(103, 65, 114, 0.5)')
          .attr('stroke', 'black')
          .on('mouseover', function(d) {
            return d3.select(this).attr('fill', 'rgba(103, 65, 114, 0.8)');
            tooltip.select('text').style('opacity', 1);
          })
          .on('mousemove', function(d) {
            var xPos = d3.mouse(this)[0] - 15;
            var yPos = d3.mouse(this)[1] - 55;
            tooltip.attr('transform', 'translate(' + xPos + ',' + yPos + ')');
            tooltip.select('text').style('opacity', 1);
            tooltip.select('text').text(d.city + ', ' + d.state + ': ' + formatComma(d.guns));
          })
          .on('mouseout', function(d) {
            tooltip.select('text').style('opacity', 0);
            return d3.select(this).attr('fill', 'rgba(103, 65, 114, 0.5)');
          });


      });
    };
  </script>
</head>

<body>
  <div id='map'></div>
  <script>
    d3.json('us_states.json', draw);
  </script>
</body>

Upvotes: 1

Views: 921

Answers (1)

Xavier Guihot
Xavier Guihot

Reputation: 61666

You can do exactly the same as you did for the map:

Within the clicked function, just after the transform on path items, you can insert the following transformation which applies on circles:

svg.selectAll('circle')
  .transition()
  .duration(750)
  .attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')scale(' + k + ')translate(' + -x + ',' + -y + ')')
  .style('stroke-width', 1 / k + 'px');

<!DOCTYPE html>
<html>

<head>
  <script src="https://d3js.org/d3.v4.min.js"></script>
  <meta charset='utf-8'>
  <meta name="viewport" content='width=device-width, initial-scale=1.0'>
  <title></title>
  <style>
    .tooltip {
      line-height: 1;
      font-weight: bold;
      padding: 12px;
      background: rgba(0, 0, 0, 0.8);
      color: #fff;
      border-radius: 2px;
    }
  </style>
  <script>
    function draw(geo_data) {

      var margin = 20,
        width = 1500 - margin,
        height = 500 - margin;

      var centered;

      var svg = d3.select('#map')
        .append('svg')
        .attr('width', width + margin)
        .attr('height', height + margin)
        .append('g')
        .attr('class', 'map');

      var formatComma = d3.format(",")

      var projection = d3.geoAlbersUsa();

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

      var map = svg.selectAll('path')
        .data(geo_data.features)
        .enter()
        .append('path')
        .attr('d', path)
        .attr('fill', 'rgba(253, 227, 167, 0.8)')
        .attr('stroke', 'black')
        .attr('stroke-width', 0.4)
        .on('mouseover', function(d) {
          return d3.select(this).attr('fill', 'rgba(149, 165, 166, 0.8)')
        })
        .on('mouseout', function() {
          return d3.select(this).attr('fill', 'rgba(253, 227, 167, 0.8)')
        })
        // Why is this not working???
        .on('click', function() {
          return d3.select(this).attr('fill', 'rgba(149, 165, 166, 0.8)')
        })
        // Calls click-to-zoom function defined below.
        .on('click', clicked);

      var tooltip = svg.append('g')
        .attr('class', tooltip);

      tooltip.append('text')
        .attr('x', 15)
        .attr('class', 'tooltip')
        .attr('dy', '1.2em')
        .style('font-size', '1.5em')
        .attr('font-weight', 'bold');

      // Click-to-zoom function taken from Mike Bostock's code: https://bl.ocks.org/mbostock/2206590

      function clicked(d) {
        var x, y, k;

        if (d && centered !== d) {
          var centroid = path.centroid(d);
          x = centroid[0];
          y = centroid[1];
          k = 4;
          centered = d;
        } else {
          x = width / 2;
          y = height / 2;
          k = 1;
          centered = null;
        }

        map.selectAll('path')
          .classed('active', centered && function(d) {
            return d === centered;
          });

        map.transition()
          .duration(750)
          .attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')scale(' + k + ')translate(' + -x + ',' + -y + ')')
          .style('stroke-width', 1 / k + 'px');

        svg.selectAll('circle')
          .transition()
          .duration(750)
          .attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')scale(' + k + ')translate(' + -x + ',' + -y + ')')
          .style('stroke-width', 1 / k + 'px');
      }

      d3.csv('https://raw.githubusercontent.com/dieterholger/US-Gun-Manufacturing-Interactive/master/top100cities.csv', function(error, data) {

        // Converts strings in csv to integers so they can be used.

        data.forEach(function(d) {
          return d.guns = +d.guns;
        })

        // This returns the max number of guns.

        var guns = data.map(function(d) {
          return d.guns;
        });

        var guns_extent = d3.extent(data, function(d) {
          return d.guns;
        });

        var radius = d3.scaleSqrt()
          .domain(guns_extent)
          .range([0, 50]);

        svg.append('g')
          .attr('class', 'bubble')
          .selectAll('circle')
          .data(data)
          .enter()
          .append('circle')
          .attr('cx', function(d) {
            return projection([d.lon, d.lat])[0];
          })
          .attr('cy', function(d) {
            return projection([d.lon, d.lat])[1];
          })
          .attr('r', function(d) {
            return radius(d.guns);
          })
          .attr('fill', 'rgba(103, 65, 114, 0.5)')
          .attr('stroke', 'black')
          .on('mouseover', function(d) {
            return d3.select(this).attr('fill', 'rgba(103, 65, 114, 0.8)');
            tooltip.select('text').style('opacity', 1);
          })
          .on('mousemove', function(d) {
            var xPos = d3.mouse(this)[0] - 15;
            var yPos = d3.mouse(this)[1] - 55;
            tooltip.attr('transform', 'translate(' + xPos + ',' + yPos + ')');
            tooltip.select('text').style('opacity', 1);
            tooltip.select('text').text(d.city + ', ' + d.state + ': ' + formatComma(d.guns));
          })
          .on('mouseout', function(d) {
            tooltip.select('text').style('opacity', 0);
            return d3.select(this).attr('fill', 'rgba(103, 65, 114, 0.5)');
          });


      });
    };
  </script>
</head>

<body>
  <div id='map'></div>
  <script>
    d3.json('https://raw.githubusercontent.com/dieterholger/US-Gun-Manufacturing-Interactive/master/us_states.json', draw);
  </script>
</body>

Upvotes: 3

Related Questions