Add text within links in d3.js sankey diagram - d3.js

I'm trying to create a Sankey diagram based off the example shown here: https://bost.ocks.org/mike/sankey/
While I am able to recreate the diagram, I'd like to write the tooltip text directly onto the links themselves (so they can be read as a static image). I've attempted to add a .append("text") call to the links just like I saw done for the nodes, but it doesn't seem to be working. Below is the code I added to the example above:
link.append("text")
.attr("x", 25)
.attr("y", function(d) { return d.dy / 2; })
.attr("dy", ".35em")
.attr("text-anchor", "end")
.attr("transform", null)
.text(function(d) { return d.source.name + " → " + d.target.name + "\n" + format(d.value); });
.attr("text-anchor", "start");
Yet this doesn't show display anything in the diagram.
UPDATE: I pasted Mike Bostock's code into a gist to make recreating the issue easier: http://bl.ocks.org/almsuarez/50eab68a645d7fc12183384000ea8c82

As was said in the comments you can't append text to SVG elements. So say at the moment you append your link to a container. For example :
var link = container.append('path')....//and so on
You have to do the same for text. So, for example :
var linkText = container.append('text')
You say you can't keep track of the x and y positions ?
Basically as your link is in between two points, you need to find the centre. Here is how I would do it :
.attr("x", function(d) { console.log(d); return d.source.x + (d.target.x - d.source.x) / 2; })
.attr("y", function(d) { return d.source.y + (d.target.y - d.source.y) / 2; })
So you get the source' x position then add half the difference of targetX - sourceX and do the same for y.
I have put this in a fiddle for you to see : https://jsfiddle.net/thatOneGuy/8ayq5nwa/2/
You can hover over the links to see the title that way you can check it's all correct :)

Related

Collabsible tree with Pie as Nodes

I already created the collapsible tree by appending circle,rect,symbols and images.
But I want to build it and need to place pie charts on place of nodes. I am new to D3, so try to add Pie chart as place of nodes.But it is only appending to first .
Can you please let me know the way to do that.Here is what that i tried so far.
nodeEnter.append("svg").data([data]).attr("width", "50")
.attr("height", "50").append("g")
.attr("transform", "translate(" + r + "," + r + ")")
.selectAll("g.slice").data(pie).enter().append("g").attr("class", "slice")
.append("svg:path")
.attr("fill", function(d, i){
return color[i];
})
.attr("d", function (d) {
return arc(d);
});
I am attaching the working fiddle : https://jsfiddle.net/0odgqgsk/
Please modify it accordingly.
Thanks in advance.

D3.js add link with label

I'm using a force layout and i want to add new node with link on an existing graph.
Adding node is not a problem, I have an issue with the links.
I put labels on link like this :
Here's the code I use to draw the link :
var link = svg.selectAll(".link");
var links = []; // link array with source and target
[...]
var force = d3.layout.force()
.nodes(nodes)
.links(links)
[...]
link = link.data(force.links(), function (d) {
return d.source.id + "-" + d.target.id;
});
var appendL = link.enter().append("g")
.attr("class", "link");
linkLine = appendL.append("line")
.attr("class", "linkLine")
.style("stroke-width", 1);
linkLabel = appendL.append("text")
.attr("class", "linkLabel")
.attr("dy", 5)
.attr("filter", "url(#solid)")
.text(function (d) {
return d.type;
});
link.exit().remove();
(The filter is use to put a white background on the label).
But when I change my link list, the result is this :
The new link can move but the old ones are fixed and not attach to the node.
I think D3 is having problem to detect if a link is already on the graph.
If you have any idea ... thanks !
PS : Sorry for my english.
Basically you weren't selecting all the lines to update them.
When you update them, you need to select what's already there to 'overwrite' it so to speak. So move this into your update function :
node = svg.selectAll(".node").data(nodes)
link = svg.selectAll(".linkLine").data(links);
Notice I use the selectAll on .linkLine. This is as you give the links a class of linkLine so we need to select them all. Also the way you are appending the line is wrong. This is the correct way :
linkLine = link.enter().append("line")
.attr("class", "linkLine")
.style("stroke-width", 1);
linkLabel = link.append("text")
.attr("class", "linkLabel")
.attr("dy", 5)
.attr("filter", "url(#solid)")
.text(function(d) {
return d.type;
});
link.exit().remove();
Here is your updated fiddle : https://jsfiddle.net/reko91/wdouvab8/1/
Also need to do the labels. As you cant append text to a line in svg, the best way I could think of was to do something similar with the labels as was done with the links.
Create the container with the link data :
linkLabelContainer = svg.selectAll(".linkLabel").data(links);
Create the actual text element :
linkLabel = linkLabelContainer.enter().append("text")
.attr("class", "linkLabel")
.attr("dy", 5)
.attr("filter", "url(#solid)")
.text(function(d) {
return d.type;
});
Don't forget to remove :
linkLabelContainer.exit().remove();
Now position :
linkLabelContainer.attr("transform", function(d) { //calcul de l'angle du label
var angle = Math.atan((d.source.y - d.target.y) / (d.source.x - d.target.x)) * 180 / Math.PI;
return 'translate(' + [((d.source.x + d.target.x) / 2), ((d.source.y + d.target.y) / 2)] + ')rotate(' + angle + ')';
});
Notice I am moving the container on the tick event not the actual text element.
Updated fiddle : https://jsfiddle.net/reko91/wdouvab8/3/

