最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - d3.js - how to automatically calculate arc lengths in radial dendrogram - Stack Overflow

programmeradmin0浏览0评论

I'm creating a modified version of Mike Bostock's hierarchical edge bundling diagram:

.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);
  }
}

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);
  }
}
Share Improve this question edited May 27, 2014 at 14:02 VividD 10.5k8 gold badges66 silver badges112 bronze badges asked Oct 28, 2012 at 11:38 mheaversmheavers 30.2k62 gold badges198 silver badges326 bronze badges 4
  • Hi, I'm trying to do the same thing... I'm just curious if you have found the solution? Or you know where the source code is perhaps? I think I've seen it somewhere, but couldn't find it now... Thanks :) – wceo Commented Nov 26, 2012 at 17:55
  • Nope - but I promise to let you know if I find something if you do the same! – mheavers Commented Nov 27, 2012 at 19:20
  • can you set up a jsFiddle or Tributary I think a clever application of nest and a scale will fill in the pieces you are looking for. I would be happy to see what I can do, but the data would help! – Superboggly Commented Nov 27, 2012 at 23:46
  • 1 Here 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. – mheavers Commented Nov 29, 2012 at 0:37
Add a comment  | 

1 Answer 1

Reset to default 17 +50

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!

发布评论

评论列表(0)

  1. 暂无评论