0

I came across the following example that is located here.

It is based off this question.

The problem I have noticed with this is that it matches the first of the matching nodes and stops. The relationships are based off the first matching node of each entry, NOT all the matching nodes.

I've modified the code, but I cannot seem to figure out why the graph does not render properly.

The section of the code that does this, is located between lines 64 through 128, located below. Full source is available here : http://bl.ocks.org/hijonathan/raw/5793014/664407f1855a61250b30458b583fb59635bf3157/script.js

The script utilizes underscore.js, I believe the correction should be to utilize _.filter as opposed to _.find. Any advice on modifying this code to include all relationships, as opposed to matching the first one and stopping?

A live example of the output can be found here.

var width = document.width,
    height = document.height,
    nodeRadius = 20,
    subNodeRadius = nodeRadius / 2,
    k = Math.sqrt(12 / (width * height));

var color = d3.scale.category20();

var force = d3.layout.force()
    .linkDistance(function(d) {
      if (d.type && d.type === 'property') {
        return 10;
      }
      else {
        return 100;
      }
    })
    .charge(-10 / k)
    .gravity(100 * k)
    .size([width, height])

var svg = d3.select("body").append("svg:svg")
    .attr("width", width)
    .attr("height", height)
    .attr("pointer-events", "all")
    .append('svg:g')
      .call(d3.behavior.zoom().on("zoom", redraw))
    .append('svg:g');

svg.append('svg:rect')
    .attr('width', width)
    .attr('height', height)
    .attr('fill', 'white');

function redraw() {
  console.log("here", d3.event.translate, d3.event.scale);
  svg.attr("transform",
      "translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")");
}

d3.json("categories.json", function(error, graph) {
  // Break out property nodes
  var people = _.where(graph.nodes, {type: 'person'}),
      nodes = people;

  _.each(people, function(p) {
    _.each(p, function(prop, _k) {
      if (_k !== 'name' && _k !== 'Name' && _k !== 'id' && _k !== 'type') {
        nodes.push({
          target: p.id,
          type: 'property',
          name: _k,
          value: prop
        });
      }
    });
  });

  force.nodes(nodes);

  // Dynamically build links based on shared attributes
  var links = [];
  var _nodes = nodes.slice();  // copy
  _.each(nodes, function(n, i) {
    if (n.type === "person") {
      _nodes.shift();
      var t = i,
          matchKey,
          matchValue;
          //console.log(n);
      for (var key in n) {
        //console.log(n[key]+"THAT");
        if (key === 'type' || key === 'target') {
          continue;
        }
        var val = n[key];
        //console.log(val + "THIS");
        //should likely use _.filter()  as opposed to _.find... 
        t = _.filter(_nodes, function(_n) {

          //console.log(val);
          return _n[key] === val;
        });

        if (t) {
          //matchKey = key;
          //matchValue = val;
          //break;
        }
      }

      console.log(t);

        _.each(t, function(tv,tk) {//ITERATES OVER OBJECT COLLECTION

            for(var tk2 in tv){ //ITERATES OVER EACH KEY VAL PAIR IN OBJECT.
                if(tk2 ==='type' || tk2 ==='Likes Food'){
                    continue;
                }
                var val2 = tv[tk2];

                at = _.find(t, function(_at){
                    return _at[tk2] === val2;
                });
                //console.log(at+"THIS IS AT");
                if(at){

                    //console.log(tk2+":"+tv[tk2]);
                    var matchkey2 = tk2; 
                    var matchval2 = val2;
                    console.log(matchkey2+":"+matchval2);
                    var uniqat = _.clone(at);
                    delete uniqat[matchkey2];
                    //delete uniqat.name;
                    //delete uniqat['Name'];

                      links.push(_.extend({
          source: tk,
          target: (at && at.id) || tk,
          matchKey: matchkey2,
          matchValue: matchval2
        }, {props: uniqat}));

                }
            }


        });






    }
    else {
      links.push(_.extend(n, {
        source: i
      }));
    }

  });

  force.links(links);
  force.start();

  var link = svg.selectAll(".link")
      .data(links)
    .enter().append("g")
      .attr('class', function(d) {
        if (d.props) {
          return 'person link';
        }
        else return 'property link';
      });

  var line = link.append('line')
      .style("stroke-width", 1);

  var matchCirlce = svg.selectAll('.link.person')
      .append('circle')
        .attr('r', subNodeRadius)
        .style('fill', '#000');

  var text = svg.selectAll('.link.person')
      .append("text")
        .attr('dy', '.35em')
        .attr('text-anchor', 'middle')
        .text(function(d) {
          if (d.matchValue === true || d.matchValue === false) {
            return d.matchKey;
          }
          else {
            return d.matchKey+ ": " + d.matchValue;
          }
        });

  var node = svg.selectAll(".node")
      .data(nodes)
    .enter()
      .append('g')
      .attr("class", function(d) {
        if (d.type && d.type === 'person') {
          return 'node person'
        }
        else {
          return 'node property';
        }
      })
      .call(force.drag);

  var circle = node.append("circle")
      .attr("r", function(d) {
        if (d.type && d.type === 'person') {
          return nodeRadius;
        }
        else {
          return subNodeRadius;
        }
      })
      .style('fill', function(d) {
        if (d.type && d.type === 'person') {
          return color(d.id);
        }
        else {
          return '#eee'
        }
      });

  node.append("text")
      .attr('dy', '.35em')
      .attr('text-anchor', 'middle')
      .text(function(d) {
        if (d.type && d.type === 'person') {
          return d.name;
        }
        else {
          if (d.value === true || d.value === false) {
            return d.name;
          }
          else return d.value;
        }
      });

  force.on("tick", function() {

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

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

    text.attr("transform", translateBetween);
    matchCirlce.attr("transform", translateBetween);

  });

});

function translateBetween(d) {
  var x1 = d.source.x;
  var y1 = d.source.y;
  var x2 = d.target.x;
  var y2 = d.target.y;
  return "translate(" + (x1 + x2) / 2 + "," + (y1 + y2) / 2 + ")";
}
Community
  • 1
  • 1
user2355051
  • 605
  • 1
  • 8
  • 26
  • So you want to have links between all related entities instead of just the first one for each? I'm not quite sure I understand how that would be different. What would that look like? – Lars Kotthoff Oct 18 '13 at 20:48
  • Lars, fairly straight forward o-o (current) vs o=o (my question). If there are more nodes for each entity that have something in common, it would make sense to have them to be displayed. Imagine if a relational database simply matched joins that way, that's why we can , and should, specify. The connections are important, I'm looking for that visualization. From the looks of it, the _.filter would build an object, then something would need to be done in order to loop through and structure all the matches. _.find stops at the first match. – user2355051 Oct 18 '13 at 22:56
  • Ok, so you want to have (potentially) several connections between two nodes? You should be able to simply replace `_find` with `_filter` to get all matches. – Lars Kotthoff Oct 19 '13 at 10:58
  • I'm looking for help with the logic to do that... I understand _.filter will obtain all the matches. Any advice would be appreciated. – user2355051 Oct 19 '13 at 16:51
  • Basically you would need to replace the `if(t)` after the loop with the `_find` in it with a loop that iterates over all the found elements. – Lars Kotthoff Oct 19 '13 at 17:06
  • Lars, I modified the script as suggested and put a live example up. Still can't seem to pinpoint what I'm doing wrong here. – user2355051 Oct 21 '13 at 15:02
  • It may be easier if you start by generating (and verifying) the data structure only without drawing it at the same time. – Lars Kotthoff Oct 21 '13 at 15:06
  • This is where I'm stuck... I've followed the logic recommendations, but I'm not quite sure that I'm doing it properly. – user2355051 Oct 21 '13 at 15:15
  • You can print debug output after each step to make sure that you're doing it properly. – Lars Kotthoff Oct 21 '13 at 15:18

0 Answers0