D3 zoom callback - javascript

Take this timeless example of D3 zoom. Shortened code below:
var svg = d3.select("svg")
...;
var zoom = d3.zoom()
.scaleExtent([1, 40])
.translateExtent([[-100, -100], [width + 90, height + 100]])
.on("zoom", zoomed);
...
d3.select("button")
.on("click", resetted);
svg.call(zoom);
function zoomed() {
view.attr("transform", d3.event.transform);
gX.call(xAxis.scale(d3.event.transform.rescaleX(x)));
gY.call(yAxis.scale(d3.event.transform.rescaleY(y)));
}
function resetted() {
svg.transition()
.duration(750)
.call(zoom.transform, d3.zoomIdentity);
}
In the D3 Zoom library one specifies the zoom behavior, then applies the zoom to the canvas or SVG target. How/where could I send a callback as a param to zoomed() without redefining the zoom behavior?

Since you asked...
How/where could I send a callback as a param?
... your question is dangerously too broad. But here is a possibility (out of many):
Suppose this simple callback function:
function callback(x){
console.log(x);
}
We can send it to the zoom function changing the listener. So, instead of:
.on("zoom", zoomed);
We can do:
.on("zoom", function(){
zoomed(callback)
});
And, in the zoom function, setting the callback argument. For instance, let's log the d3.event.transform:
function zoomed(fn) {
fn(d3.event.transform);
//rest of the code...
}
Here is a demo bl.ocks: http://blockbuilder.org/GerardoFurtado/38c77c024ba4cc42c86221117fed4164

Related

d3 zoom listener does not take into account programmatic zoom

Before all, I can make the zoom behaviour work when attached to a "zoom" event (with the mouse for example).
AND I can programmatically zoom (triggered by an update in a document for example).
But if I have a zoom event, -->THEN programmatic zoom<--, then zoom event again, the second zoom event restarts from the position of the SVG drawing at the end of the previous mouse "zoom" event.
It is annoyingly discarding what happened during the programmatic zoom.
"zoom" event:
const zoomed = () => select("g").attr("transform", event.transform);
svg
.call(
zoom()
.extent([[0, 0], [600, 600]])
.scaleExtent([0.1, 5000])
.clickDistance(10)
.on("zoom", zoomed)
)
programmatic zoom:
let transform = zoomTransform(select("g").node());
let t = zoomIdentity
.translate(transform.x + 1000, transform.y + 1000)
.scale(transform.k);
select("g").attr("transform", t);
Help much appreciated
You need to call the transform on your zoom function, like so
svg.transition().duration(300).call(zoomFunc.transform, d3.zoomIdentity.translate(0, 0).scale(1));
where zoomFunc would be something like that
var zoomFunc = d3.zoom()
.extent([[0, 0], [600, 600]])
.scaleExtent([0.1, 5000])
.on("zoom", zoomed);
svg.call(zoomFunc)
I also made a demo JSFiddle.

d3 v4 zoom event not firing for mousewheel/touchscreen

