How to draw circles around circular path with D3js - javascript

How to draw circles with random sizes around circular path with D3js, so that small circles will be randomly distributed and not overlapping one with each other.
Here is how it should look:
and here is what i was able to get
jQuery(document).ready(function () {
var angle, offset, data,
size = [8, 15],
width = 500,
color = d3.scale.category10(),
height = 600,
radius = 200,
dispersion = 10,
svgContainer = d3.select('body').append("svg")
.attr("width", width)
.attr("height", height);
data = d3.range(100).map(function () {
angle = Math.random() * Math.PI * 2;
offset = Math.max(size[0], size[1]) + radius + dispersion;
return {
cx : offset + Math.cos(angle) * radius + rand(-dispersion, dispersion),
cy : offset + Math.sin(angle) * radius + rand(-dispersion, dispersion),
r : rand(size[0], size[1])
};
});
svgContainer.selectAll("circle")
.data(data)
.enter().append("circle")
.attr({
r : function (d) {return d.r},
cx : function (d) {return d.cx},
cy : function (d) {return d.cy},
fill : function (d, i) {return color(i % 3)}
});
function rand(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
});
http://jsfiddle.net/yb8bgcrn/1/
UPDATED QUESTION
Is there a way to display it with d3 force layout but without using links ?

I have made some updates to your fiddle and applied collision detection as in the demo I mentioned in the comment. Hope this helps.
var angle, offset, data,
size = [8, 15],
width = 500,
color = d3.scale.category10(),
height = 600,
radius = 200,
dispersion = 10,
svgContainer = d3.select('body').append("svg")
.attr("width", width)
.attr("height", height);
var force = d3.layout.force()
.gravity(0.05)
.charge(function(d, i) {
return i ? 0 : -2000;
})
.distance(500)
.size([width, height]);
data = d3.range(100).map(function() {
angle = Math.random() * Math.PI * 2;
offset = Math.max(size[0], size[1]) + radius + dispersion;
return {
x: offset + Math.cos(angle) * radius + rand(-dispersion, dispersion),
y: offset + Math.sin(angle) * radius + rand(-dispersion, dispersion),
radius: rand(size[0], size[1])
};
});
force
.nodes(data)
.start();
root = data[0],
color = d3.scale.category10();
root.radius = 0;
root.fixed = true;
root.px = 250; //Center x
root.py = 275; //Center y
var nodes = svgContainer.selectAll("circle")
.data(data)
.enter().append("circle")
.attr({
r: function(d) {
return d.radius
},
cx: function(d) {
return d.x
},
cy: function(d) {
return d.y
},
fill: function(d, i) {
return color(i % 3)
}
});
force.on("tick", function(e) {
var q = d3.geom.quadtree(data),
i = 0,
n = data.length;
while (++i < n) q.visit(collide(data[i]));
svgContainer.selectAll("circle")
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
});
});
function rand(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
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 src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
EDIT:
Do you mean something like this?
var angle, offset, data,
size = [8, 15],
width = 500,
color = d3.scale.category10(),
height = 600,
radius = 200,
dispersion = 10,
svgContainer = d3.select('body').append("svg")
.attr("width", width)
.attr("height", height).append("g");
var force = d3.layout.force()
.gravity(0.05)
.charge(function(d, i) {
return i ? -20 : -2000;
})
.distance(500)
.size([width, height]);
data = d3.range(100).map(function() {
angle = Math.random() * Math.PI * 2;
offset = Math.max(size[0], size[1]) + radius + dispersion;
return {
x: offset + Math.cos(angle) * radius + rand(-dispersion, dispersion),
y: offset + Math.sin(angle) * radius + rand(-dispersion, dispersion),
radius: rand(size[0], size[1])
};
});
force
.nodes(data)
.start();
root = data[0],
color = d3.scale.category10();
root.radius = 0;
root.fixed = true;
root.px = 250; //Center x
root.py = 275; //Center y
var nodes = svgContainer.selectAll("circle")
.data(data)
.enter().append("circle")
.attr({
r: function(d) {
return d.radius
},
cx: function(d) {
return d.x
},
cy: function(d) {
return d.y
},
fill: function(d, i) {
return color(i % 3)
}
});
var rotation = 0;
setInterval(function(){
if(force.alpha()==0){
if(!rotation)
rotation = Math.random() * 50;
else
rotation = rotation+1;
svgContainer.attr("transform","rotate("+rotation+", "+(width/2)+","+(height/2)+")");
}
//force.theta(0.5);
},250);
force.on("tick", function(e) {
var q = d3.geom.quadtree(data),
i = 0,
n = data.length;
while (++i < n) q.visit(collide(data[i]));
svgContainer.selectAll("circle")
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
});
});
function rand(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
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 src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

Yes, you can achieve this by force layout.
The idea is to keep all the links display none and center node display none none.
something like this:
nodeEnter.append("circle")
.style("display", function (d) {
return d.children ? "none" : ""; //ceneter node has children thus display will be none
})
In such a case it will just look like the visualization you want to have.
Working code here
Hope this helps!

I found solution in detecting collision function when generating random coordinates, here is the jsfiddle link and code:
(function () {
var angle, offset, data, x, y, r,
collision, circle1, circle2,
circles = [],
size = [8, 15],
width = 500,
color = d3.scale.category10(),
height = 600,
radius = 130,
dispersion = 10,
svgContainer = d3.select('body').append("svg")
.attr("width", width)
.attr("height", height);
function detectCollision(c2, c1) {
var dx = c1.cx - c2.cx;
var dy = c1.cy - c2.cy;
var rSum = c1.r + c2.r;
return ((Math.pow(dx, 2) + Math.pow(dy, 2)) < Math.pow(rSum, 2));
}
var sh = 2, elements = 55;
data = d3.range(elements).map(function (i) {
do {
// dispersion += i / 50;
angle = Math.random() * Math.PI * 2;
offset = Math.max(size[0], size[1]) + radius + dispersion + (elements/sh);
x = offset + Math.cos(angle) * radius + rand(- (dispersion + i / sh), dispersion + i / sh);
y = offset + Math.sin(angle) * radius + rand(- (dispersion + i / sh), dispersion + i / sh);
r = rand(size[0], size[1]);
circle2 = {cx : x, cy : y, r : r};
collision = false;
if (circles.length > 1) {
circles.forEach(function (d) {
circle1 = {cx : d.cx, cy : d.cy, r : d.r};
if (detectCollision(circle1, circle2)) {
collision = true;
}
});
}
} while (collision);
circles.push(circle2);
return circles[circles.length - 1];
});
svgContainer.selectAll("circle")
.data(data)
.enter().append("circle")
.attr({
r : function (d) {
return d.r
},
cx : function (d) {
return d.cx
},
cy : function (d) {
return d.cy
},
fill : function (d, i) {
return color(i % 3)
}
});
function rand(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
})();

Related

d3 js triangle in svg path

i must create 30 triangles that move away from current mouse position. i try with this code:
var body = d3.select("body");
var mouse = [];
var width = 1000;
var height = 600;
var numberOfTriangles = 30;
var isMouseMoving = false;
var triangle = d3.svg.symbolType["triangle-up"]
function drawTriangles(number) {
for (var i = 0; i < number; i++) {
var dim = Math.random() * 400;
svg.append("path")
.attr("d", triangle.size(dim))
.attr("transform", function(d) {
return "translate(" + Math.random() * width + "," + Math.random() * height + ")";
})
.attr("fill", "rgb(" + parseInt(Math.random() * 255) + "," + parseInt(Math.random() * 255) + "," + parseInt(Math.random() * 255) + ")")
.attr("opacity", 2)
.attr("class", "path" + i);
}
}
function moveMouse() {
if (isMouseMoving) {
svg.selectAll('path').each(function(d, i) {
var self = d3.select(this);
self.attr('transform', function() {
return "translate(" + mouse[0] + "," + mouse[1] + ")";
})
})
}
}
var svg = body.append("svg")
.attr("width", width)
.attr("height", height)
.style("border", "1px solid black")
.on("mousemove", function() {
mouse = d3.mouse(this);
isMouseMoving = true;
});
drawTriangles(numberOfTriangles);
d3.timer(function() {
moveMouse()
});
but i have this error: "Uncaught TypeError: Cannot read property 'size' of undefined at drawTriangles".
Can someone help me? Thanks.
Your error is because of:
var triangle = d3.svg.symbolType["triangle-up"];
If you fix the typo on symbolTypes, this returns undefined. d3.svg.symbolTypes simply returns an array of available symbols, it is not a mechanism to create a new symbol path generator. That said, what you really wanted is:
var triangle = d3.svg.symbol().type("triangle-up");
This creates a proper triangle symbol generator.
Taking this a little further, I'm not sure what you mean by
that move away from current mouse position
Your code does the exact opposite and puts all the triangles on the mouse cursor...
EDITS
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
var width = 300,
height = 300;
var nodes = d3.range(200).map(function() { return {radius: Math.random() * 12 + 4}; }),
root = nodes[0],
color = d3.scale.category10();
root.radius = 0;
root.fixed = true;
var force = d3.layout.force()
.gravity(0.05)
.charge(function(d, i) { return i ? 0 : -1000; })
.nodes(nodes)
.size([width, height]);
force.start();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.style("border", "1px solid black")
.style("margin","20px");
var triangle = d3.svg.symbol().type("triangle-up");
svg.selectAll("path")
.data(nodes.slice(1))
.enter().append("path")
.attr("d", function(d) {
triangle.size(d.radius);
return triangle();
})
.style("fill", function(d, i) { return color(i % 3); });
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("path")
.attr("transform", function(d){
return "translate(" + d.x + "," + 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 + 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;
}
if (node.x > width) node.x = width;
if (node.x < 0) node.x = 0;
if (node.y > height) node.y = height;
if (node.y < 0) node.y = 0;
}
return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
};
}
</script>

