This block is a continuation of a previous one. The main difference resides in the version of D3 (eg. v3.5 VS v4-beta).
I was not totally satisfied of my previous block because nodes could overlap, making hovering of individual nodes possibly tedious. With the new/future release of D3 v4, this can be handled easily with the colision detection feature embeded in the new version of the force layout.
By the way, there is still some overlapping. Playing with forces' strength and/or radius/distance may help ...
xxxxxxxxxx
<meta charset="utf-8">
<style>
circle {
stroke-width: 1.5px;
}
line {
stroke: #999;
}
</style>
<body>
<script src="https://d3js.org/d3.v4.0.0-alpha.35.min.js"></script>
<script>
var width = 960,
height = 500,
radius = 4;
var fill = d3.scaleLinear().domain([1,150]).range(['lightgreen', 'pink']);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
svg.append("line")
.attr("x1", 0)
.attr("y1", height/2)
.attr("x2", width)
.attr("y2", height/2)
.style("stroke", "lightgrey");
var tooltip = svg.append("g")
.attr("transform", "translate("+[width/2, 50]+")")
.style("opacity", 0);
var titles = tooltip.append("g").attr("transform", "translate("+[-5,0]+")")
titles.append("text").attr("text-anchor", "end").text("stem(fr):");
titles.append("text")
.attr("text-anchor", "end")
.attr("transform", "translate("+[0,15]+")")
.text("rank:");
titles.append("text")
.attr("text-anchor", "end")
.attr("transform", "translate("+[0,30]+")")
.text("x-value:");
var values = tooltip.append("g").attr("transform", "translate("+[5,0]+")")
var stem = values.append("text");
stem.attr("text-anchor", "start");
var rank = values.append("text");
rank.attr("text-anchor", "start")
.attr("transform", "translate("+[0,15]+")");
var value = values.append("text");
value.attr("text-anchor", "start")
.attr("transform", "translate("+[0,30]+")");
function dottype(d) {
d.stem = d.stem;
d.rank = +d.rank;
d.trend = +d.trend;
d.originalX = width/2+d.trend*6000;
d.x = d.originalX;
d.y = height/2;
return d;
}
d3.csv("data.csv", dottype, function(error, trendData) {
if (error) throw error;
var node = svg.selectAll("circle")
.data(trendData)
.enter().append("circle")
.attr("r", radius - .75)
.attr("cx", function(d) { return d.x; })
.attr("cy", height/2)
.style("fill", function(d) { return fill(d.rank); })
.style("stroke", function(d) { return d3.rgb(fill(d.rank)).darker(); })
.on("mouseenter", function(d) {
stem.text(d.stem);
rank.text(d.rank);
value.text(d.trend);
tooltip.transition().duration(0).style("opacity", 1); // remove fade out transition on mouseleave
})
.on("mouseleave", function(d) {
tooltip.transition().duration(1000).style("opacity", 0);
});
var iterationCount = 0;
var force = d3.forceSimulation()
.nodes(trendData)
//.force("collide", d3.forceCollide(radius)) //handles nodes's overlapping; radius is constantly increased in 'tick' function in order to handle jitter effect
//.force("maintainXPosition", d3.forceX(function(d) { return d.x; }).strength(1)) //does not replaces first line of code in 'tick' function because it can NOT garanty x-position with regards to other forces
.force("groupOnXAxis", d3.forceY(function(d) { return height/2; }).strength(0.5)) //allows to regroup nodes if somes are ejected too far away by 'forceCollide'
.on("tick", tick);
force.restart();
function tick() {
//begin: handle jitter effect by constantly increase collision radius
var jitterHandlingPhase = 150;
var newCollideRadius;
iterationCount++;
if (iterationCount<jitterHandlingPhase) {
newCollideRadius = 1+(radius-1)*Math.pow((iterationCount/jitterHandlingPhase),2);
force.force("collide", d3.forceCollide(newCollideRadius)) //handles nodes's overlapping
}
//end: handle jitter effect by constantly update increase radius
node.each(function(d){d.x = d.originalX; }) //constrains/fixes x-position
node.attr("cx", function(d) {return d.x = Math.max(radius, Math.min(width - radius, d.x)); })
.attr("cy", function(d) {return d.y = Math.max(radius, Math.min(height - radius, d.y)); });
}
});
</script>
https://d3js.org/d3.v4.0.0-alpha.35.min.js