lnNoam
lnNoam

Reputation: 1055

Resize points based on zoom

I am trying to create a map in d3.js where the points (US Science funding agencies), read in from a .csv, scale with zoom level (based on this starter kit). I have seen other solutions to the problem elsewhere, but they are often instantiated in much more complex projects (making it very hard for me to extract this capability from them).

Read in data:

var data;
function draw(){
    ....
    d3.csv("data/funders.csv", function(err, locations) {
      locations.forEach(function(i){
          addPoint(i['lng'], i['lat'], i['TotalFunding']);
      });
    });
}

I define svg like this:

svg = d3.select("#container").append("svg")
    .attr("width", width)
    .attr("height", height)
    .call(zoom) // my zoom function
    .on("click", click) // my click function
    .append("g");

g = svg.append("g");

Next, I've defined a function that appends "g" to the SVG and then I proceed to add a class "gpoint" (geographic point).

function addPoint(lat, lon, size){

    var gpoint = g.append("g").attr("class", "gpoint");
    var location = projection([lat,lon])
    var x = location[0];
    var y = location[1];

// Here I append 'circle' to the svg.
    gpoint.append("svg:circle")
          .attr("cx", x)
          .attr("cy", y)
          .attr("class","point")
          .style("fill", "blue")
          .attr("r", size/10); //*
}

*this original size information needs to be preserved, just scaled.

Here, I'd like to multiply size by the current level of zoom (which can be obtained from var scale = d3.event.scale;). In the code I have, I use scale to adjust a CSS element controlling the stroke-width of country outlines with this:

d3.selectAll(".country").style("stroke-width", 1.5 / scale);

However, this is easy because I can create a simple chain to access the '.country' CSS and mutate this property. However, I am unclear on how I would select and mutate elements in this gpoint class.

I'm happy to add any additional information, I'm just becoming wary of posting a wall of code.

funders.csv:

funder,lat,lng,TotalFunding
NIH,39.000443,-77.102394,5000
NASA,38.883,-77.0163,1000

Edit

I've found that I can alter the radius of the circles with

g.attr("class", "gpoint").selectAll("circle").attr("r", s)

However, I still am having trouble accessing the current radius of the circle and mutating it, e.g.,

g.attr("class", "gpoint").selectAll("circle").data(data).attr("r", function(d){return(d.r*s);}) 

Edit 2

Thanks to the help from @kscandrett, I was able to make this work.

One of the requirements was that the original size was maintained. Simply altering 'r' will not do this, but the amount information can be set as the dot's id when the points are created (I'm sure there are better solutions, such as using an object to store this information...but, this works).

1.

Save the funding amount information as the dot's 'id' when they're being created (again, probably not idiomatic, but it will do for now).

gpoint.append("svg:circle")
//...
.attr("id", Math.sqrt(parseInt(amount) * 0.001))
.attr("r", Math.sqrt(parseInt(amount) * 0.001))
//...

EDIT 3:

I now know how to do this 'properly'. Instead of attr, one should use datum to attach this information to each circle, i.e., .datum(Math.sqrt(parseInt(amount) * 0.001)).

2.

Define a pointScale Function

  function pointScale(amount, zoomScale){
  // Ugly code that will almost certainly be replaced, but it is good enough for now.
        var maxPossibleZoom = 100; 
        var sizeFloor = 0.12;
        var size = amount * (maxPossibleZoom/zoomScale*0.05);
        if (size > amount){
            return amount;
        } else if (size < sizeFloor){
            return sizeFloor * amount;
        } else {
            return size;
        }
   }
3.

The starter kit I link to above has a move() function. The final step is to add some code to it using @kscandrett's answer to extract the value assigned to attr('id') and use d3.event.scale to scale this value based on the current level of zoom. ...

function move(){
//...
   var s = d3.event.scale;
//...
   d3.selectAll('circle').attr('r', function (d, i){
     var amount = d3.select(this).attr('id');
     return pointScale(amount, s); 
   });
}

Works perfectly!

Upvotes: 3

Views: 834

Answers (1)

K Scandrett
K Scandrett

Reputation: 16540

This example will increase the circles by 10%

function changeSize() {
  d3.selectAll('circle').attr('r', function (d, i)
  {
    return d3.select(this).attr('r') * 1.1;
  });
}

Example: http://codepen.io/anon/pen/vXRdGx

As a starting point I used some data created by Jerome Cuckier http://www.jeromecukier.net/blog/2012/05/28/manipulating-data-like-a-boss-with-d3/

Upvotes: 2

Related Questions