This example demonstrates the force_labels plugin for D3.
It is based on /ZJONSSON/1691430 but has been updated for D3 4.x.
d3-force-labels.js |
This example demonstrates the force_labels plugin for D3.
It is based on /ZJONSSON/1691430 but has been updated for D3 4.x.
<!DOCTYPE html> | |
<html> | |
<head> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
<!-- <script type="text/javascript" src="d3-force-labels.js"></script> --> | |
<!-- <script src="https://raw.githack.com/TylerRick/d3-force-labels/master/src/d3-force-labels.js"></script> --> | |
<script src="https://rawcdn.githack.com/TylerRick/d3-force-labels/master/src/d3-force-labels.js"></script> | |
<style> | |
.anchor { fill:blue } | |
.labelbox { | |
fill:black; | |
opacity:0.8 | |
} | |
.labeltext { | |
fill:white; | |
font-weight:bold; | |
text-anchor:middle; | |
font-size:16; | |
font-family: serif | |
} | |
.link { | |
stroke:gray; | |
stroke-width:0.35 | |
} | |
rect { | |
fill: none; | |
stroke: #aaa; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="controls"> | |
<div style="width:150px; float:left"> | |
<span id="corr-label"></span><br> | |
<input type="range" min="-1.0" max="1.0" id="corr" step="0.01" /> | |
</div> | |
<div style="width:150px; float:left"> | |
<span id="charge-label"></span><br> | |
<input type="range" min="0" max="100" id="charge" step="1" value="60.0" /> | |
</div> | |
<div style="width:150px; float:left"> | |
<span id="collide-label"></span><br> | |
<input type="range" min="0" max="20" id="collide" step="1" /> | |
</div> | |
<button type="button" id="add-one">Add one point</button> | |
<button type="button" id="randomize20">Replace with 20</button> | |
<button type="button" id="randomize50">Replace with 50</button> | |
<button type="button" id="randomize100">Replace with 100</button> | |
</div> | |
<script type="text/javascript"> | |
var width = 960, height = 500, | |
label_radius = 11, | |
x_mean = width/2, | |
x_std = width/10, | |
y_mean = height/2, | |
y_std = height/10, | |
labelBox, link, | |
data = []; | |
var svg = d3.select("body") | |
.append("svg:svg") | |
.attr("height", height) | |
.attr("width", width) | |
svg | |
.append("rect") | |
.attr("width", width - 1) | |
.attr("height", height - 1) | |
function refresh() { | |
// Plot the data | |
anchors = svg.selectAll(".anchor") | |
.data(data, function(d, i) { return i }) | |
anchors.exit() | |
.attr("class", "exit") | |
.transition() | |
.duration(1000) | |
.style("opacity", 0) | |
.remove() | |
anchors = anchors.enter() | |
.append("circle") | |
.merge(anchors) | |
.attr("class", "anchor") | |
.attr("r", 4) | |
.attr("cx", function(d) { return d.x }) | |
.attr("cy", function(d) { return height - d.y }) | |
anchors | |
.transition() | |
.delay(function(d, i) { return i*10 }) | |
.duration(1500) | |
.attr("cx", function(d) { return d.x }) | |
.attr("cy", function(d) { return height - d.y }) | |
// Now for the labels | |
anchors.call(labelSim.update) | |
labels = svg.selectAll(".labels"). | |
data(data, function(d, i) { return i}) | |
labels.exit() | |
.attr("class", "exit") | |
.transition() | |
.delay(0) | |
.duration(500) | |
.style("opacity", 0) | |
.remove() | |
// Draw the labelbox, caption and the link | |
newLabels = labels.enter() | |
.append("g") | |
.attr("class", "labels") | |
newLabelBox = newLabels | |
.append("g") | |
.attr("class", "labelbox") | |
newLabelBox | |
.append("circle") | |
.attr("r", label_radius) | |
newLabelBox | |
.append("text") | |
.attr("class", "labeltext") | |
.attr("y", 6) | |
newLabels. | |
append("line") | |
.attr("class", "link") | |
labelBox = svg.selectAll(".labels").selectAll(".labelbox") | |
links = svg.selectAll(".link") | |
labelBox.selectAll("text") | |
.text(function(d) { return d.num}) | |
labelSim.alpha(1).restart() | |
} | |
function redrawLabels() { | |
labelBox | |
.attr("transform", function(d) { | |
return "translate(" + d.labelPos.x + " " + d.labelPos.y + ")" | |
}) | |
links | |
.attr("x1", function(d) { return d.anchorPos.x }) | |
.attr("y1", function(d) { return d.anchorPos.y }) | |
.attr("x2", function(d) { return d.labelPos.x }) | |
.attr("y2", function(d) { return d.labelPos.y }) | |
} | |
// Functions to generate random data points | |
function randomize(count) { | |
z1 = d3.randomNormal() | |
z2 = d3.randomNormal() | |
data = data.concat(d3.range(count).map(function(d, i) { | |
return { | |
z1: z1(), | |
z2: z2(), | |
num: data.length + i | |
}}) | |
) | |
correlate() | |
} | |
function correlate() { | |
var corr = d3.select("#corr").property("value") | |
d3.select("#corr-label").data([corr]) | |
updateControls() | |
data.forEach(function(d) { | |
d.x = x_mean + (d.z1*x_std); | |
d.y = y_mean + y_std*(corr*d.z1 + d.z2*Math.sqrt(1 - Math.pow(corr, 2))) | |
}) | |
refresh() | |
} | |
// Hook up the controls | |
function updateControls() { | |
d3.select("#corr-label").text(function(d) { | |
return "Correlation: " + d3.format(".1%")(d) | |
}) | |
d3.select("#charge-label").text(function(d) { | |
return "Label charge: " + d3.format(".3")(d) | |
}) | |
d3.select("#collide-label").text(function(d) { | |
return "Collide radius: " + d3.format(".3")(d) | |
}) | |
} | |
function changeCharge(strength) { | |
labelSim.force('charge').strength(-strength) | |
d3.select("#charge").property("value", strength) | |
d3.select("#charge-label").data([strength]) | |
updateControls() | |
} | |
function changeCollide(newRadius) { | |
if (newRadius <= 0) { | |
labelSim.force('collide', null) | |
} else { | |
labelSim.force('collide', d3.forceCollide(newRadius)) | |
} | |
d3.select("#collide").property("value", newRadius) | |
d3.select("#collide-label").data([newRadius]) | |
updateControls() | |
} | |
d3.select("#randomize20"). on("click", function() { data = []; randomize(20) }) | |
d3.select("#randomize50"). on("click", function() { data = []; randomize(50) }) | |
d3.select("#randomize100").on("click", function() { data = []; randomize(100) }) | |
d3.select("#add-one"). on("click", function() { randomize(1) }) | |
d3.select("#corr") | |
.on("mouseup", correlate) | |
d3.select("#charge") | |
.on("change", function() { | |
changeCharge(this.value) | |
labelSim.alpha(0.2).restart() | |
}) | |
d3.select("#collide") | |
.on("change", function() { | |
changeCollide(this.value) | |
labelSim.alpha(0.1).restart() | |
}) | |
// Start the simulation | |
labelSim = d3.forceLabels() | |
.on("tick", redrawLabels) | |
randomize(100) | |
changeCharge(50) | |
changeCollide(0) | |
</script> | |
</body> | |
</html> |