Changing zoom level of d3 SVG canvas? - javascript

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

Related

d3.js rewriting zoom example in version4

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.

How to make a d3 sankey link flow out the bottom of the diagram?

With a d3.js sankey diagram, how can I make a link flow out of a node and off the diagram towards either the bottom or top? This necessitates a 90deg turn in the link and for it to not end at a node (or at least not one in the diagram) and symbolizes a flow going out of the modeled system.
I want it to look something like the 'Finished Petroleum Products' link in this diagram.
Specifically I'm using this boilerplate, but a more generic answer is fine I'm stuck on which methods I can use to make this happen but once I know what to use and how to use them I can implement it.
Update: Basically, what I'm wondering is how to re-write one of the d3 functions (which?) to make a link terminate with a horizontal line (ie flush with the SVG ceiling or floor) instead of a vertical line (ie another node's side face).
The code below is a function I used to create a link path which leaves a node on its right, goes horizontally briefly, bends upwards with a quarter-circle and exits the ceiling of the diagram. I was naive in that the solution had nothing to do with re-writing a D3 function..it just took learning how to use the SVG path language.
function drawPathLeavingDiagram(link) {
var x0 = link.source.x + link.source.width,
x1 = x0 + link.source.width / 4,
x2 = x1 + link.source.width / 2,
y0 = link.source.y + link.sourceY + link.thickness / 2,
y1 = y0 - link.source.width / 2,
rx = link.source.width / 2,
ry = link.source.width / 2;
return "M" + x0 + "," + y0 + "L" + x1 + "," + y0 +
"A" + rx + "," + ry + "," + 0 + "," + 0 + "," + 0 + "," + x2 + "," + y1 +
"V" + 0;
}

How do I change the speed of the zoom in D3

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

placing d3 label at end of arc

Given the following speed dial, which is constructed using arcs in D3:
segmentArc = d3.svg.arc().outerRadius(radius - chartInset).innerRadius(radius - chartInset - barWidth).startAngle(arcStartRad + startPadRad).endAngle(arcEndRad - endPadRad);
How do I move the labels in each segment so that it appears right justified (at the end of each segment opposed to center)?
the labels are currently added likes this:
chart.append('text')
.attr('transform', () => {
var x = Math.round(segmentArc.centroid()[0]);
var y = Math.round(segmentArc.centroid()[1]);
return 'translate(' + x + ',' + y + ')';
})
.style("text-anchor", "middle")
.text(sectionLabel);
I solved this problem by replicating each segment arc, giving it a transparent fill and making it exactly twice as long. From there it is as simple as working out the centroid for the transparent arc.

Updating Text Labels in D3JS

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!

Categories

Resources