block by davo 490f38ed9b559f61d1401f64f0bc450d

Geodesic Rainbow

Full Screen

Use the range slider to change the degree of subdivision in this geodesic sphere. The base shape, visible when subdivision is disabled, is the icosahedron. Built with the d3.geodesic plugin.

index.html

<!DOCTYPE html>
<meta charset="utf-8">
<style>

#subdivision {
  position: absolute;
  top: 20px;
  left: 20px;
}

#subdivision input {
  width: 200px;
}

</style>
<div id="subdivision">
  <input type="range" min="1" max="12" value="8">
  <output name="subdivision"></output>
</div>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="d3.geodesic.min.js"></script>
<script>

var width = 960,
    height = 500;

var velocity = [.010, .005],
    t0 = Date.now();

var projection = d3.geo.orthographic()
    .scale(height / 2 - 10);

var canvas = d3.select("body").append("canvas")
    .attr("width", width)
    .attr("height", height);

var context = canvas.node().getContext("2d");

context.strokeStyle = "#000";
context.lineWidth = .5;

var faces;

var output = d3.select("output");

var input = d3.select("input")
    .on("input", function() { geodesic(+this.value); })
    .on("change", function() { geodesic(+this.value); })
    .each(function() { geodesic(+this.value); });

d3.timer(function() {
  var time = Date.now() - t0;
  projection.rotate([time * velocity[0], time * velocity[1]]);
  redraw();
});

function redraw() {
  context.clearRect(0, 0, width, height);

  faces.forEach(function(d) {
    d.polygon[0] = projection(d[0]);
    d.polygon[1] = projection(d[1]);
    d.polygon[2] = projection(d[2]);
    if (d.visible = d.polygon.area() > 0) {
      context.fillStyle = rgba(0,0,0,.2);
      context.beginPath();
      drawTriangle(d.polygon);
      context.fill();
    }
  });

  context.beginPath();
  faces.forEach(function(d) {
    if (d.visible) {
      drawTriangle(d.polygon);
    }
  });
  context.stroke();
}

function drawTriangle(triangle) {
  context.moveTo(triangle[0][0], triangle[0][1]);
  context.lineTo(triangle[1][0], triangle[1][1]);
  context.lineTo(triangle[2][0], triangle[2][1]);
  context.closePath();
}

function geodesic(subdivision) {
  output.text(subdivision);

  faces = d3.geodesic.polygons(subdivision).map(function(d) {
    d = d.coordinates[0];
    d.pop(); // use an open polygon
    d.fill = d3.hsl(d[0][0], 1, .5) + "";
    d.polygon = d3.geom.polygon(d.map(projection));
    return d;
  });

  redraw();
}

</script>

d3.geodesic.min.js

!function(){function n(n){return d3.merge(e.map(function(t){var o=r(t[0],t[1]),u=r(t[0],t[2]),i=[];i.push([t[0],o(1/n),u(1/n)]);for(var a=1;n>a;++a){for(var e=r(o(a/n),u(a/n)),c=r(o((a+1)/n),u((a+1)/n)),f=0;a>=f;++f)i.push([e(f/a),c(f/(a+1)),c((f+1)/(a+1))]);for(var f=0;a>f;++f)i.push([e(f/a),c((f+1)/(a+1)),e((f+1)/a)])}return i}))}function t(t){function r(n,t){var r;(n[0]<t[0]||n[0]==t[0]&&(n[1]<t[1]||n[1]==t[1]&&n[2]<t[2]))&&(r=n,n=t,t=r),u[n.map(o)+" "+t.map(o)]=[n,t]}function o(n){return d3.round(n,4)}var u={};return n(t).forEach(function(n){r(n[0],n[1]),r(n[1],n[2]),r(n[2],n[0])}),d3.values(u)}function r(n,t){var r=n[0],o=n[1],u=n[2],i=t[0]-r,a=t[1]-o,e=t[2]-u;return function(n){return[r+n*i,o+n*a,u+n*e]}}function o(n){var t=n[0],r=n[1],o=n[2];return[Math.atan2(r,t)*i,Math.acos(o/Math.sqrt(t*t+r*r+o*o))*i-90]}var u=1.618033988749895,i=180/Math.PI,a=[[1,u,0],[-1,u,0],[1,-u,0],[-1,-u,0],[0,1,u],[0,-1,u],[0,1,-u],[0,-1,-u],[u,0,1],[-u,0,1],[u,0,-1],[-u,0,-1]],e=[[0,1,4],[1,9,4],[4,9,5],[5,9,3],[2,3,7],[3,2,5],[7,10,2],[0,8,10],[0,4,8],[8,2,10],[8,4,5],[8,5,2],[1,0,6],[11,1,6],[3,9,11],[6,10,7],[3,11,7],[11,6,7],[6,0,10],[9,1,11]].map(function(n){return n.map(function(n){return a[n]})});d3.geodesic={multipolygon:function(t){return{type:"MultiPolygon",coordinates:n(~~t).map(function(n){return n=n.map(o),n.push(n[0]),n=[n]})}},polygons:function(n){return d3.geodesic.multipolygon(~~n).coordinates.map(function(n){return{type:"Polygon",coordinates:n}})},multilinestring:function(n){return{type:"MultiLineString",coordinates:t(~~n).map(function(n){return n.map(o)})}}}}();