Inspired by a recent report on how cellular automata generates skin color patterns in lizards.
Last active
April 23, 2017 22:24
-
-
Save feyderm/77146d973ddd33294d2da4e4ef380413 to your computer and use it in GitHub Desktop.
Cellular automata
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<style> | |
rect { | |
height: 10px; | |
width: 10px; | |
fill: lightgray; | |
} | |
.state_0 { | |
fill: lightgray; | |
} | |
.state_1 { | |
fill: red; | |
} | |
</style> | |
<body> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
<script type="text/javascript"> | |
// CELLULAR AUTOMATA VISUALIZATION | |
// | |
// inspired by: | |
// - https://www.nature.com/nature/journal/v544/n7649/full/nature22031.html | |
// | |
// references: | |
// - https://bl.ocks.org/mbostock/70d5541b547cc222aa02 | |
// - http://natureofcode.com/book/chapter-7-cellsular-automata/ | |
addArrayEqualityMethod(); | |
// initial params | |
let svg_dx = 800, | |
svg_dy = 800, | |
n_rows = 50, | |
n_cols = 75, | |
rows = d3.range(n_rows), | |
cols = d3.range(n_cols), | |
cells = d3.cross(rows, cols, (row, col) => { | |
return {"row": row, "col": col, "state": 0}; | |
}); | |
// set initial state of cell in first row, center col to 1 | |
cells[Math.round(n_cols/2)].state = 1; | |
let svg = d3.select("body") | |
.append("svg") | |
.attr("width", svg_dx) | |
.attr("height", svg_dy); | |
// plot rects | |
svg.selectAll("rect") | |
.data(cells) | |
.enter() | |
.append("rect") | |
.attr("class", assignClass) | |
.attr("x", cell => cell.col * 11) | |
.attr("y", cell => cell.row * 11); | |
// update cell states for each row | |
rows.forEach(row => { | |
d3.selectAll(".row_" + row) | |
.call(plotStates, row) | |
}); | |
function plotStates(selection, row) { | |
let old_states = selection.data().map(cell => cell.state), | |
new_states = computeNewState(old_states); | |
// update next row | |
// NB: arrow function does not bind 'this' | |
d3.selectAll(".row_" + (row + 1)) | |
.each(function(cell, i) { | |
cell.state = new_states[i]; | |
d3.select(this) | |
.transition() | |
.delay(() => row * 50) | |
.attr("class", assignClass); | |
}); | |
} | |
function computeNewState(states) { | |
// state of first cell remains 0 | |
let new_states = [0]; | |
// NB: loop begins at second element in array and end at second | |
// to last element in array; state for first and last elements | |
// in array remain 0 | |
for (let i = 1; i < states.length - 1; i++) { | |
// NB: slice end not included | |
let context = states.slice(i - 1, i + 2); | |
// rule 126 | |
if (context.equals([1, 1, 1])) { | |
new_states.push(0); | |
} else if (context.equals([1, 1, 0])) { | |
new_states.push(1); | |
} else if (context.equals([1, 0, 1])) { | |
new_states.push(1); | |
} else if (context.equals([1, 0, 0])) { | |
new_states.push(1); | |
} else if (context.equals([0, 1, 1])) { | |
new_states.push(1); | |
} else if (context.equals([0, 1, 0])) { | |
new_states.push(1); | |
} else if (context.equals([0, 0, 1])) { | |
new_states.push(1); | |
} else if (context.equals([0, 0, 0])) { | |
new_states.push(0); | |
} | |
} | |
// state of last cell remains 0 | |
new_states.push(0); | |
return new_states; | |
} | |
function assignClass(cell) { | |
return "row_" + cell.row + (cell.state == 1 ? " state_1" : " state_0"); | |
} | |
function addArrayEqualityMethod() { | |
// from http://stackoverflow.com/questions/7837456/how-to-compare-arrays-in-javascript | |
// | |
// attach the .equals method to Array's prototype to call it on any array | |
Array.prototype.equals = function (array) { | |
for (var i = 0, l=this.length; i < l; i++) { | |
if (this[i] != array[i]) { | |
return false; | |
} | |
} | |
return true; | |
} | |
} | |
</script> | |
</body> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment