Reorganize d3 cluster-force layout based on dynamic radii - javascript

I initialize my cluster force diagram with even sized radii for every circle. The radii of the circles get updated when the user presses a button on my site. I would like the clusters to reorganize with the larger nodes in the center. Right now I can get the radii to change but they don't reorganize and the nodes often overlap. I've tried messing with charge and gravity. Any help would be greatly appreciated.
Here are some images to illustrate what I'm trying to do.
Initialized nodes:
After radii are changed:
More or less the behavior I'd like to see:
Any help would be greatly appreciated! Thank you.
<script>
var width = document.getElementById("d3_ex").clientWidth,
height = 600
padding = 3, // separation between same-color circles
clusterPadding = 6, // separation between different-color circles
maxRadius = 5;
var n = {{items[0].collection_size}}, // total number of circles
m = {{items[0].n_clusters}}, // number of distinct clusters
data = {{items[0].data|safe}},
heroNode = {{items[0].hero_id}};
var color = d3.scale.category10()
.domain(d3.range(m));
// The largest node for each cluster.
var clusters = new Array(m);
var nodes = data.map(function(d) {
var i = d.cluster,
r = d.radius * maxRadius + 1,
t = d.art_title,
id = d.id_,
d = {"cluster": i, "radius": r, "id_":id};
if (!clusters[i] || (r > clusters[i].radius)) clusters[i] = d;
return d;
});
var svg = d3.select("#d3_ex").append("svg")
.attr("width", width)
.attr("height", height);
var force = d3.layout.force()
.nodes(nodes)
.size([width, height])
.gravity(0.02)
.charge(function(d, i) {-Math.pow(data[i].radius, 2.0) / 8} )
.on("tick", tick);
var circles = svg.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("r", function(d) { return d.radius; })
.attr("id_", function(d) {return d.id_; })
.attr("url", function(d) {return d.url; })
.call(force.start)
.call(force.drag);
function updateData(json) {
var data = json['0']['data']
var nodes = data.map(function(d) {
var i = d.cluster,
r = d.radius + 1,
t = d.art_title,
id = d.id_,
u = d.url,
p = d.retail_price,
m = d.medium,
w = d.art_width,
h = d.art_height,
d = {"cluster": i, "radius": r, "id_":id};
if (!clusters[i] || (r > clusters[i].radius)) clusters[i] = d;
return d;
});
var force = d3.layout.force()
.nodes(nodes)
.size([width, height])
.gravity(0.05)
.charge(function(d, i) {-Math.pow(data[i].radius, 2.0) / 8} )
.on("tick", tick);
circles
.transition()
.duration(1000)
.attr('r', function(d, i) {return data[i].radius * maxRadius})
.call(force.start)
.call(force.drag);
}
$("#like_button").click(function() {
var art_id = this.getAttribute("data-art")
console.log('hi')
$.ajax({
type: "GET",
url: "/likes/" + art_id,
success: function(results) {
updateData(results.items)
console.log('success')
}
})
})
function tick(e) {
circles
.each(cluster(10 * e.alpha * e.alpha))
.each(collide(.5))
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}
// Move d to be adjacent to the cluster node.
function cluster(alpha) {
return function(d) {
var cluster = clusters[d.cluster];
if (cluster === d) return;
var x = d.x - cluster.x,
y = d.y - cluster.y,
l = Math.sqrt(x * x + y * y),
r = d.radius + cluster.radius;
if (l != r) {
l = (l - r) / l * alpha;
d.x -= x *= l;
d.y -= y *= l;
cluster.x += x;
cluster.y += y;
}
};
}
// Resolves collisions between d and all other circles.
function collide(alpha) {
var quadtree = d3.geom.quadtree(nodes);
return function(d) {
var r = d.radius + maxRadius + Math.max(padding, clusterPadding),
nx1 = d.x - r,
nx2 = d.x + r,
ny1 = d.y - r,
ny2 = d.y + r;
quadtree.visit(function(quad, x1, y1, x2, y2) {
if (quad.point && (quad.point !== d)) {
var x = d.x - quad.point.x,
y = d.y - quad.point.y,
l = Math.sqrt(x * x + y * y),
r = d.radius + quad.point.radius + (d.cluster === quad.point.cluster ? padding : clusterPadding);
if (l < r) {
l = (l - r) / l * alpha;
d.x -= x *= l;
d.y -= y *= l;
quad.point.x += x;
quad.point.y += y;
}
}
return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
});
};
}
</script>

Related

D3.js Error: <g> attribute transform: Expected number, "translate(NaN,NaN)"

I am trying to build a data visualization with d3 and I am running into the following error in my console.
Error: <g> attribute transform: Expected number, "translate(NaN,NaN)".
I have taken inspiration from this Plunker and tweaked a few things to make it compatible with my own data.
http://plnkr.co/edit/FiUrNHNMAXd7EBpr5uY5?p=preview&preview
Any help would be immensely appreciated.
My index.html is exactly the same.
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="style.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.9/d3.js"></script>
</head>
<body>
<script src="script.js"></script>
</body>
</html>
Here is my script.js
var width = 960,
height = 500,
padding = 1.5, // separation between same-color nodes
clusterPadding = 6, // separation between different-color nodes
maxRadius = 100;
minRadius = 1;
d3.csv("modified_data_happy.csv", function(data) {
m = d3.max(data, function(d){return d.city_index});
console.log("m", m);
color = d3.scale.category10()
.domain(d3.range(m));
clusters = new Array(m);
dataset = data.map(function(d) {
var r = parseInt(d.Happy);
// var r = d.Happy;
var dta = {
cluster: d.city_index,//group
name: d.name,//label
radius: r,//radius
x: Math.cos(d.city_index / m * 2 * Math.PI) * 100 + width / 2 + Math.random(),
y: Math.sin(d.city_index / m * 2 * Math.PI) * 100 + height / 2 + Math.random()
};
//add the one off the node inside teh cluster
if (!clusters[d.city_index] || (d.Happy > clusters[d.city_index].radius))
clusters[d.city_index] = dta;
console.log("dta", dta);
return dta;
});
console.log('dataset', dataset);
makeGraph(dataset);
});
function makeGraph(nodes) {
var force = d3.layout.force()
.nodes(nodes)
.size([width, height])
.gravity(.02)
.charge(0)
.on("tick", tick)
.start();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var node = svg.selectAll("circle")
.data(nodes)
.enter().append("g").call(force.drag);
node.append("circle")
.style("fill", function(d) {
return color(d.cluster);
}).attr("r", function(d) {
return d.Happy
})
node.append("text")
.text(function(d) {
return d.name;
})
.attr("dx", -10)
.attr("dy", ".35em")
.text(function(d) {
return d.name
})
.style("stroke", "none");
function tick(e) {
node.each(cluster(10 * e.alpha * e.alpha))
.each(collide(.5))
// .attr("transform", functon(d) {});
.attr("transform", function(d) {
var k = "translate(" + d.x + "," + d.y + ")";
return k;
})
}
// Move d to be adjacent to the cluster node.
function cluster(alpha) {
return function(d) {
var cluster = clusters[d.cluster];
if (cluster === d) return;
var x = d.x - cluster.x,
y = d.y - cluster.y,
l = Math.sqrt(x * x + y * y),
r = d.Happy + cluster.radius;
if (l != r) {
l = (l - r) / l * alpha;
d.x -= x *= l;
d.y -= y *= l;
cluster.x += x;
cluster.y += y;
}
};
}
// Resolves collisions between d and all other circles.
function collide(alpha) {
var quadtree = d3.geom.quadtree(nodes);
return function(d) {
var r = d.Happy + maxRadius + Math.max(padding, clusterPadding),
nx1 = d.x - r,
nx2 = d.x + r,
ny1 = d.y - r,
ny2 = d.y + r;
quadtree.visit(function(quad, x1, y1, x2, y2) {
if (quad.point && (quad.point !== d)) {
var x = d.x - quad.point.x,
y = d.y - quad.point.y,
l = Math.sqrt(x * x + y * y),
r = d.Happy + quad.point.radius + (d.cluster === quad.point.cluster ? padding :
clusterPadding);
if (l < r) {
l = (l - r) / l * alpha;
d.x -= x *= l;
d.y -= y *= l;
quad.point.x += x;
quad.point.y += y;
}
}
return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
});
};
}
}
My csv file looks like this (I replaced 'group' with 'city_index, and 'radius' with 'Happy'). That's it.
index | name | city_index | Happy -----> Header Row
0 | Joe | 3 | 60 -----> Data Row
So that's where I am right now. I'm not sure why it's not working as the data structures I replaced the example with are the same. I tried removing the parser to no avail. I also tried modifying the "transform, translate" properties as well.
Any help would be immensely appreciated.
The problem indicates an SVG element group ( tag ) has NaN for position coordinates.
I see a problem maybe here in your code : .attr("dy", ".35em")
Try to get rid of the em value and use raw numbers.
I'm not certain it will solve your problem though, if you could post the error in the console and the element it applies to I could help you more.

How create legend for bubble chart in d3? Legend not showing up

My aim is to add a legend to Clustered Bubble Chart based on the color of a cluster. The way that I did has no results.
In my CSV file, I created 5 clustered with different colors. In fact, I want to differentiate each cluster by name and color.
The code does not have any errors but nothing showing up. Can someone take look at it and tell what is wrong with it? Do you have any other suggestions to add a legend to the bubble chart?
<!DOCTYPE html>
<meta charset="utf-8">
<style type="text/css">
text {
font: 10px sans-serif;
}
circle {
stroke: #565352;
stroke-width: 1;
}
</style>
<body>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script>
var width = 1000,
height = 1000,
padding = 1.5, // separation between same-color nodes
clusterPadding = 6, // separation between different-color nodes
maxRadius = 65;
var color = d3.scale.ordinal()
.range(["#5499C7", "#8E44AD", "#138D75", "#F1C40F", "#D35400"]);
d3.text("word_groups.csv", function(error, text) {
var legendRectSize = 18;
var legendSpacing = 4;
if (error) throw error;
var colNames = "text,size,group\n" + text;
var data = d3.csv.parse(colNames);
data.forEach(function(d) {
d.size = +d.size;
});
//unique cluster/group id's
var cs = [];
data.forEach(function(d){
if(!cs.contains(d.group)) {
cs.push(d.group);
}
});
var n = data.length, // total number of nodes
m = cs.length; // number of distinct clusters
//create clusters and nodes
var clusters = new Array(m);
var nodes = [];
for (var i = 0; i<n; i++){
nodes.push(create_nodes(data,i));
}
var force = d3.layout.force()
.nodes(nodes)
.size([width, height])
.gravity(.02)
.charge(0)
.on("tick", tick)
.start();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var node = svg.selectAll("circle")
.data(nodes)
.enter().append("g").call(force.drag);
var legend = svg.selectAll('.legend')
.data(color.domain())
.enter()
.append('g')
.attr('class', 'legend')
.attr('transform', function(d, i) {
var height = legendRectSize + legendSpacing;
var offset = height * color.domain().length / 2;
var horz = -2 * legendRectSize;
var vert = i * height - offset;
return 'translate(' + horz + ',' + vert + ')';
});
legend.append('rect')
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style('fill', color)
.style('stroke', color);
legend.append('text')
.attr('x', legendRectSize + legendSpacing)
.attr('y', legendRectSize - legendSpacing)
.text(function(d) { return "Hans"; });
node.append("circle")
.style("fill", function (d) {
return color(d.cluster);
})
.attr("r", function(d){return d.radius})
node.append("text")
.attr("dy", ".3em")
.style("text-anchor", "middle")
.text(function(d) { return d.text.substring(0, d.radius / 3); });
function create_nodes(data,node_counter) {
var i = cs.indexOf(data[node_counter].group),
r = Math.sqrt((i + 1) / m * -Math.log(Math.random())) * maxRadius,
d = {
cluster: i,
radius: data[node_counter].size*1.5,
text: data[node_counter].text,
x: Math.cos(i / m * 2 * Math.PI) * 200 + width / 2 + Math.random(),
y: Math.sin(i / m * 2 * Math.PI) * 200 + height / 2 + Math.random()
};
if (!clusters[i] || (r > clusters[i].radius)) clusters[i] = d;
return d;
};
function tick(e) {
node.each(cluster(10 * e.alpha * e.alpha))
.each(collide(.5))
.attr("transform", function (d) {
var k = "translate(" + d.x + "," + d.y + ")";
return k;
})
}
// Move d to be adjacent to the cluster node.
function cluster(alpha) {
return function (d) {
var cluster = clusters[d.cluster];
if (cluster === d) return;
var x = d.x - cluster.x,
y = d.y - cluster.y,
l = Math.sqrt(x * x + y * y),
r = d.radius + cluster.radius;
if (l != r) {
l = (l - r) / l * alpha;
d.x -= x *= l;
d.y -= y *= l;
cluster.x += x;
cluster.y += y;
}
};
}
// Resolves collisions between d and all other circles.
function collide(alpha) {
var quadtree = d3.geom.quadtree(nodes);
return function (d) {
var r = d.radius + maxRadius + Math.max(padding, clusterPadding),
nx1 = d.x - r,
nx2 = d.x + r,
ny1 = d.y - r,
ny2 = d.y + r;
quadtree.visit(function (quad, x1, y1, x2, y2) {
if (quad.point && (quad.point !== d)) {
var x = d.x - quad.point.x,
y = d.y - quad.point.y,
l = Math.sqrt(x * x + y * y),
r = d.radius + quad.point.radius + (d.cluster === quad.point.cluster ? padding : clusterPadding);
if (l < r) {
l = (l - r) / l * alpha;
d.x -= x *= l;
d.y -= y *= l;
quad.point.x += x;
quad.point.y += y;
}
}
return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
});
};
}
});
Array.prototype.contains = function(v) {
for(var i = 0; i < this.length; i++) {
if(this[i] === v) return true;
}
return false;
};
</script>
The color.domain array is empty when you join it with the .legend selection, so no 'g' elements are appended.
The color.domain array is populated later in your code, when you append the circles to your nodes selection.
If you switch the order, then the legend items are created:
var node = svg
.selectAll('circle')
.data(nodes)
.enter()
.append('g')
.call(force.drag)
////MOVED BEFORE THE LEGEND CODE
node
.append('circle')
.style('fill', function (d) {
return color(d.cluster)
})
.attr('r', function (d) {
return d.radius
})
var legend = svg
.selectAll('.legend')
.data(color.domain())
.enter()
.append('g')
.attr('class', 'legend')
.attr('transform', function (d, i) {
var height = legendRectSize + legendSpacing
var offset = height * color.domain().length / 2
var horz = -2 * legendRectSize
var vert = i * height - offset
return 'translate(' + horz + ',' + vert + ')'
})
legend
.append('rect')
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style('fill', color)
.style('stroke', color)
legend
.append('text')
.attr('x', legendRectSize + legendSpacing)
.attr('y', legendRectSize - legendSpacing)
.text(function (d) {
return 'Hans'
})
PS: Some of the legend items are currently being translated off the SVG view, so your horz and vert variables need looking at.

After a few clicks the animation freezes, why?

I have the following code, which I took and modify from here
Here is my jsfiddle in action
I am currently developing a user interaction that needs these bubbles, exactly 5 bubbles that gravitate to the center of the screen. The thing is that I know how many times a user will click on each of these bubbles. The thing I noticed is that at some point the bubbles keep growing but the collision among them will stop working.
Here you can see the code that modify:
var width = 500,
height = 500,
padding = 1.5, // separation between same-color circles
clusterPadding = 4, // separation between different-color circles
maxRadius = 40;
var n = 5, // total number of circles
m = 1; // number of distinct clusters
var color = d3.scale.category10()
.domain(d3.range(m));
// The largest node for each cluster.
var clusters = new Array(m);
var nodes = d3.range(n).map(function() {
var i = Math.floor(Math.random() * m),
//r = Math.sqrt((i + 1) / m * -Math.log(Math.random())) * maxRadius,
r = maxRadius,
d = {cluster: i, radius: r};
if (!clusters[i] || (r > clusters[i].radius)) clusters[i] = d;
return d;
});
// Use the pack layout to initialize node positions.
d3.layout.pack()
.sort(null)
.size([width, height])
.children(function(d) { return d.values; })
.value(function(d) { return d.radius * d.radius; })
.nodes({values: d3.nest()
.key(function(d) { return d.cluster; })
.entries(nodes)});
var force = d3.layout.force()
.nodes(nodes)
.size([width, height])
.gravity(.02)
.charge(0)
.on("tick", tick)
.start();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var node = svg.selectAll("circle")
.data(nodes)
.enter().append("circle")
.style("fill", function(d) { return color(d.cluster); })
.on("click", function(d) {
d.radius *= 1.1;
d3.select(this).attr("r", d.radius);
})
.call(force.drag);
node.transition()
.duration(750)
.delay(function(d, i) { return i * 5; })
.attrTween("r", function(d) {
var i = d3.interpolate(0, d.radius);
return function(t) { return d.radius = i(t); };
});
function tick(e) {
node
.each(cluster(10 * e.alpha * e.alpha))
.each(collide(.5))
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}
// Move d to be adjacent to the cluster node.
function cluster(alpha) {
return function(d) {
var cluster = clusters[d.cluster];
if (cluster === d) return;
var x = d.x - cluster.x,
y = d.y - cluster.y,
l = Math.sqrt(x * x + y * y),
r = d.radius + cluster.radius;
if (l != r) {
l = (l - r) / l * alpha;
d.x -= x *= l;
d.y -= y *= l;
cluster.x += x;
cluster.y += y;
}
};
}
// Resolves collisions between d and all other circles.
function collide(alpha) {
var quadtree = d3.geom.quadtree(nodes);
return function(d) {
var r = d.radius + maxRadius + Math.max(padding, clusterPadding),
nx1 = d.x - r,
nx2 = d.x + r,
ny1 = d.y - r,
ny2 = d.y + r;
quadtree.visit(function(quad, x1, y1, x2, y2) {
if (quad.point && (quad.point !== d)) {
var x = d.x - quad.point.x,
y = d.y - quad.point.y,
l = Math.sqrt(x * x + y * y),
r = d.radius + quad.point.radius + (d.cluster === quad.point.cluster ? padding : clusterPadding);
if (l < r) {
l = (l - r) / l * alpha;
d.x -= x *= l;
d.y -= y *= l;
quad.point.x += x;
quad.point.y += y;
}
}
return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
});
};
}
That behaviour you described happens because the force simulation has ended. So, the simplest solution is reheating the force at every click:
.on("click", function(d) {
d.radius *= 1.1;
d3.select(this).attr("r", d.radius);
force.resume();
})
Here, force.resume():
Sets the cooling parameter alpha to 0.1. This method sets the internal alpha parameter to 0.1, and then restarts the timer. Typically, you don't need to call this method directly; it is called automatically by start. It is also called automatically by drag during a drag gesture.
Here is your updated fiddle: https://jsfiddle.net/usb7nhfm/

Adding text to circle nodes d3

I am trying to add text to some circle nodes but I am having trouble doing so.
Here is the code for the main file. I am trying to create a second set of nodes called namenodes that contain text and then I am trying to attach namenodes to the original nodes that contain the circles. I'm not sure if this is the correct approach for this problem. When ever I run the code below, a bunch of black circles appear in the top left corner. I don't care if all of the nodes say the same thing but I would at least like to have some text appear. I think that the problem lies within the line
var namenodes = d3.range(200).map(function() { return {name: "hello"};}),
but I am not sure. It compiles without errors but it's not doing what I want. Any insight would be appreciated.
var nodes = d3.range(200).map(function() {
return {
radius: Math.random() * 12 + 4
};
}),
root = nodes[0],
color = d3.scale.category10();
//my code
var namenodes = d3.range(200).map(function() { return {name: "hello"};}),
nameroot = namenodes[0],
color = "black";
root.radius = 0;
root.fixed = true;
nameroot.radius = 0;
nameroot.fixed = true;
var force = d3.layout.force()
.gravity(0.05)
.charge(function(d, i) {
return i ? 0 : -2000;
})
.nodes(nodes)
.size([width, height]);
// var force = d3.layout.force()
//.gravity(0.05)
//.charge(function(d, i) {
//.nodes(namenodes)
//.size([width, height]);
force.start();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
svg.selectAll("circle")
.data(nodes.slice(1))
.enter().append("circle")
.attr("r", function(d) {
return d.radius;
})
.style("fill", function(d, i) {
return color(i % 50);
});
//my code
//svg.selectAll("names")
//.data(namenodes.slice(1))
//.enter().append("names")
//.style(color);
force.on("tick", function(e) {
var q = d3.geom.quadtree(nodes),
i = 0,
n = nodes.length;
while (++i < n) q.visit(collide(nodes[i]));
svg.selectAll("circle")
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
});
});
svg.on("mousemove", function() {
var p1 = d3.mouse(this);
root.px = p1[0];
root.py = p1[1];
//force.resume();
});
svg.on("click", function() {
if (force.alpha()) {
force.stop();
} else {
force.resume();
}
});
svg.selectAll("circle").on("click", function() {
d3.event.stopPropagation();
this.remove();
});
function collide(node) {
var r = node.radius + 16,
nx1 = node.x - r,
nx2 = node.x + r,
ny1 = node.y - r,
ny2 = node.y + r;
return function(quad, x1, y1, x2, y2) {
if (quad.point && (quad.point !== node)) {
var x = node.x - quad.point.x,
y = node.y - quad.point.y,
l = Math.sqrt(x * x + y * y),
r = node.radius + quad.point.radius;
if (l < r) {
l = (l - r) / l * .5;
node.x -= x *= l;
node.y -= y *= l;
quad.point.x += x;
quad.point.y += y;
}
}
return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
};
}
</script>

