I am currently using the mouse-wheel to zoom in and out on my D3 force directed graph.
Is there a way of zooming in slower or quicker
For example, say the scale currently moves like this when I move the mouse wheel :
1,2,3,4,5.
I wish to move it :
1,1.5,2,2.5,3 and so on.
Here is my zooming function :
var minZoom = 0.2,
maxZoom = 2.5;
var zoom = d3.behavior.zoom()
.on("zoom", redraw)
.scaleExtent([minZoom, maxZoom])//-call zoom but put scale extent on to limit zoom in/out
;
My redraw :
function redraw() //-redraw network
{
var trans=d3.event.translate;
var scale=d3.event.scale;
svg.attr("transform","translate(" + trans + ")" + " scale(" + scale + ")"); //-translates and scales
}
You can intercept and adjust the scale inside the redraw function:
function redraw() //-redraw network
{
var trans=d3.event.translate;
var scale=d3.event.scale;
var newScale = scale / 2;
zoom.scale(newScale);
svg.attr("transform","translate(" + trans + ")" + " scale(" + newScale + ")"); //-translates and scales
}
Or if you want more specific interpolation, you can add an interpolator like d3.linear.scale() into the redraw function:
function redraw() //-redraw network
{
scaleInterpolator = d3.linear.scale().domain([1,10,100,1000]).range9([1,3,9,99]);
var trans=d3.event.translate;
var scale=d3.event.scale;
svg.attr("transform","translate(" + trans + ")" + " scale(" + scaleInterpolator(scale) + ")"); //-translates and scales
}
I mark as duplicate and the original reply is in here:
How to change speed of translate and scale when zooming in and out in D3 (using zoom.on & d3.event.translate, d3.event.zoom)?
You can adapt the solution with you height and your width.
You can get this info as well from this.getBBox().height. This height have the value after apply the scale, this mind that you are always an iteration behind. Better if you add the real height and width.
function redraw() {
var scale = Math.pow(d3.event.scale,.1);
var translateY = (height - (height * scale))/2;
var translateX = (width - (width * scale))/2;
svg.attr("transform", "translate(" + [translateX,translateY] + ")" + " scale(" +scale+ ")");
};
In this line: var scale = Math.pow(d3.event.scale,.1); .1 is the factor of velocity, slowly when smaller
Related
Drag and Drop Example
I am trying to rewrite part of this example above to use in my code, specifically this piece:
function centerNode(source) {
scale = zoomListener.scale();
x = -source.y0;
y = -source.x0;
x = x * scale + viewerWidth / 2;
y = y * scale + viewerHeight / 2;
d3.select('g').transition()
.duration(duration)
.attr("transform", "translate(" + x + "," + y + ")scale(" + scale + ")");
zoomListener.scale(scale);
zoomListener.translate([x, y]);
}
However I am getting stuck since the v4 package has changed quite a bit. I wrote my zoomListener function to be
var zoomListener = d3.zoom()
.scaleExtent([0.3,2])
.on("zoom", zoomed);
function zoomed() {
transform = d3.event.transform;
console.log(d3.event);
svg.attr("transform", transform);
}
function centerNode(source){
t = transform;
console.log(t);
x = t.x*t.k; //I only want things to be centered vertically
y = (t.y + -source.x0)*t.k + (viewerHeight)/2 ;
svg.transition()
.duration(duration)
.attr("transform","translate(" + x + "," + y +")scale(" + t.k + ")");
transform.scale(t.k); //DOES NOT WORK
transform.translate([x, y]); //DOES NOT WORK
}
and I know that according to the doc things have changed and info are no longer are stored on what would be my zoomListener
D3 V4 release note on zoom I guess I am just confused on how I am suppose to do it with the new version. The last few lines of my centerNode function don't work which has for effect that when I center the node the zooming and panning reset...
Any suggestion?
So after much digging and trial and error I cam up with an answer that works pretty well for my purposes. Note that this code below is only the relevant part of my code not the whole code, certain variable were self explanatory so did not include them. ALSO THIS IS IN VERSION 4 of d3.js.
var zoom = d3.zoom()
.scaleExtent([0.3,2])
.on("zoom", zoomed);
var svg = d3.select("body")
.append("svg")
.attr("width", viewerWidth)
.attr("height", viewerHeight);
var zoomer = svg.append("rect")
.attr("width", viewerWidth)
.attr("height", viewerHeight)
.style("fill", "none")
.style("pointer-events", "all")
.call(zoom);
var g = svg.append("g");
zoomer.call(zoom.transform, d3.zoomIdentity.translate(150,0)); //This is to pad my svg by a 150px on the left hand side
function zoomed() {
g.attr("transform", d3.event.transform);//The zoom and panning is affecting my G element which is a child of SVG
}
function centerNode(source){
t = d3.zoomTransform(zoomer.node());
console.log(t);
x = t.x;
y = source.x0;
y = -y *t.k + viewerHeight / 2;
g.transition()
.duration(duration)
.attr("transform", "translate(" + x + "," + y + ")scale(" + t.k + ")")
.on("end", function(){ zoomer.call(zoom.transform, d3.zoomIdentity.translate(x,y).scale(t.k))});
}
As per the examples for v4 on the d3.js page, I used a rectangle to apply the zoom to
The zoom behavior is applied to an invisible rect overlaying the SVG
element; this ensures that it receives input, and that the pointer
coordinates are not affected by the zoom behavior’s transform. Pan & Zoom Example
In the Center node function I am using d3.zoomTransform(zoomer.node()); to get the current transform applied to the page.
The purpose of this function is only to center the collapsible tree vertically not horizontally, so I am keeping the current transform.x (here t.x) the same.
The coordinate in my svg are flip hence why y= source.x0, source is a what node was clicked in my collapsible tree. ("Look to the example referenced to the top of this thread to understand what I am trying to convert to version 4)
I am apply the transformation to my G element and then I want to commit those changes to the zoom transform, to do so I use the .on("end", function(){}) otherwise it was doing weird behavior with the transition, by doing that all it does is setting the current state of the transform.
zoomer.call(zoom.transform, d3.zoomIdentity.translate(x,y).scale(t.k))
This line above is applying a translation of x and y and a scale -- that is equal to what the current state -- to the identiy matrix has to get a new transform for G, i then apply it to zoomer which is the element I called zoom on earlier.
This worked like a charm for me!
Calling transform.scale and transform.translate returns a new transform, and modifies nothing. Therefore:
transform = transform.translate([x, y]).scale(k)
svg.call(zoomListener.transform, newTransform)
(At this point zoomListener is a pretty inaccurate name for this, but regardless...)
k, x, and y can be derived from source, maybe as you show, but I'm not sure, because I don't know what source is. But to me, t.x*t.k looks suspicious, because it's multiplying the existing transforms x by its scale. Seems like it would cause a feedback loop.
For more into about the zoom in v4, check out this related StackOverflow post, or this example by mbostock demonstrating programmatic control over the zoom transform of an element (canvas in this case) and includes transitions.
I have an svg country map and I'm trying to achieve the effect when you click on a district and then see an animated scaling relative to selected district, i.e. scaling with transform-origin point in the center of a clicked district. And I would prefer not to use any libraries.
First of all, why here, when we have svg viewBox attribute setted, polygons drawn with getBoundingClientRect() points don't match their actual elements svg, map, e.target?
If we add some coefficients like here, then what viewBoxMapCoefX1 and viewBoxMapCoefX1 we need so the mapPolygon drawn with getBoundingClientRect() points matches actual map element?
When I try to scale map by adding:
// ANIMATION HERE
map.style.transformOrigin = transformOriginX + "px " + transformOriginY + "px";
map.style.transform = "scale(" + scale + ")";
it looks like transformOrigin value is wrong. If I try to change viewBox value by adding:
// ANIMATION HERE
svg.viewBox.baseVal.x = bounding.left;
svg.viewBox.baseVal.y = bounding.top;
svg.viewBox.baseVal.width = bounding.width;
svg.viewBox.baseVal.height = bounding.height;
then how can I make scaling animation with CSS (no SMIL)?
Any help or advice on this is greatly appreciated.
1) You should be using .getBBox() instead of BoundingClientRect to get the coordinates, as this will be based on the SVG itself and not the screen.
3) You are applying the transforms to the group tag, whereas it makes sense for these to be applied to the SVG element itself instead. You may have tried this already, but the transform origin value will all be based on the co-ordinates you're getting previously? So long as the animation works as intended, there is no issue with the animation front from what I can see.
// ANIMATION HERE
var svg = document.querySelector("svg");
svg.style.transformOrigin = '50% 50%';
svg.style.transform = 'scale(4)';
Thanks to #OwenAyres's answer 1 part of question has been solved.
Instead of scaling map straight relative to the selected district center we can find district's center in % insted of px and then move district's center to map's center and then make scale:
// ANIMATION HERE
var transformOriginXPercent = (50 - transformOriginX * 100 / mapBounding.width) * scale;
var transformOriginYPercent = (50 - transformOriginY * 100 / mapBounding.height) * scale;
var scaleText = "scale(" + scale + ")";
var translateText = "translate(" + transformOriginXPercent + "%," + transformOriginYPercent + "%)";
map.style.transformOrigin = "50% 50%";
map.style.transform = translateText + " " + scaleText;
and get expected result.
But there is still a question what coefficients for transformOriginX and transformOriginY are correct to get the result in the following form:
map.style.transformOrigin = (transformOriginX) + "px " + (transformOriginY) + "px";
map.style.transform = "scale(" + scale + ")";
I'm using d3 tree layout similar to this example: http://bl.ocks.org/mbostock/4339083
I implemented a search box that when typing, centers your screen on a virtual "average" position of all the appropriate nodes.
I want to adjust the scale, so that selected nodes will be
All Visible
As zoomed in as possible.
If the search match is exactly 1, simulate the clicking on the node, else center to this virtual position.
if (matches[0].length === 1) {
click(matches.datum(), 0, 0, false);
}
else {
var position = GetAveragePosition(matches);
centerToPosition(position.x, position.y, 1);
}
This is what the centerToPosition function looks like:
function centerToPosition(x0, y0, newScale) {
if (typeof newScale == "undefined") {
scale = zoomListener.scale();
}
else {
scale = newScale;
}
var x = y0 * -1; //not sure why this is.. but it is
var y = x0 * -1;
x = x * scale + viewerWidth / 2;
y = y * scale + viewerHeight / 2;
d3.select('g').transition()
.duration(duration)
.attr("transform", "translate(" + x + "," + y + ")scale(" + scale + ")");
zoomListener.scale(scale);
zoomListener.translate([x, y]);
}
So how can I calculate the new scale? I tried different variations by taking the extents of the data points
var xExtent = d3.extent(matches.data(), function (d) {
return d.x0;
});
var yExtent = d3.extent(matches.data(), function (d) {
return d.y0;
});
Also tried looking at the transform properties of the group before centering the screen.
var components = d3.transform(svgGroup.attr("transform"));
I'll try to add a js fiddle soon!
EDIT: Here it is: http://jsfiddle.net/7SJqC/
Interesting project.
The method of determining the appropriate scale to fit a collection of points is fairly straightforward, although it took me quite a while to figure out why it wasn't working for me -- I hadn't clued in to the fact that (since you were drawing the tree horizontally) "x" from the tree layout represented vertical position, and "y" represented horizontal position, so I was getting apparently arbitrary results.
With that cleared up, to figure out the zoom you simply need to find the height and width (in data-coordinates) of the area you want to display, and compare that with the height and width of the viewport (or whatever your original max and min dimensions are).
ScaleFactor = oldDomain / newDomain
Generally, you don't want to distort the image with different horizontal and vertical scales, so you figure out the scale factor separately for width and height and take the minimum (so the entire area will fit in the viewport).
You can use the d3 array functions to figure out the extent of positions in each direction, and then find the middle of the extent adding max and min and dividing by two.
var matches = d3.selectAll(".selected");
/*...*/
if ( matches.empty() ) {
centerToPosition(0, 0, 1); //reset
}
else if (matches.size() === 1) {
click(matches.datum(), 0, 0, false);
}
else {
var xExtent = d3.extent(matches.data(), function (d) {
return d.x0;
});
var yExtent = d3.extent(matches.data(), function (d) {
return d.y0;
});
//note: the "x" values are used to set VERTICAL position,
//while the "y" values are setting the HORIZONTAL position
var potentialXZoom = viewerHeight/(xExtent[1] - xExtent[0] + 20);
var potentialYZoom = viewerWidth/(yExtent[1] - yExtent[0] + 150);
//The "20" and "150" are for height and width of the labels
//You could (should) replace with calculated values
//or values stored in variables
centerToPosition( (xExtent[0] + xExtent[1])/2,
(yExtent[0] + yExtent[1])/2,
Math.min(potentialXZoom, potentialYZoom)
);
}
http://jsfiddle.net/7SJqC/2/
I have been struggling with this issue for the past couple days: I have a force directed graph that labels its edges just like this example does it. The problem I am facing is that when the graph updates (ie: a node on the graph is added upon a user's click) it updates the graph but it leaves the old edge labels that I wrote previously behind:
BEFORE & AFTER A NEW GRAPH IS APPENDED:
As you can see, my edge labels are hanging around after an update. I have a function that is called everytime new data comes in, and in this function I have the following code that draws the labels:
path_text = svg.selectAll(".path")
.data(force.links(), function(d){ return d.name;})
.enter().append("svg:g");
path_text.append("svg:text")
.attr("class","path-text")
.text(function(d) { return d.data.label; });
The svg variable is declared once at a top level closure like so:
var svg = d3.select("body").append("svg:svg")
.attr("viewBox", "0 0 " + width + " " + height)
.attr("preserveAspectRatio", "xMidYMid meet");
My graph has a tick() function that calculates the location of each label like so:
function tick()
{
// Line label
path_text.attr("transform", function(d)
{
var dx = (d.target.x - d.source.x),
dy = (d.target.y - d.source.y);
var dr = Math.sqrt(dx * dx + dy * dy);
var sinus = dy/dr;
var cosinus = dx/dr;
var l = d.data.label.length * 6;
var offset = (1 - (l / dr )) / 2;
var x=(d.source.x + dx*offset);
var y=(d.source.y + dy*offset);
return "translate(" + x + "," + y + ") matrix("+cosinus+", "+sinus+",
"+-sinus+", "+cosinus+", 0 , 0)";
});
.
.
.
I have tried moving this svg declaration down into the update function, so that this is instantiated each time there is a graph change. This actually works - but it makes an entire duplicate of the entire graph. The first, original copy still keeps the old labels - but the second copy acts exactly how I want it to. Is there a way, perhaps, instead of appending svg, there is a way of replacing? I have also tried calling exit().remove() without any luck as well.
Thank you so much for your time. This has been killing me as to how I'm supposed to do this.
I placed the svg declaration inside my graph update function, attached it to a div, and clear the div before appending it again:
jQuery('#v').empty();
var svg = d3.select("#v").append("svg:svg")
.attr("viewBox", "0 0 " + width + " " + height)
.attr("preserveAspectRatio", "xMidYMid meet");
Not the cleanest solution in my opinion, but will go with this unless you all have a better solution!
I have a test site where I am building a d3-based force-directed network graph from my own data.
If I pick about five or six genes, the nodes in my graph start to get drawn outside the canvas.
What parts of the d3 API do I need to call to control zoom level, so that nodes do not disappear off the edge of the canvas?
If available, I would definitely appreciate any code snippets that briefly explain the concept, unless the implementation is fairly simple. Thanks for your advice.
D3 allows to use zoom and it's fairly easy to implement. You'll only need to wrap your graph inside a "g" element that I'll call "viewport". Then you'll assign to the zoom event of the svg element:
svg.call(d3.behavior.zoom().on("zoom", redraw))
the following function:
function redraw() {
d3.select("#viewport").attr("transform",
"translate(" + d3.event.translate + ")"
+ " scale(" + d3.event.scale + ")");
}
var x, y, k;
if (d && centered !== d) {
var centroid = path.centroid(d);
x = centroid[0];
y = centroid[1];
k = 4;
centered = d;
} else {
x = w / 2;
y = h / 2;
k = 1;
centered = null;
}
write the following code in zoom function
svg.attr("transform", "translate(" + w / 2 + "," + h / 2 + ")scale(" + k + ")translate(" + -x + "," + -y + ")");
for absolute scale you cann use this code
this will be helpfull for maps zooming and panning