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