A simulation of polymer dynamics using d3.
The physics are not quite right, namely the bond lengths should be much more rigid, but it illustrates the basic physical picture of random thermal fluctuations in the polymer chain.
A simulation of polymer dynamics using d3.
The physics are not quite right, namely the bond lengths should be much more rigid, but it illustrates the basic physical picture of random thermal fluctuations in the polymer chain.
<head> | |
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/4.8.0/d3.min.js"></script> | |
<link rel="stylesheet" href="style.css"> | |
</head> | |
<body> | |
<script src="index.js"></script> | |
</body> |
const width = window.innerWidth; | |
const height = window.innerHeight; | |
const originX = width / 2; | |
const originY = height / 2; | |
const monomerRadius = 20; | |
const bondLength = monomerRadius * 2.2; | |
const monomers = [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5].map(xPos => { | |
return { | |
x: originX + (xPos * bondLength), | |
y: originY, | |
vx: 0, | |
vy: 0, | |
r: monomerRadius | |
} | |
}); | |
const bonds = monomers.slice(0, monomers.length - 2).map((mer, index) => { | |
return { | |
source: index, | |
target: index + 1 | |
} | |
}); | |
const bondForce = d3.forceLink(bonds) | |
.distance(bondLength) | |
.iterations(20); | |
const polymerSim = d3.forceSimulation() | |
.nodes(monomers) | |
.force('link', bondForce) | |
.force('collide', d3.forceCollide(monomerRadius)) | |
.alphaDecay(0.1); | |
const line = d3.line() | |
.x(d => d.x) | |
.y(d => d.y); | |
const svg = d3.select("body").append("svg") | |
.attr("width", width) | |
.attr("height", height); | |
svg.append("path"); | |
const path = svg.selectAll("path").data(monomers); | |
const balls = svg.selectAll('circle') | |
.data(monomers) | |
.enter().append('circle') | |
.attr('r', monomerRadius) | |
.attr('cx', d => d.x) | |
.attr('cy', d => d.y); | |
function thermalize () { | |
let updatedMonomers = polymerSim.nodes().map(node => { | |
return { | |
x: node.x + d3.randomNormal(0, 0.5)(), | |
y: node.y + d3.randomNormal(0, 0.5)(), | |
vx: node.vx , | |
vy: node.vy , | |
r: node.r | |
}; | |
}) | |
polymerSim.nodes(updatedMonomers); | |
balls.data(updatedMonomers) | |
.attr('cx', d => d.x) | |
.attr('cy', d => d.y); | |
path.attr("d", d => line(updatedMonomers)); | |
}; | |
setInterval(() => { | |
polymerSim.stop(); | |
thermalize(); | |
polymerSim.tick(); | |
polymerSim.restart(); | |
}, 100); |
body { | |
margin: 0; | |
text-align: center; | |
font-family: sans-serif; | |
font-size: 14px; | |
} | |
path { | |
stroke: rgb(70, 70, 70); | |
stroke-linejoin: round; | |
stroke-width: 10px; | |
fill: none; | |
} | |
circle { | |
fill: rgb(70, 70, 70); | |
stroke: none; | |
} |