d3.js - how to automatically calculate arc lengths in radial dendrogram
I've seen your json data structure here: http://mikeheavers.com/transfers/projects/data/projects.json. Firstly, in order to group the data and append the tag correctly, it'll be better to change your data like this: https://raw.github.com/gist/4172625/4de3e6a68f9721d10e0068d33d1ebb9780db4ae2/flare-imports.json to create a hirarchical structure.
We can then use the groups to draw the arcs.
First we create groups by "selectAll" and filter your nodes. Here you could add other group names of your data:
var groupData = svg.selectAll("g.group")
.data(nodes.filter(function(d) {return (d.key=='Jobs' || d.key == 'Freelance' || d.key == 'Bayard') && d.children; }))
.enter().append("group")
.attr("class", "group");
I just checked that in my case, so you'd better verify the result of the filter and make change according to your case (our data structure is a little bit different).
Now we got a list of groups. Then we'll go through the children of each group, and choose the smallest and largest x as the start and end angle. We can create a function like this:
function findStartAngle(children) {
var min = children[0].x;
children.forEach(function(d){
if (d.x < min)
min = d.x;
});
return degToRad(min);
}
And similarly a findEndAngle function by replacing min by max. Then we can create the arcs' format:
var groupArc = d3.svg.arc()
.innerRadius(arcData[0].rI)
.outerRadius(arcData[0].rO)
.startAngle(function(d){return findStartAngle(d.children);})
.endAngle(function(d){return findEndAngle(d.children);});
Then we can create arcs in "dynamic" way:
svg.selectAll("g.arc")
.data(groupData[0])
.enter().append("arc")
.attr("d", groupArc)
.attr("class", "arc")
.append("svg:text")
...;
In my case it is groupData[0], maybe you should check it in your case. For adding tags to arcs you just need to add d.key or d.name according to the result of your selection.
The full code is available here: https://gist.github.com/4172625. Every time I get json from database so if there's no dynamic way to generic arcs I will be dead :P Hope it helps you!
Related videos on Youtube
mheavers
Updated on August 06, 2022Comments
-
mheavers over 1 year
I'm creating a modified version of Mike Bostock's hierarchical edge bundling diagram:
http://mbostock.github.com/d3/talk/20111116/bundle.html
but I want to make arcs which span certain groups of data, like this:
I'm currently just hardcoding the length of the arc, but I want to do it dynamically. How can I accomplish this? Here's my current code:
/* MH - USER DEFINED VARIABLES */ var chartConfig = { "Tension" : .85, "canvasSize" : 800, "dataFile" : "../data/projects.json", "linePadding" : 160, "textPadding" : 30, "arcPadding" : 5, "arcWidth" : 30 } var pi = Math.PI; var radius = chartConfig.canvasSize / 2, splines = []; var cluster = d3.layout.cluster() //Cluster is the diagram style, a node to link dendrogram dendrogram (tree diagram) .size([360, radius - chartConfig.linePadding]); //MH - sets the size of the circle in relation to the size of the canvas var bundle = d3.layout.bundle(); //Bundles the node link lines so that they spread at the end but keep close initially var arcInner = radius - chartConfig.linePadding + chartConfig.arcPadding; var arcOuter = arcInner + chartConfig.arcWidth; var arc = d3.svg.arc().innerRadius(arcInner).outerRadius(arcOuter); var line = d3.svg.line.radial() .interpolate("bundle") .tension(chartConfig.Tension) //How tightly to bundle the lines. No tension creates straight lines .radius(function(d) { return d.y; }) .angle(function(d) { return d.x / 180 * Math.PI; }); var vis = d3.select("#chart").append("svg") .attr("width", radius * 2) .attr("height", radius * 2) .attr("class","svg") .append("g") .attr("class","chart") .attr("transform", "translate(" + radius + "," + radius + ")"); d3.json(chartConfig.dataFile, function(classes) { var nodes = cluster.nodes(packages.root(classes)), links = packages.imports(nodes), splines = bundle(links); var path = vis.selectAll ("path.link") .data(links) .enter().append("path") .attr("class", function(d){ return "link source-" + d.source.key + " target-" + d.target.key; }) .attr("d", function(d,i){ return line(splines[i]); }); vis.selectAll("g.node") .data(nodes.filter(function(n) { return !n.children; })) .enter().append("g") .attr("class", "node") .attr("id",function(d){ return "node-" + d.key; }) .attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; }) .append("text") .attr("dx", function(d) { return d.x < 180 ? chartConfig.textPadding : -chartConfig.textPadding; }) //dx Moves The text out away from the lines in a positive or negative direction, depending on which side of the axis it is on .attr("dy", ".31em") //moves the text up or down radially around the circle .attr("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; }) .attr("transform", function(d) { return d.x < 180 ? null : "rotate(180)"; }) .text(function(d) { textString = d.key; textString = textString.split('_').join(' '); //MH replace underscores with spaces return textString; }) .on("mouseover",textOver) .on("mouseout",textOut); }); /* ARCS ARE HARDCODED, SHOULD BE DYNAMIC */ var arcData = [ {aS: 0, aE: 45,rI:radius - chartConfig.linePadding + chartConfig.arcPadding,rO:radius - chartConfig.linePadding + chartConfig.textPadding-chartConfig.arcPadding} ]; var arcJobsData = d3.svg.arc().innerRadius(arcData[0].rI).outerRadius(arcData[0].rO).startAngle(degToRad(1)).endAngle(degToRad(15)); var g = d3.select(".chart").append("svg:g").attr("class","arcs"); var arcJobs = d3.select(".arcs").append("svg:path").attr("d",arcJobsData).attr("id","arcJobs").attr("class","arc"); g.append("svg:text").attr("x",3).attr("dy",15).append("svg:textPath").attr("xlink:href","#arcJobs").text("JOBS").attr("class","arcText"); //x shifts x pixels from the starting point of the arc. dy shifts the text y units from the top of the arc ... function degToRad(degrees){ return degrees * (pi/180); } function updateNodes(name,value){ return function(d){ if (value) this.parentNode.appendChild(this); vis.select("#node-"+d[name].key).classed(name,value); } }
-
mheavers over 11 yearsHere is a js fiddle but for some reason it is not drawing the bundles: jsfiddle.net/QNtHr. You can see the working example here: mikeheavers.com/transfers/projects/html. As you can see the arcs are currently hard coded but they need to be dynamic.
-
-
mccannf over 11 yearsIn your
findStartAngle(d.children)
andfindEndAngle(d.children)
above, where isd
defined? -
wceo over 11 years@mccannf Sorry, my mistake, it should be
function(d){return findStartAngle(d.children);}
-
mheavers over 11 yearsThis looks great. I will test it this weekend and accept your answer if everything goes well. Thanks!
-
wceo over 11 years@mheavers You won't give me your bounties? :)
-
mheavers over 11 yearsYes! Sorry - been busy, but this is a great solution.
-
mheavers over 11 yearsOne last question - In my example above, I appended some text to the arcs to indicate to what group the arcs belonged - agencies, jobs, freelance, etc. - but in your example that is missing. I know I can retrieve that data using d.__data__.key - but I can't figure out how to place it dynamically inside the arc. Any pointers on how to do that?
-
luftikus143 over 10 years@wceo: I am pretty new to this type of vis. Would it be possible to link say the "Freelancer" to "Hertz", too? I don't see how. But that way, when I hover over "Hertz" I would see that both "Freelance" and "Bayard" link to it. In the moment, if there is textual link between them, then it highlights the two lines; but it should preferably be one line only. Am I clear?! Hmm... Thanks for any hints!
-
wceo over 10 years@luftikus143 Maybe I don't understand very clearly. Why when "Freelance" and "Bayard" are both linked to "Hertz" there should be one line only?
-
luftikus143 over 10 yearsBecause the same item shouldn't appear twice. And as it appears only once, by hovering over it one can see that it is linked to two items. In this case, it would be a company XY for which the person has worked as a freelancer, but as well as an employee at Bayard. And thus, Bayard and Freelancer are linking to XY. If you hover over Bayard you see the link; same if you hover over Freelancer. And if you hover over XY you see the link to both Bayard and Freelancer. That's for me the big advantage of these types of graphs.
-
wceo over 10 years@luftikus143 I'm still not sure I know what you mean exactly :P I believe if in the data freelance and bayard are both linked to XY, there will be only one XY, not two. (If this is what you mean...
-
luftikus143 over 10 years@wceo Yes, that's what I mean, indeed.
-
luftikus143 over 10 years@wceo: Any ideas how to proceed?
-
wceo over 10 years@luftikus143 Sorry I still don't get you even I know what you mean. If in the data " freelance and bayard are both linked to XY, there will be only one XY", what else do you have to achieve exactly? Sorry if I've been misunderstanding...
-
luftikus143 over 10 yearsHmm. Can Bayard be linked to items on the left side so that both Freelancer and Bayard are linking to the same item? Or, differently asked, I want to have a connection from one item to both Bayard and Hertz. Is that possible?
-
wceo over 10 years@luftikus143 Yes you can. Look at the example data here: gist.github.com/geow812/6696698. "region1.Montrose_High_School" is in the "imports" of both "employers.Freelance" and "employers.Bayard", this will do the trick. The "region1" and "region2" are used to separate the two parts :)