Labels on bilevel D3 partition / sunburst layout

I am trying to add labels to the bilevel sunburst / partition shown here - http://bl.ocks.org/mbostock/5944371:
I have added labels to the first 2 levels and rotated them correctly. But I cant now add the new level's labels during a transition. To get started I have put ...
text = text.data(partition.nodes(root).slice(1), function(d) { return d.key; });
straight after the ...
path = path.data(partition.nodes(root).slice(1), function(d) { return d.key; });
line but it throws the following error ...
Uncaught TypeError: Cannot read property '__data__' of undefined
What am I doing wrong?
I used bilevel partition to add labels via a mouseover, and found that in this version (unlike the other sunburst partition), there are two different sections where "path" is defined and updated. The first is here:
var path = svg.selectAll("path")
and then again below the transition you highlighted in your code:
path.enter().append("path")
When i added my mouseover labels, I needed to reference my text function in both places or it wouldn't work after transition.
If you can post your code to JSFiddle or bl.ocks.org we can try to play with it and see, but that was where I got tripped up at first.
Hope that helps.
*NOTE: Comment didn't post:
I'm sorry I'm not able to help more, but I'm also a newbie at D3. Here's where I got:
copy and paste your svg.selectAll("text") snippet after the second "path.enter().append("path") snippet. This will cause it to appear on subsequent zooms as well.
Problems I see: there's no "g" element so you need separate transitions for text as well or they all pile up. Also, I can't understand why they're positioned at their original partition spot instead of where the arc exists now.
My approach was add labels on mouseOver. I've uploaded that here: https://github.com/johnnymcnugget/d3-bilevelLabels
In this, I'm calling the two functions in both sets of "path" snippets, and the transitions are handled within the single variable of "legend"
Another D3 newbie, but I managed to resolve this. Beggining from the original Bilevel Partition:
Add the text for the first time the chart is drawn:
var path = svg.selectAll("path")
.data(partition.nodes(root).slice(1))
.enter().append("path")
.attr("d", arc)
.style("fill", function(d) { return d.fill; })
.each(function(d) { this._current = updateArc(d); })
.on("click", zoomIn);
var text = svg.selectAll("text")
.data(partition.nodes(root).slice(1))
.enter().append("text")
.attr("transform", function(d) { return "rotate(" + computeTextRotation(d) + ")"; })
.attr("x", function(d) {return radius / 3 * d.depth; })
.attr("dx", "6") // margin
.attr("dy", ".35em") // vertical-align
.html(function(d) { return d.name; });
when zooming in or out you have to redraw labels, add the following in function zoom(root, p)
text.enter().append("text")
.attr("transform", function(d) { return "rotate(" + computeTextRotation(d) + ")"; })
.attr("x", function(d) {return radius / 3 * d.depth; })
.attr("dx", "6") // margin
.attr("dy", ".35em") // vertical-align
.text(function(d) { return d.name; });
text.exit().transition()
.style("fill-opacity", function(d) { return d.depth === 1 + (root === p) ? 1 : 0; })
.remove();
text.transition()
.style("fill-opacity", 1)
.attr("transform", function(d) { return "rotate(" + computeTextRotation(d) + ")"; })
.attr("x", function(d) {return radius / 3 * d.depth; })
.attr("dx", "6") // margin
.attr("dy", ".35em") // vertical-align
Finally modify the computeTextRotation function as:
function computeTextRotation(d) {
return (d.x + (d.dx)/2) * 180 / Math.PI - 90;
}
Hope I haven't missed anything.
Indeed one modified line is missing in "2.". In function zoom(root, p), you should also add a line after path = path.data(partition ... .key; });:
path = path.data(partition.nodes(root).slice(1), function (d) {
return d.key;
});
text = text.data(partition.nodes(root).slice(1), function (d) {
return d.key;
});

d3.js: Updating a pie with lables

I'm trying to set up a pie on d3.js that will constantly update its data (slices) and change the labels.
The slices of data may vary and are not constant (could be a 2 slices pie and 5 slices, etc.)
I've reviewed several of examples such as:
http://jsfiddle.net/emepyc/VYud2/3/
http://bl.ocks.org/mbostock/3808218
But the thing is, my implementation is quite different since I use both text lables and paths.
The problem: data is not updated and text is not removed.
Here is my code: http://jsfiddle.net/Kitt0s/vfkSs/
function tests (data) {
data = data ? data : { "slice1": Math.floor((Math.random()*10)+1), "slice2": Math.floor((Math.random()*10)+1), "slice3": Math.floor((Math.random()*10)+1), "slice4": Math.floor((Math.random()*10)+1) };
var dataa = d3.entries(data);
var cv_path = cv_svg.selectAll("g.slice")
.data(cv_pie(dataa));
cv_path.enter()
.append("g")
.attr("class", "slice")
.append("path")
.attr("fill", function(d, i) { return cv_color(i); } )
.attr("d", cv_arc)
.each(function(d) { this._current = d; });
cv_path.transition().duration(750).attrTween("d", cv_arcTween);
cv_path.exit().remove();
var cv_text = d3.selectAll(".slice")
.append("text")
.attr("transform", function(d) {
d.innerRadius = 0;
d.outerRadius = cv_r;
return "translate(" + cv_arc.centroid(d) + ")";
})
.attr("text-anchor", "middle")
.attr("font-weight", "bold")
.attr("fill", "#FFFFFF")
.attr("font-size", "30px")
.text(function(d) { return d.data.key + "(" + d.data.value + ")"; });
cv_text.exit().remove();
}
I've spent an awful lot of time trying to get this right, but still seem to get stuck (every time something else breaks.. :P)
Your help would be highly appreciated!
You were almost there. The key is to treat the text exactly the same as the paths and handle the .exit() selection by removing it. I've modified your jsfiddle here. I've removed the g elements to make it a bit easier to understand.

D3.js donut chart... arc.centroid(d) is not influenced by d.innerRadius and d.outerRadius

I'm creating a donut (or Piechart) and I want to place the labels just outside the area.
I've created a fiddle http://jsfiddle.net/VeeTee/mA3V7/ for it.
arcs.append("svg:text")
.attr("transform", function(d) {
//this is where I want to make a translation to the outside border
d.innerRadius = radius;
d.outerRadius = height/2;
return "translate(" + arc.centroid(d) +")";
})
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d, i) { return d.value.toFixed(2); });
arc.centroid(d) -> is always giving the same result (and therefore the same translation)
Not sure what you mean by that it's always giving you the same result, but you can place the labels just outside the chart by multiplying the coordinates of the centroid by 1.5. The code is something like this.
.attr("transform", function(d) {
var c = arc.centroid(d);
return "translate(" + c[0]*1.5 +"," + c[1]*1.5 + ")";
})
Updated jsfiddle here.

Resources