Search code examples
javascriptd3.jsdagre-d3dagre

Preserve d3 zoom state when changing out SVG contents?


I'm rendering something using d3, and using d3.zoom to zoom with the mouse. It works, but I also need to change the contents of the svg periodically (doing a full $('svg').html($('svg').html()) call, because my SVG contains HTML).

The problem is that when I do this, I lose the zoom state. I've tried to keep track of it by storing the zoom transform and reapplying it after the update, but it's not working.

Here's a pen with my attempt, using the dagre-d3 example diagram.

Here's what I'm doing, in short:

let transform;

function dag(){
  // ... set up stuff here

  var svg = d3.select("svg"),
      inner = svg.select("g");

  // Set up zoom support
  var zoom = d3.zoom().on("zoom", function() {
        //store transform
        transform = d3.event.transform
        inner.attr("transform", d3.event.transform);
      });
  svg.call(zoom);

  // render graph
  new dagreD3.render()(inner, g);

  // Center the graph
  var initialScale = 0.75;
  svg.call(zoom.transform, d3.zoomIdentity.translate((svg.attr("width") - g.graph().width * initialScale) / 2, 20).scale(initialScale));

  svg.attr('height', g.graph().height * initialScale + 40);
}

setInterval(() => {
  // simulate reloading content
  dag()
  console.log('render again')
  //try and apply saved transform state
  transform && inner.attr("transform", transform);
}, 2000)

Solution

  • The svg scale is set again using the 'initialScale' variable after the transform is set in the zoom func, the problem can be seen here:

      // Center the graph
      var initialScale = 0.75;
      svg.call(zoom.transform, d3.zoomIdentity.translate((svg.attr("width") - g.graph().width * initialScale) / 2, 20).scale(initialScale));
    

    To get around this you need to change the value in the 'initialScale' variable based on the current scale transform of the svg. As seen here:

      // Center the graph
      var initialScale = (typeof transform != 'undefined') ? transform.k : 0.75;
      svg.call(zoom.transform, d3.zoomIdentity.translate((svg.attr("width") - g.graph().width * initialScale) / 2, 20).scale(initialScale));
    

    A codepen of the code with the fix: https://codepen.io/anon/pen/MBwBYY?editors=0011