neubert
neubert

Reputation: 16782

d3.js tree layout is bunching everything up at the top

I'm trying to use d3js to create a tree graph with two nodes connected to each other. My JS is as follows:

var width = window.innerWidth;
var height = window.innerHeight;

var nodes = [{"id":"1","name":"a"},{"id":"2","name":"b"}];
var links = [{"source":0,"target":1}];

var svg = d3.select("body").append("svg");
svg.attr("width", width);
svg.attr("height", height);
svg.append("svg:g");

var tree = d3.layout.tree();
tree.size([width, height]);
tree.nodes(nodes);
tree.links(links);

var diagonal = d3.svg.diagonal.radial()
    .projection(function(d) { return [d.y, d.x / 180 * Math.PI]; });

  var link = svg.selectAll(".link")
      .data(links)
    .enter().append("path")
      .attr("class", "link")
      .attr("d", diagonal);

  var node = svg.selectAll(".node")
      .data(nodes)
    .enter().append("g")
      .attr("class", "node")
      .attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; })

  node.append("circle")
      .attr("r", 4.5);

  node.append("text")
      .text(function(d) { return d.name; });

So I'm setting the width and height of the SVG equal to the window width / height. And yet everything is bunched up at the top right.

My JS Fiddle: https://jsfiddle.net/eeLfog4m/1/

Any ideas?

What'd be helpful is a d3js demo without all the bells and whistles. http://bl.ocks.org/mbostock/4063550 rotates everything around the center. http://bl.ocks.org/d3noob/8375092 seems to have a lot of extra code for handling redrawing / collapsing of nodes and https://github.com/mbostock/d3/wiki/Tree-Layout doesn't have any examples at all.

Upvotes: 1

Views: 914

Answers (1)

Ankit
Ankit

Reputation: 6980

The reason you are seeing everything squished at the top-left corner is because all attributes are either NaN or undefined. This is because you are using

.projection(function(d) { return [d.y, d.x / 180 * Math.PI]; });

and

.attr("transform", function(d) { 
         return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; 
 });

which is accessing d.x and d.y, which are undefined.

Ideally, d.x and d.y are generated by the layout algorithms like d3.layout.tree(). However, in the code you provided, the data that you are passing into the tree-layout algorithm is incorrect.

d3.tree.layout() expects a hierarchical data structure, whereas you are providing it links and nodes, which will not work without some major workaround. If you want to use tree-layout, I suggest you convert your data into a hierarchical structure and then visualize it. Here is an example of doing that

var width = window.innerWidth;
var height = window.innerHeight;

var root = {
 "name": "1",
 "children": [
  {"name": "2"}
 ]
};

var svg = d3.select("body").append("svg")
            .attr("width", width)
            .attr("height", height);

var tree = d3.layout.tree()
          .size([width, height-40]);

var nodes = tree.nodes(root);
var links = tree.links(nodes);

var path = d3.svg.line()
    .x(function(d) { return d.x; })
    .y(function(d) { return d.y;  });

  var link = svg.selectAll(".link")
      .data(links)
      .enter()
      .append("path")
      .attr("class", "link")
      .attr("stroke", "black")
      .attr("stroke-width", 2);
      .attr("d", function(d){
        return path([d.source, d.target])
      });

  var node = svg.selectAll(".node")
      .data(nodes)
      .enter().append("g")
      .attr("class", "node")
      .attr("transform", function(d) {
        return "translate(" + d.x + "," + (d.y + 20) + ")";       })

  node.append("circle")
      .attr("r", 4.5);

  node.append("text")
      .text(function(d) { return d.name; });

If however, you want to stick with the current data format:

var nodes = [{"id":"1","name":"a"},{"id":"2","name":"b"}];
var links = [{"source":0,"target":1}];

you should use force-directed layout.

Here is an example of using force-directed layout with the data structure you have (http://jsfiddle.net/ankit89/3kL11j6j/)

var graph = {
 "nodes": [
     {"name": "Leo"},
     {"name": "Mike"},
     {"name": "Raph"},
     {"name": "Don"},
     {"name": "Splinter"}
],
"links": [
    {"source": 0, "target": 4, "relation": "son"},
    {"source": 1, "target": 4, "relation": "son"},
    {"source": 2, "target": 4, "relation": "son"},
    {"source": 3, "target": 4, "relation": "son"}
]}


var force = d3.layout.force()
    .nodes(graph.nodes)
    .links(graph.links)
    .size([400, 400])
    .linkDistance(120)
    .charge(-30)
    .start();


 var svg = d3.select("svg");

var link = svg.selectAll("line")
.data(graph.links)
.enter().append("line")
.style("stroke", "black");

var node = svg.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.attr("r", 20)
.style("fill", "grey")
.call(force.drag);

node.append("title")
.text(function(d) { return d.name; });

force.on("tick", function() {
    link.attr("x1", function(d) { return d.source.x; })
    .attr("y1", function(d) { return d.source.y; })
    .attr("x2", function(d) { return d.target.x; })
    .attr("y2", function(d) { return d.target.y; });

    node.attr("cx", function(d) { return d.x; })
    .attr("cy", function(d) { return d.y; });
})

And finally, you do not necessarily need d3 layouts, you can even use your custom layout like this

http://jsfiddle.net/ankit89/uts5orrd/5/

var graph = {
 "nodes": [
     {"name": "Leo", "level": 1},
     {"name": "Mike", "level": 1},
     {"name": "Raph", "level": 1},
     {"name": "Don", "level": 1},
     {"name": "Splinter", "level": 2}
],
"links": [
    {"source": 0, "target": 4, "relation": "son"},
    {"source": 1, "target": 4, "relation": "son"},
    {"source": 2, "target": 4, "relation": "son"},
    {"source": 3, "target": 4, "relation": "son"}
]}

 var svg = d3.select("svg");

svg.selectAll("circle")
.data(graph.nodes)
.enter()
.append("circle")
.attr({
    "cx": function(d, i){
        var x;
        if(d.level == 1){
            x = i*100 + 100;
        }else{
            x = 250;
        }
        d.x = x;
        return x;
     },
    "cy": function(d, i){
            var y;
            if(d.level == 1){
                y = 260;
            }else{
                y = 60;
            }

            d.y = y;
            return y;
        },
    "r" : 30,
    "fill": "gray",
    "opacity": .5
})

svg.selectAll("text")
.data(graph.nodes)
.enter()
.append("text")
.attr({
    "x": function(d){return d.x},
    "y": function(d){return d.y},
    fill: "steelblue"
})
.text(function(d){
    return d.name;
})


svg.selectAll("line")
.data(graph.links)
.enter()
.append("line")
.attr({
    "x1": sourceX,
    "y1": sourceY,
    "x2": targetX,
    "y2": targetY,
    "stroke-width": 2,
    "stroke": "grey"
})

function sourceX(d, i){
    var t = graph.nodes[d.source].x;
    return t;
}

function sourceY(d, i){
    var t = graph.nodes[d.source].y;
    return t;
}

function targetX(d, i){
    var t = graph.nodes[d.target].x;
    return t;
}

function targetY(d, i){
    var t = graph.nodes[d.target].y;
    return t;
}
//console.log(graph.nodes)

Here is the gist of the story:

  1. If using d3 layout, make your data structure match the data-structure expected by the layout and d3 will compute the x and y coordinates for you.

  2. If using want a custom layout, write function to determine the x and y coordinates for nodes and the links.

Upvotes: 2

Related Questions