Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save chooblarin/4bfe6733686c0616a5d86bf937ab097b to your computer and use it in GitHub Desktop.
Save chooblarin/4bfe6733686c0616a5d86bf937ab097b to your computer and use it in GitHub Desktop.
1-dimensional reaction-diffusion simulation with D3.js
/*
This is replication from this article http://www.degeneratestate.org/posts/2017/May/05/turing-patterns/
*/
console.clear();
const normalRandom = () => {
let u = 0;
let v = 0;
while (u === 0) u = Math.random();
while (v === 0) v = Math.random();
return Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v);
};
const { BehaviorSubject, interval } = rxjs;
const { take } = rxjs.operators;
const margin = { top: 20, right: 20, bottom: 40, left: 45 }
const width = 640 - margin.left - margin.right
const height = 400 - margin.top - margin.bottom
const svg = d3.select('body')
.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
const plotArea = svg
.append('g')
.attr('class', 'plot')
.attr('transform', `translate(${margin.left}, ${margin.top})`);
const x = d3.scaleLinear().range([0, width]);
const y = d3.scaleLinear().range([0, height]);
const size = 100;
const initialCondition = Array(size).fill(0).map((_, i) => {
const x = i;
const ya = normalRandom() * 0.025;
const yb = normalRandom() * 0.025;
return {
a: { x, y: ya },
b: { x, y: yb }
}
});
const xAxis = d3.axisBottom(x).ticks(5);
const yAxis = d3.axisLeft(y).ticks(5);
const dataset = new BehaviorSubject(initialCondition);
x.domain([0.0, size]);
y.domain([1.0, -1.0]);
const valueLine = d3.line()
.x(d => x(d.x))
.y(d => y(d.y))
.curve(d3.curveCardinal);
svg
.append('g')
.attr('class', 'axis')
.attr(
'transform',
`translate(${margin.left}, ${margin.top + height})`
)
.call(xAxis);
svg
.append('g')
.attr('class', 'axis')
.attr(
'transform',
`translate(${margin.left}, ${margin.top})`
)
.call(yAxis);
dataset.subscribe(data => {
plotArea.selectAll('.line').remove();
plotArea.append('path')
.attr('class', 'line a')
.attr('d', valueLine(data.map(d => d.a)));
plotArea.append('path')
.attr('class', 'line b')
.attr('d', valueLine(data.map(d => d.b)));
});
const laplacian1D = (data, dx) => {
const result = [];
for (let i = 0; i < data.length; i += 1) {
if (i === 0) {
const v = (data[data.length - 1] + data[i + 1] - 2 * data[i]) / (dx * dx);
result.push(v);
} else if (i === data.length - 1) {
const v = (data[i - 1] + data[0] - 2 * data[i]) / (dx * dx);
result.push(v);
} else {
const v = (data[i - 1] + data[i + 1] - 2 * data[i]) / (dx * dx);
result.push(v);
}
}
return result;
};
// parameters of the equations
const Da = 1.0;
const Db = 100.0;
const alpha = -0.005;
const beta = 10.0;
const reactionA = (a, b) => (a - Math.pow(a, 3) - b + alpha);
const reactionB = (a, b) => (beta * (a - b));
const reactions = (data) => {
const result = [];
for (let i = 0; i < data.length; i += 1) {
const xa = data[i].a.x;
const xb = data[i].b.x;
const ya = data[i].a.y;
const yb = data[i].b.y;
const r = {
a: { x: xa, y: reactionA(ya, yb) },
b: { x: xb, y: reactionB(ya, yb) }
};
result.push(r);
}
return result;
};
const dt = 0.0025;
const dx = 1.0;
const update = (current) => {
const laplA = laplacian1D(current.map(({ a }) => a.y), dx);
const laplB = laplacian1D(current.map(({ b }) => b.y), dx);
const reacts = reactions(current);
const next = [];
for (let i = 0; i < current.length; i += 1) {
const { a, b } = current[i];
const nextA = {
x: a.x,
y: a.y + (Da * laplA[i] + reacts[i].a.y) * dt
};
const nextB = {
x: b.x,
y: b.y + (Db * laplB[i] + reacts[i].b.y) * dt
};
next.push({ a: nextA, b: nextB });
}
return next;
};
interval(0)
.pipe(
take(20000)
)
.subscribe(n => {
const current = dataset.value;
const next = update(current);
dataset.next(next);
});
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.2.0/rxjs.umd.js"></script>
body {
font-family: system-ui, "Helvetica Neue", Helvetica, sans-serif;
font: 10px;
background-color: hsl(195, 45%, 18%);
}
svg {
margin-left: 20px;
background: hsl(195, 45%, 18%);
}
.axis line,
.axis path {
stroke: white;
}
.axis text {
fill: white;
}
.line {
stroke: hsl(72, 75%, 66%);
stroke-width: 3;
fill: none;
}
.a {
stroke: hsl(12, 75%, 66%);
}
.b {
stroke: hsl(132, 75%, 66%);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment