Having trouble panning and zooming on my d3 map - javascript

I have this map in d3
http://107.170.20.64/
that renders topojson with a custom projection and path, like this
var projection = d3.geo.mercator().translate([width / 2, height / 2]).scale(width * 185).center([-89.99, 29.975]);
var path = d3.geo.path().projection(projection);
I am trying to adapt it so that it pans and zooms using Bostock's tutorial. Here is the function that fires once the topojson loads (showing my adaptations of Bostock's method):
function ready(error, us) {
var zoom = d3.behavior.zoom()
.translate([0, 0])
.scale(1)
.scaleExtent([1, 8])
.on("zoom", zoomed);
var features = svg.append("g");
features.append("g")
.attr("class", "precincts")
.selectAll("path")
.data(topojson.feature(us, us.objects.orleansgeojson).features)
.enter().append("path")
.attr("class", (function(d) {
return wards.get(d.id) + " precinct";
}))
.attr("title", (function(d) {
return votesone.get(d.id) + "-" + votestwo.get(d.id);
}))
.attr("id", function(d) {
return d.id;
})
.attr("d", path);
svg.append("rect")
.attr("class", "overlay")
.attr("width", width)
.attr("height", height)
.call(zoom);
function zoomed() {
features.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
features.select(".precinct").style("stroke-width", 1.5 / d3.event.scale + "px");
}
Somehow, zoomed is never getting called. If I set a breakpoint on zoomed it never catches. I think that the final append to svg calls zoom which somehow sets up a d3 behavior that creates listeners for mouse events and calls the zoomed function. That's what I understand so far about what is going on (clarification or detailed answers would be great). Is the problem that the listeners are not getting set? If so, how do I debug why they are not getting set? The overlay shows up in my svg -- it just does not seem to be picking up mouse events.

The problem in your case is unrelated to the zoom behaviour. You're setting the z-index of the div containing the map to be -1. This means that it's behind the containing div, which gets all the mouse events. So the map is "obscured" by the element that contains it.
To fix, either set the z-index of the map div to be higher than -1, or set the z-index of all the containing elements (including the body) to be -1 or less.

Related

d3 how to tie text to top right corner of view port while zooming and panning

I am creating a mapping application in d3 and want to tie some text to the top right corner of my view port. Additionally, I want the text to remain in the top right corner while I zoom and pan across the application.I think I can solve my problem by figuring out how to get the coordinates of the top right corner of my view. Knowing this information would allow me to then set the coordinates of my text element. I've tried manually setting the dimensions of the containing svg element and then moving the text to that location but interestingly this didn't work. I was hoping to be able to find the coordinates programatically rather than setting coordinates manually. How can I do this in d3/javascript?
EDIT:
My code is a modification of this code by Andy Barefoot: https://codepen.io/nb123456/pen/zLdqvM
My own zooming and panning code has essentially remained the same as the above example:
function zoomed() {
t = d3
.event
.transform
;
countriesGroup
.attr("transform","translate(" + [t.x, t.y] + ")scale(" + t.k + ")")
;
}
I'm trying to append the text at the very bottom of the code:
countriesGroup.append("text")
.attr("transform", "translate(" How do I get top right coordinates? ")")
.style("fill", "#ff0000")
.attr("font-size", "50px")
.text("This is a test");
My idea is to be able to get the top right coordinates of the view port through the code rather than setting it manually and then have the coordinates of the text update as the user zooms or pans.
To keep something in place while zooming and panning you could invert the zoom:
point == invertZoom(applyZoom(point))
This isn't particularly efficient, as we are using two operations to get to the original number. The zoom is applied here:
countriesGroup
.attr("transform","translate(" + [t.x, t.y] + ")scale(" + t.k + ")");
While the inversion would need to look something like:
text.attr("x", d3.zoom.transform.invert(point)[0])
.attr("y", d3.zoom.transform.invert(point)[1])
.attr("font-size", baseFontSize / d3.zoom.transform.k);
Where point and base font size are the original anchor point and font size. This means storing that data somewhere. In the example below I assign it as a datum to the text element:
var width = 500;
var height = 200;
var data = d3.range(100).map(function() {
return {x:Math.random()*width,y:Math.random()*height}
})
var zoom = d3.zoom()
.on("zoom",zoomed);
var svg = d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height)
.call(zoom);
var g = svg.append("g")
var circles = g.selectAll()
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", 5)
.attr("fill","steelblue")
var text = g.append("text")
.datum({x: width-10, y: 20, fontSize: 12})
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; })
.style("text-anchor","end")
.attr("font-size",function(d) { return d.fontSize; })
.text("This is a test");
function zoomed() {
g.attr("transform", d3.event.transform);
var d = text.datum();
var p = d3.event.transform.invert([d.x,d.y]);
var x1 = p[0];
var y1 = p[1];
text.attr("x",x1)
.attr("y",y1)
.attr("font-size", d.fontSize / d3.event.transform.k)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
Better Solution
The above is the solution to the approach you seem to be looking for. But the end result is best achieved by a different method. As I mention in my comment, the above approach goes through extra steps that can be avoided. There can also be some size/clarity changes in the text when zooming (quickly) using the above method
As noted above, you are applying the zoom here:
countriesGroup
.attr("transform","translate(" + [t.x, t.y] + ")scale(" + t.k + ")")
The zoom transform is applied only to countriesGroup, if your label happens to be in a different g (and not a child of countriesGroup), it won't be scaled or panned.
We wouldn't need to apply and invert the zoom, and we wouldn't need to update the position or font size of the text at all.
var width = 500;
var height = 200;
var data = d3.range(100).map(function() {
return {x:Math.random()*width,y:Math.random()*height}
})
var zoom = d3.zoom()
.on("zoom",zoomed);
var svg = d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height)
.call(zoom);
var g = svg.append("g");
var g2 = svg.append("g"); // order does matter in layering
var circles = g.selectAll()
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", 5)
.attr("fill","steelblue")
// position once and leave it alone:
var text = g2.append("text")
.attr("x", width - 10)
.attr("y", 20 )
.style("text-anchor","end")
.attr("font-size", 12)
.text("This is a test");
function zoomed() {
// apply the zoom to the g that has zoomable content:
g.attr("transform", d3.event.transform);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>

Zoom/Pan D3.js Polar Scatter Plot

I created a polar scatter plot using D3.js (based on this post) .
I would like to add the functionality to zoom and pan. I've seen examples for rectangular plots, but nothing for zooming/panning on circular plots.
I am just a beginner with using D3 so I'm a little lost. Can anyone help/offer suggestions?
I'm not entirely sure what your goal is, but I tried something below.
First you should add zoom behaviour. I used the r scale for both your x and y directions like:
var zoomBeh = d3.behavior.zoom()
.x(r)
.y(r)
.on("zoom", zoom);
And call the zoom behaviour into your svg:
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
.call(zoomBeh);
Finally you should make a zoom function.
function zoom() {
var t = svg.transition().duration(750);
svg.selectAll(".point").transition(t)
.attr("transform", function(d) {
var coors = line([d]).slice(1).slice(0, -1);
return "translate(" + coors + ")"
})
}
Here is an updated fiddle. It's a little bit staggering, I'm not sure why yet.

How do I fix zooming and panning in my cluster bundle graph?

I've created a hierarchical edge bundling graph with some data and after trying to implement zooming and dragging on the graph I've run into some issues.
Here is a similar working jsfiddle of what I have so far: https://jsfiddle.net/hnjvxd48/1/
and the relevant code:
var zoom = d3.behavior.zoom()
.scaleExtent([0,8])
.on("zoom", zoomhandler);
var drag = d3.behavior.drag()
.origin(function(d) { return d; })
.on("dragstart", dragstarted)
.on("drag", dragged)
.on("dragend", dragended);
var svg = d3.select(".container").append("svg")
.attr("width", diameter)
.attr("height", diameter)
.append("g")
.attr("transform", "translate(" + radius + "," + radius + ")")
.call(zoom)
.call(drag);
function zoomhandler(){
svg.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
function dragstarted(d) {
d3.event.sourceEvent.stopPropagation();
d3.select(this).classed("dragging", true);
}
function dragged(d) {
d3.select(this).attr("cx", d.x = d3.event.x).attr("cy", d.y = d3.event.y);
}
function dragended(d) {
d3.select(this).classed("dragging", false);
}
You'll notice:
1) Dragging and zooming only occur on the outer edges and not the inner part of the graph.
2) Dragging the graph around causes flickering and centering of the graph to change and become cut off.
3) Zooming (done via mouse scroll wheel) also centers the graph incorrectly and places it in an unpredictable position, partly out of the view port.
4) Attempting to drag the graph after it has been zoomed out causes it to flicker and disappear.
What's causing these issues and how can I fix them? How can I give my graph (which is much bigger than the sample one I provided) an initially "zoomed out" state and perhaps trigger the zooming functionality using a button click event rather than the native scroll wheel implementation?
The big thing to notice here is that the drag functions are actually redundant. In this (http://bl.ocks.org/mbostock/6123708) d3 drag + zoom example, they're being used to move individual 'dots'. You want to move the whole graph at once, and this is handled by the 'translate' portion of the 'zoomhandler' function you've included.
Here's a working fiddle: https://jsfiddle.net/14f9f4k3/1/
And the key code that with changes noted in comments:
var zoom = d3.behavior.zoom()
.scaleExtent([0,8])
.on("zoom", zoomhandler);
//added another group as a child of the group having zoom called on it w/ id 'draggroup' to append nodes and links to
var svg = d3.select(".container").append("svg")
.attr("width", diameter)
.attr("height", diameter)
.append("g")
.attr("transform", "translate(" + radius + "," + radius + ")")
.call(zoom)
.append('g')
.attr('id','draggroup');
//added a rect behind the other elements to make an easy target for the pointer
d3.select('#draggroup')
.append('rect')
.attr("transform", "translate(" + -radius + "," + -radius + ")")
.attr('width',diameter)
.attr('height',diameter)
.attr('fill','#fff');
//no need for separate drag functions, translate and scale here do what you want
function zoomhandler(){
svg.attr("transform", "translate(" + d3.event.translate + ") scale(" + d3.event.scale + ")");
}
//append the links and nodes to the group we created above instead of the base svg
var link = d3.select('#draggroup').append("g").selectAll(".link"),
node = d3.select('#draggroup').append("g").selectAll(".node");

D3 Panning Chart with Dates

I'm trying to get a multi-line date based chart to pan nicely across the X date axis and I simply cannot figure out what the problem is.
I have the zoom behaviour set up in the code but it's just not performing as expected. If you click on a point in a line and scroll it appears to be scrolling the axis, if it click on the labels in the axis it also scrolls but the actual visualisation of data doesn't scroll.
var zoom = d3.behavior.zoom()
.x(x)
.scaleExtent([1, 1])
.on("zoom", function () {
svg.select(".x.axis").call(xAxis);
svg.select(".lines").call(xAxis);
});
svg.call(zoom);
Also if you click directly on the back ground the mouse event doesn't seem to make it's way to the control at all.
I read a few examples on this and each seem to take a vastly different approach which I've tried but none have worked for my chart.
There are possibly a number of issues that exist as barriers to getting this working so I thought the best way to illustrate the problem was in a JsFiddle.
D3 Chart Panning Fiddle
What I'm trying to achieve is when there is a lot of data to visualise the chart can adapt to the data set and allow the data to extend beyond the bounds of the chart.
Currently clicking on the background does not allow panning because you have applied zoom behavior to the g element not to the svg.
var svg = d3.select('#host')
.data(plotData)
.append("svg")
.attr("id", "history-chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.call(zoom);
Right now on zoom you have updated x and y axes but not the visualization. So you have update the lines and circles also like this.
var zoom = d3.behavior.zoom()
.x(x)
.scaleExtent([1, 1])
.on("zoom", function () {
svg.select(".x.axis").call(xAxis);
svg.select(".lines").call(xAxis);
svg.selectAll("path.lines")
.attr("d", function(d) { return line(d.values); });
svg.selectAll("circle")
.attr("cx", function(d) { return x(d.date); })
.attr("cy", function(d) { return y(d.value); });
});
Since you are panning the map you will have to use clip path for restricting visualization from moving outside the chart
var clip = svg.append("svg:clipPath")
.attr("id", "clip")
.append("svg:rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
Apply clip path to the g elment which contains lines and cicrles.
var attribute = svg.selectAll(".attribute")
.data(plotData)
.enter().append("svg:g")
.attr("clip-path", "url(#clip)")
.attr("class", "attribute");

How to apply d3.js svg clipping after zooming

I am trying to use an svg-clippath with d3.js and the zoom behaviour.
The following code creates a rectangle, which will then be clipped by a rectangualar clipping region.
<svg class="chart"></svg>
<script>
var width = 800;
var height = 600;
var svg = d3.select(".chart")
.attr("width", width)
.attr("height", height)
.append("g");
var clip = svg.append("defs")
.append("clipPath")
.attr("id","clip")
.append("rect")
.attr("width",200)
.attr("height",200)
.attr("x",100)
.attr("y",100);
var zoom = d3.behavior.zoom().
on("zoom",zoomed);
function zoomed(){
container.attr("transform", "translate(" + d3.event.translate
+")scale(" + d3.event.scale + ")");
container.attr("clip-path","url(#clip)");
}
svg.call(zoom);
var container = svg.append("g")
.attr("clip-path","url(#clip)");
var rect = container.append("rect")
//.attr("clip-path","url(#clip)")
.attr("class","bar")
.attr("x",150)
.attr("y",150)
.attr("width",350)
.attr("height",350);
</script>
What I want is for the clipping to be applied again after zooming / moving (so that I cannot
move the rectangle outh of the clipping region, which right now i can do without any problems.) How do I do that?
I am assuming that the current behaviour is caused by the fact that the clipping is applied before the transformation.
I had the same problem and spent the last couple of hours trying to figure out a solution. Apparently, the clip-path operates on the object prior to transformation. So I tried to reverse-transform the clip object when performing the zoom transformation, and this worked !
It is something in the spirit of:
var clip_orig_x = 100, clip_orig_y = 100;
function zoomed() {
var t = d3.event.translate;
var s = d3.event.scale;
// standard zoom transformation:
container.attr("transform", "translate(" + t +")scale(" + s + ")");
// the trick: reverse transform the clip object!
clip.attr("transform", "scale(" + 1/s + ")")
.attr("x", clip_orig_x - t[0])
.attr("y", clip_orig_y - t[1]);
}
where clip is the rectangle in the clipPath. Because of interactions between zooming and translation, you need to set "x" and "y" explicitly instead of using transform.
I am sure experienced d3 programmers out there will come up with a better solution, but this works !

Categories

Resources