I recently migrated to D3 v4.10.2. I am calling the zoom behavior as instructed in the documentation, however the callback is not executed when zooming with the mouse-wheel or touch-screen. I am using the latest version of chrome:
var some_svg = d3.select('body').select("#some-svg");
var some_svg_rect = some_svg.append("g").append("rect")
.attr("fill","none")
.attr("width",900)
.attr("height",400);
some_svg_rect.call(d3.zoom()
.on("zoom", function () {/*this code fails to execute*/}));
Thanks the zooming is working after setting "pointer-events" to "all" however the drag behavior only works on drag "start" is something missing there?:
some_svg_rect.call(d3.drag()
.on("start", function () {/*this code works*/})
.on("drag", function () {/*this code fails toexecute*/})
.on("end", function () {/*this code fails to execute*/}));
The problem with your code is the default pointer-events for the SVG, in which:
The element can only be the target of a mouse event when the visibility attribute is set to visible and when the mouse cursor is over the interior (i.e., 'fill') of the element and the fill attribute is set to a value other than 'none', or when the mouse cursor is over the perimeter (i.e., 'stroke') of the element and the stroke attribute is set to a value other than none. (emphasis mine)
However, I advise you to do not set the fill to transparent (as suggested in the other answer), which is normally a bad idea: despite some browsers supporting it, transparent is not part of the SVG specs.
Instead of that, just set the pointer-events to visible or all:
.attr("pointer-events", "all")
Here is a demo (shamelessly copying the code of the other answer)
var some_svg = d3.select('body').select("#some-svg");
var some_svg_rect = some_svg.append("g").append("rect")
.attr("fill", "none")
.attr("pointer-events", "all")
.attr("width", 400)
.attr("height", 250);
some_svg_rect.call(d3.zoom()
.on("zoom", function() {
console.log("zoom works")
}));
.as-console-wrapper { max-height: 20% !important;}
svg{
border: 1px solid gray;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg id="some-svg" width=400 height=250></svg>
Instead of
.attr("fill","none")
use
.attr("fill","transparent")
working code below
var some_svg = d3.select('body').select("#some-svg");
var some_svg_rect = some_svg.append("g").append("rect")
.attr("fill","transparent")
.attr("width",900)
.attr("height",400);
some_svg_rect.call(d3.zoom()
.on("zoom", function () {
console.log("hi")
}));
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg id="some-svg" width=1000 height=2000></svg>

Using d3 drag, why doesn't dragstart have d3.event.x set?

I can access d3.event.x from inside on("drag"...) but x doesn't exist inside on("dragstart"...); why? And how can I get it another way, preferably not very hackish?
Check console output for this example:
d3.select("body").append("svg").append("rect")
.attr("width", 100)
.attr("height", 100)
.style("color", "black")
.call(
d3.behavior.drag()
.on("dragstart", function(){
console.log(d3.event);
})
.on("drag", function(){
console.log(d3.event);
})
.on("dragend", function(){
console.log(d3.event);
})
);
JSFiddle
#Lars explains why, so how do you do it? d3.mouse is pretty slick:
d3.behavior.drag()
.on("dragstart", function(){
console.log('relative to rect: ' + d3.mouse(this));
console.log('relative to body: ' + d3.mouse(d3.select("body").node()));
})
Updated example.
This is the intended behaviour (see the documentation):
Drag events (but not dragstart and dragend events) expose "x" and "y" properties representing the current position of the drag gesture in local coordinates.
The start and end events are fired before and after the actual drag takes place, i.e. there's no coordinate change compared to the first and last drag events.

In D3 How to enable mouse over event for current particular path?

I was creating a world map using d3.js.In that map i need to bind the mouseover event for every country.
For example: If i mouseover the india i need to change the Fill(Background) color for india only.
I implemented the mouseover event.But my problem is whenever i mouseover over the country(India) that function effecting all the countries.I mean fill color effecting all the countries.But it need to effect only current country.
I tried using this also but no luck for me.
.on("mouseover", function(){d3.select(this).style("fill", "aliceblue");})
Please help any one to solve my problem.
My Full Code
var width = 1000,
height = 500;
var projection = d3.geo.robinson()
.scale(150)
//.translate(100,100)
.precision(.5);
var path = d3.geo.path()
// .attr("class","path")
.projection(projection);
var svg = d3.select('#'+id)
.append('svg')
.attr("width", width)
.attr("height", height)
.attr("style", "background:" + json.bc);
//shape
d3.json("world.json", function(error, world) {
svg
.datum(topojson.feature(world, world.objects.countries))
.append("path")
.on("mouseover", function(){d3.select(this).style("fill", "red");})
.on("mouseout", function(){d3.select(this).style("fill", "white");})
.attr("style", "fill:" + json.cbc)
.attr("class", "country")
.attr("d", path)
;
});
Before mouseover
After MouseOver
This code:
svg
.datum(topojson.feature(world, world.objects.countries))
.append("path")
...
says --> I have one piece of data, draw me a path from it.
Change it up to this:
svg.selectAll(".countries")
.data(topojson.feature(world, world.objects.countries).features)
.enter()
.append("path")
...
which says --> I have multiple data (features), bind the data to my selection (selectAll) and draw me a path for each component.
Example here.

Resizing D3 force layout

Is it possible to resize a force layout in d3.js? For example, I have a layout defined by
var force = d3.layout.force()
.nodes(documents.nodes)
.linkDistance(0)
.friction(.2)
.size([width, height]);
force.start();
and I wish to resize it later in the project. Is this possible?
Shortly, on a resize event you should change the attributes of your svg object (it changes also the center of gravity) and resume force calculations:
window.onresize = function() {
width = window.innerWidth, height = window.innerHeight;
svg.attr('width', width).attr('height', height);
force.size([width, height]).resume();
};
An example for the d3 force resize is provided here.
According to the documentation, the .size() parameter of the force layout affects only the center of gravity and initial distribution of nodes, not the space available. You will have to enforce any constraints on that yourself, e.g. in the tick function:
force.on("tick", function() {
node.attr("cx", function(d) { return Math.min(maxX, Math.max(minX, d.x)); })
.attr("cy", function(d) { return Math.min(maxY, Math.max(minY, d.y)); });
}

Categories

Resources