Automated collision detection and rectification for a force directed wordcloud in D3.js

I have been experimenting with creating a force directed layout using D3.js. In the following code, I populate a series of objects which are later rendered into text elements. I wish to avoid collision between the words, and adjust their representation on the grid accordingly.
I utilised the code written by Eric Dobbs here http://bl.ocks.org/dobbs/1d353282475013f5c156
but it is still not working for me. The objects end up flying all over the screen. I have spent many hours puzzling over this and I would greatly appreciate any help available.
here is my code
<!DOCTYPE html>
<html>
<head>
<script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</head>
<body>
<script>
//arguments passed in when this class is modularised
var metaDataObject = {"type":"wordcloud","label":"data1","data":"data2","color":"color"};
var dataObject = {"data1":["apple","orange","pear","grapes","mango","papaya","kiwi","banana", "watermelon","strawberry","honeydew","dragonfruit","durian","pineapple","jackfruit","lychee","mangosteen","passionfruit","raspberry","blueberry","rockmelon","coconut","lemon","lime","pomelo","rambutan","aguave","longan","mandarin","calamansi","sugarcane","avocado","bittergourd","wintermelon","dates"],"data2":[100,50,150,40,70,60,30,35,95,120,60,70,80,15,30,140,100,170,200,40,90,20,180,99,66,55,130,20,50,55,100,120,30,20,90],"color":["#23af50"]};
//transform raw data
var frequency_list = transformData(metaDataObject, dataObject);
frequency_list.sort(function(a, b) {
return b.size-a.size;
});
//set cloud container variables
var cloudWidth = 600;
var cloudHeight = 400;
var cloudContainer = d3.select("body").append("svg")
.attr("width", cloudWidth)
.attr("height", cloudHeight);
//set approximate scaling
var largestQty = d3.max(dataObject[metaDataObject.data]);
var rangeCap = cloudWidth*cloudHeight/7500;
var scale = d3.scale.linear()
.domain([0, largestQty])
.range([5, rangeCap]);
var color = d3.scale.linear()
.domain([0,1,2,3,4,5,6,10,15,20,30,largestQty])
.range(["#ddd", "#ccc", "#bbb", "#aaa", "#999", "#888", "#777", "#666", "#555", "#444", "#333", "#222"]);
var words = createText(frequency_list);
var rendered = cloudContainer.selectAll("node")
.data(words)
.enter()
.append("text")
.attr("id", function(d, i) {
return "t"+i;
})
.text(function(d) {
return d.text;
})
.attr("font-family", "sans-serif")
.attr("fill", function(d, i) {
return color(i);
})
.attr("font-size", function(d) {
return scale(d.size)+"px";
})
.on("mouseover", handleMouseOver)
.on("mouseout", handleMouseOut);
var force = d3.layout.force()
.nodes(words)
.size([cloudWidth, cloudHeight])
.charge(-50)
.gravity(0.1)
.on("tick", tick)
.start();
rendered.call(force.drag);
function tick(e) {
var q = d3.geom.quadtree(words);
for(var i=0; i<words.length; i++) {
var word = words[i];
q.visit(collide(word));
}
// console.log(d.x2);
rendered
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; });
}
function collide(node) {
var nx1, nx2, ny1, ny2, padding;
padding = 32;
nx1 = node.x - padding;
nx2 = node.x2 + padding;
ny1 = node.y - padding;
ny2 = node.y2 + padding;
return function(quad, x1, y1, x2, y2) {
var dx, dy;
console.log(node.x2);
if (quad.point && (quad.point !== node)) {
if (overlap(node, quad.point)) {
dx = Math.min(node.x2 - quad.point.x, quad.point.x2 - node.x) / 2;
node.x -= dx;
quad.point.x += dx;
dy = Math.min(node.y2 - quad.point.y, quad.point.y2 - node.y) / 2;
node.y -= dy;
quad.point.y += dy;
}
}
return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
};
};
function overlap(a, b) {
return !(a.x2 < b.x ||
a.x > b.x2 ||
a.y2 < b.y ||
a.y > b.y2);
}
function createText(frequency_list) {
var words = [];
for(var wordIndex=0; wordIndex<frequency_list.length; wordIndex++) {
var word = {
x: Math.random() * (cloudHeight - 40) +20,
y: Math.random() * (cloudWidth -40) +20,
text: frequency_list[wordIndex].text,
size: frequency_list[wordIndex].size,
// x2: word.x + word.text.length * word.size /1.5,
// y2: word.y + scale(word.size) * 1.1
};
word.x2 = word.x + word.text.length * word.size /1.5;
word.y2 = word.y + scale(word.size) * 1.1;
words.push(word);
}
return words;
}
function transformData(metaDataObject, dataObject) {
//To transform data to this format:
// var frequency_list = [{"text":"apple","size":100}, {"text":"orange","size":100}, {"text":"pear","size":25}, {"text":"grapes","size":301}, {"text":"mango","size":56}];
var frequency_list = [];
var wordFieldName = metaDataObject.label;
var valuesFieldName = metaDataObject.data;
var wordList = dataObject[wordFieldName];
var valuesList = dataObject[valuesFieldName];
for(var itemIndex=0; itemIndex<wordList.length; itemIndex++) {
var item = {
text: wordList[itemIndex].toUpperCase(),
size: valuesList[itemIndex]
}
frequency_list.push(item);
}
return frequency_list;
}
function handleMouseOver(d, i) {
d3.select(this).transition().attr({
fill: "black",
"font-size": scale(d.size) + 5 + "px"
});
cloudContainer.append("text").attr({
id: "t" + d.text + "-" + d.size,
x: 10,
y:20
})
.text(function() {
return ["weight: " + d.size];
// return [""+occupiedSpaces[1].top];
})
}
function handleMouseOut(d, i) {
d3.select(this).transition().attr({
fill: color(i),
"font-size": scale(d.size) + "px"
});
d3.select("#t" + d.text + "-" + d.size).remove();
}
</script>
</body>
</html>

Updating data in a Clustered Force Layout D3 bubble Chart

Morning !
I'm working on a bubble chart in D3.js, I'm quite close of what I need,
but I've got a behavior problem when updating data (changing set).
I would like the bubbles to stay in place, and update size and position without the bouncy effect due to colision :(
function convertData(m){
var nodes = m;
nodes.forEach(function(d) {
clusters[d.cluster] = d;
d.radius = Math.ceil(Math.sqrt(d.pop/Math.PI))*10;
});
return nodes;
}
function updateAnnee(annee){
nodes = convertData(annee);
var svg = d3.select("svg")
var node = svg.selectAll("circle")
.data(nodes)
node.transition()
.duration(0)
.attr('r', function(d){ return d.radius});
node
.enter().append("circle")
node
.exit().remove();
var force = d3.layout.force()
.nodes(nodes)
.size([width, height])
.gravity(.02)
.charge(0)
.on("tick", tick)
.start();
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-50; })
}
// 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;
});
};
}
}
What I have so far is viewable here : https://jsfiddle.net/hf998do7/1/
This is based on the work of Mr Bostock http://bl.ocks.org/mbostock/7881887
Thanks

Adding nodes to the existing clustered force layout in d3js

From two days I am trying to add new nodes to existing clustered force layout. I am able to add nodes to existing force and pack layout but gravity and charge force is not applying on newly added nodes and I think 'tick' event call back is also not occurring for newly added nodes. I have attached my code below.
var width = 960,
height = 500,
padding = 1.5, // separation between same-color nodes
clusterPadding = 20, // separation between different-color nodes
maxRadius = 12;
var n = 200, // total number of nodes
m = 10; // 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,
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.
var pack = 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(0.01)
.charge(function(d) {
if(d.radius == clusters[d.cluster].radius) {
return(-10 * d.radius);
}
else {
return(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); })
.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); };
});
//This setInterval function is for adding new node to existing
//force and pack layout for every one second
setInterval(function() {
var i = Math.floor(Math.random() * m),
r = Math.sqrt((i + 1) / m * -Math.log(Math.random())) * maxRadius,
d = {cluster: i, radius: r, depth: 2};
if(d.radius < clusters[d.cluster].radius ) {
nodes.push(d);
}
force.nodes(nodes).start();
var node = svg.selectAll("circle")
.data(nodes)
.enter().append("circle")
.style("fill", function(d) { return color(d.cluster); })
.attr({r: function(d) { return(d.radius); },
cx: function(d) { return(d.x); },
cy: function(d) { return(d.y); },
})
.call(force.drag);
}, 1000);
function tick(e) {
node
.each(cluster(e.alpha * 0.1))
.each(collide(e.alpha * 0.3))
.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 + 10;
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;
});
};
}
I have gone through various documentation from two days and tried everything and I could not able to understand what is going on inside my JavaScript code. Finally I end up here. Please some one help me out.
You were not managing the general update pattern properly.
Here is the correct way to do it..
setInterval(function() {
var i = Math.floor(Math.random() * m),
r = Math.sqrt((i + 1) / m * -Math.log(Math.random())) * maxRadius,
d = {cluster: i, radius: r, depth: 2};
if(d.radius < clusters[d.cluster].radius ) {
nodes.push(d);
}
node = node.data(nodes);
node.enter().append("circle")
.style("fill", function(d) { return color(d.cluster); })
.attr({r: function(d) { return(d.radius); },
cx: function(d) { return(d.x); },
cy: function(d) { return(d.y); },
})
.call(force.drag);
force.start();
}, 1000);
And here is a working version...
function drawAnimation() {
var width = 960,
height = 500,
padding = 1.5, // separation between same-color nodes
clusterPadding = 20, // separation between different-color nodes
maxRadius = 12;
var n = 200, // total number of nodes
m = 10; // 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,
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.
var pack = 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(0.01)
.charge(function(d) {
if(d.radius == clusters[d.cluster].radius) {
return(-10 * d.radius);
}
else {
return(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); })
.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); };
});
setInterval(function() {
var i = Math.floor(Math.random() * m),
r = Math.sqrt((i + 1) / m * -Math.log(Math.random())) * maxRadius,
d = {cluster: i, radius: r, depth: 2};
if(d.radius < clusters[d.cluster].radius ) {
nodes.push(d);
}
node = node.data(nodes);
node.enter().append("circle")
.style("fill", function(d) { return color(d.cluster); })
.attr({r: function(d) { return(d.radius); },
cx: function(d) { return(d.x); },
cy: function(d) { return(d.y); },
})
.call(force.drag);
force.start();
}, 1000);
function tick(e) {
node
.each(cluster(e.alpha * 0.1))
.each(collide(e.alpha * 0.3))
.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 + 10;
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;
});
};
}
}
drawAnimation();
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

How can I force surrounding elements to move in response to a change in size of one of the elements?

I found some code that displays a group of circles. I slightly modified the code to allow the user to click on any of the circles to change its radius. The problem is that that enlarged circle overlaps the other other circle. I want the other circles to move so that the larger circle doesn't overlap any of the adjacent circles. Here is the code I have:
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var width = 960,
height = 500;
var nodes = d3.range(200).map(function() { return {radius: Math.random() * 12 + 4}; }),
root = nodes[0],
color = d3.scale.category10();
root.radius = 0;
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, i) { return color(i % 3); })
.on("click", function (d) {
d3.select(this).attr("r", 30);
force.start();
});
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; });
});
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>

Categories