A simple force layout with labeled links. The labels appear only if the adjacent nodes are hovered.
Last active
August 29, 2015 14:02
-
-
Save mzur/b30b932d1b9544644abd to your computer and use it in GitHub Desktop.
Force layout with labeled links
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[ | |
{ | |
"source":"fgf", | |
"label":"mediated", | |
"target":"sulf1 regulation" | |
}, | |
{ | |
"source":"sulf1", | |
"label":"inhibit", | |
"target":"fgf activity" | |
}, | |
{ | |
"source":"fgf4", | |
"label":"inhibited", | |
"target":"mesodermal apoptosis" | |
}, | |
{ | |
"source":"sulf1a", | |
"label":"enhances", | |
"target":"wnt signalling," | |
}, | |
{ | |
"source":"sulf1b", | |
"label":"inhibits", | |
"target":"wnt signalling" | |
}, | |
{ | |
"source":"sulf1b", | |
"label":"promotes", | |
"target":"angiogenesis" | |
}, | |
{ | |
"source":"qsulf1", | |
"label":"is", | |
"target":"protein" | |
}, | |
{ | |
"source":"enzyme", | |
"label":"modulates", | |
"target":"sulfation state" | |
}, | |
{ | |
"source":"enzymes", | |
"label":"regulate", | |
"target":"activities" | |
}, | |
{ | |
"source":"enzymes", | |
"label":"follow", | |
"target":"and" | |
}, | |
{ | |
"source":"sulf1", | |
"label":"plays", | |
"target":"key role" | |
}, | |
{ | |
"source":"sulf1", | |
"label":"inhibits", | |
"target":"co-receptor function" | |
}, | |
{ | |
"source":"sulf1", | |
"label":"promotes", | |
"target":"drug-induced apoptosis" | |
}, | |
{ | |
"source":"sulf1", | |
"label":"inhibits", | |
"target":"and" | |
}, | |
{ | |
"source":"sulf1", | |
"label":"seems", | |
"target":"be" | |
}, | |
{ | |
"source":"sulf1", | |
"label":"be", | |
"target":"essential" | |
} | |
] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"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