Home > Enterprise >  d3js v4: Scale circles with zoom
d3js v4: Scale circles with zoom

Time:11-18

I have a world map made with d3js v4 and topojson which has Zoom / Drag / Circles. Everything seems fine except I cant scale the circles togheter with the zoom.

When I scroll into the map, my circles stay at the same size, which makes them way to big compared to the map.

How can I apply the transformation to the circles when I zoom?

  var width = 660,
      height = 400;

      var zoom = d3.zoom()
          .scaleExtent([1, 10])
          .on("zoom", zoomed);


  var projection = d3.geoMercator()
      .center([50, 10]) //long and lat starting position
      .scale(150) //starting zoom position
      .rotate([10,0]); //where world split occurs

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

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

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


//Zoom functionality
  function zoomed() {
const currentTransform = d3.event.transform;
g.attr("transform", currentTransform);
}

d3.select(".zoom-in").on("click", function() {
  zoom.scaleBy(svg.transition().duration(750), 1.2);
});
d3.select(".zoom-out").on("click", function() {
  zoom.scaleBy(svg.transition().duration(750), 0.8);
});

  // load and display the world and locations
  d3.json("https://gist.githubusercontent.com/d3noob/5193723/raw/world-110m2.json", function(error, topology) {

  var world = g.selectAll("path")
                .data(topojson.object(topology, topology.objects.countries).geometries)
                .enter()
                .append("path")
                .attr("d", path)
          ;


  var locations = g.selectAll("circle")
        .data(devicesAll)
        .enter()
        .append("circle")
        .attr("cx", function(d) {return projection([d.LastLocation.lon, d.LastLocation.lat])[0];})
        .attr("cy", function(d) {return projection([d.LastLocation.lon, d.LastLocation.lat])[1];})
        .attr("r", 2)
        .style("fill", "black")
        .style("opacity", 1)
        ;
        var simulation = d3.forceSimulation()
            .force('x', d3.forceX().x(function(d) {return projection([d.LastLocation.lon, d.LastLocation.lat])[0]}))
            .force('y', d3.forceY().y(function(d) {return projection([d.LastLocation.lon, d.LastLocation.lat])[1]}))
            .force("charge", d3.forceManyBody().strength(0.5)) // Nodes are attracted one each other of value is > 0
            .force("collide", d3.forceCollide().strength(.1).radius(2).iterations(2)) // Force that avoids circle overlapping

        // Apply these forces to the nodes and update their positions.
        // Once the force algorithm is happy with positions ('alpha' value is low enough), simulations will stop.
        simulation
            .nodes(devicesAll)
            .on("tick", function(d){
              locations
                  .attr("cx", function(d){ return d.x; })
                  .attr("cy", function(d){ return d.y; })
            });

CodePudding user response:

If i understood your problem correctly, you need to add it to your zoom behaviour.

//Zoom functionality
  function zoomed() {
const currentTransform = d3.event.transform;
g.attr("transform", currentTransform);
}

here, you are applying your transformation to the elements, which is fine. However, you're not applying any logic to the radius. That logic is up to you to make, and it will depend on the k property of the transform event (currentTransform.k). I will use a some dummy logic for your radius. Your scale extent is between 1 and 10, you need a logic in which the radius decreases as the zoom increases (bigger k). It is also important that your radius doesn't go lower than 1, because the area of the circle will decrease much faster (remember the area depends on r^2, and r^2 < r for r < 1) So my logic will be: the radius is 2.1 - (k / 10). Again, I'm oversimplifying, you can change it or tune it for your specific case.

In the end, it should look something like this:

//Zoom functionality
  function zoomed() {
const currentTransform = d3.event.transform;
g.attr("transform", currentTransform);

g.selectAll("circle")
     .attr("r", 2.1 - (currentTransform.k / 10))
}

I haven't tested the code, but tell me if this works! Maybe you can add it to a jsfiddle if needed

  • Related