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> |