d3 Re-center group of elements in SVG - d3.js

I have an svg that looks like this:
It's basically a network diagram comprised of nodes and links that has the ability to pan and zoom. Let's say for instance I accidentally drag the entire group off the screen. What is the best way for me to re-center my group so I can see it again?
I've tried to do a transform and changed the scale value, but things will still be outside the view.
this.zoomTrans.scale = this.zoomTrans.scale - .1;
this.container.attr('transform', 'translate(' + this.zoomTrans.x + ',' + this.zoomTrans.y + ') scale(' + this.zoomTrans.scale + ')');

In most situations d3 generates points starting from the origin (0, 0) which is also obviously the default translate. Moving back to (0, 0) should therefore get your back to a visible part of your chart.
To really center your chart, you need to calculate the center point, which is generally done by calculating the minX, maxX, minY and maxY from your points and then calculating (minX + maxX) / 2 and (minY + maxY) / 2 and center that point in your window.
A more advanced solution is to track the translation and limiting it in a way that the chart will never go out of the screen. Again, you need the min and max values for your coordinates. The min horizontal translation is then -maxX and the max horizontal translation is windowWidth + minX. With zoom not equal to 1, it's only about multiplying the limits correctly with the zoom.

Related

Zoom world map in D3 upto particular level Around a given latitude and longitude

Is there any way that I can focus into d3 world Map around a specific latitude and longitude on load of file.
Here is working plunker in which I can zoom around a d3 world Map.
plunker
Below code is used to zoom in for click.
function clicked() {
currScale2 = projection.scale();
if(beforeClickValue == 0)
beforeClickValue = 150;
beforeClickValue = beforeClickValue + 100;
projection.scale(beforeClickValue);
g.selectAll("path").attr("d", path);
}
I need to zoom in near or around Kenya, if I provide a particular location in Kenya, eg:
Latitude 0.55378653650984688
Longitude 35.661578039749543
If your centering point is determined by a feature
If your point is a feature centroid, then you can automatically center your map using that feature:
There are a few ways to achieve this, one would be to set your projection to be centered on your features:
projection.fitSize([width,height],geoJSONKenyaTurkana);
fitSize takes the width and height of a bounding box - your svg - and sets the scale and translate of the projection to maximize the size of the features within that bounding box. .fitExtent will allow a bit more flexibility regarding margins:
projection.fitExtent([[10,10],[width-10,height-10]],geoJSONKenyaTurkana);
This will provide margins of 10 pixels: the first coordinate is the top left of the bounding box, while the second coordinate is the bottom right.
After setting your projection to be centered with either method, then you can append the features - your zoom constraints, however, will be relative to this starting point - as you have zoomed in on the projection. Here's a plunkr with this approach (using fitSize):
https://plnkr.co/edit/E7vqcwwISmmxUarCsWvw?p=preview
I've used your featureCollection as the feature, but you could center it on an individual feature in the feature collection.
Alternatively, and possibly more in line with your title, you can use a zoom identity to set the intitial zoom factor with d3.zoom, this manipulates the svg rather than the projection and uses your zoom function:
var bounds = path.bounds(geoJSONKenyaTurkana),
dx = bounds[1][0] - bounds[0][0],
dy = bounds[1][1] - bounds[0][1],
x = (bounds[0][0] + bounds[1][0]) / 2,
y = (bounds[0][1] + bounds[1][1]) / 2,
scale = .9 / Math.max(dx / width, dy / height),
translate = [width / 2 - scale * x, height / 2 - scale * y];
svg.call(_zoom.transform, d3.zoomIdentity
.scale(scale)
.translate(translate[0]/scale,translate[1]/scale)
);
This gives us something that looks like this:
https://plnkr.co/edit/CpL4EDUntz853WzrjtU0?p=preview
If you want to manually set a centering point
If however, you want to set your map to be centered according to a manually set point, you can accomplish this much the same way as above: modifying the projection, or modifying the zoom:
To modify the projection, you can use .center() which takes a coordinate and centers the map on this point:
projection.center([longitude,latitude])
Of course, points don't have area, so you will have to set the scale factor yourself, the value will depend on what you want to show:
projection.center([longitude,latitude]).scale(k);
Larger values are more zoomed in.
Alternatively, to manipulate the zoom function, we can use something like:
var x = projection([35.661578039749543,0.55])[0],
y = projection([35.661578039749543,0.55])[1],
scale = 20,
translate = [width / 2 - scale * x, height / 2 - scale * y];
svg.call(_zoom.transform, d3.zoomIdentity
.scale(scale)
.translate(translate[0]/scale,translate[1]/scale)
);
As with setting the projection to center on a specific point, you'll need to set a scale value manually. Here I've arbitrarily chosen 20.

Let's make a map tutorial, stuck at projection step

I'm trying to recreate a map of the Netherlands from Mike Bostock's tutorial. I get the first step done and actually see the smaller map. But when I go to the second step and try to change the projection everything is blank. Is it possible that this is caused by the data including the Dutch Antilles? They are so far it apart that I might be looking at the ocean?
My files
It seems to me that setting the center attribute on the projection was messing things up, it might be because your data was crossing date line? I notice the the bounding box stretch way off to the south and west.
My approach was to set the scale and translation based on one of the centrally located provinces. The scale and translation were automatically calculated based on Mike's code in this stack overflow question. The idea is to use a unit projection and then to calculate the bounds of a given feature. These bounds are then used to calculate appropriate scale and translation (avoiding using the center attribute). The projection is then updated using the recalculated scale and transform.
The relevant code is:
//select a province to center on
var l = topojson.feature(nld, nld.objects.subunits).features[8],
//calculate the bounds
b = path.bounds(l),
//calculate the scale based on the bounds (note that I've set the proportion to
//less than one so you can see all of the Netherlands)
s = .125 / Math.max((b[1][0] - b[0][0]) / width, (b[1][1] - b[0][1]) / height),
//calculate the translation based on the bounds
t = [(width - s * (b[1][0] + b[0][0])) / 2, (height - s * (b[1][1] + b[0][1])) / 2];
//update the projection
projection
.scale(s)
.translate(t);
And you can find an example here

D3 force layout: Finding relative center based on current visible view

I have a d3.js graph that is a forced layout design. I have allowed for users to zoom in and out of the graph with bounds set so they can't zoom in past 1 and can't zoom out past 0.1. Right now, when I plot values on the graph, I automatically send them to the center of the graph (based on the height and width of the SVG container). This works fine until I zoom out then zoom in to some where else and plot a new node. The new node will end up back at the original center and not my new relative center.
How I scale when zooming right now:
function onZoom() {
graph.attr("transform", "translate(" + zoom.translate() + ")" + " scale(" + zoom.scale() + ")");
}
I was unable to find any calls to get the current visible coordinates of the graph, but even with those, how would I use them to calculate the relative center of the graph if my SVG graph size always remains static?
I know this post is very old but I found it useful. Below is the update for d3 v5.
var el = d3.select('#canvas').node().getBoundingClientRect();
var z = d3.zoomTransform(svg.node());
var w = el.width;
var h = el.height;
var center = {
x: (z.x / z.k * -1) + (w / z.k * 0.5),
y: (z.y / z.k * -1) + (h / z.k * 0.5)
};
One thing of note, however... is that I found I also needed to divide the pan x/y by the scale factor z.k. Which, you did not do in your formula.
For simple geometric zoom, it's fairly straightforward to figure out the visible area from the visible area dimensions plus the translation and scale settings. Just remember that the translation setting is the position of the (0,0) origin relative to the top left corner of your display, so if translation is (-100,50), that means that top left corner is at (+100,-50) in your coordinate system. Likewise, if the scale is 2, that means that the visible area covers 1/2 as many units as the original width and height.
How to access the current transformation? graph.attr("transform") will give you the most recently set transform attribute string, but then you'll need to use regular expressions to access the numbers. Easier to query the zoom behaviour directly using zoom.translate() and zoom.scale().
With those together, you get
var viewCenter = [];
viewCenter[0] = (-1)*zoom.translate()[0] + (0.5) * ( width/zoom.scale() );
viewCenter[1] = (-1)*zoom.translate()[1] + (0.5) * ( height/zoom.scale() );
I.e., the position of the center of the visible area is the position of the top-left corner of the visible area, plus half the visible width and height.

Painless method to zoom&pan so that all elements are within drawing area - d3js

I have a neat script to draw for me using d3, but sometimes, when I have lots of data some of my nodes go off the div. I could code something to handle this at the co-ordinates level, I guess, but I can amend this easily using zoom and pan manually and was wondering whether there's a good, simple way to have it done automatically.
I can consider any other solution too.
To zoom/pan automatically, you would need to get the extent of your node positions and calculate the scale and offset accordingly. To get the min/max coordinates, you can simply iterate over your nodes. Once you have these, scale and offset can be calculated as follows.
scale = Math.min(width / (maxX - minX), height / (maxY - minY));
where width and height denote the dimensions of the container (i.e. the SVG). Assuming that you're zooming/panning by setting the SVGs transform attribute, this is what you would need to do.
svg.attr("transform",
"translate(" + minX*scale + "," + (-minY)*scale + ") scale(" + scale + ")");
What this does is compute the scale such that the larger of the x/y dimensions fits into the respective dimension of the container and repositions the container such that the top left corner of the extent of node positions corresponds to the top left corner of the container.

How to scale and translate together?

I want to scale and translate D3 force graph, both at the same time. E.g. On clicking a button it shoud scale to 400% and then make itself center on the screen. This should all happen with a smooth animation effect.
//animate vis to visible area
vis.transition()
.duration(2000)
.attr("transform", "scale(" + someScaleValue + ")" + "center("0,0)");
Doing this, scaling works fine, but graph is not centered. It shifts towards right-bottom corner.
vis.transition()
.duration(2000)
.attr("transform", "scale(" + someScaleValue + ")");
Why is scale is getting reset to 100% when I translate it second time.
I also tried using:
vis.transition()
.duration(2000)
.attr("transform", "scale(" + scaleValue + ")" + "translate(0,0)");`
This is not working too. Please help me.
center(0,0) is not a valid transform-definition to be used with transform, as per the spec.
If you want translate(0, 0) to take the object to the center of the screen (usually the top-left corner of vis), then you might want to set viewBox of the outer svg element to be: "-width/2 -height/2 width height". This would set the co-ordinate system inside the svg element such that the center lies at (0, 0). Alternatively, you can use translate(width/2, height/2).
Also, each time you call .attr('transform', ...), you overwrite the old value of the transform attribute. This is the possible reason why you are losing the original scaling on translating. The best solution would be to put the vis element inside a g which has the scaling in the transform attribute which remains constant.

Resources