D3 Force layout DIV vs SVG - d3.js
I am trying to modify the d3 force layout from SVG to DIV's. It seems the collision detection doesnt work as well with DIV's. You can see the working examples below.
(Another quick question, anyone know why css transform:translate isnt used for hardware acceleration)
DIV Version
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body,
html {
margin: 0;
width: 100%;
height: 100%
}
.divs div {
border-radius: 50%;
background: red;
position: absolute;
}
</style>
<body>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.3/jquery.js"></script>
<script>
var width = $('body').width(),
height = $('body').height(),
padding = 10, // separation between nodes
maxRadius = 30;
var n = 20, // total number of nodes
m = 1; // number of distinct clusters
var color = d3.scale.category10()
.domain(d3.range(m));
var xPos = d3.scale.ordinal()
.domain(d3.range(m))
.rangePoints([width, width], 1);
var x =
d3.scale.linear()
.domain([0, width])
.range([0, width]);
var y =
d3.scale.linear()
.domain([0, height])
.range([0, height]);
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.random() * maxRadius + 20,
color: color(i),
cx: xPos(i),
cy: height
};
});
var force = d3.layout.force()
.nodes(nodes)
.size([width, height])
.gravity(0)
.charge(0)
.on("tick", tick)
.start();
var $body = d3.select("body")
.append("div").attr('class', 'divs')
.attr('style', function(d) {
return 'width: ' + width + 'px; height: ' + height + 'px;';
});
var $div = $body.selectAll("div")
.data(nodes)
.enter()
.append("div")
.attr('style', function(d) {
return 'width: ' + (d.radius * 2) + 'px; height: ' + (d.radius * 2) + 'px; margin-left: -' + d.radius + 'px; margin-top: -' + d.radius + 'px;';
})
.call(force.drag);
function tick(e) {
$div
.each(gravity(.2 * e.alpha))
.each(collide(.5))
.style('left', function(d) {
return x(Math.max(d.radius, Math.min(width - d.radius, d.x))) + 'px';
})
.style('top', function(d) {
return y(Math.max(d.radius, Math.min(height - d.radius, d.y))) + 'px';
});
}
// 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;
});
};
}
</script>
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body, html { margin: 0;width: 100%; height: 100%}
circle {
}
</style>
<body>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.3/jquery.js"></script>
<script>
var width = $('body').width(),
height = $('body').height(),
padding = 10, // separation between nodes
maxRadius = 40;
var n = 10, // total number of nodes
m = 1; // number of distinct clusters
var color = d3.scale.category10()
.domain(d3.range(m));
var x = d3.scale.ordinal()
.domain(d3.range(m))
.rangePoints([width - 200, width], 1);
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.random() * maxRadius + 30,
color: color(i),
cx: x(i),
cy: height
};
});
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);
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; });
.attr("cx", function(d) { return d.x = Math.max(d.radius, Math.min(width - d.radius, d.x)); })
.attr("cy", function(d) { return d.y = Math.max(d.radius, Math.min(height - d.radius, 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;
});
};
}
</script>
The reason is you are not updating the d.x and d.y in the tick so the layout will never come to know if they are colliding or not.
.style('left', function(d) {
//update the d.x
d.x = x(Math.max(d.radius, Math.min(width - d.radius, d.x)))
return d.x + 'px';
})
.style('top', function(d) {
//update the d.y
d.y=y(Math.max(d.radius, Math.min(height - d.radius, d.y)));
return d.y + "px"
});
Working code:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body,
html {
margin: 0;
width: 100%;
height: 100%
}
.divs div {
border-radius: 50%;
background: red;
position: absolute;
}
</style>
<body>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.3/jquery.js"></script>
<script>
var width = $('body').width(),
height = $('body').height(),
padding = 10, // separation between nodes
maxRadius = 30;
var n = 20, // total number of nodes
m = 1; // number of distinct clusters
var color = d3.scale.category10()
.domain(d3.range(m));
var xPos = d3.scale.ordinal()
.domain(d3.range(m))
.rangePoints([width, width], 1);
var x =
d3.scale.linear()
.domain([0, width])
.range([0, width]);
var y =
d3.scale.linear()
.domain([0, height])
.range([0, height]);
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.random() * maxRadius + 20,
color: color(i),
cx: xPos(i),
cy: height
};
});
var force = d3.layout.force()
.nodes(nodes)
.size([width, height])
.gravity(0)
.charge(0)
.on("tick", tick)
.start();
var $body = d3.select("body")
.append("div").attr('class', 'divs')
.attr('style', function(d) {
return 'width: ' + width + 'px; height: ' + height + 'px;';
});
var $div = $body.selectAll("div")
.data(nodes)
.enter()
.append("div")
.attr('style', function(d) {
return 'width: ' + (d.radius * 2) + 'px; height: ' + (d.radius * 2) + 'px; margin-left: -' + d.radius + 'px; margin-top: -' + d.radius + 'px;';
})
.call(force.drag);
function tick(e) {
$div
.each(gravity(.2 * e.alpha))
.each(collide(.5))
.style('left', function(d) {
d.x = x(Math.max(d.radius, Math.min(width - d.radius, d.x)))
return d.x + 'px';
})
.style('top', function(d) {
d.y=y(Math.max(d.radius, Math.min(height - d.radius, d.y)));
return d.y + "px"
});
}
// 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;
});
};
}
</script>
Hope this helps!
Related
How can I add icons to this force simulation example in d3js and avoid code duplication?
I have two questions, one about the icons and other about code duplication. I'm using this example: http://bl.ocks.org/rveciana/6184054/bd294b921ebf2180eccc3aca548c895367fca2d2 (Thanks Roger Veciana) I want to add an icon to each of those cirlces but I need to put the url inside a json instead of using the script to generate those cirlces (that is inside the code). Not sure what I'm doing wrong but is not working. Any tip on how can I add the icon to each circle ? I've also added some text, and it is working. Basically I do this: var circlemaint = svgmaint.selectAll("circle") .data(nodes.maint) .enter().append("g").append("circle") .attr("r", function(d) { return d.radius; }) .attr("cx", function(d) { return d.x; }) .attr("cy", function(d) { return d.y; }) .style("fill", function(d) { return d.color; }) .call(d3.drag); var txtmaint = svgmaint.selectAll("circle") .select('text') .data(nodes.maint) .enter().append('text') .text(node => node.id) .attr('font-size', 18) .attr('dx', -25) .attr('dy',20) The issue here, is that I have 9 different svg's, and I don't want to duplicate this per svg. How can avoid code duplication? I finally call it within the tick function and I do something like this: function tick(e) { simulation.alpha(0.2) circlemaint .each(gravity(this.alpha())) .each(collide(.5)) .attr("cx", function(d) { return d.x; }) .attr("cy", function(d) { return d.y; }); txtmaint .each(gravity(this.alpha())) .each(collide(.5)) .attr("x", function(d) { return d.x; }) .attr("y", function(d) { return d.y; }); circleXXXX ...... txtXXXXXX ...... } And here is the code of the example: //Based in ///http://bl.ocks.org/mbostock/1804919 var margin = { top: 0, right: 0, bottom: 0, left: 0 }, width = 960 - margin.left - margin.right, height = 500 - margin.top - margin.bottom; var rect = [50, 50, width - 50, height - 50]; var n = 20, m = 4, padding = 6, maxSpeed = 3, radius = d3.scale.sqrt().range([0, 8]), color = d3.scale.category10().domain(d3.range(m)); var nodes = []; for (i in d3.range(n)) { nodes.push({ radius: radius(1 + Math.floor(Math.random() * 4)), color: color(Math.floor(Math.random() * m)), x: rect[0] + (Math.random() * (rect[2] - rect[0])), y: rect[1] + (Math.random() * (rect[3] - rect[1])), speedX: (Math.random() - 0.5) * 2 * maxSpeed, speedY: (Math.random() - 0.5) * 2 * maxSpeed }); } 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 + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); svg.append("svg:rect") .attr("width", rect[2] - rect[0]) .attr("height", rect[3] - rect[1]) .attr("x", rect[0]) .attr("y", rect[1]) .style("fill", "None") .style("stroke", "#222222"); var circle = svg.selectAll("circle") .data(nodes) .enter().append("circle") .attr("r", function(d) { return d.radius; }) .attr("cx", function(d) { return d.x; }) .attr("cy", function(d) { return d.y; }) .style("fill", function(d) { return d.color; }) .call(force.drag); var flag = false; function tick(e) { force.alpha(0.1) circle .each(gravity(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) { if ((d.x - d.radius - 2) < rect[0]) d.speedX = Math.abs(d.speedX); if ((d.x + d.radius + 2) > rect[2]) d.speedX = -1 * Math.abs(d.speedX); if ((d.y - d.radius - 2) < rect[1]) d.speedY = -1 * Math.abs(d.speedY); if ((d.y + d.radius + 2) > rect[3]) d.speedY = Math.abs(d.speedY); d.x = d.x + (d.speedX * alpha); d.y = d.y + (-1 * d.speedY * alpha); }; } // Resolve collisions between nodes. function collide(alpha) { var quadtree = d3.geom.quadtree(nodes); return function(d) { var r = d.radius + radius.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.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; }); }; } <script src="https://d3js.org/d3.v3.js"></script> Thank you so much!!
Colored text and line-breaks for D3 node labels
I am working on a D3 chart, but can't get the node labeling right (I would like to put the "size" on a second line, and change all the text to white. For instance, the "Toyota" node would say "Toyota Motor" and on a new line just below, "61.84"). Here is my starting point. I tried to add a pre block with the CSV data so it could run on JSfiddle, but I got stuck. I know I need to change this code in order to get the data from the pre block instead of the external CSV: d3.text("./car_companies.csv", function(error, text) { After that, I need to add a new "node append", something like this: node.append("revenue") .style("text-anchor", "middle") .attr("dy", "1.5em") .text(function(d) { return d.revenue.substring(0, d.radius / 3); }); http://jsfiddle.net/nick2ny/pqo1x670/4/ Thank you for any ideas.
Not directly related to the question, but to use the <pre> element to hold your data, you have to use: var text = d3.select("pre").text(); Instead of d3.text(). Back to the question: For printing those values, you just need: node.append("text") .attr("dy", "1.3em") .style("text-anchor", "middle") .style("fill", "white") .text(function(d) { return d.size; }); Adjusting dy the way you want. However, there is an additional problem: you're not populating size in the data array. Therefore, add this in the create_nodes function: size: data[node_counter].size, Here is the code with those changes: <!DOCTYPE html> <meta charset="utf-8"> <style type="text/css"> text { font: 10px sans-serif; } pre { display: none; } circle { stroke: #565352; stroke-width: 1; } </style> <body> <pre id="data"> Toyota Motor,61.84,Asia,239 Volkswagen,44.54,Europe,124 Daimler,40.79,Europe,104 BMW,35.78,Europe,80 Ford Motor,31.75,America,63 General Motors,30.98,America,60 </pre> <script src="https://d3js.org/d3.v3.min.js"></script> <script> Array.prototype.contains = function(v) { for (var i = 0; i < this.length; i++) { if (this[i] === v) return true; } return false; }; var width = 500, height = 500, padding = 1.5, // separation between same-color nodes clusterPadding = 6, // separation between different-color nodes maxRadius = 12; var color = d3.scale.ordinal() .range(["#0033cc", "#33cc66", "#990033"]); var text = d3.select("pre").text(); var colNames = "text,size,group,revenue\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); 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") .style("fill", "white") .text(function(d) { return d.text; }); node.append("text") .attr("dy", "1.3em") .style("text-anchor", "middle") .style("fill", "white") .text(function(d) { return d.size; }); 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, size: data[node_counter].size, revenue: data[node_counter].revenue, 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; }); }; } </script>
D3 force collision detection for ellipses
All, I know there are many examples for D3.js collision detection for circles and rectangles. I'm trying to do force simulation of ellipse nodes. I tried following snippet which is originally for rectangles, but it's not perfect. var width = 960, height = 500, minSize = 10, maxSize = 30; var n = 20, m = 10; var color = d3.scaleOrdinal(d3.schemeCategory10) .domain(d3.range(m)); var nodes = d3.range(n).map(function() { var c = Math.floor(Math.random() * m), rx = Math.sqrt((c + 1) / m * -Math.log(Math.random())) * (maxSize - minSize) + minSize, ry = Math.sqrt((c + 1) / m * -Math.log(Math.random())) * (maxSize - minSize) + minSize, d = {color: c, rx: rx, ry: ry}; return d; }); var collide = function(alpha) { var quadtree = d3.quadtree() .x((d) => d.x) .y((d) => d.y) .addAll(nodes); nodes.forEach((d) => { quadtree.visit((quad, x0, y0, x1, y1) => { let updated = false; if (quad.data && (quad.data !== d)) { let x = d.x - quad.data.x, y = d.y - quad.data.y, xSpacing = (quad.data.rx + d.rx), ySpacing = (quad.data.ry + d.ry), absX = Math.abs(x), absY = Math.abs(y), l, lx, ly; if (absX < xSpacing && absY < ySpacing) { l = Math.sqrt(x * x + y * y); lx = (absX - xSpacing) / l * alpha; ly = (absY - ySpacing) / l * alpha; if (Math.abs(lx) > Math.abs(ly)) { lx = 0; } else { ly = 0; } d.x -= x *= lx; d.y -= y *= ly; quad.data.x += x; quad.data.y += y; updated = true; } } return updated; }); }); }; var force = d3.forceSimulation() .nodes(nodes) .force("center", d3.forceCenter()) .force("collide", (alpha) => collide(alpha)) .force("x", d3.forceX().strength(.01)) .force("y", d3.forceY().strength(.01)) .on("tick", tick); var svg = d3.select("body").append("svg") .attr("width", width) .attr("height", height) .append('g') .attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')'); var ellipse = svg.selectAll("ellipse") .data(nodes) .enter().append("ellipse") .attr("rx", function(d) { return d.rx; }) .attr("ry", function(d) { return d.ry; }) .style("fill", function(d) { return color(d.color); }) .call(d3.drag() .on("start", dragstarted) .on("drag", dragged) .on("end", dragended)); function tick() { ellipse .attr("cx", function(d) { return d.x; }) .attr("cy", function(d) { return d.y; }); } function dragstarted(d) { if (!d3.event.active) force.alphaTarget(0.3).restart(); d.x = d.x; d.y = d.y; } function dragged(d) { d.x = d3.event.x; d.y = d3.event.y; } function dragended(d) { if (!d3.event.active) force.alphaTarget(0); d.x = d3.event.x; d.y = d3.event.y; } <script src="https://d3js.org/d3.v4.min.js"></script> There are too many gaps between nodes, and I know it's because ellipses are treated as rectangles in the collision detection. Anybody who's got a good solution for this? Thanks, in advance.
I've figured this out by myself. Here's the collision detection library for d3. ellipse-collision-detection I've attached working example in the above repository. Thanks!
Continent zoom topojson
I'm trying to make the map able to zoom in on continenents on mouseclick. The code is based on Mike Bostock's tutorial on making maps and uses a CSV dataset to bind some values to the countries. https://bost.ocks.org/mike/map/. I've tried to use various examples like this one: https://bl.ocks.org/mbostock/2206590 but nothing seems to work. The map just dissapears when I try to add a .onclick attribute. Does anyone have an idea how I can make the zoom work? HTML <!DOCTYPE html> <meta charset="utf-8"> <style> body { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; width: 960px; height: 500px; position: relative; } #canvas { } #canvas-svg { } .land { fill: #222; } .boundary { fill: none; stroke: #fff; stroke-width: 1px; } #tooltip-container { position: absolute; background-color: #fff; color: #000; padding: 10px; border: 1px solid; display: none; } .tooltip_key { font-weight: bold; } .tooltip_value { margin-left: 20px; float: right; } </style> <div id="tooltip-container"></div> <div id="canvas-svg"></div> <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/topojson/1.6.20/topojson.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script> <script> d3.csv("import.csv", function(err, data) { var config = {"data0":"Country","data1":"Total","label0":"label 0","label1":"label 1","color0":"#99ccff","color1":"#0050A1","width":800,"height":400} var width = 960, height = 960; var COLOR_COUNTS = 9; function Interpolate(start, end, steps, count) { var s = start, e = end, final = s + (((e - s) / steps) * count); return Math.floor(final); } function Color(_r, _g, _b) { var r, g, b; var setColors = function(_r, _g, _b) { r = _r; g = _g; b = _b; }; setColors(_r, _g, _b); this.getColors = function() { var colors = { r: r, g: g, b: b }; return colors; }; } function clicked(d) { var x, y, k; if (d && centered !== d) { var centroid = path.centroid(d); x = centroid[0]; y = centroid[1]; k = 4; centered = d; } else { x = width / 2; y = height / 2; k = 1; centered = null; } g.selectAll("path") .classed("active", centered && function(d) { return d === centered; }); g.transition() .duration(750) .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")scale(" + k + ")translate(" + -x + "," + -y + ")") .style("stroke-width", 1.5 / k + "px"); } function hexToRgb(hex) { var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? { r: parseInt(result[1], 16), g: parseInt(result[2], 16), b: parseInt(result[3], 16) } : null; } function valueFormat(d) { if (d > 1000000000) { return "$" + Math.round(d / 1000000000 * 10) / 10 + "M"; } else if (d > 1000000) { return "$" + Math.round(d / 1000000 * 10) / 10 + "M"; } else if (d > 1000) { return "$" + Math.round(d / 1000 * 10) / 10 + "B"; } else { return "$" + d + "M"; } } var COLOR_FIRST = config.color0, COLOR_LAST = config.color1; var rgb = hexToRgb(COLOR_FIRST); var COLOR_START = new Color(rgb.r, rgb.g, rgb.b); rgb = hexToRgb(COLOR_LAST); var COLOR_END = new Color(rgb.r, rgb.g, rgb.b); var startColors = COLOR_START.getColors(), endColors = COLOR_END.getColors(); var colors = []; for (var i = 0; i < COLOR_COUNTS; i++) { var r = Interpolate(startColors.r, endColors.r, COLOR_COUNTS, i); var g = Interpolate(startColors.g, endColors.g, COLOR_COUNTS, i); var b = Interpolate(startColors.b, endColors.b, COLOR_COUNTS, i); colors.push(new Color(r, g, b)); } var MAP_KEY = config.data0; var MAP_VALUE = config.data1; var projection = d3.geo.mercator() .scale((width + 1) / 2 / Math.PI) .translate([width / 2, height / 2]) .precision(.1); var path = d3.geo.path() .projection(projection); var graticule = d3.geo.graticule(); var svg = d3.select("#canvas-svg").append("svg") .attr("width", width) .attr("height", height); svg.append("path") .datum(graticule) .attr("class", "graticule") .attr("d", path); var valueHash = {}; function log10(val) { return Math.log(val); } data.forEach(function(d) { valueHash[d[MAP_KEY]] = +d[MAP_VALUE]; }); var quantize = d3.scale.quantize() .domain([0, 1.0]) .range(d3.range(COLOR_COUNTS).map(function(i) { return i })); quantize.domain([d3.min(data, function(d){ return (+d[MAP_VALUE]) }), d3.max(data, function(d){ return (+d[MAP_VALUE]) })]); d3.json("./world-topo-min.json", function(error, world) { var countries = topojson.feature(world, world.objects.countries).features; svg.append("path") .datum(graticule) .attr("class", "choropleth") .attr("d", path); var g = svg.append("g"); g.append("path") .datum({type: "LineString", coordinates: [[-180, 0], [-90, 0], [0, 0], [90, 0], [180, 0]]}) .attr("class", "equator") .attr("d", path); var country = g.selectAll(".country").data(countries); country.enter().insert("path") .attr("class", "country") .attr("d", path) .attr("id", function(d,i) { return d.id; }) .attr("title", function(d) { return d.properties.name; }) .style("fill", function(d) { if (valueHash[d.properties.name]) { var c = quantize((valueHash[d.properties.name])); var color = colors[c].getColors(); return "rgb(" + color.r + "," + color.g + "," + color.b + ")"; } else { return "#ccc"; } }) .on("mousemove", function(d) { var html = ""; html += "<div class=\"tooltip_kv\">"; html += "<span class=\"tooltip_key\">"; html += d.properties.name; html += "</span>"; html += "<span class=\"tooltip_value\">"; html += (valueHash[d.properties.name] ? valueFormat(valueHash[d.properties.name]) : ""); html += ""; html += "</span>"; html += "</div>"; $("#tooltip-container").html(html); $(this).attr("fill-opacity", "0.8"); $("#tooltip-container").show(); var coordinates = d3.mouse(this); var map_width = $('.choropleth')[0].getBoundingClientRect().width; if (d3.event.pageX < map_width / 2) { d3.select("#tooltip-container") .style("top", (d3.event.layerY + 15) + "px") .style("left", (d3.event.layerX + 15) + "px"); } else { var tooltip_width = $("#tooltip-container").width(); d3.select("#tooltip-container") .style("top", (d3.event.layerY + 15) + "px") .style("left", (d3.event.layerX - tooltip_width - 30) + "px"); } }) .on("mouseout", function() { $(this).attr("fill-opacity", "1.0"); $("#tooltip-container").hide(); }); g.append("path") .datum(topojson.mesh(world, world.objects.countries, function(a, b) { return a !== b; })) .attr("class", "boundary") .attr("d", path); }); function clicked(d) { if (active.node() === this) return reset(); active.classed("active", false); active = d3.select(this).classed("active", true); var bounds = path.bounds(d), dx = bounds[1][0] - bounds[0][0], dy = bounds[1][1] - bounds[0][1], x = (bounds[0][0] + bounds[1][0]) / 2, y = (bounds[0][1] + bounds[1][1]) / 2, scale = .9 / Math.max(dx / width, dy / height), translate = [width / 2 - scale * x, height / 2 - scale * y]; g.transition() .duration(750) .style("stroke-width", 1.5 / scale + "px") .attr("transform", "translate(" + translate + ")scale(" + scale + ")"); } function reset() { active.classed("active", false); active = d3.select(null); g.transition() .duration(750) .style("stroke-width", "1.5px") .attr("transform", ""); } d3.select(self.frameElement).style("height", height + "px"); }); </script> CSV ,Country,2000,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,Total ,Afghanistan,,,34,,,35,3,41,150,344,377,660,521,235,288,74,2763 ,African Union**,,,,,,53,9,,10,,,,,2,,2,76 ,Albania,,,,2,,7,,5,,,,,15,7,7,7,50 ,Algeria,412,546,249,195,242,159,340,509,1529,1075,829,1115,889,373,487,636,9585 ,Angola,147,156,148,48,8,39,7,31,29,20,,,,31,2,61,726 ,Argentina,213,6,16,12,168,3,9,24,23,16,14,46,36,44,14,23,668 ,Armenia,,,,,118,,,1,,46,52,5,,16,,,238 ,Australia,338,1236,663,801,472,459,727,675,445,775,1507,1567,876,255,932,1574,13301 ,Austria,25,15,69,42,55,23,4,269,190,286,7,8,8,6,2,,1008 ,Azerbaijan,3,,89,,4,53,159,211,29,142,146,557,333,398,602,285,3012 ,Bahamas,33,,,,,3,,,,1,,,,,29,22,88 ,Bahrain,299,30,54,1,10,66,63,26,20,,103,1,26,78,10,,786 ,Bangladesh,203,266,41,8,32,10,221,88,13,,35,193,252,727,257,653,2997 ,Barbados,,,,,,,,10,10,10,,,,,,,29 ,Belarus,,,,,,6,140,,,3,,75,75,75,60,164,598 ,Belgium,35,29,56,21,15,3,27,174,203,90,32,21,32,46,103,49,934 ,Belize,1,,,,,,,,,,,,,,,,1 ,Benin,,,7,,,,,3,,1,0,0,20,2,,,33 ,Bhutan,,,,,0,,,,,,,,,,,1,1 ,Bolivia,19,,,5,1,1,9,2,3,5,1,25,12,1,46,7,136 ,Botswana,53,15,1,9,8,,,,,13,10,,,8,,,119 ,Brazil,122,626,213,100,81,224,179,189,179,189,318,312,311,223,284,289,3840 ,Brunei,,4,1,0,0,1,2,,,33,33,229,22,15,122,36,498 ,Bulgaria,,,,2,12,132,22,48,116,119,8,29,5,,,3,495 ,Burkina Faso,,,,,,12,1,4,,2,0,15,4,,5,6,48 ,Burundi,,1,3,,,,,,,1,2,,5,,,1,14 ,Cambodia,,,,,,,14,56,,4,34,,77,67,,,252 ,Cameroon,,7,6,,,5,2,,1,,10,,,39,107,2,177 ,Canada,570,538,428,152,352,124,104,456,408,106,236,351,211,194,306,395,4931 ,Cape Verde,1,,,,,,,,,2,,,10,,,,12 ,Central African Republic,,,,,,,9,,0,,,7,,,,,16 ,Chad,,16,,,,,17,20,75,35,38,1,4,59,57,,322 ,Chile,202,60,74,187,70,449,1095,662,397,335,475,316,62,51,125,114,4673 ,China,2552,2833,2891,2373,3316,3558,2900,1692,1892,1407,1045,1128,1703,1452,1184,1214,33140 ,Colombia,60,278,171,147,18,16,56,234,110,248,255,145,207,162,190,215,2510 ,Comoros,,,,,,,,,6,,,,1,,,,7 ,Democratic Republic of Congo,0,,,,,4,1,0,,1,27,,23,12,13,,81 ,Costa Rica,,,,,,,,,,,,,1,,1,,2 ,Cote d'Ivoire,0,,32,61,9,,,,,,,,,,6,6,113 ,Croatia,,57,2,24,8,,,15,99,4,10,17,17,,63,11,326 ,Cyprus,4,115,1,6,16,20,26,17,,,44,41,6,,,,295 ,Czech Republic,14,65,47,97,7,593,45,10,23,6,13,71,22,15,,0,1029 ,Denmark,50,153,51,54,228,120,83,146,95,129,17,,109,42,177,0,1455 ,Djibouti,1,,3,,4,7,,,,,,,6,5,12,7,44 ,Dominican Republic,13,5,0,8,35,2,,,,10,51,12,,,,1,137 ,DR Congo,88,15,12,,,19,17,,18,41,151,,10,1,,,374 ,Ecuador,,10,1,,15,48,17,2,102,77,90,68,88,11,53,1,583 ,Egypt,837,847,719,630,654,778,678,651,333,159,686,630,281,675,368,1475,10401 ,El Salvador,,,19,10,,,,,4,,,,2,,20,,55 ,Equatorial Guinea,,7,,,7,12,,28,30,68,3,82,65,2,92,,394 ,Eritrea,20,57,18,17,80,98,,4,,,,,,,,,293 ,Estonia,27,,1,13,8,23,7,19,29,43,13,1,2,18,2,22,228 ,Ethiopia,140,,20,174,239,,,,,,54,76,193,153,30,,1079 ,Finland,474,9,12,227,77,107,136,108,135,42,50,58,76,280,156,228,2172 ,France,107,66,43,57,93,2,67,74,5,76,103,33,91,107,17,34,976 ,Gabon,,,,,6,,20,18,,,22,5,1,2,,3,77 ,Gambia,,,,,5,,,,,16,,,,,,,21 ,Georgia,6,85,,1,37,92,97,155,91,36,,2,,5,1,,608 ,Germany,113,133,71,64,239,204,416,82,292,339,282,84,157,113,114,102,2804 ,Ghana,1,12,1,6,35,0,0,16,,11,2,76,39,63,14,13,290 ,Greece,708,787,400,2274,1368,405,731,1712,516,1229,652,80,37,52,199,762,11910 ,Guatemala,,,,,,,,,0,,,,2,,,33,35 ,Guinea,18,6,,2,,1,,0,,,4,1,4,,5,,40 ,Guyana,,7,3,,,,,,0,,,,,,,,10 ,Palestine,,,,,,,,,,,1,2,,,0,,3 ,Lebanon,0,0,3,0,1,0,9,,,20,20,,,,,,54 ,Honduras,,,,,,,,,0,,,0,0,20,13,6,39 ,Hungary,14,14,,,72,13,253,196,5,4,18,9,,2,7,,604 ,Iceland,,,,,,,,,,,,50,,,,,50 ,India,995,1321,1911,2878,2180,1161,1480,2299,1867,1945,3017,3706,4545,5291,3487,3078,41160 ,Indonesia,151,27,68,356,79,36,61,577,239,436,225,250,218,802,1136,683,5341 ,Iran,418,524,435,282,121,57,423,331,62,62,103,103,47,31,13,13,3025 ,Iraq,,,,,71,186,305,263,377,398,453,603,474,353,650,1215,5348 ,Ireland,0,45,22,,19,5,13,19,22,0,5,2,1,,40,41,232 ,Israel,368,131,349,189,852,1133,1122,862,676,153,67,85,120,153,510,617,7387 ,Italy,241,221,243,559,446,162,433,525,220,109,115,298,219,91,145,596,4623 ,Jamaica,,,,,,10,10,13,2,,1,,,1,,2,38 ,Japan,491,441,477,480,400,464,511,498,680,525,426,292,270,306,480,310,7050 ,Jordan,133,160,121,281,157,49,48,176,161,234,87,214,175,81,252,198,2526 ,Kazakhstan,121,139,20,,47,42,44,82,25,39,58,52,84,141,93,419,1406 ,Kenya,15,,,,,,,10,,,112,14,60,1,17,30,258 ,Kosovo,,,,,,,,,,,,,,,1,,1 ,Kuwait,245,67,23,45,2,12,5,279,5,9,85,113,31,73,692,366,2052 ,Kyrgyzstan,,,,9,5,3,2,,,,,,,,,,19 ,Laos,7,36,,0,,4,,,7,26,,14,19,19,,7,138 ,Latvia,3,13,3,25,12,4,9,49,44,11,14,27,9,5,5,1,234 ,Lebanon,4,,,,,1,,5,1,52,64,2,31,24,5,50,239 ,Lesotho,,6,,,1,,1,,,,,,,,,,8 ,Liberia,8,,,,,,,,,,,,,,,,8 ,Libya,1,14,15,16,26,24,8,73,120,10,44,349 ,Lithuania,6,16,12,1,47,9,52,4,27,27,8,1,1,73,3,21,308 ,Uganda,,,0,,,,,,,,,,,,,,0 ,Sri Lanka,1,,1,,,0,,,,,,,,,,,2 ,Luxembourg,,,,1,,,,,,7,7,,1,,,,16 ,Macedonia,14,112,,,,,0,,,,,,,1,,,128 ,Madagascar,,,,,,,,,,0,,,,0,,,0 ,Malawi,,,,,,,,,3,,,,1,2,2,,7 ,Malaysia,26,26,154,147,65,57,398,526,508,1512,421,5,47,71,73,190,4227 ,Maldives,,,,,,,15,,,,5,,,5,,,24 ,Mali,7,,1,,,13,,8,3,10,,,9,6,,,57 ,Malta,0,0,,,,18,,,,,,8,8,,12,,45 ,Mauritania,31,,26,7,,,,,,,9,5,11,10,5,,103 ,Mauritius,,,,,6,,,,,,,,,,45,,51 ,Mexico,227,152,97,31,320,33,75,,22,59,112,229,261,69,97,500,2284 ,Mongolia,,,,,,,,,14,,27,3,51,,,13,107 ,Morocco,125,11,156,12,14,101,47,27,46,39,306,1398,826,82,572,42,3803 ,Mozambique,0,,,1,,,,,,,,0,0,9,12,,22 ,Myanmar,16,149,157,83,194,160,175,138,93,52,63,664,478,237,300,320,3277 ,Namibia,,21,11,,16,,72,6,66,15,,1,57,9,2,,276 ,NATO**,,18,,,,,116,,,420,,,22,,,,576 ,Nepal,,11,9,9,34,6,,,1,,,3,,,5,14,91 ,Netherlands,125,178,244,125,127,102,320,240,157,288,193,149,376,332,10,86,3052 ,New Zealand,,45,17,107,49,10,5,81,2,48,58,23,25,26,79,56,631 ,Nicaragua,,,,,,,,14,,14,,,,,,,28 ,Niger,,,,14,,,,,8,1,0,,,13,3,9,48 ,Nigeria,36,7,6,62,6,,15,52,19,65,186,65,4,35,224,188,971 ,Macedonia,,0,,,,,,,,,,,,,,,0 ,Afghanistan,19,207,,,,,,,,,,,,,,,226 ,North Korea,18,28,9,8,8,5,15,5,5,4,1,,,,,,103 ,Norway,229,99,90,14,6,14,542,567,624,588,159,614,149,62,23,143,3921 ,Libya,,,,,,,,,,,,0,,,,,0 ,Oman,118,33,40,36,41,173,301,16,75,93,30,21,120,490,736,148,2469 ,Pakistan,175,408,541,628,455,421,338,670,1047,1214,2176,1063,1028,1144,752,735,12797 ,Palestine,,,,,,,,2,,14,6,,,,,,21 ,Panama,,,1,,,,,,,7,,,15,30,,,52 ,Papua New Guinea,,,,,,,,,,,,,,,3,,3 ,Paraguay,,6,,,4,1,,,,,3,8,7,0,,,29 ,Peru,24,5,16,22,47,368,193,172,,43,46,63,10,49,153,169,1380 ,Philippines,,10,5,10,34,12,23,16,10,1,3,63,16,75,12,158,449 ,PIJ (Israel/Palestine)*,,,,,,,,,,,,,0,,,,0 ,Poland,148,36,255,349,225,112,463,990,594,169,158,92,207,155,252,131,4336 ,Portugal,2,19,,29,70,157,220,60,144,369,892,168,40,13,4,11,2196 ,PRC (Israel/Palestine)*,,,,,,,,,,,,,,,0,,0 ,Qatar,14,11,11,11,,,,27,,286,30,198,319,73,55,655,1690 ,Romania,21,19,16,17,285,437,61,98,78,56,105,80,21,20,0,22,1335 ,Russian Federation,,,,,,,4,100,,8,22,11,98,153,206,88,690 ,Rwanda,14,,,,,,2,15,8,11,,,5,11,14,7,86 ,Saudi Arabia,85,61,567,167,1170,167,205,214,363,796,1070,1237,1080,1672,2782,3161,14796 ,Senegal,,,,,,15,9,18,6,4,4,20,,6,7,11,99 ,Serbia,1,27,,,,,,,,,16,,0,,,,44 ,Seychelles,,,,,,15,,,,,,8,,7,16,,45 ,Sierra Leone,,,,,,,9,,,,,2,0,1,,,12 ,Singapore,798,254,234,83,376,538,69,355,1113,1481,1020,935,828,780,683,98,9645 ,Slovakia,0,,27,,,4,,0,,9,9,,2,6,,,57 ,Slovenia,2,41,2,17,17,3,3,2,,6,40,28,,,,,162 ,Somalia,,,,,,,,,,,,,,3,,0,3 ,South Africa,6,18,,,,262,708,881,486,128,180,212,132,2,50,,3065 ,South Korea,1396,773,528,752,1059,804,1650,1755,1683,796,1250,1553,1066,182,715,245,16207 ,South Sudan,,,,,,,,37,44,1,,61,3,5,18,22,190 ,Spain,334,168,289,253,315,363,315,337,346,235,290,181,238,176,127,153,4119 ,Sri Lanka,297,161,45,35,49,58,97,89,71,,5,21,,,5,,934 ,Sudan,,106,49,204,293,132,65,33,128,89,182,173,119,196,61,27,1858 ,Suriname,2,,,,,,,,,,,,,7,,3,11 ,Swaziland,1,1,,,,,,,,,,,,,,,2 ,Sweden,204,165,75,64,47,78,122,41,44,54,51,191,206,52,43,43,1481 ,Switzerland,23,68,63,117,203,168,83,108,18,39,47,31,9,4,0,2,983 ,Syrian Arab Republic,64,28,45,69,25,35,100,20,276,193,298,368,371,361,15,,2267 ,Syria rebels*,,,,,,,,,,,,,1,1,0,,2 ,Taiwan (ROC),585,345,298,117,319,691,503,12,11,60,97,198,425,553,1084,681,5978 ,Tajikistan,,,,,,,13,7,,32,,,,3,,,55 ,Tanzania,3,,,51,,10,12,1,,25,,78,115,92,26,20,433 ,Thailand,85,117,152,131,114,70,47,8,13,64,49,270,289,392,93,185,2078 ,Timor-Leste,,,,,,,,,,,18,,,,,,18 ,Togo,,,,,,,,,,,1,,4,,16,,21 ,Tonga,,,,,,,,,,,,,,,,5,5 ,Trinidad and Tobago,11,0,2,,,,,6,,,24,12,12,,,46,113 ,Tunisia,,,83,1,,168,2,,3,,7,7,,38,43,6,357 ,Turkey,1190,510,905,330,257,1113,531,692,710,780,484,770,1503,650,1556,448,12427 ,Turkmenistan,,,,18,21,,,,7,36,14,236,115,69,102,122,739 ,UAE,247,186,222,685,1210,2170,2021,919,752,561,605,1210,1088,2235,731,1289,16128 ,Uganda,6,,34,,36,10,5,,5,23,37,471,219,20,,3,868 ,UIC (Somalia)*,,,,,,,0,,,,,,,,,,0 ,Ukraine,,,,,,,,,,,,,,,1,10,11 ,Ukraine Rebels*,,,,,,,,,,,,,,,24,,24 ,United Kingdom,871,1277,719,761,212,27,308,764,508,383,511,368,586,492,214,382,8381 ,United Nations**,31,,,,2,1,2,1,0,,,,,5,23,4,69 ,United States,330,487,499,592,560,520,641,819,951,968,1111,995,1180,802,566,565,11587 ,United Wa State (Myanmar)*,,1,,,,,,,,,,,,,,,1 ,Unknown country,,2,,0,,,,,12,,8,6,,30,31,51,139 ,Unknown rebel group*,,0,0,,,,,,,,,,,,,,0 ,Uruguay,4,,11,,,20,8,3,78,29,20,,,6,9,8,196 ,Uzbekistan,,8,9,,,,,,,,,,,,,62,79 ,Venezuela,108,103,50,15,9,21,380,774,737,358,208,594,680,1165,173,162,5535 ,Viet Nam,7,85,66,28,304,297,41,8,204,78,184,1039,766,362,1078,870,5414 ,Yemen,158,85,593,62,254,317,38,66,40,5,258,45,38,23,6,12,2000 ,Zambia,33,,2,,,0,30,5,2,,,1,66,,,24,161 ,Zimbabwe,3,10,,16,,25,25,,,,,,,,,,78
how to add title to d3js bubble chart with force layout
This is the code of the bubble chart i created. I have used force layout to create the chart. var margin = { top: 10, right: 10, bottom: 10, left: 10 }, width = 1000 - margin.left - margin.right, height = 600 - margin.top - margin.bottom; d3.select('#' + divId).append('div').attr('id', 'chart').attr('class', 'chart'); var n = data.vistaJson.length; m = 1, padding = 5, radius = d3.scale.sqrt().range([10, 50]), color = d3.scale.category10().domain(d3.range(m)), x = d3.scale.ordinal().domain(d3.range(m)).rangePoints([0, width], 1); var xscale = d3.scale.linear() .domain([0, 500]) .range([20, 500]); var nodes = []; for(var i=0; i< n; i++){ var coordinates = data.vistaJson[i].SLAB.split('_'); v = data.vistaJson[i].COUNT nodes.push({ radius: radius(v), color: color(i), count: v, cx: xscale(x(i)), cy: xscale(height / 2), xAxis: coordinates[0], yAxis: coordinates[1] }); } var svg = d3.select("#chart").append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); var force = d3.layout.force() .nodes(nodes) .size([width, height]) .gravity(0.5) .charge(0.5) .on("tick", tick) .start(); 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); var labels = svg.selectAll("text") .data(nodes) .enter() .append("text") .attr({"x":function(d){return d.x;}, "y":function(d){return d.y;}}) .text(function(d){return d.count;}) .call(force.drag); circle.each(gravity(.2 * e.alpha)) .each(collide(.5)) .attr("cx", function (d) { return d.x; }) .attr("cy", function (d) { return d.y; }); labels.attr("x", function(d) { return d.x; }) .attr("y", 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 + radius.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.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; }); }; } I want to add title to the node which is displayed when mouse is hovered on the node . Earlier I used pack layout and I gave title like this : var node = vis.selectAll("g.node") .data(bubble.nodes(classes(json), function(d) { return d.name; }) .filter(function(d) { return !d.children; })) .enter() .append("svg:g") .attr("class", "node") .attr("transform", function(d) { return "translate(" + xscale(d.x) + "," + xscale(d.y) + ")"; }); node.append("svg:title") .text(function(d) { return d.xAxis + ": " + d.yAxis; }); How can we show title when using forceLayout . Please help .
I think it should look something like this in the force layout: var node = svg.selectAll(".node") .data(graph.nodes) .enter().append("circle") .attr("class", function(d) { return d.nodes ? "nonleaf" : "leaf"; }) .attr("r", 5) .call(force.drag); node.append("title") .text(function(d) { return d.count; }); I hope this helps somehow. For me it works like that..