I'm trying to have D3 pan & zoom as in this example:
It works except for releasing the zoom/pan on mouseup() - once I click, there's no way to stop panning without reloading the page. Here's the piece that calls the zoom function, which in turn is identical to the example:
var svg = d3.select("#chart").append("svg")
.attr("id", "scatter")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(d3.behavior.zoom().x(x).y(y).scaleExtent([1, 10]).on("zoom", zoom))
I've been looking through the D3 source code and mouseup() never gets called, in contrast to mousedown() and mousemove() which respond to the respective events:
function mousedown() {
var target = this,
event_ = event.of(target, arguments),
eventTarget = d3.event.target,
moved = 0,
w = d3.select(d3_window).on("mousemove.zoom", mousemove).on("mouseup.zoom", mouseup),
l = location(d3.mouse(target)),
selectEnable = d3_event_userSelectSuppress("zoom");
function mousemove() {
moved = 1;
translateTo(d3.mouse(target), l);
dispatch(event_);
}
function mouseup() {
if (moved) d3_eventCancel();
w.on("mousemove.zoom", null).on("mouseup.zoom", null);
selectEnable();
if (moved && d3.event.target === eventTarget) d3_eventSuppress(w, "click.zoom");
}
}
I'm using D3.v3, whereas the example uses D3.vs2, so perhaps the code changed - but I haven't been able to find any useful hint. I'm new to javascript, so this may of course be a different issue altogether.
Related
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");
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 !
I have a d3 globe, and I have it scaling up (zooming in) when I doubleclick it. However, the zoom only works the first time I doubleclick. After that, I see that the program is entering the dblclick function, but no zooming is taking place. This is probably a stupid question, but I would be grateful if anyone were able to tell me how to make the zoom happen each time the globe is doubleclicked.
var width = 800,
height = 800,
centered;
var feature;
var projection = d3.geo.azimuthal()
.scale(380)
.origin([-71.03,42.37])
.mode("orthographic")
.translate([380, 400]);
var circle = d3.geo.greatCircle()
.origin(projection.origin());
// TODO fix d3.geo.azimuthal to be consistent with scale
var scale = {
orthographic: 380,
stereographic: 380,
gnomonic: 380,
equidistant: 380 / Math.PI * 2,
equalarea: 380 / Math.SQRT2
};
var path = d3.geo.path()
.projection(projection);
var svg = d3.select("#globe").append("svg:svg")
.attr("width", 800)
.attr("height", 800)
.on("dblclick", dblclick)
.on("mousedown", mousedown);
var g = svg.append("g");
d3.json("simplified.geojson", function(collection) {
g.append("g")
.attr("id", "countries")
.selectAll("path")
.data(collection.features)
.enter().append("svg:path")
.attr("d", clip)
.attr("id", function(d) { return d.properties.ISO3; })
.on("mouseover", pathOver)
.on("mouseout", pathOut)
.on( "dblclick", dblclick)
.on("click", click);
feature = svg.selectAll("path");
feature.append("svg:title")
.text(function(d) { return d.properties.NAME; });
});
...
function dblclick(d) {
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 = width / 2;
y = height / 2;
k = 1;
centered = null;
}
g.selectAll("path")
.classed("active", centered && function(d) { return d === centered; });*/
g.transition()
.duration(750)
//.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")scale(" + k + ")translate(" + -x + "," + -y + ")")
.attr("transform", "scale(1.5)");
//.style("stroke-width", 1.5 / k + "px");
}
I agree with Erik E. Lorenz (no way to link to Erik's answer, it appears). Right now you're setting the zoomscale in the line
.attr("transform", "scale(1.5)");
The problem is that each time you call dblclick(), you're "resetting" it to 1.5. It's not multiplying by 1.5 it's just getting set. D3 doesn't remember what it used to be. That's why the first time you call dblclick() it works (because you're transforming the scale to 1.5 from 1). But from then on, the scale is already transformed to 1.5 and you just keep setting the scale transform to 1.5.
You need to keep track of "how far you've zoomed". And to do that you need a variable that keeps it's value between calls to dblclick(). I'd do something like this:
/* given the structure of your code, you can probably just declare the
variable before the function declaration. the function `dblclick` will
have access to the variable via closure */
var zoomScale = 1;
/* then you can just do this */
function dblclick(d) {
// you'll probably want to play with the math here
// that is, "1.5" might not be best
zoomScale = zoomScale * 1.5; // or some shorthand
g.transition()
.duration(750)
.attr("transform", "scale(" + zoomScale + ")");
}
I think that that scale(1.5) might be the problem. Have you tried dynamically increasing that factor every time dblclick() is called?
I'm trying to update elements in the treemap:
drawTreeMap = (data) ->
margin =
top: 0
right: 0
bottom: 0
left: 0
width = window.innerWidth
height = window.innerHeight
color = d3.scale.category20c()
treemap = d3.layout.treemap().size([width, height]).sticky(true).value((d) -> d.size)
div = d3.select("body").append("div")
.style("position", "relative")
.style("width", (width + margin.left + margin.right) + "px")
.style("height", (height + margin.top + margin.bottom) + "px")
.style("left", margin.left + "px")
.style("top", margin.top + "px")
node = div.datum(data).selectAll(".node")
.data(treemap.nodes)
node.enter()
.append("div")
.attr("class", "node")
.call(position)
.style("background", (d) -> (if d.children then color(d.name) else null))
.on "click", ->
el = d3.select(this)
data = el[0][0].__data__
while (data.parent.parent)
data = data.parent
console.log("updated to data:")
console.log(data)
drawTreeMap(data)
#updateTreemap(div, treemap, data)
node.exit()
.remove()
node.transition().duration(1500).call position
data is what I want it to be in the console.log statement, but the treemap isn't getting updated. Most of the code is directly from the treemap example.
Like Lars points out, you're creating a new div (the one assigned to var div) every time you call drawTreeMap(). For starters, you need to either simply move that div creation outside of drawTreeMap so that it only runs once. Or, if you want to get fancy, you could leave the creation inside the function, but do something like this:
div = d3.select("body").selectAll("div.treemap-container").data([null])
div.enter()
.append('div')// only creates the div the first time this runs
.attr('class', 'treemap-container')
// etc...
That's the only odd thing I can see. Maybe there's another bug, but it's hard to track down without a working jsFiddle. If you can provide one, post a comment here and I'll take a look further.
As an aside, for style, rather than doing this: data = el[0][0].__data__, you should just do this: data = el.datum()
Finally, note that you're not using a key function for the data binding, so even if you get the treemap to re-render, there would be no object persistence (i.e. existing divs could get reassigned to a different data point arbitrarily).
So I've been given an assignment where I need to work with the US map, divided into states and further into counties. I already have the current code and need to extend on it.
I am not able to understand the following snippet from the code.
var colorRange = [ 'rgb(247,251,255)',
'rgb(222,235,247)',
'rgb(198,219,239)',
'rgb(158,202,225)',
'rgb(107,174,214)',
'rgb(66,146,198)',
'rgb(33,113,181)',
'rgb(8,81,156)',
'rgb(8,48,107)'];
var quantile = d3.scale.quantile()
.range(colorRange);
var path = d3.geo.path();
var svg = d3.select("#map")
.attr("width", width)
.attr("height", height)
.append('svg:g')
.call(d3.behavior.zoom().on("zoom", redraw))
.append('svg:g');
svg.attr("transform", "scale( " + .9 + ")");
function redraw() {
console.log("here", d3.event.translate, d3.event.scale);
svg.attr("transform",
"translate(" + d3.event.translate + ")"
+ " scale(" + d3.event.scale + ")");
}
What exactly is happening in each line of this snippet?
Full Code taken from here
Thanks
the var colorRange is just a variable holding the colors to be applied to the various counties, the var quantile sets the scale for the color to be applied, the var path calls the path function and the var svg creates an svg. In this the command .call has a functionality .on("zoom", which calls the function redraw.
function redraw defines the function, which translates and scales the svg accordinglly to the zoom