I am trying to implement a simple zoom in d3.js, simpler than all the examples I have gone through (I suppose) but it just doesn't wanna work. So, the functionality that I want to implement is: the user clicks on a section of the graph and that section zooms at a predefined fixed size in the centre of the chart; the user cannot zoom it any further, no panning either. And when the user clicks at any other section of the chart, the zoomed section translates back to its normal/original position.
var container = svg.append("g").classed("container-group", true);
container.attr({transform: "translate(" + 40*test_data.row + "," + 40*test_data.col + ")"});
container.call(d3.behavior.zoom().scaleExtent([1,5]).on("zoom", zoom));
function zoom() {
container.attr("transform","translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
I have tried zoom.translate and zoom.size but couldn't get them right. And don't know how to reset the zoomed section either.
Any help would be much appreciated !
I´ll give an example of zooming some circles. Clicking on the red rectangle will zoom out to 50%, clicking on the blue one will return to a 100% scale. The exact functions you are looking for are zoomOut() and initialZoom()
var zoomListener = d3.behavior.zoom().scaleExtent([0.1, 3]);
width = 200 ;
height = 200 ;
//svg
var svg = d3.select("body").append("svg").attr("id","vis")
.attr("width", width )
.attr("height", height );
//transition listener group
var svgGroup = svg.append("g").call(zoomListener);
//zoom in and zoom out buttons
svg.append("rect").attr("x",0).attr("y",0).attr("width",50).attr("height",50).style("fill","red").on("click",zoomOut);
svg.append("rect").attr("x",0).attr("y",50).attr("width",50).attr("height",50).style("fill","blue").on("click",initialZoom);
var i,k;
for(i=90;i<width-20;i+=20){
for( k=20;k<height-20;k+=20){
svgGroup.append("circle").attr("cx", i).attr("cy", k).attr("r", 10);
}
}
function zoomOut(){
//fix transition to center of canvas
x = (width/2) * 0.5;
y = (height/2) * 0.5;
//zoom transition- scale value 150%
svgGroup.transition().duration(500).attr("transform", "translate("+x+","+y+")scale(0.5)" );
}
function initialZoom(){
//fix transition to center of canvas
x = (width/2) ;
y = (height/2) ;
//zoom transition- scale value 100%
svgGroup.transition().duration(500).attr("transform", "scale(1)" );
}
Related
I am trying to implement panning, zooming and scrolling with scroll bars.
I have two problems with zoom,pan and scroll bars combination:
1.pan/drag the svg is working with moving scrollbars, when you pan/drag the chart->scroll with scrollbars->pan/drag with cursor on svg, It goes to old position where last pan/drag is performed.
2.Scrollbar with zoom is not working wonders, when you zoom->scroll->zoom it zooms at the old location where the first zoom happened.
how to implement this lines "wrapper.call(d3.zoom().translateTo, x / scale, y / scale);" in d3.v3. It would be great help if anyone shows the solution to implement in d3 version 3. below code is for zooming, panning and scrolling.
function zoomed() {
var scale = d3.event.scale;
const scaledWidth = (newWidth+1000) * scale;
const scaledHeight = height * scale;
// Change SVG dimensions.
d3.select('#hierarchyChart svg')
.attr('width', scaledWidth)
.attr('height', scaledHeight);
// Scale the image itself.
d3.select('#hierarchyChart svg g').attr('transform', "scale("+scale+")");
// Move scrollbars.
const wrapper = d3.select('#hierarchyChart')[0];
if(d3.event.translate >= [0,0] && d3.event.translate <=[scaledWidth,scaledHeight]){
wrapper[0].scrollLeft = -d3.event.translate[0];
wrapper[0].scrollTop = -d3.event.translate[1];
}
console.log(wrapper[0].scrollLeft);
console.log(wrapper[0].scrollTop);
// If the image is smaller than the wrapper, move the image towards the
// center of the wrapper.
const dx = d3.max([0, wrapper[0].clientWidth/ 2 - scaledWidth / 2]);
const dy = d3.max([0, wrapper[0].clientHeight / 2 - scaledHeight / 2]);
// d3.select('svg').attr('transform', "translate(" + dx + "," + dy + ")");
}
function scrolled() {
const x = wrapper[0].scrollLeft + wrapper[0].clientWidth / 2;
const y = wrapper[0].scrollTop + wrapper[0].clientHeight / 2;
const scale = d3.event.scale;
// Update zoom parameters based on scrollbar positions.
// wrapper.call(d3.zoom().translateTo, x / scale, y / scale);
}
I'm using my mouse click position to zoom and pan to a specific part of a map. Without scale the translate moves to the correct spot. However, if I scale and translate together it misses the click spot. I understand why but I don't understand how to compensate for it:
//get mouse interaction in svg path
let clickPoint = d3.mouse(this);
if (!d) {
//reset center and zoom level back to sart
projection.translate([width/2,height/2]).scale(scaleDefault);
centered = null;
scaleStartLevel = 1;
} else {
let translate = projection.translate();
//calc scale if the user hasn't zoomed to max zoom more
let getScale = scaleStartLevel < scaleMaxLevels ? projection.scale() + (scaleInc*scaleStartLevel) : projection.scale();
scaleStartLevel++;
//scale the map to the zoom level and translate to click point
projection.scale(getScale)
.translate([
translate[0] - clickPoint[0] + width / 2,
translate[1] - clickPoint[1] + height / 2
]);
centered = d;
}
//move the map
map.selectAll("path").transition()
.duration(750)
.attr("d", path);
I'm attempting to make an interactive pan/zoom SVG floorplan/map using the d3.behavior.zoom() functionality. I have based my code loosely on Zoom to Bounding Box II.
I am asynchronously loading a svg via $.get() then using a button.on('click') handler to get the .getBBox() of a specific <g> by element ID, #g-1101 (represented as a red circle on the svg). I then would like to center the viewport of the svg to the middle of #g-1101's bounding box.
As a cursory try I was just trying to translate the top-level svg > g by using g#1101's .getBBox().x && .getBBox().y. It seems to me my math is off.
I've tried incorporating the (svg.node().getBBox().width / 2) - scale * g.getBBox().x) to center the middle point of the bounding box to viewport but it's translation is not even in the ballpark.
Code
(function(){
var svg, g; $.get('http://layersofcomplexity.com/__fp.svg', function(svg){
$('body').append($(svg));
init();
},'text');
function init() {
console.log('init');
svg = d3.select('svg');
g = d3.select('svg > g');
var zoom = d3.behavior.zoom()
.translate([0, 0])
.scale(1)
.scaleExtent([1, 8])
.on("zoom", zoomed);
svg
.call(zoom)
.call(zoom.event);
$('.pan').on('click', function(){
// var id = '#g-1011';
var scale = 4;
var bbox = $('#g-1101')[0].getBBox();
// d3.select(id).node().getBBox();
var x = bbox.x;
var y = bbox.y;
// var scale = .9 / Math.max(dx / width, dy / height),
// var translate = [width / 2 - scale * x, height / 2 - scale * y];
var width = svg.node().getBBox().width;
var height = svg.node().getBBox().height;
var translate = [-scale*x,-scale*y];
g.transition().duration(750) .call(zoom.translate(translate).scale(scale).event);
});
}
function zoomed() {
g.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
})();
-- EDIT JSBin was broken --
What am I missing? JSBin.
One small change in your code to center the marked in red g#g-1101
var bbox = $('#g-1101')[0].getBBox();
var x = bbox.x-((bbox.width));
var y = bbox.y-((bbox.height));
//scale should be 1 less
var translate = [-(x*(scale-1)),-(y*(scale-1))];
working code here
Hope this helps
I have a zoom event handler on my tree graph like so:
d3.select("#"+canvasId+" svg")
.call(d3.behavior.zoom()
.scaleExtent([0.05, 5])
.on("zoom", zoom));
Which calls the zoom function which handles the translation bounding logic:
function zoom() {
console.log(d3.event.translate[0]);
var wcanvas = $("#"+canvasId+" svg").width();
var hcanvas = $("#"+canvasId+" svg").height();
var displayedWidth = w*scale;
var scale = d3.event.scale;
var h = d3.select("#"+canvasId+" svg g").node().getBBox().height*scale;
var w = d3.select("#"+canvasId+" svg g").node().getBBox().width*scale;
var padding = 100;
var translation = d3.event.translate;
var tbound = -(h-hcanvas)-padding;
var bbound = padding;
var lbound = -(w-wcanvas)-padding;
var rbound = padding;
// limit translation to thresholds
translation = [
Math.max(Math.min(translation[0], rbound), lbound),
Math.max(Math.min(translation[1], bbound), tbound)
];
console.log("Width: "+w*scale+" || Height: "+h*scale+" /// "+"Left: "+translation[0]+" || Top: "+translation[1]);
d3.select("#"+canvasId+" svg g")
.attr("transform", "translate(" + translation + ")" +" scale(" + scale + ")");
console.log(d3.select("#"+canvasId+" svg g")[0]);
}
However, translations beyond the bounds cause the d3.event.translate values to increase. The result is that even if the translation is not causing the graph to move as it has reached its limit for translation, the value for the translation within successive events can continue to increase.
The result is that say I drag the graph far to the left, even though it will stop moving past a certain point, because the value within the events continues to increase, I would then have to drag it a long way back right before it actually begins to move right again.
Is there a good way to prevent this behaviour?
Okay I worked it out. The trick is to set the translation for the d3.behaviour.zoom so that successive zoom pans start at the bounded translation rather than with the additional panning that didn't actually give any movement.
To do this, we declare the zoom behaviour as a separate variable and add it to our zoomable element:
var zoomBehaviour = d3.behavior.zoom()
.scaleExtent([0.05, 5])
.on("zoom", zoom)
d3.select("#"+canvasId+" svg")
.call(zoomBehaviour);
Then we set the translation of this zoomBehaviour to our bounded translation in the zoom function:
function zoom() {
...
translation = [
Math.max(Math.min(translation[0], rbound), lbound),
Math.max(Math.min(translation[1], bbound), tbound)
];
zoomBehaviour.translate(translation);
d3.select("#"+canvasId+" svg g")
.attr("transform", "translate(" + translation + ")" +" scale(" + scale + ")");
}
Im using d3.js zoom function to zoom in on an image inside an svg. Im using a mask to reveal an underlying image beneath. If i dont zoom the mask and mouse cursor coordinates match up perfectly. However, when i start to zoom the mouse coordinates are not translating to the zoom level, thus the map reveal is not lining up with the cursor anymore.
here is what im using so far...Im assuming there needs to be some sort of coordinate translation when zooming?
var lightMap = d3.select("#lightMap").call(d3.behavior.zoom().scaleExtent([1, 3]).on("zoom", zoom));
var darkMap = d3.select("#darkMap").call(d3.behavior.zoom().scaleExtent([1, 3]).on("zoom", zoom));
function zoom() {
lightMap.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
darkMap.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
var svg = d3.select("svg");
svg.on('mousemove', function () {
var coordinates = [0, 0];
coordinates = d3.mouse(this);
var x = coordinates[0];
var y = coordinates[1];
primaryCircle.setAttribute("cy", y + 'px');
primaryCircle.setAttribute("cx", x + 'px');
});
(I know this is a late answer but I had the same problem and thought I'd share how I fixed it for future people who see this)
Fix: Use coordinates=mouse(lightMap.node()) / darkMap.node() instead of mouse(this). Alternatively, and probably more correctly, call the zoom behavior on svg and keep using mouse(this).
Explanation: You call the mousemove function on svg, so mouse(this) gets the coordinates within the svg element. However you don't apply the zoom behavior to svg, so you get wrong coordinates. Call mouse(_) on an element that zooms.