d3 v4 Hierarchical Edge Bundling - d3.js

I am trying to move my Hierarchical Edge Bundling chart to d3v4.
but could not find an alternative to d3.layout.bundle().
d3v3 example is https://bl.ocks.org/mbostock/7607999
any example with d3v4? Please help.

You'll need two things: the hierarchy and the array of links between nodes in the hierarchy.
You can load a hierarchy from JSON or create one from CSV by using d3.stratify. Then pass the hierarchy to d3.hierarchy. See the d3-hierarchy documentation.
Then you'll need to construct an array of links. Each link is an object with a source and target, each of which points to a node in the hierarchy.
You can calculate and draw the bundles using a combination of node.path (which replaces d3.layout.bundle) and d3.curveBundle. See the d3.curveBundle documentation.
If links is your array of links, the code looks something like this:
const line = d3.line()
.x(d => d.data.x)
.y(d => d.data.y)
.curve(d3.curveBundle.beta(0.95));
const edges = g.selectAll('.link').data(links);
edges.enter().append('path')
.attr('class', 'link')
.merge(edges)
.attr('d', d => line(d.source.path(d.target)));
edges.exit().remove();

Related

Can 'click' change an attribute for a nested group?

I have a circle packing diagram in which many of the top most circles (the leaves) have titles in common.
I would like to add an interaction which highlights every leaf with a common title when a user clicks on any one leaf. So, for instance, if this data set had say 1000 leaves a user could click on any leaf labeled 'CD 19' and easily see all the other 'CD 19' leaves.
I am doing this for my own edification. I have a half-working solution. I would appreciate any help pointing me in the right direction or any explanation why my solution isn't working.
here is my naive solution:
group the leaves by name using d3.nest and .object.
const nodes = packLayout(root).descendants()
const nameNest = d3.nest().key(d => d.data.name).object(nodes);
now nameNest returns an array of objects with the same name, when passed a name as a key.
console.log(nameNest['CD 19']) // => Array(3)
then use .forEach(...) to set a common attribute on each member of the array.
nameNest['CD 19'].forEach(item => item.r = 50)
this has the desired effect. All the circles labeled 'CD 19' get a radius of 50. But what I want to do is turn this snipped of code into a function and then pass that function into an event on my circles as follows:
const namez = d => nameNest[d.data.name]
.forEach(item => item.r = 50)
...
const circles = nodesEnter
.append('circle')
...
.on('click', d => namez(d));
this does not work. cosnole.log(d => namez(d)) returns 'undefined'.
However when I take off the .forEach(...) console.log(d => namez(d)) returns the expected array of objects with the same name as the node I click. So why can't I use .forEach to modify the array returned by namez(d) from within .on()?
here is my code: bl.ocks: circle packing diagram with common node names
Your code is working! However, you're just changing the data, not the actual SVG elements.
For changing the circles you have to repaint them. For instance:
.on('click', d => {
namez(d);
circles.attr('r', d => d.r)
});
Here is the updated code: https://bl.ocks.org/GerardoFurtado/raw/3caee8c936d1cb3b98cce0706d52d890/f1a38bf56af9a66f418cc08b55f04a64b3c4b494/

Using fields in inherited bound data

I am attempting to get my head around using bound data with d3.js. I'm following the documentation and am now a little confused.
I want to produce donut charts with radii that vary depending on the data. I am comfortable producing the arcs to make up a donut using an array, but am having a hard time working out how to pass along a size parameter with the data binding of the arc. For example, if the data bound to the parent of the arc is something like {size: 20, cont: [1, 7]}, how can I bind the first element of the array as well as the size element? I have a fiddle attempting to show what I am talking about. In that example, the two donuts should be different sizes. I have commented out the kind of thing I suspect should be going on on line 14.
I have tried variations on:
var arcs = donuts.selectAll(".arc")
.data(function(d) { var temp = [];
temp.push(d.cont);
temp.push(d.size);
return temp; })
.enter()
.append("g")
.attr("class", "arc");
But it is clearly not producing what I expect.
The problem here isn't really the data inheritance, but the fact that you're passing the original data to a layout and then only the result of that to your drawing functions. The pie layout does store the original datum in the .data member of the result, but you're only passing it part of the original data.
The "proper" thing to do would be to refactor your data structure such that you can pass it in as-is and use the pie layout's .value() function to tell it how to access the data. Then you can directly access the original data.
There's however a quicker solution -- you can simply use the indices that are passed to your function to index into the original array. The code for this would look like this.
.attr("d", function(d, i, j) { return arc.outerRadius(dataset[j].size)(d); })
Note that you need two indices here because you have nested data -- i would be the index within your array of values for a single pie chart, whereas j denotes the index of the element at the level above that. Updated jsfiddle here.

Why do GeoJSON features appear like a negative photo of the features themselves?

I have a pretty standard code thats reads a GeoJSON file and renders its features using D3.js. It works fairly well except with this file: https://github.com/regiskuckaertz/d3/blob/master/circonscriptions.json
The file doesn't look weird or anything, in fact you can preview it on GitHub or geojsonlint.com. However, D3 draws paths that look like the features were used as a clipping mask, i.e. all the shapes are negatives of the features themselves. The code is pretty standard though:
var proj = d3.geo.mercator()
.scale(25000)
.center([6.08642578125,49.777716951563754])
.rotate([-.6, -.2, 0]);
var path = d3.geo.path().projection(proj);
function ready(error, luxembourg) {
svg
.selectAll("path")
.data(luxembourg.features)
.enter().append("path")
.attr("d", path)
.attr("class", function(d) { return quantize(rateById.get(d.properties.name)); })
}
You can have a look here: http://jsfiddle.net/QWZXd/
The same code works with another file, which comes from the same source.
For some reason, the points in these polygons are in reverse order - they ought to be clockwise, but are defined as counterclockwise, and d3 follows the right-hand rule for polygon interpretation.
To fix, reverse the points, either in the file or in JS:
luxembourg.features.forEach(function(feature) {
feature.geometry.coordinates[0].reverse();
});
Fixed fiddle: http://jsfiddle.net/nrabinowitz/QWZXd/1/

Drawing map with d3js from openstreetmap geojson

Hy
I'm trying to draw an svg with d3.v3.js from geojson. I fetch the geojson from openstreetmap(my test data: http://pastebin.com/4GQne42i) and try to render it to svg.
My JS code:
var path, vis, xy, jdata;
xy = d3.geo.mercator().translate([0, 0]).scale(200);
path = d3.geo.path().projection(xy);
vis = d3.select("body").append("svg").attr("width", 960).attr("height", 600);
//22.json is the name of the file which contains the geojson data
d3.json("22.json", function(error, json) {
jdata = json;
if(error!=null)
console.log(error);
return vis.append("svg:g")
.selectAll("path")
.data(json.coordinates)
.enter().append("path")
.attr("d", path);
});
And somehow my svg result is this:
<svg width="960" height="600">
<g>
<path></path>
</g>
</svg>
I know the projection is not good, but I think the svg should have nodes.
What is the problem with my code? Would you post a correct solution?
The first problem is with your data join:
vis.append("g")
.selectAll("path")
.data(json.coordinates)
.enter().append("path")
.attr("d", path);
This would mean you want one path element for each element in the json.coordinates array. Since your test data is a polygon, that would mean one path element for the exterior ring, and then perhaps multiple other path elements for any interior holes, if your data has them. I expect you just want a single path element for the entire polygon.
The second problem is that you’re not passing valid GeoJSON to the d3.geo.path instance. Because the data in your data join is json.coordinates, you’re just passing an array of coordinates directly to path, when you need to pass a GeoJSON geometry object or a feature. (See the GeoJSON specification.)
Fortunately both of these problems are easy to fix by eliminating the data join and rendering the full polygon. To add just one path element, just call selection.append:
vis.append("path")
.datum(json)
.attr("d", path);
Your projection will probably need adjusting (translate and scale), too. You might find the project to bounding box example useful here.
Do you really need to do it with D3?
I would suggest to go with more map oriented libraries like:
polymaps
Leaflet
Leaflet vector layer has support for GeoJSON and its size is quite compact.
Open Layers is also an option but it's size is quite big.
Here is an example how I have used Leaflet + GeoJSON to display suburb shape http://www.geolocation.ws/s/6798/en

Update multi-line graph D3.js

I'm attempting to update a d3 multi-line graph by pulling data at 5 second intervals from a mySQL database using PHP. At the moment the graph is displaying but not updating - I know that there is something not right with my updateData function but have tried everything can think of and nothing seems to work. Can anyone please help?
https://gist.github.com/Majella/ab32fe0151fd487da3f6
UPDATE:
As you can see the x-axis line is only showing sporadically and some of the lines aren't lined up with the y-axis.
Updated gist:
https://gist.github.com/Majella/ab32fe0151fd487da3f6
UPDATE 2: For some bizarre reason the lines are changing colour - or moving completely not exactly sure. So while on graph above the lines are from top - blue, orange then white - when graph updating the blue might move to bottom with orange on top and white in middle etc - but happening randomly?
In your original drawing of the graph, you correctly use:
var parameter = svg.selectAll(".parameter")
.data(data, function(d) { return d.key; })
.enter().append("g")
.attr("class", "parameter");
which joins the data (data) to the elements (g.parameter)
During your update function, you will need to join the data again in order to perform updates, deletes, and adds of elements. The 3 little circles tutorial is an excellent place to learn more about this.
Anyway, in your update function, you may want something like this (untested):
// re-acquire joined data
var containers = svg.selectAll("g.parameter")
.data( data );
// update existing elements for this data
containers
.select( "path.line" )
.attr( "d", function(d) { return line(d.values); })

Resources