3

Using D3.js's force layout I'm trying to make the links automatically generate based on the node data. The nodes already appear exactly as expected.

The database json strings use the following format:

{
 "id": 1,
 "title": "name"
}, 
{
 "id": 2,
 "title": "other name",
 "primaryDependants": 1
}

and I'm basically trying to have it say:

'for each node, check if it has a primaryDependant property, if it does then make a link between that node and the one identified as the primary dependant.'

however I haven't dealt with force graphs before and tutorials are few and far between so I'm really struggling with how to make any change to my code without breaking it. Currently it is based on the answer here and I use the Meteor framework if that's of any relevance.

Template.tree.rendered = function () {

  var graph = new myGraph("#svgdiv");

  Lessons.find().observe({ 
    added: function (doc) { 
      graph.addNode(doc._id, doc.title); 
    },
    removed: function (doc) { 
      graph.removeNode(doc._id); 
    }
  });

function myGraph(el) { 

  w = 1500,
  h = 1000; 

  var svg = d3.select(el) 
    .append("svg") 
    .attr("width", w)
    .attr("height", h)
    .attr("pointer-events", "all") 

    svg.append("rect") 
    .attr("width", "100%")
    .attr("height", "100%")
    .attr("fill", "lightgrey");

  var vis = svg.append('g'); 
  var force2 = d3.layout.force(); 
  var links = force2.links(); 
  var nodes = force2.nodes(); 

  var update = function () { 

    var link = vis.selectAll("line") 
      .data(links, function(d) {return d.source.id + "-" + d.target.id;}); 

    link.enter().append("line") 
      .attr("id",function(d){return d.source.id + "-" + d.target.id;}) 
      .attr("class","link"); 

    link.append("title") 
    .text(function(d){
      return d.value;
    });

    link.exit().remove();

    var node = vis.selectAll("g") 
      .data(nodes, function(d) { return d.id;}); 

    var nodeEnter = node.enter() 
      .append("g") 
        .call(force2.drag)
      .append("circle") 
        .attr("r", 8)
        .attr("fill", "#585858")
        .attr("stroke", "#008db7")
        .attr("stroke-width", 3)
        .attr("id", function(e) { return "Node;"+e.id;})
        .attr("class", ( function(f){return f.id;}));

    node.exit().remove(); 

    force2.on("tick", function() { 
      node.attr("transform", function(g) { return "translate(" + g.x + "," + g.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; });
    });

    force2 
      .gravity(.02)
      .linkDistance( 200 ) 
      .size([w, h])
      .start();
  };

  update(); 

  var findNode = function(id) { 
    for (var i in nodes) {
      if (nodes[i]["id"] === id) return nodes[i];};
  };

  var findNodeIndex = function(id) { 
    for (var i=0;i<nodes.length;i++) {
      if (nodes[i].id==id){
        return i;
      }
    };
  };

  this.addNode = function (id, title) { 
    nodes.push({"id":id,"title":title});
    update(); 
  };

  this.addLink = function (source, target, value) {
    links.push({"source":findNode(source),"target":findNode(target),"value":value});
    update();
  }; 

  this.removeNode = function (id) { 
    var i = 0;
    var n = findNode(id);
    nodes.splice(findNodeIndex(id),1); 

    update(); 
  };
}
Community
  • 1
  • 1
Josh McGee
  • 443
  • 6
  • 16

1 Answers1

2

To create the links array based on your description:

var dataset = [{
 "id": 1,
 "title": "name"
}, 
{
 "id": 2,
 "title": "other name",
 "primaryDependants": 1
}];

var links = [];

dataset.forEach(function(d){
  if(d.primaryDependants){
    links.push({source: d.id,
                target: d.primaryDependants});
    }});

console.log(links)
Gerardo Furtado
  • 100,839
  • 9
  • 121
  • 171
  • thanks for the response, is there any chance you could help me weave that into my code? I came up with a similar forEach function but when I add it in to everything else (before, during or after the update function) it doesn't have the expected result. The links variable remains blank when logged to the console. – Josh McGee Nov 02 '16 at 06:21
  • I'd put that just after declaring `dataset` (or whatever its name is). – Gerardo Furtado Nov 02 '16 at 06:29
  • hmm can I ask what you think of [this image] (https://s17.postimg.org/9mkfrqg4v/fhr.jpg) which shows the nodes variable when logged to the console? As you can see they have a primaryDependants property, however I tweaked your code to now say 'nodes.forEach(function(d){ if(d.primaryDependants){ console.log("hello"); }});' but nothing is being logged to the console. I feel like this should be obvious but I can't see what is missing... – Josh McGee Nov 02 '16 at 06:47
  • Sorry, I can't help you more than this. You can easily see that my snippet is working, so your problem lies elsewhere. You'll have to paste you entire code, or better, creating a working fiddle or Plunker. – Gerardo Furtado Nov 02 '16 at 08:00
  • I've been playing with the code and this works when the forEach function is set on a timer because not all of the data is available instantly for some reason. – Josh McGee Nov 05 '16 at 04:50
  • This is probably happening because you're using an asynchronous call, like `d3.csv` or `d3.json`. To check this I'd have to see all the code... – Gerardo Furtado Nov 05 '16 at 05:19
  • the code is in the original post if you want to look at it, it's built with Meteor so I'm not sure how to get it working in fiddle or Plunker – Josh McGee Nov 05 '16 at 05:33
  • That can't be all the code, I can't see where do you get the data. – Gerardo Furtado Nov 05 '16 at 05:39
  • at the very top the data comes from the "Lessons" database – Josh McGee Nov 05 '16 at 05:52
  • My bad, I don't know meteor. But I was just looking at their API and it seems to be asynchronous. – Gerardo Furtado Nov 05 '16 at 06:06
  • no worries, I don't know anything other than meteor haha. This solution here has sparked a new question that maybe I can ask for your help on also? http://stackoverflow.com/questions/40434463/perform-function-when-database-is-loaded – Josh McGee Nov 05 '16 at 06:12