Skip to content

Instantly share code, notes, and snippets.

@owendall
Forked from christophermanning/README.md
Created April 13, 2017 19:17
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 owendall/1d5859d3ba6a05525996c75fe5350580 to your computer and use it in GitHub Desktop.
Save owendall/1d5859d3ba6a05525996c75fe5350580 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment