2

US map with d3.v3 using Mike Bostock's example:

US map with d3.v3 using mike bostock's example

I want the map to zoom into the marked locations initially when the page loads but the entire map should be rendered so that a user can zoom out if he wants to.

var w = 300;
var h = 280;
//Define map projection
var projection = d3.geo.albersUsa()
                   .translate([w/2, h/2])
                   .scale([300]);

//Define path generator
var path = d3.geo.path()
             .projection(projection);



//Create SVG element
var svg = d3.select("#map1").append("svg")
                        .attr("width", w)
                        .attr("height", h)
var g = svg.append("g");

var tooltip = d3.select("body")
.append("div")
.style("position", "absolute")
.style("z-index", "1000")
.style('opacity', 0)
.style("font-family", "sans-serif")
.style("background-color", "white")
.style("border-radius", "5px")
.style("padding", "10px")
.style('color', '#000')
.style("font-size", "12px");

//Load in GeoJSON data
d3.json("us-states.json", function(json) {
        d3.csv("cities.csv", function(error, data) {    
            g.selectAll("circle")
                .data(data)
                .enter()
               .append("circle")
                .attr("cx", function(d) {
                    return projection([d.longi, d.lati])[0];
                })
                .attr("cy", function(d) {
                    return projection([d.longi, d.lati])[1];
                })
                .attr("r", 4)
                .style("fill", "#4F6D88")
                .on("mouseover", function(d){
                    tooltip.transition().style("opacity", 0.9)
                    .style('left', (d3.event.pageX) + 'px')
                    .style('top', (d3.event.pageY) + 'px')
                    .text(d.city)
                })
                .on("mousemove", function(event){
                    tooltip.style("top", (event.pageY-10)+"px").style("left",(event.pageX+10)+"px");
                })
                .on("mouseout", function(){
                    tooltip.transition().delay(500).style("opacity", 0);
                });
        });

        //Bind data and create one path per GeoJSON feature
        g.selectAll("path")
           .data(json.features)
           .enter()
           .append("path")
           .attr("d", path);

});
    var zoom = d3.behavior.zoom()
    .scaleExtent([1, 50])
    .on("zoom", function() {
      var e = d3.event,
          tx = Math.min(0, Math.max(e.translate[0], w - w * e.scale)),
          ty = Math.min(0, Math.max(e.translate[1], h - h * e.scale));
      zoom.translate([tx, ty]);
      g.attr("transform", [
        "translate(" + [tx, ty] + ")",
        "scale(" + e.scale + ")"
      ].join(" "));
    });
svg.call(zoom)

I have the code to zoom in with scroll which i have pasted above but i want it to zoom on load to those specific locations. How i want it to be:

How i want it to be

Gerardo Furtado
  • 100,839
  • 9
  • 121
  • 171
Muzaffarhssn5
  • 25
  • 1
  • 7

1 Answers1

1

There are two primary ways to zoom a map in d3:

  • modify the projection which will re-draw the paths, or
  • modify the drawn paths with scale and transform.

Modifying the projection is easiest in d3v4 with fitSize or fitExtent - though you would need to turn your points into geojson. You can also manually calculate the translate and scale values to update a projection (see this answer by Mike Bostock which explains this common d3v3 approach).

Alternatively, you can modify the drawn paths by calling the zoom function - this question asked yesterday has an excellent example of doing so (in d3v4). Or you can calculate and apply the zoom manually and then update the zoom to indicate the current scale and translate. I'll use the common method of modifying a d3v3 projection mentioned above (with Mike's answer) and apply it to the transform on the paths - rather than modifying the projection. Though it should not be difficult to see how my answer could be changed to modify the projection instead.


First you need to determine the maximum difference between the x and y coordinates of your points. If dealing with two points, this will be fairly easy:

var data = [[-100,45],[-110,45]];
var p1 = projection(data[0]);
var p2 = projection(data[1]);

var dx = Math.abs(p1[0] - p2[0]);
var dy = Math.abs(p1[1] - p2[1]);

I'm assuming a simple data format for the sake of a shorter answer. Also, if dealing with many points, this would be a bit more complex. One potential option would be to place your points in geojson and get the bounding box of the points.

Now we need to find out the centroid of the points - in the case of two points this is just the average of the x and y values:

var x = (p1[0] + p2[0])/2;
var y = (p1[1] + p2[1])/2;

Next we need to calculate a new scale, while also determining if the scale is restricted by the difference in x values of the coordinates or the difference in y values of the coordinates:

var scale = 0.9 / Math.max( dx/w , dy/h );

The 0.9 reduces the scale slightly, it is the same as 0.9 * scale and allows a variable amount of margin. The value returned by dx/w is one over the scale value we need to stretch the difference across the width of the svg container.

(it would probably make more sense written like: var scale = 0.9 * Math.min(w/dx,h/dy); - we want to limit the zoom by the lowest scale value and multiply it by some percentage to give margins. But the other representation is ubiquitous in online examples)

Now we have a scale, we only need to determine a translate. To do so we find out how far we need to re-position the values held in the x and y variables so that those values would be centered:

var translate = [w/2 - scale * x, h/2-scale*y];

Now you can set the initial scale and translate of the map:

g.attr("transform", "translate("+translate+")scale("+scale+")");

But, you probably want to update the zoom parameters on page load to reflect the initial zoom and translate:

zoom.translate(translate);
zoom.scale(scale);

This way when you zoom in or out from the initial view, the change is relative to your initial zoom.

Now all you have to do is include the above code when you add the points. Note that this technique might require some modification if you want to return to the initial position.

Andrew Reid
  • 37,021
  • 7
  • 64
  • 83