Skip to content

Instantly share code, notes, and snippets.

@bengarvey
Last active May 12, 2016 16:32
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 bengarvey/a2529aae58968ba9b5e31595971fa88f to your computer and use it in GitHub Desktop.
Save bengarvey/a2529aae58968ba9b5e31595971fa88f to your computer and use it in GitHub Desktop.
Force directed graph with multiple tick functions
height: 500
border: no

Force directed graph with multiple tick functions

One of the more interesting things to do in force directed graphs is to swap out the function that controls their position. Doing that allows you to view the same dataset in many ways.

(mouse click to swap between tick functions)

Credit

RJMetrics

D3 Tooltips by Lab Rat Revenge

<!DOCTYPE html>
<html>
<meta charset="utf-8">
<style>
.node {
stroke: #fff;
stroke-width: 1.5px;
}
.link {
stroke: #999;
stroke-opacity: .6;
}
.d3-tip {
font-family: sans-serif;
line-height: 1;
font-weight: bold;
padding: 12px;
background: rgba(0, 0, 0, 0.8);
color: #fff;
border-radius: 2px;
}
.d3-tip:after {
box-sizing: border-box;
display: inline;
font-size: 10px;
width: 100%;
line-height: 1;
color: rgba(0, 0, 0, 0.8);
content: "\25BC";
position: absolute;
text-align: center;
}
.d3-tip.n:after {
margin: -1px 0 0 0;
top: 100%;
left: 0;
}
</style>
<body></body>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-tip/0.6.7/d3-tip.min.js"></script>
<script>
var width = 960,
height = 500;
var force = d3.layout.force()
.charge(-120)
.linkDistance(30)
.size([width, height]);
var forceLabels = d3.layout.force()
.size([width, height]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function(d) {
return "<strong>" + d.name + "</strong> <span style='color:#CCC'>" + d.role + "</span>";
});
svg.call(tip);
d3.csv("org.csv", function(error, org) {
var color = d3.scale.category20();
var indexMap = getIndexMap(org);
var nameMap = getNameMap(org);
var links = getLinks(indexMap, nameMap, org);
var projects = getProjects(org);
var fociMap = getFociMap(projects);
var labels = [];
var displayedLinks = [];
var graph = {
"links": links,
"nodes": org
}
var node = svg.selectAll(".node");
var link = svg.selectAll(".link");
var label = svg.selectAll(".label");
force
.nodes(graph.nodes)
.links(graph.links)
.start();
function initTeam() {
displayedLinks = [];
labels = [
{name: 'Web'},
{name: 'Core'},
{name: 'Data'}
];
force.charge(-50).gravity(0.05);
forceLabels.charge(-2000).gravity(0.1).start();
}
function initHierarchy() {
displayedLinks = links;
labels = [
{name:"RJMetrics Engineering"}
];
force.charge(-220).gravity(0.05);
forceLabels.charge(-10).gravity(0.05).start();
}
function initProject() {
displayedLinks = [];
labels = [];
for(i=0; i<projects.length; i++) {
labels.push( {name:projects[i]} );
}
force.charge(-10).gravity(0.05);
forceLabels.charge(-10).gravity(0.05).start();
}
function start() {
link = link.data(displayedLinks);
link.enter().append("line")
.attr("class", "link")
.style("stroke-width", function(d) { return Math.sqrt(d.value); });
link.exit().remove();
force.links(displayedLinks).start();
node = node.data(force.nodes());
node.enter().append("circle")
.attr("class", "node")
.attr("r", 5)
.style("fill", function(d) { return color(d.team); })
.call(force.drag)
.on("mousedown", function() { d3.event.stopPropagation(); })
.on('mouseover', tip.show)
.on('mouseout', tip.hide);
node.exit().remove();
node.append("title")
.text(function(d) { return d.name; });
label = svg.selectAll(".label")
.data(labels);
label.enter().append("text")
.attr("class", "label")
.attr("font-family", "sans-serif")
.attr("font-size", "20px")
.attr("fill", "#999");
label.exit().remove();
forceLabels.nodes(labels).start();
label.text(function(d) { return d.name; });
}
var hierarchy = function() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
label.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; });
};
var team = function(e) {
var k = 6 * e.alpha;
function getAnchor(team, dir) {
if (dir == 'x') {
result = ['Web', 'Core'].indexOf(team) > -1 ? true : false;
}
else {
result = ['Web', 'Data'].indexOf(team) > -1 ? true : false;
}
return result;
}
graph.nodes.forEach(function(o, i) {
o.y += getAnchor(o.team, 'y') ? k : -k;
o.x += getAnchor(o.team, 'x') ? k : -k;
});
labels.forEach(function(o, i) {
o.y += getAnchor(o.name, 'y') ? k : -k;
o.x += getAnchor(o.name, 'x') ? k : -k;
});
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
label.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; });
};
var project = function(e) {
var k = 0.1 * e.alpha;
graph.nodes.forEach(function(o, i) {
o.y += (fociMap[o.project].y - o.y) * k;
o.x += (fociMap[o.project].x - o.x) * k;
});
labels.forEach(function(o, i) {
o.y += (fociMap[o.name].y - o.y) * k;
o.x += (fociMap[o.name].x + 100 - o.x) * k;
});
node
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
label
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; });
}
var forceFns = [team, hierarchy, project];
var initForceFns = [initTeam, initHierarchy, initProject];
var forceFnIndex = 1;
var forceFn = forceFns[forceFnIndex];
initForceFns[forceFnIndex]();
force.on("tick", forceFn);
start();
function nextForceFn() {
forceFnIndex += 1;
if (forceFnIndex == forceFns.length) {
forceFnIndex = 0;
}
forceFn = forceFns[forceFnIndex];
force.on("tick", forceFn);
}
d3.select("body")
.on("mousedown", mousedown)
.on("touchstart", mousedown);
function mousedown() {
nextForceFn();
initForceFns[forceFnIndex]();
start();
}
});
function getIndexMap(items) {
map = {};
for(i=0; i<items.length; i++) {
map[i] = items[i];
}
return map;
}
function getNameMap(items) {
map = {};
for(i=0; i<items.length; i++) {
map[items[i].name.trim()] = i;
}
return map;
}
function getFociMap(items) {
var map = {};
var baseX = 300;
var baseY = 50;
var dy = 60;
var dx = 0;
for(i=0; i<items.length; i++) {
y = baseY + (i*dy);
map[items[i]] = { x: baseX+(i*dx), y: baseY+(i*dy) };
}
return map;
}
function getProjects(items) {
projects = [];
for(i=0;i<items.length;i++) {
if (projects.indexOf(items[i].project) < 0) {
projects.push(items[i].project);
}
}
return projects;
}
function getLinks(indexMap, nameMap, items) {
var links = [];
for(i=0; i<items.length; i++) {
source = nameMap[items[i].name];
target = nameMap[items[i].manager];
color = "#CCC";
link = {
"source": source,
"target": target,
"color": color,
"value": 1,
"weight": 1
}
if (target != null) {
links.push(link);
}
}
return links;
}
</script>
</html>
name role team manager project
Alex Mann Software Engineer Data Connor McArthur Aries
Alex Smith Software Engineer Core Ben Garvey Ursus
Andrew Madonna Software Engineer Data Ben Garvey Striketeam
Ben Garvey Engineering Manager Web Chris Merrick Unassigned
Bill Piel Director of Engineering Core Chris Merrick Sourcerer
Buck Ryan Senior Software Engineer Core Connor McArthur Striketeam
Cathy Lennon Senior Software Engineer Data Connor McArthur Unassigned
Chris Capurso Software Engineer Core Ben Garvey Aries
Chris Merrick VP of Engineering Data Bob Moore Unassigned
Connor McArthur Engineering Manager Core Chris Merrick Unassigned
Hooman Javaheri Software Engineer Data Connor McArthur Aries
Jana Veliskova Front End Engineer Web Ben Garvey Striketeam
Josh Karstendick Software Engineer Data Connor McArthur Gandalf
Keith Gregory Senior Software Engineer Data Connor McArthur Ursus
Kurt Wheeler Software Engineer Core Ben Garvey Sourcerer
Matt Bilyeu Software Engineer Web Connor McArthur Gandalf
Mike DeLaurentis Senior Software Engineer Data Connor McArthur Aries
Owen Jones Software Engineer Data Ben Garvey Sourcerer
Paul Santa Clara Senior Software Engineer Core Ben Garvey Aries
Richard Clark Software Engineer Web Ben Garvey Migration
Rob Sutton DevOps Engineer Core Connor McArthur Boxcutter
Rohan Shah Software Engineer Data Connor McArthur Striketeam
Steve Cirner Software Engineer Core Connor McArthur Gandalf
Tim Visher Software Engineer Core Connor McArthur Boxcutter
Yiannis Nicolaou Senior Software Engineer Web Ben Garvey Migration
Bob Moore PHPCEO Exec Unassigned
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment