csotelo
csotelo

Reputation: 1485

D3 add zoom and pan

I need to zoom and pan a group of svg elements, like this example:

https://embed.plnkr.co/kVw0rWMo728dmbm8EjuG/

I've done the zoom behavior, but I can't move the graphic when I a dragging the mouse (pan). I've tried with:

var zoom = d3.zoom()
  .scaleExtent([1, 100])
  .translateExtent([[150, 150],[150, 150]])
  .on('zoom', zoomFn);

  d3.select('svg')
  .select('g')
  .style("transform-origin", "50% 50% 0");

function zoomFn() {
  d3.select('svg').select('g')
    .style('transform', 'translate(' + d3.event.translate + ')' + ' scale(' + d3.event.transform.k + ')');
}

d3.select('svg')
  .select('rect')
  .call(zoom);

How I add the pan behavior in that example?

Upvotes: 1

Views: 385

Answers (1)

Andrew Reid
Andrew Reid

Reputation: 38161

There are a few things:

First

d3.event.translate doesn't hold the transform x and y values, use d3.event.transform to get these values. The transform object has x, y, and k properties.

Second

If using .style() to place a transform you'll need to specify that the unit is pixels for the offset:

var t = d3.event.transform;
d3.select("zoomedElement")
  .style('transform', 'translate('+t.x+"px,"+t.y + 'px)scale(' + t.k + ')');

Alternatively, you can use .attr() without px:

var t = d3.event.transform;
d3.select("zoomedElement")
  .style('transform', 'translate('+[t.x,t.y]+')scale(' + t.k + ')');

Let's see your plunker with those changes (and no translate extent):

var zoom = d3.zoom()
  .scaleExtent([1, 100])
  .on('zoom', zoomFn);

  d3.select('svg')
  .select('g')
  .style("transform-origin", "50% 50% 0");

function zoomFn() {
   var t = d3.event.transform;
  d3.select('svg').select('g')
    .style('transform', 'translate('+t.x+"px,"+t.y + 'px)scale(' + t.k + ')');


}

d3.select('svg')
  .select('rect')
  .call(zoom);
    .zoom-layer {
      fill: #EEE;
      fill-opacity: 0.25;
    }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.0.0/d3.min.js"></script>
<svg width="300" height="300">
    <g>
      <circle cx=150 cy=150 r=20 style="fill:#F22"></circle>
      <rect x=0 y=0 width=300 height=300 class="zoom-layer"></rect>
    </g>
  </svg>

You probably want to make a selection that doesn't inlcude the rectangle though - that should remain the same so the entire SVG surface remains interactive

Third

It appears as though you want a translate extent, this is a bit tricky to set sometimes. This answer goes into greater depth on zoom extent/translate extent, but I'll go over how to constrain your circle to the screen:

With a translate of [0,0] your circle sits in the middle of the svg. If we a translate of [-150,-150], your circle is in the top left corner of the svg. If the circle is centered on cx=150 cy=150, then the top left of the viewable area is now [150,150]. Consequently the bottom right is [450,450]. The viewable extent is: [[150,150],[450,450]].

With a translate of [150,150], your circle is in the bottom right of the SVG. Because the circle is still at [150,150], the bottom right corner is at [150,150] (doesn't help the clarity that the translate and circle position have the same value here). The top left corner is at [-150,-150]. The viewable extent must then be: [[-150,-150],[150,150]].

We take the extremes from the viewable extent limits, as this is the translate extent that keeps the circle in view:

var translateExtent = [[-150,-150],[450,450]]

Naturally if you had things in all corners rather than the middle, you'd have a larger extent.

And we can apply everything to the snippet:

var zoom = d3.zoom()
  .scaleExtent([1, 100])
  .translateExtent([[-150,-150],[450, 450]])
  .on('zoom', zoomFn);

  d3.select('svg')
  .select('g')
  .style("transform-origin", "50% 50% 0");

function zoomFn() {
   var t = d3.event.transform;
  d3.select('svg').select('g')
    .style('transform', 'translate('+t.x+"px,"+t.y + 'px)scale(' + t.k + ')');

  console.log(t.x,t.y)

}

d3.select('svg')
  .select('rect')
  .call(zoom);
    
    .zoom-layer {
      fill: #aaa;
      fill-opacity: 0.25;
    }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="300" height="300">
    <g>
      <circle cx=150 cy=150 r=20 style="fill:#F22"></circle>
    </g>
    <rect x=0 y=0 width=300 height=300 class="zoom-layer"></rect>
  </svg>

Upvotes: 1

Related Questions