d3.js sankey diagram: where does ".dy" and .dx" get set? - d3.js

I am busy cannibalizing the great tutorial on D3.js sankey diagrams at http://www.d3noob.org/2013/02/sankey-diagrams-description-of-d3js-code.html . I want to tweak the stroke-width setting on the paths.
There is a significant block of code at this URL for context, but I want to ask about this passage:
var link = svg.append("g").selectAll(".link")
.data(graph.links)
.enter().append("path")
.attr("class", "link")
.attr("d", path)
.style("stroke-width", function(d) { return Math.max(1, d.dy); })
.sort(function(a, b) { return b.dy - a.dy; });
See the "d.dy" reference there? How/where exactly does that get set? I don't see an explicit reference to in its the larger code passage, or in its data source. I will go and continue googling on it now, but if you know a simple answer or resource I would appreciate your help. Right now my best guess is that some part of the sankey plugin, or the link class, sets it on the fly by looking at the size and position of the node rectangles.

It happens internally in sankey.js in the .layout call. See dx here and dy here.

Related

D3 Force Nodes Clustering at Top Left

https://jsfiddle.net/abalasky/b28mxfL6/7/
function update() {
//Add Nodes
simulation.nodes(graph.nodes)
updateNodes();
simulation.on("tick", ticked);
//Add links
simulation.force('link').links(graph.links)
updateLinks();
}
Link to fiddle above. Was following along with this great D3 guide https://tomroth.com.au/fdg-minimal/. Was able to have my force graph up quickly but now that i am trying to repackage everything in functions all my nodes are clustering at the top. I've tried to move around where i call .on('tick') but have been banging my head against the wall with this for hours. Any guidance would beg greatly appreciated.
The problem is in the definition of node and link variables:
In the original code, these contain the enter selection, e.g.
//draw circles for the nodes
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(nodes_data)
.enter()
.append("circle")
.attr("r", 5)
.attr("fill", "red");
In the jsfiddle code, the selection itself is assigned to the variable:
node = nodesG.selectAll("circle.node")
.data(graph.nodes)
This causes tick not to have anything on what to iterate.
Here is a corrected version of the jsfiddle: https://jsfiddle.net/cb9t7fjo/
how to identify the cause of this type of problems
console.log is your friend in situations like this:
console.log('tick', node) inside the tick function shows that the tick function is executed as expected.
console.log(d.x) inside node.attr('x') shows you that this is never executed.
This helps identify that the problem is with node (and respectively link) variables.

How to dynamically translate an SVG group in D3.js?

I'am working on a bubble-chart using d3. Now there should be an arrow as a graphical asset below the text elements. What I wan to achieve is a dynamic positioning of the arrow-group having a defined gap between itself and the last text:
I've already tried to position it with a percentage value:
arr.append("g").attr("class", "arrow").style('transform', 'translate(-1%, 5%)');
Which does not give the effect I want to.
I also tried to insert a dynamic value based on the radius of the circles which is simply not working and I don't know why:
arr.append("g")
.attr("class", "arrow")
.attr('transform', function(d, i) { return "translate(20," + d.r / 2 + ")");});
OR
arr.append("g")
.attr("class", "arrow")
.style('transform', (d, i) => 'translate(0, ${d.r/2})');
Please find my pen here.
Thank you very much in advance!
Ok.. solved it! For everyone who is interested or having the same trouble:
My last attempt was nearly correct but I was not able to transform via .style(...). I had to use .attr(...) like this:
arr.append("g")
.attr("class", "arrow")
.attr('transform', (d, i) => translate(0, ${d.r/2})');

D3.js binding nested data

I'm really new to coding, and also to asking questions about coding. So let me know if my explanation is overly complex, or if you need more context on anything, etc.
I am creating an interactive map of migration flows on the Mediterranean Sea. The flows show origin and destination regions of the migrant flows, as well as the total number of migrants, for Italy and Greece. Flows should be displayed in a Sankey diagram like manner. Because I am displaying the flows on a map and not in a diagram fashion, I am not using D3’s Sankey plugin, but creating my own paths.
My flow map, as of now (curved flows are on top of each other, should line up next to each other)
For generating my flows I have four points:
2 points for the straight middle part of the flow (country total)
1 point each for the curved outer parts (origin and destination region), using the two points of the straight middle part as starting points
The straight middle and both curved outer parts are each generated independently from their own data source. Flow lines are updated by changing the data source and calling the function again. The flow lines are generated using the SVG path mini-language. In order for the curved outer parts of the flows to show correctly, I need them to be lined up next to each other. To line them up correctly, I need to shift their starting points. The distance of the shift for each path element is determined by the width of the path elements before it. So, grouping by country, each path element i needs to know the sum of the width of the elements 0-i in the same group.
After grouping my data with d3.nest(), which would allow me to iterate over each group, I am not able to bind the data correctly to the path elements
I also can't figure out a loop function that adds up values for all elements 0-i. Any help here? (Sorry if this is kind of unrelated to the issue of binding nested data)
Here is a working function for the curved paths, working for unnested data:
function lineFlow(data, flowSubGroup, flowDir) {
var flowSelect = svg.select(".flowGroup").select(flowSubGroup).selectAll("path");
var flow = flowSelect.data(data);
var flowDirection = flowDir;
flow.enter()
.append("path").append("title");
flow
.attr("stroke", "purple")
.attr("stroke-linecap", "butt")
.attr("fill", "none")
.attr("opacity", 0.75)
.transition()
.duration(transitionDur)
.ease(d3.easeCubic)
.attr("d", function(d) {
var
slope = (d.cy2-d.cy1)/(d.cx2-d.cx1),
dist = (Math.sqrt(Math.pow((d.rx2-d.rx1),2)+Math.pow((d.ry2-d.ry1),2)))*0.5,
ctrlx = d.rx1 + Math.sqrt((Math.pow(dist,2))/(1+Math.pow(slope,2)))*flowDirection,
ctrly = slope*(ctrlx-d.rx1)+d.ry1;
return "M"+d.rx1+","+d.ry1+"Q"+ctrlx+","+ctrly+","+d.rx2+","+d.ry2})
.attr("stroke-width", function(d) {return (d.totalmig)/flowScale});
flowSelect
.select("title")
.text(function(d) {
return d.region + "\n"
+ "Number of migrants: " + addSpaces(d.totalmig)});
};
I tried adapting the code to work with data grouped by country:
function lineFlowNested(data, flowSubGroup, flowDir) {
var g=svg.select(".flowGroup").select(flowSubGroup).append("g").data(data).enter();
var gflowSelect=g.selectAll("path");
var gflow=gflowSelect.data (function(d) {return d.values});
gflow.enter()
.append("path");
gflow.attr("stroke", "purple")
.attr("stroke-linecap", "butt")
.attr("fill", "none")
.attr("opacity", 0.75)
// .transition()
// .duration(transitionDur)
// .ease(d3.easeCubic)
.attr("d", function(d) {
var
slope = (d.cy2-d.cy1)/(d.cx2-d.cx1),
dist = (Math.sqrt(Math.pow((d.rx2-d.rx1),2)+Math.pow((d.ry2-d.ry1),2)))*0.5,
ctrlx = d.rx1 - Math.sqrt((Math.pow(dist,2))/(1+Math.pow(slope,2)))*flowDirection,
ctrly = slope*(ctrlx-d.rx1)+d.ry1;
return "M"+d.rx1+","+d.ry1+"Q"+ctrlx+","+ctrly+","+d.rx2+","+d.ry2})
.attr("stroke-width", function(d) {return (d.totalmig)/flowScale});
};
which isn't working. What am I doing wrong? Thanks for any hints!

How to switch from d3.svg.line to a choice that allows different coloured segments?

Is there an easy way to draw the line chart, using d3.svg.line, such that you can specify the colour of each segment individually? Or a slot-in replacement for d3.svg.line that allows this kind of control?
My start point was basically this example:
https://gist.github.com/mbostock/1642874
I tried this first:
var line = d3.svg.line()
.x(function(d, i) { return x(i); })
.y(function(d, i) { return y(d.v); })
.style("stroke",function(d){return d>0?"#f00":#0f0";})
;
But it got a function not found complaint.
Then I tried:
var path = svg.append("g")
.attr("clip-path", "url(#clip)")
.append("path")
.data([data])
.attr("class", "line")
.style('stroke',function(d,i){return (d>0?'#f00':'#0f0');})
.attr("d", line)
;
But that just gave me all green lines (It turns out d is not my data).
This is a similar question from a couple of years ago, which says "not now, maybe soon". I tried looking at the cool examples like this and this but cannot work out how to relate their code to the code I have.
The short answer is no, there is no drop-in replacement. You have two options for making lines with different coloured segments.
Create the line segments separately.
Use a gradient fill with hard stops to create the illusion of different segments.
For the former, there's a very nice answer to this question that gives more details. The latter way I've used in this visualisation.

error computing area of a closed space in a force layout using d3js

I have a graph which I've laid out using force layout. Basically i want to color the area contained within the cycles in the graph. Here's my code.
I was trying to paint the cycles when the force layout stabilized.
force.on("tick",function(){
// ... I update the position of nodes and the links
if(force.alpha() < 0.006){
force.stop();
// I dont know if there's an easier way of doing this
var xnodes = [];
force.nodes().forEach(function(d){
xnodes.push([d.x, d.y]);
});
// I tried creating a path and filling with with green
vis.select("path.area")
.data([xnodes])
.enter().append("path")
.style("fill", "#00ff00")
.attr("class", "area")
.attr("svg:d", d3.svg.area());
}
});
When i run it in chrome, the debugger shows that it does create a path but the area of the path is 0px x 0px. I'm really confused with this. I even tried setting the array manually. I get the same error.
var ynodes = [[352.3554660996234,304.3361316660001],[449.88953454311286,313.14153937680953],
[392.0458559272036,389.6656922220398],[352.3554660996234,304.3361316660001]];
vis.select("path.area")
.data([ynodes])
.enter().append("path")
.style("fill", "#00ff00")
.attr("class", "area")
.attr("svg:d", d3.svg.area());
However when i put this code in a blank html file (after you include the necessary libraries) it works fine. It does draw a path and fills it with green. I'm really confused here. Any help would be much appreciated. Thanks.

Resources