Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

d3 zoom behavior when window is resized

I am using D3 to set up my chart area that makes use of the zoomable behavior. The chart includes both x and y axis.

The chart area should be responsive when window is resized. In this case, I need to reset the x and y axis domain and range on window resizing.

The issue happened with window resizing. I've noticed the zoom focus isn't lined up with the mouse anymore after the following steps:

  1. First pan and zoom in the chart area
  2. Then resize the window
  3. Then pan and zoom the chart area again

After that, the above problem happened. See the following jsfiddle which has this issue.

So what's the right way of handing this? I've noticed a couple discussions about this issue such as:

d3 Preserve scale/translate after resetting range

I tried this approach but I couldn't make it working using D3 V4 API.

like image 338
wei mao Avatar asked Sep 27 '16 22:09

wei mao


1 Answers

The main idea is the use the window.addEventListener to update the plot:

    window.addEventListener('resize', () => {

      // obtain the current transformation by calling the `d3.zoomTransform` function
      const t0 = d3.zoomTransform(
        chart.node());

      // obtain the client width by evaluating the `div` (class: `zoom-chart`)
      width = document.getElementById('zoom-chart').clientWidth;
      height = 480;

      // update the base scale
      baseX.range([0, width])
        .interpolate(d3.interpolateNumber);
      baseY.range([height, 0])
        .interpolate(d3.interpolateNumber);

      // update the view port
      vb = [0,0, width,height];
      chart  
        .attr('viewBox', vb);
        
      // replot with the original transform
      chart.call(zoom.transform, t0);
    });

I also added the whole solution here:

const data = [
         {x:1 , y:1},
         {x:9 , y:1},
         {x:9 , y:9},
         {x:1 , y:9},
        ];
        
        var width = document.getElementById('zoom-chart').clientWidth;
        var height = 480;

        var baseX = d3.scaleLinear().domain([0,10])
            .range([0, width])
            .interpolate(d3.interpolateNumber);
        var baseY = d3.scaleLinear().domain([0,10])
            .range([height, 0])
            .interpolate(d3.interpolateNumber);

        var scaleX = baseX;
        var scaleY = baseY;

        const zoom = d3.zoom()
          .on('zoom', () => {
            const transform = d3.event.transform;
            scaleX = transform.rescaleX(baseX)
              .interpolate(d3.interpolateNumber)
            scaleY = transform.rescaleY(baseY)
              .interpolate(d3.interpolateNumber)
           
            render();
          });
        
        const chart = d3.select('#zoom-chart')
          .append('svg')
          .attr('class', 'chart')
          .call(zoom); 
         
        const rect = chart
          .append('rect')
          .attr('rx', 10).attr('ry', 10)
          .attr('x', 10).attr('y', 10)
          .attr('fill', '#00222b')
          .attr('width', width-20)
          .attr('height', height-20);

        const plot = chart
          .append('g')
          .attr('class', 'plot-area');

        function render() {
          plot.selectAll('.data')
            .data(data)
            .join(
              enter => enter.append('circle')
                .attr('class', 'data')
                .attr('r', 10)
                .attr('stroke', '#004455')
                .attr('stroke-width', 5),
              update => update,
              exit => exit.remove()
            )
            .attr('cx', d => scaleX(d.x))
            .attr('cy', d => scaleY(d.y));

        }
        
        window.addEventListener('resize', () => {
          const t0 = d3.zoomTransform(
            chart.node());

          width = document.getElementById('zoom-chart').clientWidth;
          height = 480;

          baseX.range([0, width])
            .interpolate(d3.interpolateNumber);
          baseY.range([height, 0])
            .interpolate(d3.interpolateNumber);

          vb = [0,0, width,height];
          chart  
            .attr('viewBox', vb);
            
          rect
            .attr('width', width-20)
            .attr('height', height-20);
            
          chart.call(zoom.transform, t0);
        });
        
        let vb = [0,0, width,height];
        chart  
          .attr('viewBox', vb);
          
        chart.call(zoom.transform, d3.zoomIdentity);
<script src="https://unpkg.com/[email protected]/dist/d3.min.js"></script>

    <div id="zoom-chart" class="chart large">

    </div>
like image 126
minus one Avatar answered Oct 20 '22 11:10

minus one