Reputation: 1485
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
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