This force directed graph has (will have) the following features:
- SVG & force layer fills the parent container
- Nodes confined to container
- Zoomable
- Resize to fill screen
- Context menu
- Hover popup
- Node icons
{ | |
"nodes":[ | |
{"name":"Luke","group":1}, | |
{"name":"Leia","group":1}, | |
{"name":"Han","group":1}, | |
{"name":"Chewie","group":1}, | |
{"name":"Lando","group":1}, | |
{"name":"Palpatine","group":2}, | |
{"name":"Maul","group":2}, | |
{"name":"Boba Fett","group":2}, | |
{"name":"Greedo","group":2}, | |
{"name":"Jabba","group":3}, | |
{"name":"Jawa","group":4}, | |
{"name":"Jawa","group":4}, | |
{"name":"Jawa","group":4}, | |
{"name":"Jawa","group":4}, | |
{"name":"Jawa","group":4}, | |
{"name":"Ewok","group":5}, | |
{"name":"Ewok","group":5}, | |
{"name":"Ewok","group":5}, | |
{"name":"Ewok","group":5}, | |
{"name":"Ewok","group":5}, | |
{"name":"ObiWan","group":1}, | |
{"name":"R2D2","group":1}, | |
{"name":"C3PO","group":1}, | |
{"name":"Stormtrooper","group":6}, | |
{"name":"Stormtrooper","group":6}, | |
{"name":"Stormtrooper","group":6}, | |
{"name":"Stormtrooper","group":6}, | |
{"name":"Stormtrooper","group":6}, | |
{"name":"Stormtrooper","group":6}, | |
{"name":"Stormtrooper","group":6}, | |
{"name":"Salacious Crumb","group":3}, | |
{"name":"Malakili","group":3}, | |
{"name":"Rancor","group":3} | |
], | |
"links":[ | |
{"source":1,"target":0,"group":1}, | |
{"source":1,"target":2,"group":1}, | |
{"source":2,"target":3,"group":1}, | |
{"source":2,"target":4,"group":1}, | |
{"source":6,"target":5,"group":2}, | |
{"source":7,"target":5,"group":2}, | |
{"source":8,"target":5,"group":2}, | |
{"source":10,"target":11,"group":4}, | |
{"source":10,"target":12,"group":4}, | |
{"source":10,"target":13,"group":4}, | |
{"source":10,"target":14,"group":4}, | |
{"source":15,"target":16,"group":5}, | |
{"source":15,"target":17,"group":5}, | |
{"source":15,"target":18,"group":5}, | |
{"source":15,"target":19,"group":5}, | |
{"source":20,"target":0,"group":1}, | |
{"source":21,"target":0,"group":1}, | |
{"source":22,"target":1,"group":1}, | |
{"source":23,"target":24,"group":6}, | |
{"source":24,"target":25,"group":6}, | |
{"source":25,"target":26,"group":6}, | |
{"source":26,"target":27,"group":6}, | |
{"source":27,"target":28,"group":6}, | |
{"source":28,"target":29,"group":6}, | |
{"source":9,"target":30,"group":3}, | |
{"source":9,"target":31,"group":3}, | |
{"source":31,"target":32,"group":3} | |
] | |
} |
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<style> | |
#svg-container { | |
height: 500px; | |
} | |
svg { | |
height: 100%; | |
width: 100%; | |
} | |
.node { | |
stroke: #fff; | |
stroke-width: 1.5px; | |
} | |
.node text { | |
stroke: #000; | |
stroke-width: 0; | |
font-family: sans-serif; | |
font-size: .7em; | |
} | |
.link { | |
stroke: #999; | |
stroke-opacity: .6; | |
} | |
</style> | |
<body> | |
<div id="svg-container"></div> | |
<script src="https://code.jquery.com/jquery-2.1.4.min.js"></script> | |
<script src="//d3js.org/d3.v3.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.js"></script> | |
<script> | |
var color = d3.scale.category20(); | |
var force = d3.layout.force() | |
.gravity(0.1) | |
.charge(-150) | |
.chargeDistance(500) | |
.linkDistance(50); | |
var svg = d3.select("#svg-container").append("svg") | |
.attr("width", function(){ return $(this).outerWidth(); }) | |
.attr("height", function(){ return $(this).outerHeight(); }) | |
d3.json("data.json", function(error, graph) { | |
if (error) throw error; | |
nodes = graph.nodes | |
links = graph.links | |
force | |
.size([$('svg').outerWidth(),$('svg').outerHeight()]) | |
.nodes(graph.nodes) | |
.links(graph.links) | |
.start(); | |
var link = svg.selectAll(".link") | |
.data(graph.links) | |
.enter() | |
.append("line") | |
.attr("class", "link") | |
.style("stroke-width", 1) | |
.style("stroke", function(d) { return color(d.group); }); | |
var node = svg.selectAll(".node") | |
.data(graph.nodes) | |
.enter() | |
.append("g") | |
.attr("class", "node") | |
.call(force.drag); | |
node.append("circle") | |
.attr("r",5) | |
.style("fill", function(d) { return color(d.group); }); | |
node.append("title") | |
.text(function(d) { return d.name; }); | |
node.append("text") | |
.attr("dx", 12) | |
.attr("dy", ".35em") | |
.attr("class","node-label") | |
.text(function(d) { return d.name }); | |
force.on("tick", 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; }); | |
/* | |
For now, the parents are assumed to be the first item in a group. | |
*/ | |
var parentNodes = _.uniq(graph.nodes,function(n){ | |
return n.group; | |
}); | |
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; }); | |
}); | |
}); | |
</script> |