$(function() {
var gon = {};
gon.nodes = [{
username: "User1",
organization: "Org1"
}, {
username: "User2",
organization: "Org2"
}, {
username: "User3",
organization: "Org3"
}, {
username: "User4",
organization: "Org3"
}, {
username: "User5",
organization: "Org2"
}, {
username: "User6",
organization: "Org1"
}, {
username: "User7",
organization: "Org1"
}, {
username: "User8",
organization: "Org2"
}, {
username: "User9",
organization: "Org1"
}, {
username: "User10",
organization: "Org1"
}];
var relationships = [{
username: "User1",
followers: ["User2", "User3"],
following: ["User10"]
}, {
username: "User2",
followers: ["User1", "User5"],
following: ["User5"]
}, {
username: "User3",
followers: ["User1", "User6"],
following: ["User6"]
}, {
username: "User4",
followers: ["User8"],
following: ["User9", "User3"]
}, {
username: "User5",
followers: ["User1", "User2"],
following: ["User9"]
}, {
username: "User6",
followers: ["User8", "User1"],
following: ["User10"]
}, {
username: "User7",
followers: ["User3"],
following: ["User10"]
}, {
username: "User8",
followers: ["User2", "User4"],
following: ["User4"]
}, {
username: "User9",
followers: ["User8"],
following: [""]
}, {
username: "User10",
followers: ["User1", "User2"],
following: ["User3"]
}];
var loadGraphDataForUsers = function(table) {
var links = [];
for (var row in table) {
for (var follower in table[row].followers) {
links.push({
source: table[row].followers[follower],
target: table[row].username
});
}
for (var following in table[row].following) {
if (table[row].following[following] === "") {
continue;
}
links.push({
source: table[row].username,
target: table[row].following[following]
});
}
}
return links;
};
gon.links = loadGraphDataForUsers(relationships);
var links = gon.links;
var nodes = {};
links.forEach(function(link) {
link.source = nodes[link.source] || (nodes[link.source] = {
name: link.source
});
link.target = nodes[link.target] || (nodes[link.target] = {
name: link.target
});
});
for (var prop in nodes) {
for (i = 0; i < gon.nodes.length; i++) {
if (gon.nodes[i].username == nodes[prop].name) {
nodes[prop].organization = gon.nodes[i].organization;
}
}
}
var width = $("#graph-container").width();
var height = window.innerHeight;
var color = d3.scale.category20();
var force = d3.layout.force()
.nodes(d3.values(nodes))
.links(links)
.gravity(1)
.linkDistance(100)
.charge(-100)
.size([width, height])
.on("tick", tick)
.start();
var drag = force.drag()
.on("dragstart", function() {
d3.event.sourceEvent.stopPropagation();
})
.on("drag", dragstart);
var svg = d3.select("#graph-container")
.append("svg")
.attr("width", width)
.attr("height", height)
.attr("pointer-events", "all");
// draw arrows
svg.append("defs").selectAll("marker")
.data(["fill"])
.enter().append("marker")
.attr("id", function(d) {
return d;
})
.attr("viewBox", "0 -5 10 10")
.attr("refX", 25)
.attr("refY", 0)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5 L10,0 L0, -5")
.style("stroke", "#4679BD")
.style("opacity", "1");
var link = svg.selectAll(".link")
.data(force.links())
.enter().append("line")
.attr("class", "link")
.style("marker-end", "url(#fill)"); // arrow
var node = svg.selectAll(".node")
.data(force.nodes())
.enter().append("g")
.attr("class", "node")
.on("dblclick", dblclick)
.call(force.drag);
node.append("circle")
.attr("r", 7)
.attr("stroke", "white")
.attr("stroke-width", 1)
.style("fill", function(d) {
return color(d.organization);
});
node.append("text")
.attr("dx", 12)
.attr("dy", "0.35em")
.text(function(d) {
return d.name;
});
var legend = svg.selectAll(".legend")
.data(color.domain())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) {
return "translate(0," + i * 20 + ")";
});
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) {
if (d !== undefined) {
return d;
}
});
function dblclick(d) {
d3.select(this)
.classed("fixed", d.fixed = false);
}
function dragstart(d) {
d3.select(this)
.classed("fixed", d.fixed = true);
}
var padding = 50; // separation between circles
radius = 24;
function collide(alpha) {
var quadtree = d3.geom.quadtree(force.nodes());
return function(d) {
var rb = 2 * radius + padding,
nx1 = d.x - rb,
nx2 = d.x + rb,
ny1 = d.y - rb,
ny2 = d.y + rb;
quadtree.visit(function(quad, x1, y1, x2, y2) {
if (quad.point && (quad.point != d)) {
var x = d.x - quad.point.x;
y = d.y - quad.point.y;
l = Math.sqrt(x * x + y * y);
if (l < rb) {
l = (l - rb) / l * alpha;
d.x -= x *= l;
d.y -= y *= l;
quad.point.x += x;
quad.point.y += y;
}
}
// returns true if it should expand the boundary around the node
return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
});
};
}
function tick() {
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("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
node.each(collide(0.5));
}
});
<div id="graph-container">
</div>
#graph-container {
max-width: 100%;
height: 100%;
margin: auto;
}
.link {
stroke: #ccc;
stroke-width: 1px;
}
.node {
cursor: move;
fill: #ccc;
stroke: #000;
stroke-width: 1px;
}
text {
font: 10px arial;
font-weight: lighter;
}
.node.fixed {
fill: #f00;
}
External resources loaded into this fiddle: