Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to control the group node distance in Hierarchical Edge Bundling in D3.js

This is the link of HEB visualization example in D3.

http://mbostock.github.io/d3/talk/20111116/bundle.html

enter image description here

I noticed that in the visualization, there is an clear distance(or so-called space) between groups, obviously this distance between groups is bigger than the distance between nodes within the same group. But I didn't really get which part of the code is controlling this distance. Anyone has idea that how the distance is controlled here? I want to use this feature in my own HEB.

like image 267
Xiangyu Du Avatar asked May 18 '15 07:05

Xiangyu Du


1 Answers

The distance between groups is determined by D3 cluster layout. The default behavior is to leave empty space of size of one node between groups.

This can be modified by providing custom separation() function to the cluster layout. The key line to add is this:

    .separation(function(a, b) { return (a.parent == b.parent ? 1 : 5 ) })

This will increase group distance to five nodes width.

By choosing other values in place of 5 on can get:

enter image description here

enter image description here

enter image description here

enter image description here

This is the complete example:

var diameter = 300,
    radius = diameter / 2,
    innerRadius = radius - 80;

var cluster = d3.layout.cluster()
    .separation(function(a, b) { return (a.parent == b.parent ? 1 : 5 ) })
    .size([360, innerRadius])
    .sort(null)
    .value(function(d) { return d.size; });

var bundle = d3.layout.bundle();

var line = d3.svg.line.radial()
    .interpolate("bundle")
    .tension(.85)
    .radius(function(d) { return d.y; })
    .angle(function(d) { return d.x / 180 * Math.PI; });

var svg = d3.select("body").append("svg")
    .attr("width", diameter)
    .attr("height", diameter)
    .append("g")
    .attr("transform", "translate(" + radius + "," + radius + ")");

var classes = getData();

var nodes = cluster.nodes(packageHierarchy(classes)),
    links = packageImports(nodes);

svg.selectAll(".link")
    .data(bundle(links))
   .enter()
    .append("path")
    .attr("class", "link")
    .attr("d", line);

svg.selectAll(".node")
    .data(nodes.filter(function(n) { return !n.children; }))
   .enter()
    .append("g")
    .attr("class", "node")
    .attr("transform", function(d) {
        return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")";
    })
    .append("text")
    .attr("dx", function(d) { return d.x < 180 ? 8 : -8; })
    .attr("dy", ".31em")
    .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) { return d.key; });

d3.select(self.frameElement).style("height", diameter + "px");

// Lazily construct the package hierarchy from class names.
function packageHierarchy(classes) {
  var map = {};

  function find(name, data) {
    var node = map[name], i;
    if (!node) {
      node = map[name] = data || {name: name, children: []};
      if (name.length) {
        node.parent = find(name.substring(0, i = name.lastIndexOf(".")));
        node.parent.children.push(node);
        node.key = name.substring(i + 1);
      }
    }
    return node;
  }

  classes.forEach(function(d) {
    find(d.name, d);
  });

  return map[""];
}

// Return a list of imports for the given array of nodes.
function packageImports(nodes) {
  var map = {},
      imports = [];

  // Compute a map from name to node.
  nodes.forEach(function(d) {
    map[d.name] = d;
  });

  // For each import, construct a link from the source to target node.
  nodes.forEach(function(d) {
    if (d.imports) d.imports.forEach(function(i) {
      imports.push({source: map[d.name], target: map[i]});
    });
  });

  return imports;
}

function getData() {
    return [
        {"name":"Female.Ann",     "imports":["Male.Andrew"]},
        {"name":"Female.Brenda",  "imports":["Male.Bill"]},
        {"name":"Female.Claudia", "imports":["Male.Andrew"]},
        {"name":"Female.Claudia", "imports":["Male.Bill"]},
        {"name":"Female.Deborah", "imports":["Male.Darryl"]},
        {"name":"Female.Emily",   "imports":["Male.Andrew"]},
        {"name":"Female.Flora",   "imports":["Male.Darryl"]},
        {"name":"Female.Gia",     "imports":["Male.Darryl"]},
        {"name":"Female.Hannah",  "imports":["Male.Curtis"]},
        {"name":"Female.Irene",   "imports":["Male.Hugh"]},
        {"name":"Male.Andrew",    "imports":["Female.Emily"]},
        {"name":"Male.Bill",      "imports":["Female.Emily"]},
        {"name":"Male.Curtis",    "imports":["Female.Emily"]},
        {"name":"Male.Darryl",    "imports":["Female.Claudia"]},
        {"name":"Male.Edgar",     "imports":["Female.Claudia"]},
        {"name":"Male.Franklin",  "imports":["Female.Claudia"]},
        {"name":"Male.George",    "imports":["Female.Claudia"]},
        {"name":"Male.Hugh",      "imports":["Female.Claudia"]},
    ];
};
.node {
  font: 11px "Helvetica Neue", Helvetica, Arial, sans-serif;
}

.link {
  stroke: steelblue;
  stroke-opacity: .4;
  fill: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
like image 190
VividD Avatar answered Oct 15 '22 07:10

VividD