Skip to content

Instantly share code, notes, and snippets.

@christophermanning
Created December 4, 2012 20:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save christophermanning/4208494 to your computer and use it in GitHub Desktop.
Save christophermanning/4208494 to your computer and use it in GitHub Desktop.
Spherical Force-Directed Layout
<!DOCTYPE html>
<html>
<head>
<title>Spherical Force-Directed Layout</title>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/dat-gui/0.5/dat.gui.min.js"></script>
<!--<script src="/js/d3.v3.min.js"></script>-->
<!--<script src="/js/dat-gui/build/dat.gui.js"></script> -->
<style type="text/css">
body {
padding: 0;
margin: 0;
}
path.node {
stroke-width: 1.5px;
}
path.link {
stroke: #999;
fill-opacity: 0
}
</style>
</head>
<body>
<script type="text/javascript">
var projections = {
"Albers": d3.geo.albers(),
"Azimuthal Equal Area": d3.geo.azimuthalEqualArea(),
"Azimuthal Eqidistant": d3.geo.azimuthalEquidistant(),
"Conic Conformal": d3.geo.conicConformal(),
"Conic Equal Area": d3.geo.conicEqualArea(),
"Conic Equidistant": d3.geo.conicEquidistant(),
"Eqirectangular": d3.geo.equirectangular(),
"Gnomonic": d3.geo.gnomonic(),
"Mercator": d3.geo.mercator(),
"Orthographic": d3.geo.orthographic(),
"Stereographic": d3.geo.stereographic(),
"Transverse Mercator": d3.geo.transverseMercator(),
};
var config = { "projection": "Orthographic", "clip": true, "friction": .9, "linkStrength": 1, "linkDistance": 20, "charge": 30, "gravity": .1, "theta": .8 };
var gui = new dat.GUI();
//var projectionChanger = gui.add(config, "projection", ['equalarea', 'equidistant', 'gnomonic', 'orthographic', 'stereographic', 'rectangular']);
var projectionChanger = gui.add(config, "projection", Object.keys(projections));
//http://stackoverflow.com/a/3417242
function wrapIndex(i, i_max) {
return ((i % i_max) + i_max) % i_max;
}
projectionChanger.onChange(function(value) {
projection = projections[value]
.scale(height/2)
.translate([(width/2)-125, height/2])
.clipAngle(config["clip"] ? 90 : null)
path.projection(projections[value])
return
if(value == 'rectangular') {
path = d3.geo.path().projection(function(coordinates){
console.log(coordinates[0], coordinates[1])
return [
wrapIndex(coordinates[0], width),
wrapIndex(coordinates[1], height),
];
});
config['clip'] = false
} else {
projection.mode(value)
path = d3.geo.path().projection(projection)
}
force.start()
});
var clipChanger = gui.add(config, "clip").listen();
clipChanger.onChange(function(value) {
projection.clipAngle(value ? 90 : null)
force.start()
});
var fl = gui.addFolder('Force Layout');
fl.open()
var frictionChanger = fl.add(config, "friction", 0, 1);
frictionChanger.onChange(function(value) {
force.friction(value)
force.start()
});
var linkDistanceChanger = fl.add(config, "linkDistance", 0, 400);
linkDistanceChanger.onChange(function(value) {
force.linkDistance(value)
force.start()
});
var linkStrengthChanger = fl.add(config, "linkStrength", 0, 1);
linkStrengthChanger.onChange(function(value) {
force.linkStrength(value)
force.start()
});
var chargeChanger = fl.add(config,"charge", 0, 500);
chargeChanger.onChange(function(value) {
force.charge(-value)
force.start()
});
var gravityChanger = fl.add(config,"gravity", 0, 1);
gravityChanger.onChange(function(value) {
force.gravity(value)
force.start()
});
var thetaChanger = fl.add(config,"theta", 0, 1);
thetaChanger.onChange(function(value) {
force.theta(value)
force.start()
});
var width = window.innerWidth,
height = window.innerHeight - 5,
fill = d3.scale.category20(),
nodes = [{x: width/2, y: height/2}],
links = [];
var projection = projections[config["projection"]]
.scale(height/2)
.translate([(width/2)-125, height/2])
.clipAngle(config["clip"] ? 90 : null)
var path = d3.geo.path()
.projection(projection)
var force = d3.layout.force()
.linkDistance(config["linkDistance"])
.linkStrength(config["linkStrength"])
.gravity(config["gravity"])
.size([width, height])
.charge(-config["charge"]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.call(d3.behavior.drag()
.origin(function() { var r = projection.rotate(); return {x: 2 * r[0], y: -2 * r[1]}; })
.on("drag", function() { force.start(); var r = [d3.event.x / 2, -d3.event.y / 2, projection.rotate()[2]]; t0 = Date.now(); origin = r; projection.rotate(r); }))
for(x=0;x<100;x++){
source = nodes[~~(Math.random() * nodes.length)]
target = {x: source.x + Math.random(), y: source.y + Math.random(), group: Math.random()}
links.push({source: source, target: target})
nodes.push(target)
}
var link = svg.selectAll("path.link")
.data(links)
.enter().append("path").attr("class", "link")
var node = svg.selectAll("path.node")
.data(nodes)
.enter().append("path").attr("class", "node")
.style("fill", function(d) { return fill(d.group); })
.style("stroke", function(d) { return d3.rgb(fill(d.group)).darker(); })
.call(force.drag);
force
.nodes(nodes)
.links(links)
.on("tick", tick)
.start();
function tick() {
node.attr("d", function(d) { var p = path({"type":"Feature","geometry":{"type":"Point","coordinates":[d.x, d.y]}}); return p ? p : 'M 0 0' });
link.attr("d", function(d) { var p = path({"type":"Feature","geometry":{"type":"LineString","coordinates":[[d.source.x, d.source.y],[d.target.x, d.target.y]]}}); return p ? p : 'M 0 0' });
}
</script>
</body>
</html>
@ashishpanchal
Copy link

ashishpanchal commented Oct 7, 2016

hello @christophermanning, This is an excellent example of spherical force directed using d3. I am using this example and currently I am facing issue for adding label on node by trying various d3 methods for append text on node but couldn't work. can you please help me out in this example how can I add labels on nodes?

@ashishpanchal
Copy link

@christophermanning : Here is a sample example https://jsfiddle.net/kfpa14gm/5/ I have created with adding labels on node with this graph but labels are coming as a spherical view around the node I want it on the node like simple force layout. I tried many ways but not yet find a way to solve this. your urgent help will be appreciated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment