index.html
<meta charset="utf-8">
<title>Ulam Spirals</title>
<style>
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background: #000;
color: #eee;
font: 11px 'Lucida Grande', sans-serif;
}
a, a:visited {
color: #62d7ff;
text-decoration: none;
}
footer {
color: #fff;
position: absolute;
bottom: 0;
right: 15;
text-align: center;
font-size: 12px;
padding: 4px;
background: rgba(0,0,0,0.5);
}
</style>
<body>
<canvas id="canvas" width=960 height=500></canvas>
<footer>Ulam Spirals, adapted from <a href="//www.bigblueboo.com/prime/">Prime Explorer</a></footer>
</body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="dat.gui.js"></script>
<script>
window.onload = function() {
var canvas = document.getElementById("canvas");
var width = canvas.width = document.body.clientWidth || 960,
height = canvas.height = document.body.clientHeight || 500,
size = 2,
ulamN = 4,
max = 6e5,
start = 1,
numcols = Math.floor(width / size),
numrows = Math.floor(height / size),
colors = ["#ffc344", "#000000"];
var ctx = canvas.getContext("2d");
ctx.save();
var latest = 0;
function plotUlam(n) {
var current = ++latest;
ctx.restore();
ctx.save();
ctx.fillStyle = colors[0];
ctx.clearRect(0,0,width,height);
ctx.translate(width/2,height/2);
var maxRunLength = 1;
var runLength = 1;
var runCount = n - 2;
var integer = 0;
function step() {
integer++;
if (integer > max) return false;
if (latest > current) return false;
ctx.translate(size,0);
runLength--;
if (runLength < 1) {
runCount--;
if (runCount < 1) {
runCount = n - 2;
maxRunLength++;
}
runLength = maxRunLength;
ctx.rotate(Math.PI * 2 / n);
}
var prime = isPrime(integer + start - 1);
if (!prime) return;
ctx.fillRect(-size/2, -size/2, size, size);
};
d3.timer(function() {
var ii = 10000;
var done = false;
while (ii-- > 0) {
step() ? false : done = true
}
return !done;
});
}
function leastFactor(n){
if (isNaN(n) || !isFinite(n)) return NaN;
if (n==0) return 0;
if (n%1 || n*n<2) return 1;
if (n%2==0) return 2;
if (n%3==0) return 3;
if (n%5==0) return 5;
var m = Math.sqrt(n);
for (var i=7;i<=m;i+=30) {
if (n%i==0) return i;
if (n%(i+4)==0) return i+4;
if (n%(i+6)==0) return i+6;
if (n%(i+10)==0) return i+10;
if (n%(i+12)==0) return i+12;
if (n%(i+16)==0) return i+16;
if (n%(i+22)==0) return i+22;
if (n%(i+24)==0) return i+24;
}
return n;
}
function isPrime (n) {
if (isNaN(n) || !isFinite(n) || n%1 || n<2) return false;
if (n==leastFactor(n)) return true;
return false;
}
plotUlam(ulamN);
var Gui = function() {
this.ulam = ulamN;
this.size = size;
this.max = max;
this.start = start;
this.color1 = colors[0];
this.color2 = colors[1];
};
var field = new Gui();
var gui = new dat.GUI();
var ulam = gui.add(field, 'ulam', 3, 10).step(1);
var sizer = gui.add(field, 'size', 0.5, 6).step(0.5);
var maxer = gui.add(field, 'max', 500, 1e7).step(500);
var starter = gui.add(field, 'start', 1, 1e8).step(1);
var color1 = gui.addColor(field, 'color1');
var color2 = gui.addColor(field, 'color2');
ulam.onChange(function(v) { ulamN = v; plotUlam(ulamN); });
sizer.onChange(function(v) { size = v; plotUlam(ulamN); });
maxer.onChange(function(v) { max = v; plotUlam(ulamN); });
starter.onChange(function(v) { start = v; plotUlam(ulamN); });
color1.onChange(function(v) { colors[0] = v; plotUlam(ulamN); });
color2.onChange(function(v) { colors[1] = v; d3.select("body").style("background", colors[1]); });
};
</script>