d3, transitioning a scatterplot into a bar graph - d3.js

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

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.

D3 Time based scales overflowing axis

I've been working on a similar stacked bar chart to that in the example here https://bl.ocks.org/mbostock/1134768 however running d3 v4 as shown in the fiddle here https://jsfiddle.net/z75L7cfz/7/
However as you can see in the fiddle, I have a problem that the last element in the graph extends off the end of the axis. I mean I guess this makes sense. But I'm not sure how to make this show in the graph and ensure all the elements fit into the supplied width of the graph.
I assume I need to modify the domain of the x-axis which is currently
x.domain(d3.extent(data, function(d) { return d.Date; }));
But other than somehow manually adding an extra data point with no data in but the subsequent date I'm not sure how to manage this. If anyone could supply any pointers it would be hugely appreciated!
For bar charts, it is best to use band scale. It calculates the bar sizes, location and padding.
var x = d3.scaleBand()
.range([0, width])
.padding(0.05);
Here is the updated fiddle:
https://jsfiddle.net/dtb02eze/2/
Actually, there is nothing wrong with the code per se. The problem here is the very concept of a time scale, and the use of a bar chart with such scale.
If you look closely in your fiddle, each bar starts exactly where it should start, that is, the x value of each bar corresponds to the temporal value in the x axis. But a bar has a width, and here lies the problem: when you set the width to 18px (or any other value), the bar will overflow the time scale. That's normal and expected.
A time scale should be used with dots, where every dot (adimentional) sits exactly in the correct position of the time scale, or with a line, which is simply a path connecting the dots.
Having said all that, you have two options:
Use an ordinal scale. A bar chart is not supposed to use a time scale, and a time scale is not supposed to be used with a bar chart. Bostock's code, for instance, uses an ordinal scale:
var x = d3.scale.ordinal()
You can use scaleOrdinal or, better yet, scaleBand.
If you want to stick to the time scale, change your chart to a line chart. Technically speaking, a line chart is the best option to visualize a trend in data over intervals of time.

keep XY Graph legend from overlapping plot?

I have an XY graph with a legend that automatically resizes to fit the contents of the legend. The problem is that sometimes the legend text is such that the resizing overlaps the plot, see the image below. Is there a way to keep the legend from resizing over the plot data? In other words to confine the resizing operation to outside of the plot box?
Update: it would seem that part of the problem is the auto expand ability of the legend always expands to the left-hand side. Placing the legend on the left-hand side of the plot keeps the legend from covering the graph.
1:
You probably have moved the legend, changing it's anchor point. You can move the legend to the right side of the graph element and it should re-anchor.

D3 force layout: making pan on drag (zoom) smoother

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.

Resources