rikb
rikb

Reputation: 572

making d3 force layout work with labeled data

I've been trying to glue ideas from various d3 examples into what i need, starting with the basic example using miserable.json data and then adding:

  1. use of a key function for the data joins
  2. modifying the underlying graph ala this example
  3. making the links' linkDistance() function depend on an attribute of the graph's links'
  4. adding labels to the nodes ala this example

So far i'm only 3 out of 4: something about using the g elements -- using code taken directly from the Mike's "Labeled Force Layout" example -- breaks things and the nodes aren't drawn. I can make it work if i join circle elements directly, but not if I interpose g elements with attached circles and text elements.

The code below is my best effort at a minimal example. This example works, but does not if I replace the .enter().append("circle") line with the .enter().append("g") lines.

Does anyone know why?

var Width = 200;
var Height = 200;
var Pix2Len = 10;
var color = d3.scale.category10();

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

var force = d3.layout.force()
.size([Width, Height])

var graph = {
  "nodes":[
    {"name":"Myriel","idx":0},
    {"name":"Napoleon","idx":1},
    {"name":"Mlle.Baptistine","idx":2},
    {"name":"Mme.Magloire","idx":3}
  ],
  "links":[
    {"source":1,"target":0,"len":1,"idx":"1-0"},
    {"source":2,"target":1,"len":4,"idx":"2-1"},
    {"source":2,"target":0,"len":8,"idx":"2-0"},
    {"source":3,"target":0,"len":10,"idx":"3-0"},
    {"source":3,"target":1,"len":4,"idx":"3-1"},
    {"source":3,"target":2,"len":6,"idx":"3-2"}
  ]
}

console.log("data loaded. nnode="+graph.nodes.length+" nlinks="+graph.links.length);

force
.nodes(graph.nodes)
.links(graph.links)
.size([Width, Height])
.linkDistance(function(link) {
    // console.log("link: "+link.source.name+' '+link.target.name+' '+link.idx+' '+link.len)
    return link.len * Pix2Len})
    .on("tick", tick);

var link = svg.selectAll(".link")
.data(graph.links, function(d)  {return d.idx; })
.enter().append("line")
.attr("class", "link");

var node = svg.selectAll(".node")
.data(graph.nodes, function(d) {return d.idx; })

// THIS WORKS
.enter().append("circle").attr("r", 8).style("fill", function(d) { return color(0); });

// BUT THIS DOES NOT
// modeled after http://bl.ocks.org/mbostock/950642
//
//.enter().append("g")
//.attr("class", "node")
//.attr("cx", function(d) { return d.x; })
//.attr("cy", function(d) { return d.y; });
//
//node.append("circle")
//.attr("r", 10)
//.style("fill", function(d) { return color(0); });
//
//node.append("text")
//.attr("dx", 12)
//.attr("dy", ".35em")
//.text(function(d) { return d.name });

// 1. Begin with graph from JSON data
setTimeout(function() {
    start();
}, 0);

// 2. Change graph topology
setTimeout(function() {
    var new4 = {"name":"CountessdeLo","idx":4}
    var new5 = {"name":"Geborand","idx":5}
    graph.nodes.push(new4,new5);
    var link40 = {"source":4,"target":0,"len":1,"idx":"4-0"};
    var link43 = {"source":4,"target":3,"len":4,"idx":"4-3"};
    var link50 = {"source":5,"target":0,"len":1,"idx":"5-0"};
    var link52 = {"source":5,"target":2,"len":4,"idx":"5-2"};
    graph.links.push(link40,link43,link50,link52);

    start();
}, 3000);

//3. Change some link lengths

setTimeout(function() {

    // force.links().forEach(function(link) {
    graph.links.forEach(function(link) {
        if (link.idx == '1-0') 
            {link.len=10; }
        else if (link.idx == '3-0') 
            {link.len=2; }
        else if (link.idx == '5-0') 
            {link.len=10; };
    }); // eo-forEach
    start();
}, 6000);

function start() {
    link = link.data(force.links(), function(d) { return d.idx; });
    link.enter().insert("line", ".node").attr("class", "link");
    link.exit().remove();

    node = node.data(force.nodes(), function(d) { return d.idx;});
    node.enter().append("circle").attr("class", function(d) {
    // tried with the <g> version above
//  node.enter().append("g").attr("class", function(d) {
        console.log('start:'+' '+d.name);
        return d.idx; }).attr("r", 5).style("fill", function(d) { return color(1); });
    node.exit().remove();

    force.start();
}

function tick() {
    node.attr("cx", function(d) { 
        // console.log('tick:'+' '+d.name);
        return d.x; })
    .attr("cy", function(d) { return d.y; })

    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; });
}
//}  // eo-ready()

Upvotes: 0

Views: 581

Answers (1)

Gilsha
Gilsha

Reputation: 14589

In your code you are setting cx and cy attributes to the g element. g elements does not support any position attributes like x, y or cx, cy. To move the contents of a g element you will have to use the transform attribute.

Your code

var node = svg.selectAll(".node")
     .data(graph.nodes, function(d) {return d.idx; })
     .enter().append("g")
     .attr("class", "node")
     .attr("cx", function(d) { return d.x; }) //will not work
     .attr("cy", function(d) { return d.y; }); //will not work

Solution

var node = svg.selectAll(".node")
     .data(graph.nodes, function(d) {return d.idx; })
     .enter().append("g")
     .attr("class", "node");

node.append("circle")
    .attr("r", 10)
    .style("fill", function(d) { return color(0); });

node.append("text")
    .attr("dx", 12)
    .attr("dy", ".35em")
    .text(function(d) { return d.name });

Use translate function as below to move group elements.

function tick() {
    //Moving <g> elements using transform attribute
    node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });

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

JSFiddle

Upvotes: 2

Related Questions