Skip to content

Instantly share code, notes, and snippets.

@mzur
Last active August 29, 2015 14:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mzur/b30b932d1b9544644abd to your computer and use it in GitHub Desktop.
Save mzur/b30b932d1b9544644abd to your computer and use it in GitHub Desktop.
Force layout with labeled links

A simple force layout with labeled links. The labels appear only if the adjacent nodes are hovered.

<!doctype html>
<meta charset="utf-8">
<style type="text/css">
text {
font: 10pt sans-serif;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
.link {
stroke: black;
stroke-width: 1px;
opacity: 0.25;
}
.link-label {
opacity: 0.5;
}
.node {
cursor: pointer;
}
</style>
<svg class="network" width="960" height="500"></svg>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script type="text/javascript">
var $svg = d3.select('svg');
var $layout = d3.layout.force()
.size([960, 500])
.linkDistance(100)
.charge(-500)
.on('tick', tick);
var $link, $node, $linkLabel;
// rendering function for positioning links and nodes
function tick(e) {
$link.attr('d', function (d) {
// make sure the path is always drawn left to right, so the textPath
// text is not upside down
return (d.source.x < d.target.x)
? 'M' + d.source.x + ',' + d.source.y
+ 'L' + d.target.x + ',' + d.target.y
: 'M' + d.target.x + ',' + d.target.y
+ 'L' + d.source.x + ',' + d.source.y;
});
$node.attr('transform', function (d) {
return 'translate(' + d.x + ',' + d.y + ')';
});
};
// updates the d3 $linkLabel object with new data
function updateLinkLabels(links) {
// update link label data (a subset of the links)
$linkLabel = $svg.selectAll('.link-label')
.data(links);
// add a textPath element for each label to display
$linkLabel.enter()
// a textPath must be inside a text element
.append('svg:text')
.attr('text-anchor', 'middle')
.attr('class', 'link-label')
.append('svg:textPath')
// aligns the text at the middle of the path (only with text-anchor=middle)
.attr('startOffset', '50%')
// attach this label to the correct path using its id
.attr('xlink:href', function (d) {
return '#' + d.id;
})
.text(function (d) {
return d.label;
});
$linkLabel.exit()
.remove();
};
// Overwrites the link source and target attributes with references to the
// respective node objects. This makes positioning of the link more efficient
// in the 'tick' function.
var linkLinksToNodesIdCount = 0;
function linkLinksToNodes(nodes, links) {
// output is the new links array
var output = new Array(links.length);
links.forEach(function (link, i) {
output[i] = {
// now source and target are no strings but references to the actual
// node objects
source: nodes[link.source],
target: nodes[link.target],
label: link.label,
// a unique id for this link
id: 'link-' + linkLinksToNodesIdCount++
};
});
return output;
};
function makeGraph(nodes, links) {
// convert incoming links to own interlinked datastructure
links = linkLinksToNodes(nodes, links);
// add links array to every node
for (name in nodes) {
nodes[name].links = [];
};
// add link to every node that joins it
links.forEach(function (link) {
link.source.links.push(link);
link.target.links.push(link);
});
// update layout data
$layout.nodes(d3.values(nodes))
.links(links)
.start();
// update link data
$link = $svg.selectAll('.link')
.data(links);
// add links as paths to the svg
$link.enter()
.append('svg:path')
.attr('class', 'link')
// make sure the link has a unique id for it to get referenced from the
// link label textPath
.attr('id', function (d) {
return d.id;
});
// update node data
$node = $svg.selectAll('.node')
.data(d3.values(nodes));
// add nodes as text elements to the svg
$node.enter()
.append('text')
.attr('text-anchor', 'middle')
.attr('class', 'node')
// attempt to vertically center the text
.attr('dy', '0.25em')
.text(function (d) {
return d.name;
})
// display the adjacent link labels on mouseenter
.on('mouseenter', function (d) {
updateLinkLabels(d.links);
})
// remove the adjacent link labels on mouseout
.on('mouseout', function (d) {
updateLinkLabels([]);
})
.call($layout.drag);
};
d3.json('links.json', function (e, links) {
d3.json('nodes.json', function (e, nodes) {
makeGraph(nodes, links);
});
});
</script>
{
"fgf":{
"name":"fgf"
},
"sulf1 regulation":{
"name":"sulf1 regulation"
},
"fgf activity":{
"name":"fgf activity"
},
"fgf4":{
"name":"fgf4"
},
"mesodermal apoptosis":{
"name":"mesodermal apoptosis"
},
"sulf1a":{
"name":"sulf1a"
},
"wnt signalling,":{
"name":"wnt signalling,"
},
"sulf1b":{
"name":"sulf1b"
},
"wnt signalling":{
"name":"wnt signalling"
},
"angiogenesis":{
"name":"angiogenesis"
},
"qsulf1":{
"name":"qsulf1"
},
"protein":{
"name":"protein"
},
"enzyme":{
"name":"enzyme"
},
"sulfation state":{
"name":"sulfation state"
},
"enzymes":{
"name":"enzymes"
},
"activities":{
"name":"activities"
},
"and":{
"name":"and"
},
"key role":{
"name":"key role"
},
"sulf1":{
"name":"sulf1"
},
"co-receptor function":{
"name":"co-receptor function"
},
"drug-induced apoptosis":{
"name":"drug-induced apoptosis"
},
"be":{
"name":"be"
},
"essential":{
"name":"essential"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment