I'm using version 7.3.0 of d3 and I have based my code on this example. My problem is that when I'm zooming in (or out) the layout of the circles breaks and becomes
while initially was like this
The code that implements the zoom is
const zoomed = d3.zoom()
.scaleExtent([0, 3])
.on('zoom', event => {
graph.selectAll('g').attr('transform', event.transform);
});
I have noticed that if I add the following line of code, then it prevents it from breaking.
const zoomed = d3.zoom()
.scaleExtent([0, 3])
.on('zoom', event => {
graph.selectAll('g').attr('transform', event.transform);
// This line prevents zoom from breaking
simulation.restart();
});
But the downside is that it becomes noticeably slower. Is there a more efficient way to prevent it from breaking like that?
Any help would be appreciated. Thank you
Related
Running D3 v6.
This is a multi part question as in trying to solve the original problem I have a question about D3 and mouseevents. A quick note while using my fiddle, if you press the ESC key it will clear the draw line behavior.
How to draw a line from one node to another, following the cursor, regardless of zoom level and pan position?
Why does the line I draw behave differently when the .on('mousemove') is applied to an svg versus a g element?
Problem 1. The problem I am facing is that when panning and zooming, the end point of the line does not follow the cursor properly because the container I'm zooming on had it's x and y translated. Zoom in and click on a node to see the issue.
Related fiddle
This works just fine in my demo, until zooming and panning are involved. I've managed to take care of the panning issues by using d3.zoomTransform() to get the current [x,y] and apply that to the end point of the line. I cannot figure out to accommodate the zoom level though. I have tried transform(scale(zoomLevel.k)) but this doesn't work great. To recreate this issue, click a node without panning/zooming and observe the line follows the cursor. Zoom the graph and then click a node and observe the line does not follow the cursor.
Problem 2. I thought that I could solve the above issue by having the cursor react to mouse events on the g element I use for zooming and positioning rather than my parent svg element. When the mousemove event is on the g the line follows the cursor regardless of zoom/pan but is very laggy and I don't understand why.
SVG mouseevent
G mouseevent
Brief code overview, view fiddles for full code
let sourceNode;
const svg = d3.select("#chart")
.attr("viewBox", [0, 0, width, height]);
const g = svg.append('g');
const drawLine = g.append('line').attr('stroke', 'red').attr('stroke-width', 5).attr('visibility', 'hidden')
const nodes = g.append(//do node stuff)
const links = g.append(//do link stuff)
svg.call(d3.zoom().on('zoom', (event) => {
g.attr('transform', `translate(${event.transform.x}, ${event.transform.y}) scale(${event.transform.k})`)
}))
node.on('click', (event, d) => {
sourceNode = d
})
svg.on('mousemove', (event) => {
if (sourceNode) {
const currentZoom = d3.zoomTransform(svg.node());
drawLine
.attr('visibility', 'visible')
.attr('x1', sourceNode.x)
.attr('y1', sourceNode.y)
// Remove the currentZoom offset and observe the line being jank
.attr('x2', d3.pointer(event)[0] - currentZoom.x)
.attr('y2', d3.pointer(event)[1] - currentZoom.y);
}
})
I've got a D3 radial dendrogram tree that I've applied d3.zoom to, but it jitters when I drag it. The zoom behaviour itself is fine, but the drag is not. I think there might be some issue with the way the 'g' element is translated (width / 2, height / 2 + 20).
Any help would be appreciated!
Here's a codesandbox of my tree: https://codesandbox.io/s/4zr43po6l9
change 'svg' to 'g', so the zoom affects the g element directly below the svg the zoom behaviour is attached to, and it's now smooth
let zoom = d3.zoom().on("zoom", () => {
g.attr("transform", d3.event.transform);
});
I'm not sure why it works like this to be honest
Perhaps changing the svg transform freaks out the event mouse x y position a bit as it bases its values on the svg, so you get that juddering effect?
I've rendered a d3 map that has pan and zoom enabled, but when scrolling down the viewport either on desktop or mobile, the window gets stuck zooming in the map.
Is there a way to temporarily disable d3.zoom, while the window is scrolling?
I've seen ways of toggling the zoom/pan using a button as seen here: http://jsfiddle.net/0xncswrk/, but I wanted to know if it's possible without having to add a button. Here's my current zoom logic.
Thanks!
this.zoom = d3.zoom()
.scaleExtent([1, 8])
.on('zoom', () => {
this.svg.attr('transform', d3.event.transform);
});
this.svg = d3.select(this.el).append('svg')
.attr('width', '100%')
.attr('height', this.height)
.attr('class', 'bubble-map__svg-us')
.call(this.zoom)
.append('g');
EDIT: Wow old answer but never saw your comment. Sorry about that. Yeah sorry I forgot to consider mobile zooming.
In the documentation, perhaps this is new, but they recommend having more granular control of what zoom you allow by using zoom.filter. For touch related events, they also support zoom.touchable.
As specified in the d3-zoom documentation
To disable just wheel-driven zooming (say to not interfere with native scrolling), you can remove the zoom behavior’s wheel event listener after applying the zoom behavior to the selection:
selection
.call(zoom)
.on("wheel.zoom", null);
You can also consider just setting the scaleExtent to be [1,1] especially if it's just temporary so it locks the zoom to only one possible scale but preferably you opt for what the documentation says :P
Got here because I was dealing with a similar problem. Perhaps for anyone coming after this, a simple way to deal with this might be to use the filter() method that a zoom() instance provides. That will allow you to toggle between applying or ignoring zoom events altogether. It works a little better than temporarily setting null to watchers because - at least in my experience - the events then still get recorded and stacked. As a consequence, you would zoom in or out in unexpected leaps once you re-enabled the handler. The filter actually really ignores what's going on, it seems. To implement:
let isZooming = true; // Use controls to set to true or false
zoom()
// Make sure we only apply zoom events if zooming is enabled by the user
.filter(() => isZooming)
.on('zoom', event => {
// Specify whatever you want to do with the event
});
Doc: https://github.com/d3/d3-zoom#zoom_filter
I have a D3 map that I have created on a codepen. Below is the link
http://codepen.io/redixhumayun/full/VPepqM/
I have included some basic zooming and panning onto it. However, when I zoom I want the dots to re-scale to account for the zoom, not just stay the same size. I want them to separate out over the greater area that will be provided after zooming in.
I'm not sure what exactly I should be looking for, which is why I am having a hard time finding it.
Here is the code for the zoom I have currently implemented
var zoom = d3.zoom()
.on('zoom', zoomed);
svg.call(zoom);
//defining the zoomed function here
function zoomed() {
map.attr('transform', d3.event.transform)
meteorite.attr('transform', d3.event.transform);
}
If you want the circles representing the meteorites to stay at their original size, regardless the zoom, this is a possible solution:
Rename your circles' variable:
var meteorites = meteorite.selectAll('circle')
//etc..
And set their radii and stroke according to the zoom:
meteorites.attr('r', function(d) {
return weight_scale(d.properties.mass) / d3.event.transform.k
})
.attr("stroke-width", 1 / d3.event.transform.k);
Here is the CodePen: http://codepen.io/anon/pen/VPapPa?editors=1010
PS: because of scope issues, I had to move some functions.
Unhelpful Github discussion led me to ask this question here:
After changing to the new v4.0 D3, seems like using d3.zoom() blocks some events, like:
Calling this:
svg.call(d3.zoom()
.scaleExtent([1, 40])
.translateExtent([[-100, -100], [width + 90, height + 100]])
.on("zoom", zoomed));
And then this:
document.querySelector('svg').addEventListener('mouseup', function(){ alert(1) })
Won't alert anything when clicking anywhere on the SVG.
I really don't want to do:
window.addEventListener("mouseup", function(e){
if( [target or target parent is the specific SVG element] ){
// do something
}
}
Because this will introduce a global non-namespaced event mouseup on the window (this technique seems to only work on the window object) and this event might be removed somewhere else, or happen multiple times. (It isn't easy to encapsulate it, or I simply don't know how).
Any better ideas on how to capture mouseup events on the SVG?
Related to another question of mine: Event capturing namespace
Demo page
Update:
With the help of Mark's answer, here's the working demo
Still not sure I understand your intent, but you might be approaching this from the wrong angle. Instead of trying to add your own events, just work within the events provided by the zoom behavior. The zoom functionality provides for panning where mousedown starts a pan (i.e. zoom start event) and mouseup ends a pan (i.e. zoom end event). So, the real question becomes, how can you differentiate a pan vs a "click" mouseup? To do that, just check if the user moved the mouse:
svg.call(d3.zoom()
.scaleExtent([1, 40])
.translateExtent([[-100, -100], [width + 90, height + 100]])
.on("start", zoomstart)
.on("end", zoomend)
.on("zoom", zoomed));
var mousePos = null;
function zoomstart(){
mousePos = d3.mouse(this);
}
function zoomend(){
var m = d3.mouse(this);
if (mousePos[0] === m[0] && mousePos[1] === m[1]){
alert('mouseup');
}
}
function zoomed() {
svg.attr("transform", d3.event.transform);
gX.call(xAxis.scale(d3.event.transform.rescaleX(x)));
gY.call(yAxis.scale(d3.event.transform.rescaleY(y)));
}
Update fiddle.