I use dagre and d3 to display graphs. The graph elements and the viewport can be dragged, and zoomed. After dragging/zooming the viewport whenever I redraw the graph (or draw another graph) the viewport stays where it was set previously, but resets on first interaction (jumps to [0,0], and default zoom ratio).
How to reset position of the viewport in d3 with function call?
Dagre author here - are you using the dagre demo or is this custom code? If you're using the dagre demo, I can confirm the behavior you're observing and the fix is to add this line:
svgGroup.attr("transform", "translate(5, 5)");
before this code block (dagre/demo.js, line 252 in my tree):
svg.call(d3.behavior.zoom().on("zoom", function redraw() {
svgGroup.attr("transform",
"translate(" + d3.event.translate + ")"
+ " scale(" + d3.event.scale + ")");
}));
I've updated the demo in the source code under this ticket: https://github.com/cpettitt/dagre/issues/56
Thanks!
Related
I was trying to get same behavior Wil Linssen's implementation
but on d3.js version 4.
I am quite confused with zoom api in version 4.
The changes that I made in the original implementation is:
zoom.translate() replaced with
d3.zoomTransform(selection.node()) and appropriate points added instead:
svg.attr("transform",
"translate(" + t.x + "," + t.y + ")" +
"scale(" + t.k + ")"
);
This one:
zoom
.scale(iScale(t))
.translate(iTranslate(t));
replaced to
var foo = iTranslate(t);
zoom.
translateBy(selection, foo[0], foo[1]);
zoom
.scaleBy(selection, iScale(t));
But it's still has a problem, looks like with scale, zoom out...
Example: Example on d3.v4 - jsfiddle
Thanks for the help
After researching the most easiest way to use d3 v4 api that provides interpolation etc out of box. In original question zoom implemented from scratch, that doesn't make sense in version 4. Solution is similar to #Nixie answers and has few lines of code.
This version includes pan as well, if you do not need, remove svg.call(zoom);
Final example: Zoom D3 v4 - jsfiddle
One interesting note:
The function:
function transition() {
svg.transition()
.delay(100)
.duration(700)
.call(zoom.scaleTo, zoomLevel);
//.on("end", function() { canvas.call(transition); });
}
call(zoom.ScaleTo, zoomLevel) - will zoom to the center of page. But if you need the center of an image, use this one: call(zoom.transform, transform);
Where transform is a function that sets center of your image. For example of such function:
function transform() {
return d3.zoomIdentity
.translate(width / 2.75, height / 2.75)
.scale(zoomLevel)
.translate(-width/2.75, -height/2.75);
}
This piece of code does not resolve all the problems, but could be a starting point. In D3 v4 you can transition zoom in one line of code (see also https://github.com/d3/d3-zoom/blob/master/README.md#zoom_transform)
function interpolateZoom (translate, scale) {
return selection.transition().duration(350)
.call(zoom.transform,
d3.zoomIdentity.translate(translate[0], translate[1]).scale(scale))
}
Modified example: https://jsfiddle.net/2yx1cLrq/. Also, you need to replace selection with selection.node() in all calls to d3.zoomTransform
I've used Angular for this, and it's in my opinion the simplest and cleanest so far. I already had scroll to zoom behavior, adding mouse clicks was easy. Please note I import my d3 functions using ES6 modules.
Example:
import { event as d3event, zoom, scaleExtent, selectAll, transition, duration } from 'd3';
My existing zoom (mouse scroll)
this.zoom = zoom().scaleExtent([1, 8]).on('zoom', this.zoomed.bind(this));
// this.chart is my map chart svg created by d3
this.chart.call(this.zoom)
zoomed() {
// this.g is a <g> element appended to the chart that contains all countries in my case.
this.g
.selectAll('path') // To prevent stroke width from scaling
.attr('transform', d3event.transform);
}
What I added to get zoom buttons:
<!-- My markup -->
<button (click)="zoomClick(1.5)">Zoom in</button>
<button (click)="zoomClick(0.5)">Zoom out</button>
zoomClick(level): void {
this.chart.transition().duration(200).call(this.zoom.scaleBy, level);
}
The d3js library has a built-in behavior called zoom, which applies dragging and zooming in the selected element and its children. It is hard to keep the scale and good visualization when you have a great zoom scale, and text labels, for example, can become ilegible.
I'm trying to find a way to ignore a specific element when zooming the area, but I don't see a way to achieve that.
In this case, there is a circle and a text inside a g. When I zoom a simple pack layout, I have the following behavior, with the following code:
var zoom = d3.behavior.zoom()
.translate([0, 0])
.scale(1.0)
.scaleExtent([0.1, 3])
.on("zoom", function() {
child.attr("transform", "translate(" + d3.event.translate +
")scale(" + d3.event.scale + ")");
});
I need to prevent text from being zoomed, staying static:
Is there a way to achieve that or it is neeed to rewrite the zoom behavior and reapply the scroll events also?
Well I already got it working, but somehow when tested now it does not work anymore. So in my project I have a map that is zoom- and dragable. When zoomed in or dragged you can always click a Reset button to reset the whole presention. That works great.
So the zoom definition:
var g = svg.append("g");
var zoom = d3.behavior.zoom()
.translate([0, 0])
.scale(1)
.scaleExtent([1, maximumZoom])
.on("zoom", zoomed);
svg.call(zoom);
and the zoom function is:
g.attr("transform", "translate([0,0]).scale(" + 1 + ")");
zoom.translate([0, 0]);
zoom.scale(1);
The reset is done properly when the Reset button is clicked. But when now zooming or dragging anywhere, you actually see that it was the old zoom and location.
But the strange thing is that :
zoom.translate([0, 0]);
zoom.scale(1);
should fix that issue. As I said 1 month ago it worked properly.
In addition also the zoomed function:
function zoomed() {
g.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
I'm not 100% sure but it seems that calling the zoom event or let's say setting the zoom event later on on the svg element is fixing that issue.
So simply call
zoom.on("zoom", zoomed);
svg.call(zoom);
after everything is finished. I put it into a function that is loading the map. And now it's again working properly.
You can see the website here.
How can I change the position of the legend, where the legend being on the right most side, I can make it move to the left most side? I tried to do some changes in the nvd3.js code, but that hasn't worked for me at all!. I'm sure how and where can i add attributes to change the position?
Just for info, here is the legend (Key Usage', 'block Usage','other usage') whose position im trying to change:
Any ideasss? Thanks!
Absolutely right about the rightAlign Property and good news is we can set it in the addGraph function as follows:
chart.legend.rightAlign(false);
From the source code comment line 5005 in nv.d3.js:
//position legend as far right as possible within the total width
if (rightAlign) {
g.attr('transform', 'translate(' + (width - margin.right - legendWidth) + ',' + margin.top + ')');
}
else {
g.attr('transform', 'translate(0' + ',' + margin.top + ')');
}
So I guess you can not positioning the legend to left side:)
The code is designed to put the legend as far right as possible, it also contains some wrapping logic, once the length of legends in one line reach some max limit(like reach the chart's width), it will separate into two lines in well format.
Hope it helps!
There is a question here
d3.js: pan with limits
Answering the question to limiting the pan movement. But this uses axis, which I do not have.
What I have is a force directed graph which has the ability to pan and zoom.
Now I have put a limit on the zoom using :
.call(d3.behavior.zoom().on("zoom", redraw).scaleExtent([0.8, 2]))
I am wondering how do I go about limiting the pan movement of this graph so i dont drag the network/graph outside of the viewport.
(The network may change size depending on the JSON file imported, so I can't use exact figures)
inner.attr("transform","translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")");
There is no equivalent zoom.extent. You can override zoom's translate with mins and maxes inside your redraw function, though. For instance, if you wanted to limit zoom to stay within 500px of its original position:
function redraw() {
var oldT = yourZoom.translate()
var newT = [0,0]
newT[0] = Math.max(-500,oldT[0]);
newT[0] = Math.min(500,oldT[0]);
newT[1] = Math.max(-500,oldT[0]);
newT[1] = Math.min(500,oldT[0]);
yourZoom.translate(newT);
//The rest of your redraw function, now using the updated translate on your zoom
}