Search code examples
d3.jsd3fc

Add zoom to d3js map using d3fc-label-layout


I am trying to add zoom to UK map with d3fc-label-layout. But when I zoom in the labels are shifted. As I understand I have to recalculate the positions with d3fc-label-layout every time the zoom is called, but not sure how to do this Here is a fiddle https://jsfiddle.net/benderlio/cyvqase5/11/

 var zoom = d3.zoom()
      .scaleExtent([1, 28])
      .on('zoom', function () {

        svg.selectAll('path')
          .attr('transform', d3.event.transform);
        svg.selectAll("circle")
          .attr('transform', d3.event.transform);

        svg.selectAll("text")
          .attr('transform', d3.event.transform);

      });

    svg.call(zoom);

Solution

  • I was able to sync the zoom of the points and text by applying a transform on the labels themselves instead of the circles and text items.

    I recalculate the position from the projection and adjust according to the zoom transformation:

     const t=d3.event.transform;
    
     svg.selectAll('path')
       .attr('transform', t);
    
     svg.selectAll(".label")
       .attr('transform',  d => {
          const p=projection(d.geometry.coordinates)
          return `translate(${ p[0] * t.k + t.x }, ${ p[1] * t.k + t.y }) scale(${ t.k })`
       })
    

    you can see it working here: https://jsfiddle.net/p94xhorv/8/

    Edit: adding code to handle layout while zooming

    I changed the code to recalculate the layout as the user zooms to keep cities from becoming "hidden" as per the OP's comment to my original answer.

        .on('zoom', function () {
          const t=d3.event.transform;
          svg.selectAll('path')
            .attr('transform', t);
    
          labels.position(function (d) { 
            const p=projection(d.geometry.coordinates)
            return [p[0]*t.k+t.x, p[1]*t.k+t.y]
          });
    
          svg.datum(places.features)
            .call(labels);
        });
    

    and here's the updated jsfiddle https://jsfiddle.net/rpv9743n/