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>
Related
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/
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>
I am trying to do combine pie chart with force network in D3. I currently have the force network working (shown below). I want to turn each bubble into a pie chart. Is that possible since they have different radius. Any general methods would be appreciated. Thanks!
This is the current javascript code:
d3.json("data/d3data.json", function(error, graph) {
var new_nodes = convert(graph.nodes);
force
.nodes(new_nodes)
// .links(graph.links)
.start();
var root = new_nodes[0];
root.fixed = true;
var loading = svg.append("text")
.attr("x", width / 2)
.attr("y", height / 2)
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text("Simulating. One moment pleaseā¦");
var node = svg.selectAll("svg")
.data(new_nodes)
.enter().append("svg")
.attr("width", function(d) { return Math.sqrt(d.followersCount/100)*2;})
.attr("height", function(d) { return Math.sqrt(d.followersCount/100)*2;});
var g = node.append("g")
.attr("transform", function(d) { return "translate(" + d.r + "," + d.r + ")"});
var g_2 = node.selectAll("g")
.data(function(d) {
console.log(pie(d.FFratio));
return pie(d.FFratio);})
.enter() .append("g");
g_2.append("path")
.attr("d", d3.svg.arc().outerRadius(10))
.style("fill", function (d, i) {
return color(i);
})
function tick(){
var node_x = 0;
var node_y = 0;
node.attr("x", function(d) { node_x = d.x; return d.x; })
.attr("y", function(d) { node_y = d.y;return d.y; });
}
loading.remove();
force.on("tick", function ticky(e){
var q = d3.geom.quadtree(graph.nodes),
i = 0,
n = graph.nodes.length;
while (++i < n) q.visit(collide(graph.nodes[i]));
tick();
});
function convert(node_list){
var result = [];
var current_node = {};
count = 0;
while (++count<node_list.length){
current_node = node_list[count];
current_node.r = Math.sqrt(current_node.followersCount/100);
var followingR = current_node.followingCount/(current_node.followersCount+current_node.followingCount)*100;
var followerR = 1 - followingR;
current_node.FFratio = [followingR, followerR];
result.push(current_node);
};
return result;
};
function collide(node) {
var node_r = Math.sqrt(node.followersCount/100),
r = node_r + 100,
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_r + Math.sqrt(quad.point.followersCount/100);
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;
};
};
});
Here's an example I just coded up based off a clustered force layout.
The key is to replace the force clustered circle with a g that you can then loop and build pie charts inside of:
var pies = svg.selectAll(".pie")
.data(nodes)
.enter().append("g")
.attr("class","pie")
.call(force.drag);
pies.each(function(d,i){
var pieG = d3.select(this);
var arc = d3.svg.arc()
.outerRadius(d.radius)
.innerRadius(0);
var pie = d3.layout.pie()
.sort(null)
.value(function(d) {
return d;
});
var data = [Math.random(), Math.random(), Math.random(), Math.random()];
var g = pieG.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.attr("fill", function(d,i){
return colors(i);
})
});
Produces this:
I'm new to D3 and have built the following visualisation based on the collision-detection visualisation example on the D3js.io website (http://bl.ocks.org/mbostock/3231298).
I'd like to be able to specify the size and colour of each of the 10 balls in my graph but I don't understand how to do that. At the moment, the code is relying on randomly generated size and the balls are all black. Can someone please explain how I can make these changes?
Ideally, I'd like to be able to specify hex codes and a specific px-width for each of the 10 balls.
My code is pasted below, and is also on Codepen: http://codepen.io/msummers40/pen/LiBmr
Thank you, in advance, for your assistance.
Matt
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var width = 500,
height = 400;
var nodes = d3.range(11).map(function() { return {radius: Math.random() * 33 + 4}; }),
root = nodes[0],
color = d3.scale.category20();
root.radius = 80;
root.fixed = true;
var force = d3.layout.force()
.gravity(0.05)
.charge(function(d, i) { return i ? 0 : -2000; })
.nodes(nodes)
.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) { return d.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();
});
function collide(node) {
var r = node.radius + 1,
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>
</html>
The radius and fill colour for the circles are set in this code (specifically the last two lines):
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 % 3); });
The radius comes directly from the data, where it is generated like this:
{radius: Math.random() * 33 + 4}
To set a specific radius per circle, change what is returned there. If you want to specify explicitly the radii, have some static data like this:
var nodes = [{radius: 1}, {radius: 2}, ...];
The same works for the fill colour. You could add another attribute to the data to specify the colour (it looks like you're trying to do this already):
var nodes = [{radius: 1, color: "red"}, {radius: 2, color: "blue"}, ...];
You almost have it, but the style method is not ok. You should change to attr('fill'), and then use your color scale:
svg.selectAll("circle")
.data(nodes.slice(1))
.enter().append("circle")
.attr("r", function(d) { return d.radius; })
.attr("fill", function(d) { return color(d.index); });
And you can do exaclty the same for size or whatever
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.