Disable pan on mousewheel when zooming, but retain it when dragging - d3.js

I'm trying to use a zoom feature in d3. It's the simple one, with the following code
var zoom = d3.behavior.zoom()
.scaleExtent([0.5, 4])
.on("zoom", function() {
this.svg.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}.bind(this));
It's working more or less fine, so it zoom on mousewheel and pans on drag. I want that. But when zooming with mousewheel, it also pans. That, I don't want.
It should only zoom, when mousewheel and pan when dragged. Tried few things already, including separate drag functions and disabling part of the mousewheel (which I failed miserably), but to no avail.
Could You please help me?

By default, your code will zoom centered to the mouse position.
If you want to zoom to the center of the screen, you might have to explicitly set center() on your zoom behavior like so:
var zoom = d3.behavior.zoom()
.scaleExtent([0.5, 4])
.center([width / 2, height / 2])
.on("zoom", function() {
this.svg.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}.bind(this));
If at any stage you need to reset the default zoom center behaviour (to zoom to the current mouse position), you can do so with:
zoom.center(null);
Src: Zoom Center

Related

How to get the result of zoom.translate() without altering the sensibility of panning?

I have a D3.js draw in a svg and I want to be able to pan and zoom on it. I also want a rectangle to be drawn around the initial window. I want the rectangle to become smaller as I zoom out, larger as I zoom in and to move normally as I pan around. Usual stuff.
I also want to be able to redraw the rectangle to make it fit again the actual window (so it will be smaller than the first if I zoomed in between the redraw for example). I didn't included this part in the code example but it explains why I need a way to get the zoom properties.
To properly trace the rectangle I found the zoom.translate() and zoom.scale() property, supposed to give me the parameters I need to calculate the coordinates of the rectangle's parts. However since I added this part to my code the panning sensibility became to shift as I zoom in and out: The more I zoom in, the less sensible is the panning, and the more I zoom out the more sensible it becomes.
In my mind, zoom.translate() and zoom.scale() were only supposed to fetch the parameters, not to change the way zooming and panning work, how can I fix that?
I also have inexplicably a rectangle that doesn't fit the window: it is a bit larger and shorter.
Here my piece of code:
var svg;
var map;
var zoom;
var graph = d3.select("#graph");
zoom = d3.behavior.zoom()
.translate([0, 0])
.scale(1)
.on("zoom", function () {
svg.attr("transform", "translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")");
});
svg = graph.append("svg")
.attr("width",window.innerWidth)
.attr("height",window.innerHeight)
.call(zoom);
map = svg.append("g");
Here is the acquisition of the rectangle's coordinates:
var w_b = {
x_min: (0 - zoom.translate()[0])/zoom.scale(),
x_max: (svg.attr("width") - zoom.translate()[0])/zoom.scale(),
y_min: (0 - zoom.translate()[1])/zoom.scale(),
y_max: (svg.attr("height") - zoom.translate()[1])/zoom.scale()
And here is the drawing of the rectangle:
map.selectAll("line").remove();
map.append("line").attr("x1", w_b['x_min']).attr("x2", w_b['x_max']).attr("y1", w_b['y_min']).attr("y2", w_b['y_min'])
.attr("stroke-width", 1).attr("stroke", "black");
map.append("line").attr("x1", w_b['x_min']).attr("x2", w_b['x_max']).attr("y1", w_b['y_max']).attr("y2", w_b['y_max'])
.attr("stroke-width", 1).attr("stroke", "black");
map.append("line").attr("x1", w_b['x_min']).attr("x2", w_b['x_min']).attr("y1", w_b['y_min']).attr("y2", w_b['y_max'])
.attr("stroke-width", 1).attr("stroke", "black");
map.append("line").attr("x1", w_b['x_max']).attr("x2", w_b['x_max']).attr("y1", w_b['y_min']).attr("y2", w_b['y_max'])
.attr("stroke-width", 1).attr("stroke", "black");
As a bonus question, this doesn't work at all on Chrome, any idea about what it could be ?
To work correctly, the zoom must be defined like this:
zoom = d3.behavior.zoom()
.translate([0, 0])
.scale(1)
.on("zoom", function () {
map.attr("transform", "translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")");
});
Notice the "map.attr" instead of "svg.attr".
However it doesn't solve the issues about the size of the window and the fact it's not working on Chrome.

D3 - How to get correct scale and translate origin after manual zoom and pan to country path

I've got a topology map with pan and zoom functionality.
Clicking on the country, I'm zoom/panning into the country, using this:
if (this.active === d) return
var g = this.vis.select('g')
g.selectAll(".active").classed("active", false)
d3.select(path).classed('active', active = d)
var b = this.path.bounds(d);
g.transition().duration(750).attr("transform","translate(" +
this.proj.translate() + ")" +
"scale(" + .95 / Math.max((b[1][0] - b[0][0]) / this.options.width, (b[1][1] - b[0][1]) / this.options.height) + ")" +
"translate(" + -(b[1][0] + b[0][0]) / 2 + "," + -(b[1][1] + b[0][1]) / 2 + ")");
g.selectAll('path')
.style("stroke-width", 1 / this.zoom.scale())
However, if I continue to drag pan, the map jerks back into the initial position before the click happens, before panning. Code to pan/zoom is here:
this.zoom = d3.behavior.zoom().on('zoom', redraw)
function redraw() {
console.log('redraw')
_this.vis.select('g').attr("transform","translate(" +
d3.event.translate.join(",") + ")scale(" + d3.event.scale + ")")
_this.vis.select('g').selectAll('path')
.style("stroke-width", 1 / _this.zoom.scale())
}
this.vis.call(this.zoom)
In another words, after zooming into a point by clicking, and then dragging by the redraw function, the redraw does not pick up the correct translate+scale to continue from.
To continue at the right 'zoom' after the transition, I had to set the zoom to the new translate and scale.
Example of reset which is applied similarly to my click and zoom event, the set new zoom point is the critical bit:
_this.vis.select('g').transition().duration(1000)
.attr('transform', "translate(0,0)scale(1)")
/* SET NEW ZOOM POINT */
_this.zoom.scale(1);
_this.zoom.translate([0, 0]);

Zoom on tree layout preserve root node location

I am quite new to d3 and am having trouble with zooming and dragging on a tree layout.
EDIT: Updated JSFiddle with information from comments
I have created a sample, JSFiddle here.
My issue is in the zoom function:
function zoom() {
var scale = d3.event.scale,
translation = d3.event.translate,
tbound = -height * scale * 100,
bbound = height * scale,
lbound = (-width + margin.right) * scale,
rbound = (width - margin.bottom) * scale;
console.log("pre min/max" + translation);
// limit translation to thresholds
translation = [
Math.max(Math.min(translation[0], rbound), lbound),
Math.max(Math.min(translation[1], bbound), tbound)];
console.log("scale" + scale);
console.log("translation" + translation);
d3.select("g")
.attr("transform", "translate(" + translation + ")" +
" scale(" + scale + ")");
}
If you expand and collapse nodes and then try to drag, the root node always goes back to the top left corner. I added some logging that shows that the value of translation in this case is -1,-1
Is there a way I can preserve the current root node position?
The problem is that the g element you're translating with the zoom behaviour is initialised with a non-zero translation. The zoom behaviour is not aware of this, resulting in the "jump" you see. To fix, initialise the zoom behaviour with that translation.
var zb = d3.behavior.zoom().scaleExtent([0.5, 5]).on("zoom", function () {
zoom();
});
zb.translate([margin.left, margin.top]);
Complete example here.

Using the zoom and pan functionality of d3

I'm trying to use the pan/zoom ability of d3 to draw boxes on the screen so that when you click on a box a new box appears and shifts the rest of the boxes to the right so that the new box is on the center of the canvas. The panning would allow me to scroll through all the boxes I've drawn.
Here is my jsfiddle: http://jsfiddle.net/uUTBE/1/
And here is my code for initializing the zoom/pan:
svg.call(d3.behavior.zoom().on("zoom", redraw));
function redraw() {
d3.select(".canvas").attr("transform",
"translate(" + d3.event.translate + ")"
+ " scale(" + d3.event.scale + ")");
}
And here is my code for drawing the boxes:
function drawBox(x, y) {
var boxGroup = canvas.append("g");
boxGroup.append("rect")
.attr("x", x)
.attr("y", y)
.attr("height", 100)
.attr("width", 100)
.attr("fill", function () {
var i = Math.floor(Math.random() * 4);
if (i === 1) return "red";
else if (i === 2) return "blue";
else if (i === 3) return "yellow";
else return "green";
})
.on("click", function () {
counter++;
d3.select(".canvas")
.transition()
.duration(1000)
.attr('transform', "translate(300,0)");
drawBox(x - counter * 120, y);
});
}
I have multiple problems with this fiddle, but two of my main concerns is:
1) How do I make it so that when I click on a new box a second time the boxes move accordingly (i.e. when I click on the box initially the old box shifts to the right and a new box appears, but when I click on the new box, the older boxes doesnn't shift to the right).
2)Why is it that when I click on the new box, the newer box has a big spacing between it? (only happens after trying to put 3 boxes on the screen).
Thanks any hints are appreciated!
I think there's some confusion here around transform. The transform attribute is static, not cumulative, for a single element - so setting .attr('transform', "translate(300,0)") more than once will have no effect after the first time. It also looks like your placement logic for the new boxes is off.
The positioning logic required here is pretty straightforward if you take a step back (assuming I understand what you're trying to do):
Every time a new box is added, the frame all boxes are in moves right 120px, so it needs a x-translation of 120 * counter.
New boxes need to be offset from the new frame position, so they need an x setting of -120 * counter.
Zoom needs to take the current canvas offset into account.
(1) above can be done in your click handler:
canvas
.transition()
.duration(1000)
.attr('transform', "translate(" + (offset * counter) + ",0)");
(2) is pretty easily applied to the g element you're wrapping boxes in:
var boxGroup = canvas.append("g")
.attr('transform', 'translate(' + (-offset * counter) + ',0)');
(3) can be added to your redraw handler:
function redraw() {
var translation = d3.event.translate,
newx = translation[0] + offset * counter,
newy = translation[1];
canvas.attr("transform",
"translate(" + newx + "," + newy + ")" + " scale(" + d3.event.scale + ")");
}
See the working fiddle here: http://jsfiddle.net/nrabinowitz/p3m8A/

Resetting zooming properties d3

I'm trying to find a way to reset the zoom property of my svg whenever I click on an object and leave it as is. For example in this jsfiddle if you zoom out and click on a square, the zoom gets reset but then when I try to pan the screen the zoom goes back to what it was before I clicked on the square. Is there a way such that when I click on a square, the zoom goes back to 100% and stays at 100% even when panning?
JSFiddle: http://jsfiddle.net/nrabinowitz/p3m8A/
Here is my zoom call:
svg.call(d3.behavior.zoom().on("zoom", redraw));
The key is to tell the zoom behaviour that you're resetting scale and translation. To achieve this, simply save it in a variable and set the values appropriately as you're setting the transform attribute of the SVG.
var zoom = d3.behavior.zoom();
svg.call(zoom.on("zoom", redraw));
// ...
.on("click", function () {
counter++;
canvas
.transition()
.duration(1000)
.attr('transform', "translate(" + (offset * counter) + ",0)");
zoom.scale(1);
zoom.translate([offset * counter, 0]);
drawBox(x, y);
});
Updated jsfiddle here.

Resources