D3 Force Graph - Fixed Nodes that Don't Overlap

I am looking to develop a viz that consists of a node link graph. I have a series of points whose position I don't want to change unless there is a collision (one node on another) on the graph. In case of collided nodes, I want to space them so that they don't overlap. My JS code is as below
var chartWidth = 200;
var chartHeight = 200;
var widthPadding = 40;
var heightPadding = 40;
var link, node;
$(function(){
initialize();
});
function initialize() {
var jsonString = '{"nodes":[{"x":40,"y":64,"r":6,"fixed":true},{"x":40,"y":63,"r":6,"fixed":true},{"x":119,"y":53,"r":6,"fixed":true},{"x":119,"y":73,"r":6,"fixed":true},{"x":137,"y":73,"r":6,"fixed":true},{"x":140,"y":140,"r":6,"fixed":true},{"x":68,"y":57,"r":6,"fixed":true},{"x":70,"y":75,"r":6,"fixed":true},{"x":51,"y":59,"r":6,"fixed":true},{"x":51,"y":54,"r":6,"fixed":true},{"x":137,"y":40,"r":6,"fixed":true}],"links":[{"source":0,"target":1},{"source":1,"target":2},{"source":2,"target":3},{"source":3,"target":4},{"source":4,"target":5},{"source":0,"target":1},{"source":1,"target":6},{"source":6,"target":7},{"source":7,"target":4},{"source":4,"target":5},{"source":0,"target":1},{"source":1,"target":8},{"source":8,"target":9},{"source":9,"target":10},{"source":10,"target":5}]}';
drawForceDirectedNodeLink($.parseJSON(jsonString));
}
function drawForceDirectedNodeLink(graph){
var width = chartWidth + (2*widthPadding);
var height = chartHeight + (2*heightPadding);
var q = d3.geom.quadtree(graph.nodes),
i = 0,
n = graph.nodes.length;
while (++i < n) {
q.visit(collide(graph.nodes[i]));
}
var force = d3.layout.force()
.size([width, height])
.gravity(0.05)
.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; })
.attr("r", function(d) { return d.r; });
});
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var link = svg.selectAll(".link"),
node = svg.selectAll(".node");
force
.nodes(graph.nodes)
.links(graph.links)
.start();
link = link.data(graph.links)
.enter().append("line")
.attr("class", "link");
node = node.data(graph.nodes)
.enter().append("circle")
.attr("class", "node");
}
function collide(node) {
var r = node.radius + 16,
nx1 = node.x - r,
nx2 = node.x + r,
ny1 = node.y - r,
ny2 = node.y + r;
return function(quad, x1, y1, x2, y2) {
if (quad.point && (quad.point !== node)) {
var x = node.x - quad.point.x,
y = node.y - quad.point.y,
l = Math.sqrt(x * x + y * y),
r = node.radius + quad.point.radius;
if (l < r) {
l = (l - r) / l * .5;
node.x -= x *= l;
node.y -= y *= l;
quad.point.x += x;
quad.point.y += y;
}
}
return x1 > nx2
|| x2 < nx1
|| y1 > ny2
|| y2 < ny1;
};
}
As you can see, I have tried to implement the collision detection logic mentioned here. But some how I have not been able to get that part work.
Notice that within your jsonString declaration inside of initialize(), each node is being given an r property. However, further down within collide(), you're doing the following:
.attr("r", function(d) { return d.radius - 2; })
Make sure your nodes have a radius property attached to them. If not, the following change should do it:
.attr("r", function(d) { return d.r - 2; })
You can see on line 30 of Mike Bostock's script that his nodes are initially declared with a radius property, as opposed to your r property.
var nodes = d3.range(200).map(function() { return {radius: Math.random() * 12 + 4}; }),
UPDATE
Change node.radius to node.r and quad.point.radius to quad.point.r. And it should work. Looks like it was just a NaN problem.

Categories

Resources