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> |