In this D3 example:
nodes have variable radius. I managed to move node labels so that they are always just next to their circles.
However, how to move arrows? (You can see that for Microsoft, Apple etc, they are almost covered by the circle)
Related questions:
here
here
here
here
I search online, none of the answer worked, so I made my own:
Here is the code:
//arrows
svg.append("defs").selectAll("marker")
.data(["suit", "licensing", "resolved"])
.enter().append("marker")
.attr("id", function(d) { return d; })
.attr("viewBox", "0 -5 10 10")
.attr("refX", 9)
.attr("refY", 0)
.attr("markerWidth", 10)
.attr("markerHeight", 10)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5 L10,0 L0, -5")
.style("stroke", "#4679BD")
.style("opacity", "0.6");
//Create all the line svgs but without locations yet
var link = svg.selectAll(".link")
.data(forceData.links)
.enter().append("line")
.attr("class", "link")
.style("marker-end", "url(#suit)");
//Set up the force layout
var force = d3.layout.force()
.nodes(forceData.nodes)
.links(forceData.links)
.charge(-120)
.linkDistance(200)
.size([width, height])
.on("tick", tick)
.start();
function tick(){
link.attr("x1", function (d) { return d.source.x; })
.attr("y1", function (d) { return d.source.y; })
.attr("x2", function (d) {
return calculateX(d.target.x, d.target.y, d.source.x, d.source.y, d.target.radius);
})
.attr("y2", function (d) {
return calculateY(d.target.x, d.target.y, d.source.x, d.source.y, d.target.radius);
});
d3.selectAll("circle")
.attr("cx", function (d) { return d.x; })
.attr("cy", function (d) { return d.y; });
d3.select("#forcelayoutGraph").selectAll("text")
.attr("x", function (d) { return d.x; })
.attr("y", function (d) { return d.y; });
}
function calculateX(tx, ty, sx, sy, radius){
if(tx == sx) return tx; //if the target x == source x, no need to change the target x.
var xLength = Math.abs(tx - sx); //calculate the difference of x
var yLength = Math.abs(ty - sy); //calculate the difference of y
//calculate the ratio using the trigonometric function
var ratio = radius / Math.sqrt(xLength * xLength + yLength * yLength);
if(tx > sx) return tx - xLength * ratio; //if target x > source x return target x - radius
if(tx < sx) return tx + xLength * ratio; //if target x < source x return target x + radius
}
function calculateY(tx, ty, sx, sy, radius){
if(ty == sy) return ty; //if the target y == source y, no need to change the target y.
var xLength = Math.abs(tx - sx); //calculate the difference of x
var yLength = Math.abs(ty - sy); //calculate the difference of y
//calculate the ratio using the trigonometric function
var ratio = radius / Math.sqrt(xLength * xLength + yLength * yLength);
if(ty > sy) return ty - yLength * ratio; //if target y > source y return target x - radius
if(ty < sy) return ty + yLength * ratio; //if target y > source y return target x - radius
}
Related
I worked in the d3 library and created nested wheels. I have no idea how to add texts into the wheel, spirally from inside out. The number starting point doesn't matter, and numbers must spirally outwards according the previous position.
Codes
let allAxis = (data.map(function(i, j) {
return i.name
})),
total = allAxis.length,
radius = Math.min(options.width / 2, options.height / 2),
angleSlice = Math.PI * 2 / total,
Format = d3.format('');
let rScale = d3.scale.linear()
.domain([0, options.circles.maxValue])
.range([50, radius]);
let svg = d3.select("body").append("svg")
.attr("width", options.width + options.margins.left + options.margins.right)
.attr("height", options.height + options.margins.top + options.margins.bottom);
let g = svg.append("g")
.attr("transform", "translate(" + (options.width / 2 + options.margins.left) + "," + (options.height / 2 + options.margins.top) + ")");
let axisGrid = g.append("g")
.attr("class", "axisWraper");
let axis = axisGrid.selectAll(".axis")
.data(allAxis)
.enter()
.append("g")
.attr("class", "axis")
//append them lines
axis.append("line")
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", function(d, i) {
let tempX2 = radius * Math.cos(angleSlice * i - Math.PI / 2);
return tempX2;
})
.attr("y2", function(d, i) {
let tempY = radius * Math.sin(angleSlice * i - Math.PI / 2);
return tempY;
})
.attr("class", "line")
.attr("stroke", "black")
.attr("fill", "none");
//Draw background circles
axisGrid.selectAll(".levels")
.data([12,11,10,9,8,7,6, 5, 4, 3, 2, 1])
.enter()
.append("circle")
.attr("class", function(d, i) {
return `gridCircle-${d}`
})
.attr("r", function(d, i) {
return parseInt(radius / options.circles.levels * d, 10);
})
.attr("stroke", "black")
.attr("fill-opacity", function(d, i) {
return options.circles.opacity;
});
axisGrid.select(".gridCircle-1").attr("fill-opacity", 1);
axisGrid.select(".gridCircle-2").attr("fill-opacity", 1);
Expected Result
Updated #1 (with PointRadial)
Here's the fiddle: http://jsfiddle.net/arcanabliss/8yjacdoz/73/
You need pointRadial:
Returns the point [x, y] for the given angle in radians, with 0 at -y
(12 o’clock) and positive angles proceeding clockwise, and the given
radius.
You can play around with the example below in order to fit to your arrangement e.g. in order to start from 3 o'clock and rotate counter-clockwise etc you just need to play with degrees and radius:
const degrees = (i % numbersPerRing) * (360 / numbersPerRing);
const radius = (Math.floor(i / numbersPerRing) + 1) * ringSize;
// numbers
const numbersPerRing = 8;
const ringSize = 30;
const originX = 120;
const originY = 120;
const numbers = [... new Array(18)].map((d, i) => i + 1);
const diameters = [... new Array(4)].map((d, i) => (i * ringSize) + 10);
const points = numbers.map((n, i) => {
const degrees = (i % numbersPerRing) * (360 / numbersPerRing);
const radius = (Math.floor(i / numbersPerRing) + 1) * ringSize;
const point = d3.pointRadial(degrees * (Math.PI / 180), radius);
return {
"value": n,
"degrees": degrees,
"point": point
}
});
// svg
const svg = d3.select("body")
.append("svg")
.attr("width", originX * 2)
.attr("height", originY * 2);
// circles
svg.selectAll("circles")
.data(diameters)
.enter()
.append("circle")
.attr("cx", originX)
.attr("cy", originY)
.attr("r", d => d)
.attr("fill", "none")
.attr("stroke", "#aaaaaa");
// labels
svg.selectAll("labels")
.data(points)
.enter()
.append("text")
.attr("x", (d, i) => d.point[0])
.attr("y", (d, i) => d.point[1])
.attr("transform", `translate(${originX},${originX})`)
.attr("text-anchor", (d) => (d.degrees > 180) ? "end" : "start")
.text(d => d.value);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
I'm trying to understand how this beautiful example works...
http://bl.ocks.org/mbostock/1804919
I see that clustering is done by the color of the nodes, but I'm confused by the line in question in the collision detection function...
r = d.radius + quad.point.radius + (d.color !== quad.point.color) * padding;
How can you "add" the product of a comparison of the colors "d.color" and "quad.point.color"? I would have assumed this would return nothing more than a true/false? Either way, I'm not sure I follow only this reference to color will have the desired effect of clustering by color?
Anyway, I haven't been able to find any line-by-line description of the workings of the collision detection function, so I'm really hoping that someone here understands it well enough to help explain this bit to me.
All I'm ultimately trying to achieve is to adapt the example to cluster by another non-numeric node attribute (e.g. d.person_name !== quad.point.person_name).
Thanks!
The line you are asking about is calculating the allowable distance between nodes, the distance between nodes (l) is compared to r to determine if there is a collision between d and quad.point. The value padding is added to the allowable distance between nodes if they are of the same colour. The boolean result is coerced into a Number type by the context.
Instead of assuming what JS does its really easy to open the browser tools and just type the expression in to see what the result is...
But the collision detection is not involved in the clustering, that is handled by this code...
// Move nodes toward cluster focus.
function gravity(alpha) {
return function(d) {
d.y += (d.cy - d.y) * alpha;
d.x += (d.cx - d.x) * alpha;
};
}
If you have some data and you want to use the same code to group them by a particular attribute, then you need to add a cx and cy property to your data such that items with the same attribute value (the value does not need to be numeric) have the same cx and cy values.
Example (modified version of this)
var width = 600,
height = 200,
padding = 6, // separation between nodes
maxRadius = 6;
var n = 200, // total number of nodes
names = ["Givens", "Crowder", "Lannister", "Baratheon", "Stark"],
m = names.length; // number of distinct clusters
var color = d3.scale.category10()
.domain(d3.range(m));
var x = d3.scale.ordinal()
.domain(names)
.rangePoints([0, width], 1),
legend = d3.svg.axis()
.scale(x)
.orient("top")
var nodes = d3.range(n).map(function() {
var i = Math.floor(Math.random() * m),
v = (i + 1) / m * -Math.log(Math.random());
return {
radius: Math.sqrt(v) * maxRadius,
color: color(i),
cx: x(names[i]),
cy: height / 2
};
});
var force = d3.layout.force()
.nodes(nodes)
.size([width, height])
.gravity(0)
.charge(0)
.on("tick", tick)
.start();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height),
gLegend = svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0, " + height * 0.9 + ")")
.call(legend);
gLegend.selectAll(".tick text")
.attr("fill", function(d, i) {
return color(i);
});
var circle = svg.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("r", function(d) {
return d.radius;
})
.style("fill", function(d) {
return d.color;
})
.call(force.drag);
function tick(e) {
circle
.each(gravity(.2 * e.alpha))
.each(collide(.5))
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
});
}
// Move nodes toward cluster focus.
function gravity(alpha) {
return function(d) {
d.y += (d.cy - d.y) * alpha;
d.x += (d.cx - d.x) * alpha;
};
}
// Resolve collisions between nodes.
function collide(alpha) {
var quadtree = d3.geom.quadtree(nodes);
return function(d) {
var r = d.radius + maxRadius + padding,
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.color !== quad.point.color) * padding;
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;
});
};
}
circle {
stroke: #000;
}
.x.axis path {
fill: none;
}
.x.axis text {
font-family: Papyrus, Consolas, Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace, sans-serif;
}
body {
background-color: black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
I am trying to draw circles in a rectangular div. I have followed the advice from Question 13339615(the answer I used is also made available in this fiddle, and this works perfectly.
However, being completely new to d3, I cannot work out how to label the circles. What I would basically like to recreate is similar to the visualisation in this article.
I have tried the following modifications to the fiddle:
var bubbles = bubbleGroup.selectAll("circle")
.data(data)
.enter()
.append("circle")
.append("text").attr("dy", ".3em")
.style("text-anchor", "middle").text("test");
but this breaks the visualisation.
Following question 13615381 I have also tried things like:
var bubbles = bubbleGroup.selectAll("circle")
.data(data)
.enter()
.append("circle");
bubbleGroup.append("text")
.attr("dx", function(d){return -20})
.text(function(d){return "test"})
but the text does not display. I'm imaging the code should be some variation of these, but I cannot figure it out.
Thank you!
Fixed by putting the circle and text inside a g and adjusting the g css-transform.
JSFiddle
var bubbles = bubbleGroup.selectAll("g")
.data(data)
.enter().append("g").attr("class","gBubble");
bubbles.append("circle")
.on("mouseover",function(){
$(this).attr("cursor","pointer")
})
.on("click",function(){alert("clicked")});
bubbles.append("text").text(function(d){return d.name;}).style("opacity","1");
(function() {
//D3 program to fit circles of different sizes
//in a rectangle of fixed aspect ratio
//as tightly as reasonable.
//
//By Amelia Bellamy-Royds, in response to
//http://stackoverflow.com/questions/13339615/packing-different-sized-circles-into-rectangle-d3-js
//Based on Mike Bostock's
//"http://bl.ocks.org/mbostock/7882658" example:
//http://bl.ocks.org/mbostock/7882658
//parameters//
var N = 25; //number of nodes
var sortOrder = -1;
//>0 for ascending, <0 for descending, 0 for no sort
//create data array//
var data = [], i = N;
var randNorm = d3.random.normal(1,0.6);
while(i--) data.push({
"size": Math.max(randNorm(), 0.1) });
//circle area will be proportional to size
var dataMax = d3.max(data, function(d){return d.size;});
var totalSize = d3.sum(data, function(d){return d.size;});
//________________//
//Set up SVG and rectangle//
var svg = d3.select("svg");
var digits = /(\d*)/;
var margin = 50; //space in pixels from edges of SVG
var padding = 4; //space in pixels between circles
var svgStyles = window.getComputedStyle(svg.node());
var width = parseFloat(svgStyles["width"]) - 2*margin;
var height = parseFloat(svgStyles["height"]) - 2*margin;
var usableArea = Math.PI*
Math.pow( Math.min(width,height)/2 ,2)*0.667;
var scaleFactor = Math.sqrt(usableArea)/
Math.sqrt(totalSize)/Math.PI;
var rScale = d3.scale.sqrt()
//make radius proportional to square root of data r
.domain([0, dataMax]) //data range
.range([0, Math.sqrt(dataMax)*scaleFactor]);
//The rScale range will be adjusted as necessary
//during packing.
//The initial value is based on scaling such that the total
//area of the circles is 2/3 the area of the largest circle
//you can draw within the box.
/*
console.log("Dimensions: ", [height, width]);
console.log("area", width*height);
console.log("Usable area: ", usableArea);
console.log("TotalSize: ", totalSize);
console.log("Initial Scale: ", scaleFactor);
console.log("RScale: ",rScale.domain(), rScale.range());
console.log("r(1)", rScale(1) );
// */
var box = svg.append("rect")
.attr({ "height": height, "width":width,
"x":margin, "y":margin,
"class":"box"
});
var bubbleGroup = svg.append("g")
.attr("class", "bubbles")
.attr("transform",
"translate(" + [margin,margin] + ")");
//__Initialize layout objects__//
// Use the pack layout to initialize node positions:
d3.layout.pack()
.sort((
sortOrder?
( (sortOrder<0)?
function(a,b){return b.size - a.size;} : //descending
function(a,b){return a.size - b.size;} ) : //ascending
function(a,b){return 0;} //no sort
))
.size([width/scaleFactor, height/scaleFactor])
.value(function(d) { return d.size; })
.nodes({children:data});
//Use the force layout to optimize:
var force = d3.layout.force()
.nodes(data)
.size([width/scaleFactor, height/scaleFactor])
.gravity(.5)
.charge(0) //don't repel
.on("tick", updateBubbles);
//Create circles!//
var bubbles = bubbleGroup.selectAll("circle")
.data(data)
.enter()
.append("circle");
//Create text
var text = bubbleGroup.selectAll("text")
.data(data).enter().append("text")
.attr("dy", function(d){
return d.y;
})
.attr("dx", function(d){
return d.x;
}).style("text-anchor", "middle").text("test");
// Create a function for this tick round,
// with a new quadtree to detect collisions
// between a given data element and all
// others in the layout, or the walls of the box.
//keep track of max and min positions from the quadtree
var bubbleExtent;
function collide(alpha) {
var quadtree = d3.geom.quadtree(data);
var maxRadius = Math.sqrt(dataMax);
var scaledPadding = padding/scaleFactor;
var boxWidth = width/scaleFactor;
var boxHeight = height/scaleFactor;
//re-set max/min values to min=+infinity, max=-infinity:
bubbleExtent = [[Infinity, Infinity],[-Infinity, -Infinity]];
return function(d) {
//check if it is pushing out of box:
var r = Math.sqrt(d.size) + scaledPadding,
nx1 = d.x - r,
nx2 = d.x + r,
ny1 = d.y - r,
ny2 = d.y + r;
if (nx1 < 0) {
d.x = r;
}
if (nx2 > boxWidth) {
d.x = boxWidth - r;
}
if (ny1 < 0) {
d.y = r;
}
if (ny2 > boxHeight) {
d.y = boxHeight - r;
}
//check for collisions
r = r + maxRadius,
//radius to center of any possible conflicting nodes
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 = Math.sqrt(d.size) + Math.sqrt(quad.point.size)
+ scaledPadding;
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;
});
//update max and min
r = r-maxRadius; //return to radius for just this node
bubbleExtent[0][0] = Math.min(bubbleExtent[0][0],
d.x - r);
bubbleExtent[0][1] = Math.min(bubbleExtent[0][1],
d.y - r);
bubbleExtent[1][0] = Math.max(bubbleExtent[1][0],
d.x + r);
bubbleExtent[1][1] = Math.max(bubbleExtent[1][1],
d.y + r);
};
}
function updateBubbles() {
bubbles
.each( collide(0.5) ); //check for collisions
text.each(collide(0.5));//check for text collisions
//update the scale to squeeze in the box
//to match the current extent of the bubbles
var bubbleWidth = bubbleExtent[1][0] - bubbleExtent[0][0];
var bubbleHeight = bubbleExtent[1][1] - bubbleExtent[0][1];
scaleFactor = (height/bubbleHeight +
width/bubbleWidth)/2; //average
/*
console.log("Box dimensions:", [height, width]);
console.log("Bubble dimensions:", [bubbleHeight, bubbleWidth]);
console.log("ScaledBubble:", [scaleFactor*bubbleHeight,
scaleFactor*bubbleWidth]);
//*/
rScale
.range([0, Math.sqrt(dataMax)*scaleFactor]);
//shift the bubble cluster to the top left of the box
bubbles
.each( function(d){
d.x -= bubbleExtent[0][0];
d.y -= bubbleExtent[0][1];
});
//update positions and size according to current scale:
bubbles
.attr("r", function(d){return rScale(d.size);} )
.attr("cx", function(d){return scaleFactor*d.x;})
.attr("cy", function(d){return scaleFactor*d.y;});
text
.attr("dy", function(d){
return (scaleFactor*d.y)+4;
})
.attr("dx", function(d){
return scaleFactor*d.x*2;
});
}
force.start();
})();
rect.box {
fill:none;
stroke:royalblue;
stroke-width:5;
shape-rendering: crispEdges;
}
g.bubbles circle {
fill:rgba(255,0,64,0.5);
stroke:rgb(255,0,64);
stroke-width:3;
}
g.bubbles text {
fill:royalblue;
font-family:sans-serif;
text-anchor:middle;
alignment-baseline:middle;
opacity:1;
pointer-events:all;
transition:1s;
}
g.bubbles text:hover {
opacity:1;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg height=500 width=500></svg>
I've added text to circles, and also collision behavior too.
Initially the text is invisible because in the CSS they mentioned like below
g.bubbles text {
fill:royalblue;
font-family:sans-serif;
text-anchor:middle;
alignment-baseline:middle;
opacity:0;//See this value, this makes text to invisible
pointer-events:all;
transition:1s;
}
g.bubbles text:hover {
opacity:1;
}
In my snippet I changed it visible by making it's opacity to 1.
And updated fiddle
I am developing a d3 sunburst type.
Everything is ok, It is taking the flare JSON correctly but, when I go to label the path look what is happening:
The code is the following:
var width = 960,
height = 700,
radius = Math.min(width, height) / 2;
var x = d3.scale.linear()
.range([0, 2 * Math.PI]);
var y = d3.scale.linear()
.range([0, radius]);
var hue = d3.scale.ordinal().range(["#feec76","#aec7e8","#ff00bf","#7f7f7f"]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + (height / 2 + 10) + ")");
var partition = d3.layout.partition()
.value(function(d) { return d.size; });
var arc = d3.svg.arc()
.startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); })
.endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); })
.innerRadius(function(d) { return Math.max(0, y(d.y)); })
.outerRadius(function(d) { return Math.max(0, y(d.y + d.dy)); });
d3.json("http://api.printoriente.com/treemap.php", function(error, root) {
var g = svg.selectAll("g")
.data(partition.nodes(root))
.enter().append("g");
var path = g.append("path")
.attr("d", arc)
.style("fill", function(d) { return hue((d.children ? d : d.parent).name); })
.on("click", click);
var text = g.append("text")
.attr("transform", function(d) { return "rotate(" + computeTextRotation(d) + ")"; })
.attr("x", function(d) { return y(d.y); })
.attr("dx", "6") // margin
.attr("dy", ".35em") // vertical-align
.text(function(d) { return d.name; });
And the rotation code is:
function computeTextRotation(d) {
return (x(d.x + d.dx / 2) - Math.PI / 2) / Math.PI * 180;
}
This script works for all others d3 but I have to put those colors for each path.
Where is the problem?
Regards.
UPDATED: d3 sunburst with small font-size:
UPDATED: I want something like this:
UPDATED: Take a look of internal labels:
I'm not sure where you got the code to calculate the angle from, but it seems to be completely off. If you look at this example, the code to compute the angle is (with everything else being equal)
var angle = (d.x + d.dx / 2) * 180 / Math.PI - 90;
Replacing the code in your example with that fixes the angles. To fix the positions, you can adjust the dx offset of the labels, e.g.
.attr("dx", 50")
Complete example here.
I have a set of data that I am visualizing using d3.js. I am representing data points in the form of bubbles, where the configuration for bubbles is as follows:
var dot = svg.selectAll("g")
.data(data)
.enter()
.append("g");
dot.append("circle")
.attr("class", "dot")
.attr("cx", function(d) { return xp(x(d)); })
.attr("cy", function(d) { return yp(y(d)); })
.style("fill", function(d) { return colorp(color(d)); })
.attr("r", function(d) { return radiusp(radius(d)*2000000); });
dot.append("text")
.attr("x", function(d) { return xp(x(d)); })
.attr("y", function(d) { return yp(y(d)); })
.text(function(d) { return d.name; })
Where xp, yp, colorp and radiusp are defined as follows:
var xp = d3.scale.log().domain([300, 1e5]).range([0, width]),
yp = d3.scale.linear().domain([10, 85]).range([height, 0]),
radiusp = d3.scale.sqrt().domain([0, 5e8]).range([0, 40]),
colorp = d3.scale.category10();
At this point, the bubbles are being displayed as static on their positions (where position is defined by xp and yp), while the size of the bubble is basically coming from radiusp and color is defined by colorp.
Right now I am showing them exactly as this example:
http://bl.ocks.org/mbostock/4063269
What I need is to display them in this form:
http://jsfiddle.net/andycooper/PcjUR/1/
That is: They should be packed using gravity function, have some charge, can be dragged and repel each other to some extent. I can see that there is a way through d3.layout.force() but not really able to integrate that into this.. I will be really thankful if you can suggest me the right path or some working example or even a hint. Thank you.
I think you were almost there but the specification of your dot variable is not the best one. I would transform it like this:
var dot = svg.selectAll(".dot")
.data(data)
.enter()
Afterwards, once the circles have been plotted, what you do is that you create a force layout, instantiate it with the nodes you just created, add a on("tick") method, and then start the layout. An example is the following:
var force = d3.layout.force().nodes(data).size([width, height])
.gravity(0)
.charge(0)
.on("tick", function(e){
dot
.each(gravity(.2 * e.alpha))
.each(collide(.5))
.attr("cx", function (d) {return d.x;})
.attr("cy", function (d) {return d.y;});
})
.start();
To have a complete answer, I will add also the gravity and collide methods from your fiddle (with adjusted variable names)
function gravity(alpha) {
return function (d) {
d.y += (d.cy - d.y) * alpha;
d.x += (d.cx - d.x) * alpha;
};
}
function collide(alpha) {
var padding = 6
var quadtree = d3.geom.quadtree(dot);
return function (d) {
var r = d.r + radiusp.domain()[1] + padding,
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.r + quad.point.r + (d.color !== quad.point.color) * padding;
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 think the problem you had was that perhaps you were applying the force layout to the g element of each of the circles, which unfortunately was not working. I hope this will give you an idea how to proceed. Your last line of the dot declaration was adding a g element for each circle, which was a little difficult to handle.
Thanks.
PS I assume that the x, y, and r attributes of your data contain the x,y, and radius.