Created by Christopher Manning
Draws a random walk onto a sphere using canvas and geo projections.
- Click the canvas to toggle the rotation
Created by Christopher Manning
Draws a random walk onto a sphere using canvas and geo projections.
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<title>Spherical Random Walk</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; | |
} | |
</style> | |
</head> | |
<body> | |
<script type="text/javascript"> | |
var config = { "projection": "Orthographic", "clip": false, "theta": 140, "steps" : 10000, "velocityX": .01, "velocityY": .005 }; | |
var gui = new dat.GUI(); | |
var thetaChanger = gui.add(config, "theta", 1, 360).step(1) | |
thetaChanger.onChange(function(value) { | |
config.redraw() | |
}); | |
gui.add(config, "steps", 1, 50000).step(1); | |
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(), | |
}; | |
Object.keys(projections).forEach(function(o) { | |
projections[o].rotate([0, 0]).center([0, 0]); | |
}); | |
var projectionChanger = gui.add(config, "projection", Object.keys(projections)); | |
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]) | |
}); | |
var clipChanger = gui.add(config, "clip") | |
clipChanger.onChange(function(value) { | |
projection.clipAngle(value ? 90 : null) | |
}); | |
gui.add(config, "velocityX", 0, .1).step(.001).listen() | |
gui.add(config, "velocityY", 0, .1).step(.001).listen() | |
config.random = function(){ | |
gui.__controllers.forEach(function(c){ | |
if(typeof(c.__select) != 'undefined') { | |
c.setValue(c.__select[Math.floor(Math.random()*(c.__select.length-1))].value) | |
} else { | |
if(["redraw", "random", "velocityX", "velocityY"].indexOf(c.property) === -1){ | |
c.setValue(Math.floor(Math.random() * c.__max) + c.__min) | |
} | |
} | |
}) | |
} | |
gui.add(config, "random") | |
config.redraw = function(){ | |
vertices = [[0,0]] | |
} | |
gui.add(config, "redraw") | |
var width = window.innerWidth, | |
height = window.innerHeight -5 | |
vertices = [[0,0]] | |
origin = [0, 0], | |
t0 = Date.now(), | |
vertices = [[0, 0]], | |
rotate = true | |
var projection = projections[config["projection"]] | |
.scale(height/2) | |
.translate([(width/2)-125, height/2]) | |
.clipAngle(config["clip"] ? 90 : null) | |
var canvas = d3.select("body").append("canvas") | |
.attr("width", width) | |
.attr("height", height) | |
.on("click", function() { rotate = !rotate }) | |
.call(d3.behavior.drag() | |
.origin(function() { var r = projection.rotate(); return {x: 2 * r[0], y: -2 * r[1]}; }) | |
.on("drag", function() { var r = [d3.event.x / 2, -d3.event.y / 2, projection.rotate()[2]]; t0 = Date.now(); origin = r; projection.rotate(r); })) | |
var context = canvas.node().getContext("2d"); | |
var path = d3.geo.path() | |
.projection(projection) | |
.context(context) | |
d3.timer(function(t){ | |
if(vertices.length < config["steps"]) { | |
for(var i=0;i<1000;i++){ | |
var last = vertices[vertices.length-1] | |
var xo = last[0] | |
var yo = last[1] | |
var r = Math.floor(Math.random()*(360/config["theta"])) | |
var theta = Math.PI * ((r * config["theta"] % 360)/180) | |
xo += Math.cos(theta) | |
yo += Math.sin(theta) | |
vertices.push([xo, yo]) | |
} | |
} else if(vertices.length > config["steps"]) { | |
vertices.length = config["steps"] | |
} | |
if(rotate){ | |
var t = Date.now() - t0, | |
o = [origin[0] + t * config["velocityX"], origin[1] + t * config["velocityY"]]; | |
projection.rotate(o) | |
} | |
context.clearRect(0, 0, width, height); | |
context.lineWidth = 1 | |
context.beginPath() | |
path({"type":"Feature","geometry":{"type":"LineString","coordinates":vertices}}) | |
context.stroke(); | |
}); | |
</script> | |
</body> | |
</html> |