D3 force layout: making pan on drag (zoom) smoother - d3.js

I've got a d3.js static force layout graph that can get rather big (sometimes parts of it are clipped), so I'd like to let the user pan the whole graph by dragging. I don't think I need dragging of individual nodes, I've got a feeling that's just going to be confusing, but would like to make it possible to show the parts of the graph that are clipped by the svg boundaries.
I've got a minimal example at http://bl.ocks.org/3811811 which uses
visF.append("rect")
.attr("class", "background")
.attr("width", width)
.attr("height", height)
.call(d3.behavior.zoom().on("zoom", redrawVisF));
function redrawVisF () {
visF.attr("transform","translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")");
}
to implement the panning, but I find it really "skittery" and not very smooth at all, to the point where I'm guessing it will stop people from trying the drag function at all. Has anyone got a clue why this happens and/or an idea for how to fix it?

The problem is that d3.behavior.zoom retrieves the current mouse position relative to the clicked item's container element, and you are moving the container element! So the relative position is constantly changing, hence the jittering effect you're seeing.
You probably want to move the background <rect> so that it's a direct child of the <svg> element. This achieves two things:
The position will now be relative to the <svg> container, which isn't moving.
Currently, you are moving the <rect> when you zoom or pan, so the zoomable area changes and some parts of the viewport are no longer zoomable. Having the background <rect> in the same place fixes this problem too.

Related

D3 polygon projection is wrong

I have a working world projection using D3 geoOrthographic and topoJSON. It rotates and everything.
I wanted to place a hexagon shape at a coordinate and projected according to its place on the globe, which works... except my shape is really weird. I get a 5-sided shape, like one of the points is just missing, that rotates properly with the globe.
And then also a circle around the edge of the globe that does not rotate.
I have a function that throws out hexagon coordinates, I've tried with several scales and offsets, always the exact same behavior.
let hex = svgOrbit.append("path")
.datum({"type":"GeometryCollection","geometries":[{"type":"Polygon","coordinates":[[[6.732,6],[5,7],[3.268,6],[3.268,4],[5,3],[6.732,4]]]}]})
.attr("d", myGeoOrthographicProjection);
The circle looks like that no matter how I rotate, the trying-to-hexagon orients as desired sans that missing point.
The path does show a d attr with these two separate polygons.
I just plain don't understand what's happening here. There aren't even any weird numbers, like a zero or NaN or anything in the coordinates. The entire planet projects correctly, but a hexagon throws it for a loop?
The outer circle indicates that you have an inverted polygon: you are drawing a feature of the world minus the intended feature. As d3 uses spherical math in calculating projections, winding order matters, as opposed to most geographic tools which treat spherical coordinates as Cartesian (even when projecting). The first map below in red shows this by applying a fill.
The missing point is a bit odd, normally D3 won't render invalid geojson syntax and it won't throw an error or warning in not rendering anything. The issue here is that the last point in your coordinate array should be the first coordinate. I've forgotten where in the spec this is, and haven't looked as to why D3 renders it like this at all. When attempting to take a look at your geojson at geojson.io I noticed it didn't render at all with the missing end point.
I've rewound the coordinates (lazily with .reverse()) and added the extra point in the map on the right.
let hex = {"type":"GeometryCollection","geometries":[{"type":"Polygon","coordinates":[[[6.732,6],[5,7],[3.268,6],[3.268,4],[5,3],[6.732,4]]]}]};
let hex2 = {"type":"GeometryCollection","geometries":[{"type":"Polygon","coordinates":[[[6.732,6],[5,7],[3.268,6],[3.268,4],[5,3],[6.732,4],[6.732,6]].reverse()]}]};
let projection = d3.geoOrthographic().scale(125).translate([125,125]);
let path = d3.geoPath(projection);
let svg = d3.select("body")
.append("svg")
.attr("width", 500)
.attr("height", 250);
svg
.append("path")
.datum(hex)
.attr("d", path)
.attr("fill", "crimson");
svg.append("g")
.attr("transform","translate(250,0)")
.append("path")
.datum(hex2)
.attr("d", path)
.attr("fill","steelblue");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

D3 V4 zoom.transform jump on drag

I'm brand new to D3 and am reading up here on how to set the current zoom transform but I'm having troubles comprehending what I'm doing wrong.. I'm using the following line to initially center a circle on the page.
g.call(zoom.transform, d3.zoomIdentity.translate(width / 2, height / 2).scale(2));
Here is a fiddle of the issue. Just click and drag anywhere in the result window. You should see the circle instantly jump to the upper left corner.
https://jsfiddle.net/hjukmqjv/3/
Am I using the incorrect method to achieve my goal perhaps?
you simply need to set the call on the svg
svg.call(zoom.transform, d3.zoomIdentity.translate(width / 2, height / 2).scale(2));
First of all, You're invoking zoom two times with svg, and g. With the g the transformation is applied (the circle is in the center of the page).
Then apply the svg zoom (with the zoom in/out event)that visualize the circle in it's (0,0) screen position and produce the jump.
However if you want to see the circle in the centre just put the circle in that position inserting the correct attribute to circle:
var g = svg.append("g");
g.append("circle")
.attr("r", 50)
.attr("cx",width / 2)
.attr("cy",height / 2)
.style("fill", "#B8DEE6");
and use the zoom on the svg element.
Updated fiddle
Hope it helps.

Responsive d3 bubble chart

I'm trying to implement a responsive d3 bubble chart that updates its container svg (and the nodes it contains) while the page is being resized (kinda like this shows: http://jsfiddle.net/63K4n/)
But instead of just shrinking the nodes down to a size that fits the screen, I want to increase the height of the container as the width is being decreased to create the additional space necessary for the nodes to fall into without being shrunk.
I played with updating the last few lines of the fiddle to this...
chart.attr("width", targetWidth);
chart.attr("height", Math.round(921600/targetWidth)); //was targetWidth/aspectRatio
(where 921,600 was simply the diameter [960] squared - i.e. the total required space)
But of course although this increases the height of the container correctly, the nodes don't exploit the new space - that's what I'm struggling to figure out - how to change the shape of the bubble chart to fit the new space (if that's even possible!)
If not possible with a standard bubble chart, I'd also be happy to try a static force layout (e.g. http://bl.ocks.org/mbostock/1667139) combined with a resizable example like http://bl.ocks.org/mbostock/3355967. Again the problem is finding a way to reshape the nodes to within the confines of their new container, so I'm guessing I'd have to adapt collision detection to account for the svg container boundaries. And with all that going on I'm pretty sure there'd be a decent lag on a mobile device while you're waiting for the force layout to tick its way to something I can update the page with.
Sorry if that's all a little vague, but I'm really just hoping someone has a general suggestion on an appropriate way to tackle the problem. Huge thanks for any thoughts at all!
When a resize event occurs call a function with:
d3.selectAll('#chart svg').remove()
to clear the current chart then pass the new height/size info into a update function that redraws the chart. Its kinda hard to give you a more specific answer without your code and where you're stuck...but that's generally how I redraw my D3 stuff when data or other groovy things change.
Ok, based on the two fiddles/code pens it looks like
var diameter = 500, // <---Feed new variable here
format = d3.format(",d"),
color = d3.scale.category20c();
var width = 450, height = 2000;
var bubble = d3.layout.pack()
.sort(null)
.size([diameter, diameter])
.padding(1.5);
var svg = d3.select("body").append("svg")
.attr("viewBox","0 0 500 500") //<----and Here
.attr("width", diameter)
.attr("height", diameter)
.attr("class", "bubble");
Changing the diameter and the veiwBox attribute makes everything shrink in the proper proportion. Sorry if i miss understood the question, and if I'm still not getting it. But a redraw function that feeds in new data there based on a change in window should work.
Also, just changing the
var height = XXX
doesn't look like its changing the height, since the
.attr('width',diameter)
is using diameter instead of height.
Anyway, I hope that helps.

d3, transitioning a scatterplot into a bar graph

Looking at the d3 showreel (http://bl.ocks.org/mbostock/1256572), the transitions are very smooth and beautiful. I'm trying to emulate this style to transition between a scatterplot and a bar graph.
I can do something like this:
svg.selectAll(".dot")
.transition()
.duration(duration)
.delay(delay)
.remove();
plot_bar_graph();
which removes the scatterplot and then plots the bar graph, but I would really like some sort of animation where the circles "turn into" rectangle elements, but am not sure how to do this.
My current idea is just to draw the circles in the scatterplot as rectangles, but give them an rx and ry attribute so they look like circles, then change them to rectangles.
Something like this:
d3.select(".bar")
.transition()
.duration(duration)
.delay(delay)
.attr("rx",0)
.attr("ry",0);
but this seems rather unwieldy.
That's the only way you can do it, short of writing your own tween and drawing with path elements (which is much harder). It's harder to set up, because rectangles draw from the top-left and not the center like circles, but it will work.
Tweens are hard but not impossible:
https://github.com/mbostock/d3/wiki/Transitions#attrTween
If you're interested in getting into tweening, you can see an extreme example here with Superformula (which includes rectangle-like and circle-like forms):
http://bl.ocks.org/mbostock/1020902

D3 Zoom Jitters

D3 drag jitters when I .call(zoom) on two overlapping elements - and try to drag on the top element.
Here is an example http://codepen.io/anon/pen/WbNjqg
In this case, zoom has been registered on the chart's bars and on the chart's background.
I can't add an invisible rectangle over the entire chart to register the zoom because that element would prevent mouseovers from registering on individual bars.
Why does this shaky horizontal scrolling occur - and how can I fix it?

Resources