Skip to content

Instantly share code, notes, and snippets.

@BadCoder1337
Forked from ZJONSSON/force_labels.js
Last active May 18, 2023 09:06
Show Gist options
  • Select an option

  • Save BadCoder1337/7f2cdc379d8c69debf8a2e6695515ede to your computer and use it in GitHub Desktop.

Select an option

Save BadCoder1337/7f2cdc379d8c69debf8a2e6695515ede to your computer and use it in GitHub Desktop.
Automatic floating labels using d3 force-layout
/*
D3 force labels v 0.1
Generates an automatic positioning for labels, using the force layout
Function .update should be called on the selection of anchors
Anchor positions are determiend by svg bounding boxes and stored separately in anchorPos x,y
After update, the label positions are found in __data__.labelPos
ziggy.jonsson.nyc@gmail.com
*/
if (typeof d3.z != "object") d3.z = {};
(function(){
d3.z.labels = function() {
var label = d3.layout.force()
,margin=-1
// Update the position of the anchor based on the center of bounding box
function updateAnchor() {
if (!label.selection) return
label.selection.each(function(d) {
var bbox = this.getBBox(),
x=bbox.x+bbox.width/2,
y=bbox.y+bbox.height/2
d.anchorPos.x=x
d.anchorPos.y=y
// If a label position does not exist, set it to be the anchor position
if (d.labelPos.x==null) {
d.labelPos.x=x
d.labelPos.y=y
}
})
}
//The anchor position should be updated on each tick
label.on("tick.labels",updateAnchor)
// This updates all nodes/links - retaining any previous labelPos on updated nodes
label.update = function(selection) {
label.selection = selection
var nodes=[],links=[];
selection[0].forEach(function(d) {
if(d && d.__data__) {
var data = d.__data__
if (!d.labelPos) d.labelPos = {fixed:false}
if (!d.anchorPos) d.anchorPos = {fixed:true}
// Place position objects in __data__ to make them available through d.labelPos/d.anchorPos for different elements
data.labelPos = d.labelPos
data.anchorPos = d.anchorPos
links.push({target:d.anchorPos,source:d.labelPos})
nodes.push(d.anchorPos)
nodes.push(d.labelPos)
}
})
label.stop().nodes(nodes).links(links)
updateAnchor()
label.start()
}
return label
}
})()
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.js"></script>
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.geom.js"></script>
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.layout.js"></script>
<script type="text/javascript" src="d3.z.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;opacity:0.8;stroke-width:0.2}
</style>
</head>
<body>
<div style="width:150px;float:left">
<span id="corr-label">Correlation: </span><br>
<input type="range" min="-1.0" max="1.0" value="0.0" id="corr" step="0.01"/>
</div>
<div style="width:150px;float:left">
<span id="charge-label">Label charge: </span><br>
<input type="range" min="0" max="100" value="60.0" id="charge" step="1"/>
</div>
<button type="button" id="addone">Add one measurement</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>
<script type="text/javascript">
var w=960,h=500,
x_mean = w/2,
x_std = w/10,
y_mean = h/2,
y_std = h/10,
labelBox,link,
data=[];
var svg=d3.select("body")
.append("svg:svg")
.attr("height",h)
.attr("width",w)
function refresh() {
// plot the data as usual
anchors = svg.selectAll(".anchor").data(data,function(d,i) { return i})
anchors.exit().attr("class","exit").transition().duration(1000).style("opacity",0).remove()
anchors.enter().append("circle").attr("class","anchor").attr("r",4).attr("cx",function(d) { return d.x}).attr("cy",function(d) { return h-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 h-d.y})
// Now for the labels
anchors.call(labelForce.update) // This is the only function call needed, the rest is just drawing the labels
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",11)
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})
}
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})
}
// Initialize the label-forces
labelForce = d3.z.labels()
.linkDistance(0.0)
.gravity(0)
.nodes([]).links([])
.charge(-60)
.on("tick",redrawLabels)
// and now for the data functionality
function randomize(count) {
z1=d3.random.normal()
z2=d3.random.normal()
data=data.concat(d3.range(count || 100).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").text("Correlation: "+d3.format("%")(corr))
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()
}
// and finally hook up the controls
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("#addone").on("click",function() { randomize(1)})
d3.select("#corr")
.on("change",function() { d3.select("#corr-label").text("Correlation: "+d3.format("%")(this.value))})
.on("mouseup",correlate)
d3.select("#charge")
.on("change",function() {
d3.select("#charge-label").text("Label charge: "+d3.format("f")(this.value))
labelForce.charge(-this.value).start()
})
randomize()
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment