|
<!DOCTYPE html> |
|
<html> |
|
<head> |
|
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"> |
|
<title>Simple Molecules</title> |
|
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.js"></script> |
|
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.geo.js"></script> |
|
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.geom.js"></script> |
|
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.layout.js"></script> |
|
<style type="text/css"> |
|
|
|
body { |
|
font: 16px sans-serif; |
|
margin: 2px 4px 2px 4px; } |
|
|
|
#description { |
|
width: 920px; } |
|
|
|
rect { |
|
fill: white; } |
|
|
|
#fullscreen { |
|
margin-top: 8px; |
|
cursor: pointer; } |
|
|
|
circle, .line { |
|
fill: none; |
|
stroke: steelblue; |
|
stroke-width: 1.5px; } |
|
|
|
circle { |
|
fill: white; |
|
fill-opacity: 0.2; |
|
cursor: move; } |
|
circle.selected { |
|
fill: #ff7f0e; |
|
stroke: #ff7f0e; } |
|
|
|
g.y { |
|
font-size: 0.9em; } |
|
|
|
g.x { |
|
font-size: 0.9em; } |
|
|
|
h1 { |
|
font: 20px sans-serif; } |
|
|
|
h2 { |
|
font: 18px sans-serif; } |
|
|
|
h3 { |
|
font: 16px sans-serif; } |
|
|
|
ul { |
|
margin: 1em 0em 1em 0em; |
|
padding: 0em 0em 0em 0em; } |
|
ul li { |
|
margin: 0.3em 0.2em 0.3em 0.2em; |
|
padding: 0em 0em 0em 0em; } |
|
|
|
#example-list ul { |
|
margin: 2em 2em 2em 2em; |
|
padding: 0em 0em 0em 2em; } |
|
#example-list ul li { |
|
margin: 0.3em 0.2em 0.3em 0.2em; |
|
padding: 0em 0em 0em 0em; } |
|
|
|
h1 { |
|
height: 32px; } |
|
|
|
#header ul { |
|
display: inline-block; |
|
list-style-type: none; |
|
overflow: hidden; |
|
margin: 0em 0em 0em 0em; } |
|
#header ul li { |
|
height: 40px; |
|
font-size: 1.6em; |
|
display: inline-table; |
|
vertical-align: middle; |
|
list-style-type: none; |
|
padding: 0.2em 1em 0.2em 0em; } |
|
#header ul.right { |
|
float: right; } |
|
|
|
#viz ul { |
|
display: inline-block; |
|
list-style-type: none; |
|
margin: 0.1em 0em 0.1em 0em; } |
|
#viz ul li { |
|
line-height: 1em; |
|
display: inline-table; |
|
vertical-align: middle; |
|
list-style-type: none; |
|
padding: 0.1em 0.1em 0.1em 0em; } |
|
#viz ul li fieldset { |
|
height: 2.2em; } |
|
#viz ul.right { |
|
float: right; } |
|
|
|
#controls ul { |
|
display: inline-block; |
|
list-style-type: none; |
|
margin: 0.5em 0em 0.5em 0em; } |
|
#controls ul li { |
|
line-height: 1em; |
|
display: inline-table; |
|
vertical-align: middle; |
|
list-style-type: none; |
|
padding: 0.2em 1em 0.2em 0em; } |
|
#controls ul li fieldset { |
|
height: 2.2em; } |
|
#controls ul.right { |
|
float: right; } |
|
|
|
#moleculecontainer { |
|
background-color: #f7f2c5; |
|
width: 480px; |
|
height: 460px; |
|
border: solid 1px #cccccc; |
|
position: relative; } |
|
#moleculecontainer circle { |
|
stroke: darkred; |
|
stroke-width: 1px; |
|
fill: white; |
|
fill-opacity: 0.2; |
|
cursor: move; } |
|
#moleculecontainer circle.selected { |
|
fill: #ff7f0e; |
|
stroke: #ff7f0e; } |
|
#moleculecontainer rect { |
|
border: solid 1px #cccccc; } |
|
|
|
#potentialchart { |
|
background-color: #f7f2c5; |
|
width: 580px; |
|
height: 460px; |
|
border: solid 1px #cccccc; |
|
position: relative; } |
|
|
|
text.index { |
|
font: sans-serif; |
|
text-anchor: middle; } |
|
|
|
</style> |
|
</head> |
|
<body> |
|
<div id='viz'> |
|
<ul> |
|
<li> |
|
<div id='moleculecontainer'></div> |
|
</li> |
|
</ul> |
|
<ul class='right'> |
|
<li> |
|
<div id='potentialchart'></div> |
|
</li> |
|
</ul> |
|
</div> |
|
<div id='controls'> |
|
<ul> |
|
<li> |
|
<form id='model-controls'> |
|
<fieldset> |
|
<legend>Model</legend> |
|
<label> |
|
<input checked='checked' name='step' type='radio' value='stop'>Stop</input> |
|
</label> |
|
<label> |
|
<input name='step' type='radio' value='step'>Step</input> |
|
</label> |
|
<label> |
|
<input name='step' type='radio' value='go'>Go</input> |
|
</label> |
|
<label> |
|
<input name='step' type='radio' value='reset'>Reset</input> |
|
</label> |
|
</fieldset> |
|
</form> |
|
<li> |
|
<fieldset> |
|
<legend>Molecules</legend> |
|
<select id='select-molecule-number'> |
|
<option value='10'>10</option> |
|
<option selected='selected' value='20'>20</option> |
|
<option value='50'>50</option> |
|
<option value='100'>100</option> |
|
<option value='200'>200</option> |
|
<option value='500'>500</option> |
|
</select> |
|
</fieldset> |
|
</li> |
|
<li> |
|
<fieldset> |
|
<legend>Temperature</legend> |
|
<select id='select-temperature'> |
|
<option value='1'>1</option> |
|
<option value='2'>2</option> |
|
<option value='3'>3</option> |
|
<option value='4'>4</option> |
|
<option selected='selected' value='5'>5</option> |
|
<option value='6'>6</option> |
|
<option value='7'>7</option> |
|
<option value='8'>8</option> |
|
<option value='9'>9</option> |
|
<option value='10'>10</option> |
|
</select> |
|
</fieldset> |
|
</li> |
|
</li> |
|
</ul> |
|
<ul class='right'> |
|
<li> |
|
<span class='alignleft size-full wp-image-1880' height='32' id='fullscreen' width='32'>Fullscreen</span> |
|
</li> |
|
</ul> |
|
</div> |
|
|
|
<script type="text/javascript"> |
|
|
|
// |
|
// fullscreen.js |
|
// |
|
|
|
(function () { |
|
/** do we have the querySelectorAll method? **/ |
|
if (document.querySelectorAll) { |
|
var fullScreenImage = document.querySelector ('#fullscreen'); |
|
if (fullScreenImage) { |
|
fullScreenImage.style.cursor = "pointer"; |
|
fullScreenImage.addEventListener ('click', function () { |
|
if (this.webkitRequestFullScreen) { |
|
if (document.webkitIsFullScreen) { |
|
document.webkitCancelFullScreen(); |
|
} else { |
|
document.documentElement.webkitRequestFullScreen(); |
|
} |
|
} else { |
|
alert('You need to install a newer browser to use the full-screen API for now.'); |
|
} |
|
}, false); |
|
} |
|
} |
|
})(); |
|
|
|
// |
|
// modeler.js |
|
// |
|
|
|
(function() { |
|
|
|
var modeler = {}; |
|
modeler.layout = {}; |
|
|
|
var root = this; |
|
|
|
modeler.VERSION = '0.1.0'; |
|
|
|
modeler.layout.model = function() { |
|
var model = {}, |
|
event = d3.dispatch("tick"), |
|
size = [1, 1], |
|
drag, |
|
stopped = true, |
|
friction = .9, |
|
charge = -20, |
|
gravity = .1, |
|
theta = .8, |
|
interval, |
|
nodes = [], |
|
links = [], |
|
distances, |
|
strengths, |
|
forces, |
|
ljforce, |
|
integration = 10, |
|
overlap; |
|
|
|
function tick() { |
|
var n = nodes.length, |
|
m = links.length, |
|
q, |
|
i, // current index |
|
o, // current object |
|
s, // current source |
|
t, // current target |
|
l, // current distance |
|
k, // current force |
|
x, // x-distance |
|
y, // y-distance |
|
iloop, |
|
leftwall = nodes[0].radius, |
|
bottomwall = nodes[0].radius, |
|
rightwall = size[0] - nodes[0].radius, |
|
topwall = size[1] - nodes[0].radius, |
|
xstep, |
|
ystep; |
|
|
|
|
|
iloop = -1; while(iloop++ < integration) { |
|
// compute quadtree center of mass and apply lennard_jones |
|
q = d3.geom.quadtree(nodes); |
|
i = -1; while (++i < n) { |
|
o = nodes[i]; |
|
q.visit(ljforces(o)); |
|
} |
|
|
|
// Continue particle movement integrating acceleration with |
|
// existing velocity managing collision with boundaries. |
|
i = -1; |
|
while (++i < n) { |
|
o = nodes[i]; |
|
x = o.x; |
|
y = o.y; |
|
o.x = 2 * x - o.px + o.ax / integration; |
|
o.y = 2 * y - o.py + o.ay / integration; |
|
xstep = o.x - x; |
|
ystep = o.y - y; |
|
o.ax = 0; |
|
o.ay = 0; |
|
if (o.x < leftwall) { |
|
o.px = o.x + xstep; |
|
o.py = y; |
|
} else if (o.x > rightwall) { |
|
o.px = o.x + xstep; |
|
o.py = y; |
|
} else if (o.y < bottomwall) { |
|
o.px = x; |
|
o.py = o.y + ystep; |
|
} else if (o.y > topwall) { |
|
o.px = x; |
|
o.py = o.y + ystep; |
|
} else { |
|
o.px = x; |
|
o.py = y; |
|
} |
|
o.vx = o.x - o.px; |
|
o.vx = o.y - o.py; |
|
} |
|
} |
|
|
|
event.tick({type: "tick"}); |
|
|
|
return stopped |
|
} |
|
|
|
function ljforces(node) { |
|
var r = node.radius * 5, |
|
nx1 = node.x - r, |
|
nx2 = node.x + r, |
|
ny1 = node.y - r, |
|
ny2 = node.y + r; |
|
return function(quad, x1, y1, x2, y2) { |
|
if (quad.point && (quad.point !== node)) { |
|
var x = node.x - quad.point.x, |
|
y = node.y - quad.point.y, |
|
l = Math.sqrt(x * x + y * y), |
|
f = ljforce(l), |
|
d = f / l, |
|
xd = x * d, |
|
yd = y * d; |
|
node.ax -= xd; |
|
node.ay -= yd; |
|
quad.point.ax += xd; |
|
quad.point.ay += yd; |
|
} |
|
return x1 > nx2 |
|
|| x2 < nx1 |
|
|| y1 > ny2 |
|
|| y2 < ny1; |
|
}; |
|
} |
|
|
|
function uncollide(node) { |
|
var r = node.radius * 5, |
|
nx1 = node.x - r, |
|
nx2 = node.x + r, |
|
ny1 = node.y - r, |
|
ny2 = node.y + r; |
|
return function(quad, x1, y1, x2, y2) { |
|
if (quad.point && (quad.point !== node)) { |
|
var x = node.x - quad.point.x, |
|
y = node.y - quad.point.y, |
|
l = Math.sqrt(x * x + y * y), |
|
r = node.radius + quad.point.radius; |
|
if (l < r) { |
|
var zx = (r - x) / 2, |
|
zy = (r - y) / 2; |
|
node.x += zx; |
|
node.y += zy; |
|
quad.point.x -= zx; |
|
quad.point.y -= zy; |
|
} |
|
} |
|
return x1 > nx2 |
|
|| x2 < nx1 |
|
|| y1 > ny2 |
|
|| y2 < ny1; |
|
}; |
|
} |
|
|
|
function repulse(node) { |
|
return function(quad, x1, y1, x2, y2) { |
|
if (quad.point !== node) { |
|
var dx = quad.cx - node.x, |
|
dy = quad.cy - node.y, |
|
dn = 1 / Math.sqrt(dx * dx + dy * dy); |
|
|
|
/* Barnes-Hut criterion. */ |
|
if ((x2 - x1) * dn < theta) { |
|
var k = quad.charge * dn * dn; |
|
node.x += dx * k; |
|
node.y += dy * k; |
|
return true; |
|
} |
|
|
|
if (quad.point && isFinite(dn)) { |
|
var k = quad.pointCharge * dn * dn; |
|
node.x += dx * k; |
|
node.y += dy * k; |
|
} |
|
} |
|
return !quad.charge; |
|
}; |
|
} |
|
|
|
function resolve_collisions(nodes) { |
|
var n = nodes.length, q, i, j, o, charges = []; |
|
|
|
var boundary = { |
|
left : nodes[0].radius, |
|
bottom : nodes[0].radius, |
|
right : size[0]-nodes[0].radius, |
|
topw : size[1]-nodes[0].radius |
|
} |
|
|
|
j = -1; while (j++ < 4) { |
|
q = d3.geom.quadtree(nodes); |
|
i = -1; while (++i < n) { |
|
if (!(o = nodes[i]).fixed) { |
|
q.visit(uncollide(o)); |
|
} |
|
} |
|
} |
|
|
|
// // compute quadtree center of mass and apply charge forces |
|
// for (i = 0; i < n; ++i) { |
|
// charges[i] = charge; |
|
// } |
|
// |
|
// modeler_forceAccumulate(q = d3.geom.quadtree(nodes), charges, boundary); |
|
// i = -1; while (++i < n) { |
|
// if (!(o = nodes[i]).fixed) { |
|
// q.visit(repulse(o)); |
|
// } |
|
// } |
|
|
|
// temperature range from 0.1 .. 10 |
|
// reset initial velocity and acceleration |
|
set_temperature(5); |
|
} |
|
|
|
function kinetic_energy() { |
|
var i, x, y, l, e = 0, |
|
n = nodes.length; |
|
i = -1; while (++i < n) { |
|
x = nodes[i].vx; |
|
y = nodes[i].vy; |
|
l = Math.sqrt(x * x + y * y); |
|
e += l; |
|
} |
|
return e/n; |
|
} |
|
|
|
function set_temperature(t) { |
|
temperature = t; |
|
integration = temperature * temperature; |
|
var n = nodes.length, i, j, o, |
|
tmap = [1, 2, 5, 10, 20, 50, 100, 200, 500, 1000], |
|
t1 = tmap[t-1]/1000, t2 = t1/2; |
|
i = -1; while (++i < n) { |
|
nodes[i].px = nodes[i].x + Math.random() * t1 - t2; |
|
nodes[i].py = nodes[i].y + Math.random() * t1 - t2; |
|
nodes[i].vx = nodes[i].x - nodes[i].px; |
|
nodes[i].vy = nodes[i].y - nodes[i].py; |
|
nodes[i].ax = 0; |
|
nodes[i].ay = 0; |
|
} |
|
} |
|
|
|
function set_temperature2(t) { |
|
temperature = t; |
|
var n = nodes.length, i, j, o; |
|
i = -1; while (++i < n) { |
|
nodes[i].px = nodes[i].x + Math.random() * t/1000 - t/2000; |
|
nodes[i].py = nodes[i].y + Math.random() * t/1000 - t/2000 |
|
nodes[i].vx = nodes[i].x - nodes[i].px; |
|
nodes[i].vy = nodes[i].y - nodes[i].py; |
|
|
|
nodes[i].ax = 0; |
|
nodes[i].ay = 0; |
|
} |
|
} |
|
|
|
function change_temperature(t) { |
|
temperature = t; |
|
var n = nodes.length, i, j, o; |
|
i = -1; while (++i < n) { |
|
nodes[i].px = nodes[i].x + Math.random() * t/10 - t/20; |
|
nodes[i].py = nodes[i].y + Math.random() * t/10 - t/20 |
|
nodes[i].vx = nodes[i].x - nodes[i].px; |
|
nodes[i].vy = nodes[i].y - nodes[i].py; |
|
|
|
nodes[i].ax = 0; |
|
nodes[i].ay = 0; |
|
} |
|
} |
|
|
|
model.on = function(type, listener) { |
|
event.on(type, listener); |
|
return model; |
|
}; |
|
|
|
model.nodes = function(x) { |
|
if (!arguments.length) return nodes; |
|
nodes = x; |
|
return model; |
|
}; |
|
|
|
model.temperature = function(x) { |
|
if (!arguments.length) return temperature; |
|
set_temperature(x) |
|
return model; |
|
}; |
|
|
|
model.ke = function() { |
|
if (!arguments.length) { |
|
return kinetic_energy(); |
|
} |
|
return model; |
|
}; |
|
|
|
model.ljforce = function(x) { |
|
if (!arguments.length) return ljforce; |
|
ljforce = x; |
|
return model; |
|
}; |
|
|
|
model.links = function(x) { |
|
if (!arguments.length) return links; |
|
links = x; |
|
return model; |
|
}; |
|
|
|
model.size = function(x) { |
|
if (!arguments.length) return size; |
|
size = x; |
|
return model; |
|
}; |
|
|
|
model.linkDistance = function(x) { |
|
if (!arguments.length) return linkDistance; |
|
linkDistance = d3.functor(x); |
|
return model; |
|
}; |
|
|
|
// For backwards-compatibility. |
|
model.distance = model.linkDistance; |
|
|
|
model.linkStrength = function(x) { |
|
if (!arguments.length) return linkStrength; |
|
linkStrength = d3.functor(x); |
|
return model; |
|
}; |
|
|
|
model.tick = function() { |
|
tick(); |
|
return model; |
|
}; |
|
|
|
model.resolve_collisions = function(nodes) { |
|
resolve_collisions(nodes); |
|
return model; |
|
}; |
|
|
|
model.friction = function(x) { |
|
if (!arguments.length) return friction; |
|
friction = x; |
|
return model; |
|
}; |
|
|
|
model.charge = function(x) { |
|
if (!arguments.length) return charge; |
|
charge = typeof x === "function" ? x : +x; |
|
return model; |
|
}; |
|
|
|
model.gravity = function(x) { |
|
if (!arguments.length) return gravity; |
|
gravity = x; |
|
return model; |
|
}; |
|
|
|
model.theta = function(x) { |
|
if (!arguments.length) return theta; |
|
theta = x; |
|
return model; |
|
}; |
|
|
|
model.start = function() { |
|
var i, |
|
j, |
|
n = nodes.length, |
|
m = links.length, |
|
w = size[0], |
|
h = size[1], |
|
neighbors, |
|
o; |
|
|
|
for (i = 0; i < n; ++i) { |
|
(o = nodes[i]).index = i; |
|
o.weight = 0; |
|
} |
|
|
|
distances = []; |
|
strengths = []; |
|
forces = []; |
|
for (i = 0; i < n; ++i) { |
|
forces[i] = charge; |
|
} |
|
|
|
return model.resume(); |
|
}; |
|
|
|
model.resume = function() { |
|
stopped = false; |
|
d3.timer(tick); |
|
return model; |
|
}; |
|
|
|
model.stop = function() { |
|
stopped = true; |
|
return model; |
|
}; |
|
|
|
// use `node.call(model.drag)` to make nodes draggable |
|
model.drag = function() { |
|
if (!drag) drag = d3.behavior.drag() |
|
.on("dragstart", dragstart) |
|
.on("drag", modeler_forceDrag) |
|
.on("dragend", modeler_forceDragEnd); |
|
|
|
this.on("mouseover.model", modeler_forceDragOver) |
|
.on("mouseout.model", modeler_forceDragOut) |
|
.call(drag); |
|
}; |
|
|
|
function dragstart(d) { |
|
modeler_forceDragOver(modeler_forceDragNode = d); |
|
modeler_forceDragForce = model; |
|
} |
|
|
|
return model; |
|
}; |
|
|
|
var modeler_forceDragForce, |
|
modeler_forceDragNode; |
|
|
|
function modeler_forceDragOver(d) { |
|
d.fixed |= 2; |
|
} |
|
|
|
function modeler_forceDragOut(d) { |
|
if (d !== modeler_forceDragNode) d.fixed &= 1; |
|
} |
|
|
|
function modeler_forceDragEnd() { |
|
modeler_forceDrag(); |
|
modeler_forceDragNode.fixed &= 1; |
|
modeler_forceDragForce = modeler_forceDragNode = null; |
|
} |
|
|
|
function modeler_forceDrag() { |
|
modeler_forceDragNode.px += d3.event.dx; |
|
modeler_forceDragNode.py += d3.event.dy; |
|
modeler_forceDragForce.resume(); // restart annealing |
|
} |
|
|
|
function modeler_forceAccumulate(quad, forces, boundary) { |
|
var cx = 0, |
|
cy = 0; |
|
|
|
quad.charge = 0; |
|
if (!quad.leaf) { |
|
var nodes = quad.nodes, |
|
n = nodes.length, |
|
i = -1, |
|
c; |
|
while (++i < n) { |
|
c = nodes[i]; |
|
if (c == null) continue; |
|
modeler_forceAccumulate(c, forces); |
|
quad.charge += c.charge; |
|
cx += c.charge * c.cx; |
|
cy += c.charge * c.cy; |
|
} |
|
} |
|
if (quad.point) { |
|
// jitter internal nodes that are coincident |
|
if (!quad.leaf) { |
|
quad.point.x += Math.random() - .5; |
|
quad.point.y += Math.random() - .5; |
|
} |
|
var k = forces[quad.point.index]; |
|
quad.charge += quad.pointCharge = k; |
|
cx += k * quad.point.x; |
|
cy += k * quad.point.y; |
|
} |
|
quad.cx = cx / quad.charge; |
|
quad.cy = cy / quad.charge; |
|
} |
|
|
|
function modeler_forceLinkDistance(link) { |
|
return 20; |
|
} |
|
|
|
function modeler_forceLinkStrength(link) { |
|
return 1; |
|
} |
|
|
|
// export namespace |
|
if (root !== 'undefined') { root.modeler = modeler; } |
|
})(); |
|
|
|
|
|
// |
|
// simplemolecules.js |
|
// |
|
|
|
var graph = {}; |
|
|
|
graph.epsilon = -0.75; // depth of the potential well |
|
graph.sigma = 2; // finite distance at which the inter-particle potential is zero |
|
|
|
graph.xmax = graph.sigma * 4; |
|
graph.xmin = 0; |
|
|
|
graph.ymax = 1.0; |
|
graph.ymin = -1.0; |
|
|
|
graph.title = "Lennard-Jones potential"; |
|
graph.xlabel = "Radius"; |
|
graph.ylabel = "Potential Energy"; |
|
|
|
graph.lennard_jones_potential = []; |
|
|
|
var mol_number = 20, |
|
temperature = 5, |
|
mol_rmin_radius_factor = 1.0; |
|
|
|
var model_controls = document.getElementById("model-controls"); |
|
var model_controls_inputs = model_controls.getElementsByTagName("input"); |
|
|
|
// |
|
// Molecule Number Selector |
|
// |
|
var select_molecule_number = document.getElementById("select-molecule-number"); |
|
|
|
function selectMoleculeNumberChange() { |
|
mol_number = select_molecule_number.value; |
|
modelReset(); |
|
} |
|
|
|
select_molecule_number.onchange = selectMoleculeNumberChange; |
|
|
|
// |
|
// Temperature Selector |
|
// |
|
var select_temperature = document.getElementById("select-temperature"); |
|
|
|
function selectTemperatureChange() { |
|
temperature = select_temperature.value; |
|
model.temperature(temperature); |
|
} |
|
|
|
select_temperature.onchange = selectTemperatureChange; |
|
|
|
// |
|
// |
|
// |
|
|
|
update_coefficients(); |
|
|
|
graph.coefficients = [ |
|
{ coefficient:"epsilon", x:graph.r_min, y:graph.epsilon }, |
|
{ coefficient:"sigma", x:graph.sigma, y:0 } |
|
]; |
|
|
|
function update_epsilon(e) { |
|
graph.epsilon = e; |
|
update_coefficients(); |
|
} |
|
|
|
function update_sigma(s) { |
|
graph.sigma = s; |
|
update_coefficients(); |
|
} |
|
|
|
function update_coefficients() { |
|
graph.r_min = Math.pow(2, 1/6) * graph.sigma; // distance at which the potential well reaches its minimum |
|
graph.lennard_jones_potential = [] |
|
graph.alpha = 4 * graph.epsilon * Math.pow(graph.sigma, 12); |
|
graph.beta = 4 * graph.epsilon * Math.pow(graph.sigma, 6); |
|
var y; |
|
for(var r = graph.sigma * 0.5; r < graph.xmax * 3; r += 0.05) { |
|
y = (graph.alpha/Math.pow(r, 12) - graph.beta/Math.pow(r, 6)) * -1; |
|
if (y < 100) { |
|
graph.lennard_jones_potential.push([r, (graph.alpha/Math.pow(r, 12) - graph.beta/Math.pow(r, 6)) * -1]); |
|
} |
|
} |
|
} |
|
|
|
function ljpotential(distance) { |
|
return (graph.alpha/Math.pow(distance, 12) - graph.beta/Math.pow(distance, 6)) * -1 |
|
} |
|
|
|
function setupScreen() { |
|
if(document.webkitIsFullScreen) { |
|
setupFullScreen(); |
|
} else { |
|
setupRegularScreen() |
|
} |
|
} |
|
|
|
// |
|
// Molecule Container |
|
// |
|
var moleculecontainer = document.getElementById("moleculecontainer"); |
|
|
|
var mc_graph = {}; |
|
mc_graph.title = "Simple Molecules"; |
|
mc_graph.xlabel = "X position"; |
|
mc_graph.ylabel = "Y position"; |
|
|
|
var mc_cx = moleculecontainer.clientWidth, |
|
mc_cy = moleculecontainer.clientHeight, |
|
mc_padding = { |
|
"top": mc_graph.title ? 40 : 20, |
|
"right": 30, |
|
"bottom": mc_graph.xlabel ? 50 : 10, |
|
"left": mc_graph.ylabel ? 70 : 45 |
|
}, |
|
mc_size = { |
|
"width": mc_cx - mc_padding.left - mc_padding.right, |
|
"height": mc_cy - mc_padding.top - mc_padding.bottom |
|
}, |
|
mc_mw = mc_size.width, |
|
mc_mh = mc_size.height, |
|
mc_tx = function(d) { return "translate(" + mc_x(d) + ",0)"; }, |
|
mc_ty = function(d) { return "translate(0," + mc_y(d) + ")"; }, |
|
mc_stroke = function(d) { return d ? "#ccc" : "#666"; } |
|
|
|
mc_graph.xmin = 0, |
|
mc_graph.xmax = 100, |
|
mc_graph.xdomain = mc_graph.xmax - mc_graph.xmin, |
|
mc_graph.ymin = 0, |
|
mc_graph.ymax = mc_graph.xmax * mc_size.height / mc_size.width, |
|
mc_graph.ydomain = mc_graph.ymax - mc_graph.ymin; |
|
|
|
var mc_cx, mc_cy, mc_padding, mc_size, |
|
mc_mw, mc_mh, mc_tx, mc_ty, mc_stroke, |
|
mc_x, mc_downscalex, mc_downx, |
|
mc_y, mc_downscaley, mc_downy, |
|
mc_dragged, |
|
mc_vis, mc_plot, mc_container; |
|
|
|
function setupFullScreen() { |
|
setupFullScreenMoleculeContainer(); |
|
setupFullScreenPotentialChart(); |
|
} |
|
|
|
function setupRegularScreen() { |
|
setupRegularScreenMoleculeContainer(); |
|
setupRegularScreenPotentialChart(); |
|
} |
|
|
|
function setupFullScreenMoleculeContainer() { |
|
moleculecontainer.style.width = screen.width * 0.40 +"px"; |
|
moleculecontainer.style.height = screen.height * 0.65 +"px"; |
|
finishSetupMoleculeContainer(); |
|
} |
|
|
|
function setupRegularScreenMoleculeContainer() { |
|
moleculecontainer.style.width = document.body.clientWidth * 0.45 +"px"; |
|
moleculecontainer.style.height = document.body.clientWidth * 0.42 +"px"; |
|
finishSetupMoleculeContainer(); |
|
} |
|
|
|
function finishSetupMoleculeContainer() { |
|
mc_cx = moleculecontainer.clientWidth, |
|
mc_cy = moleculecontainer.clientHeight, |
|
mc_padding = { |
|
"top": mc_graph.title ? 40 : 20, |
|
"right": 30, |
|
"bottom": mc_graph.xlabel ? 50 : 10, |
|
"left": mc_graph.ylabel ? 70 : 45 |
|
}, |
|
mc_size = { |
|
"width": mc_cx - mc_padding.left - mc_padding.right, |
|
"height": mc_cy - mc_padding.top - mc_padding.bottom |
|
}, |
|
mc_mw = mc_size.width, |
|
mc_mh = mc_size.height, |
|
mc_tx = function(d) { return "translate(" + mc_x(d) + ",0)"; }, |
|
mc_ty = function(d) { return "translate(0," + mc_y(d) + ")"; }, |
|
mc_stroke = function(d) { return d ? "#ccc" : "#666"; } |
|
// mc_x-scale |
|
mc_x = d3.scale.linear() |
|
.domain([mc_graph.xmin, mc_graph.xmax]) |
|
.range([0, mc_mw]), |
|
|
|
// drag x-axis logic |
|
mc_downscalex = mc_x.copy(), |
|
mc_downx = Math.NaN, |
|
|
|
// mc_y-scale (inverted domain) |
|
mc_y = d3.scale.linear() |
|
.domain([mc_graph.ymax, mc_graph.ymin]) |
|
.nice() |
|
.range([0, mc_mh]) |
|
.nice(), |
|
|
|
// drag mc_x-axis logic |
|
mc_downscaley = mc_y.copy(), |
|
mc_downy = Math.NaN, |
|
mc_dragged = null; |
|
|
|
if (undefined !== mc_vis) { |
|
d3.select(moleculecontainer).select("svg") |
|
.attr("width", mc_cx) |
|
.attr("height", mc_cy); |
|
|
|
mc_vis.select("svg") |
|
.attr("width", mc_cx) |
|
.attr("height", mc_cy); |
|
|
|
mc_vis.select("rect.mc_plot") |
|
.attr("width", mc_size.width) |
|
.attr("height", mc_size.height) |
|
.style("fill", "#EEEEEE"); |
|
|
|
mc_vis.select("svg.mc_container") |
|
.attr("top", 0) |
|
.attr("left", 0) |
|
.attr("width", mc_size.width) |
|
.attr("height", mc_size.height) |
|
.attr("viewBox", "0 0 "+mc_size.width+" "+mc_size.height); |
|
|
|
if (mc_graph.title) { |
|
mc_vis.select("text.title") |
|
.attr("x", mc_size.width/2) |
|
.attr("dy","-1em"); |
|
} |
|
|
|
if (mc_graph.xlabel) { |
|
mc_vis.select("text.xlabel") |
|
.attr("x", mc_size.width/2) |
|
.attr("y", mc_size.height); |
|
} |
|
|
|
if (mc_graph.ylabel) { |
|
mc_vis.select("text.ylabel") |
|
.attr("transform","translate(" + -50 + " " + mc_size.height/2+") rotate(-90)"); |
|
} |
|
|
|
mc_vis.selectAll("g.x").remove() |
|
mc_vis.selectAll("g.y").remove() |
|
|
|
} else { |
|
mc_vis = d3.select(moleculecontainer).append("svg:svg") |
|
.attr("width", mc_cx) |
|
.attr("height", mc_cy) |
|
.append("svg:g") |
|
.attr("transform", "translate(" + mc_padding.left + "," + mc_padding.top + ")"); |
|
|
|
mc_plot = mc_vis.append("svg:rect") |
|
.attr("class", "mc_plot") |
|
.attr("width", mc_size.width) |
|
.attr("height", mc_size.height) |
|
.style("fill", "#EEEEEE"); |
|
|
|
mc_container = mc_vis.append("svg:svg") |
|
.attr("class", "mc_container") |
|
.attr("top", 0) |
|
.attr("left", 0) |
|
.attr("width", mc_size.width) |
|
.attr("height", mc_size.height) |
|
.attr("viewBox", "0 0 "+mc_size.width+" "+mc_size.height); |
|
|
|
// add Chart Title |
|
if (mc_graph.title) { |
|
mc_vis.append("svg:text") |
|
.attr("class", "title") |
|
.text(mc_graph.title) |
|
.attr("x", mc_size.width/2) |
|
.attr("dy","-1em") |
|
.style("text-anchor","middle"); |
|
} |
|
|
|
// Add the x-axis label |
|
if (mc_graph.xlabel) { |
|
mc_vis.append("svg:text") |
|
.attr("class", "xlabel") |
|
.text(mc_graph.xlabel) |
|
.attr("x", mc_size.width/2) |
|
.attr("y", mc_size.height) |
|
.attr("dy","2.4em") |
|
.style("text-anchor","middle"); |
|
} |
|
|
|
// add y-axis label |
|
if (mc_graph.ylabel) { |
|
mc_vis.append("svg:g") |
|
.append("svg:text") |
|
.attr("class", "ylabel") |
|
.text(mc_graph.ylabel) |
|
.style("text-anchor","middle") |
|
.attr("transform","translate(" + -50 + " " + mc_size.height/2+") rotate(-90)"); |
|
} |
|
} |
|
mc_redraw() |
|
}; |
|
|
|
function mc_redraw() { |
|
if (d3.event && d3.event.transform && isNaN(mc_downx) && isNaN(mc_downy)) { |
|
d3.event.transform(x, y); |
|
}; |
|
|
|
var mc_fx = mc_x.tickFormat(10), |
|
mc_fy = mc_y.tickFormat(10); |
|
|
|
// Regenerate x-ticks… |
|
var mc_gx = mc_vis.selectAll("g.x") |
|
.data(mc_x.ticks(10), String) |
|
.attr("transform", mc_tx); |
|
|
|
mc_gx.select("text") |
|
.text(mc_fx); |
|
|
|
var mc_gxe = mc_gx.enter().insert("svg:g", "a") |
|
.attr("class", "x") |
|
.attr("transform", mc_tx); |
|
|
|
mc_gxe.append("svg:line") |
|
.attr("stroke", mc_stroke) |
|
.attr("y1", 0) |
|
.attr("y2", mc_size.height); |
|
|
|
mc_gxe.append("svg:text") |
|
.attr("y", mc_size.height) |
|
.attr("dy", "1em") |
|
.attr("text-anchor", "middle") |
|
.text(mc_fx); |
|
|
|
mc_gx.exit().remove(); |
|
|
|
// Regenerate y-ticks… |
|
var mc_gy = mc_vis.selectAll("g.y") |
|
.data(mc_y.ticks(10), String) |
|
.attr("transform", mc_ty); |
|
|
|
mc_gy.select("text") |
|
.text(mc_fy); |
|
|
|
var mc_gye = mc_gy.enter().insert("svg:g", "a") |
|
.attr("class", "y") |
|
.attr("transform", mc_ty) |
|
.attr("background-fill", "#FFEEB6"); |
|
|
|
mc_gye.append("svg:line") |
|
.attr("stroke", mc_stroke) |
|
.attr("x1", 0) |
|
.attr("x2", mc_size.width); |
|
|
|
mc_gye.append("svg:text") |
|
.attr("x", -3) |
|
.attr("dy", ".35em") |
|
.attr("text-anchor", "end") |
|
.text(mc_fy); |
|
|
|
mc_gy.exit().remove(); |
|
} |
|
|
|
// |
|
// Potential Chart |
|
// |
|
|
|
var potentialchart = document.getElementById("potentialchart"); |
|
|
|
var cx, cy, padding, size, |
|
mw, mh, tx, ty, stroke, |
|
x, downscalex, downx, |
|
y, downscaley, downy, |
|
dragged, |
|
vis, plot, container; |
|
|
|
function setupFullScreenPotentialChart() { |
|
potentialchart.style.width = screen.width * 0.50 +"px"; |
|
potentialchart.style.height = screen.height * 0.65 +"px"; |
|
finishSetupPotentialChart(); |
|
} |
|
|
|
function setupRegularScreenPotentialChart() { |
|
potentialchart.style.width = document.body.clientWidth * 0.52 +"px"; |
|
potentialchart.style.height = document.body.clientWidth * 0.42 +"px"; |
|
finishSetupPotentialChart(); |
|
} |
|
|
|
function finishSetupPotentialChart() { |
|
cx = potentialchart.clientWidth, |
|
cy = potentialchart.clientHeight, |
|
padding = { |
|
"top": graph.title ? 40 : 20, |
|
"right": 30, |
|
"bottom": graph.xlabel ? 50 : 10, |
|
"left": graph.ylabel ? 70 : 45 |
|
}, |
|
size = { |
|
"width": cx - padding.left - padding.right, |
|
"height": cy - padding.top - padding.bottom |
|
}, |
|
mw = size.width, |
|
mh = size.height, |
|
tx = function(d) { return "translate(" + x(d) + ",0)"; }, |
|
ty = function(d) { return "translate(0," + y(d) + ")"; }, |
|
stroke = function(d) { return d ? "#ccc" : "#666"; }; |
|
|
|
// x-scale |
|
x = d3.scale.linear() |
|
.domain([graph.xmin, graph.xmax]) |
|
.range([0, mw]), |
|
|
|
// drag x-axis logic |
|
downscalex = x.copy(), |
|
downx = Math.NaN, |
|
|
|
// y-scale (inverted domain) |
|
y = d3.scale.linear() |
|
.domain([graph.ymax, graph.ymin]) |
|
.nice() |
|
.range([0, mh]) |
|
.nice(), |
|
|
|
line = d3.svg.line() |
|
.x(function(d, i) { return x(graph.lennard_jones_potential[i][0]); }) |
|
.y(function(d, i) { return y(graph.lennard_jones_potential[i][1]); }), |
|
|
|
// drag x-axis logic |
|
downscaley = y.copy(), |
|
downy = Math.NaN, |
|
dragged = null, |
|
selected = graph.coefficients[0]; |
|
|
|
if (undefined !== vis) { |
|
d3.select(potentialchart).select("svg") |
|
.attr("width", cx) |
|
.attr("height", cy); |
|
|
|
vis.select("svg") |
|
.attr("width", cx) |
|
.attr("height", cy); |
|
|
|
vis.select("rect.plot") |
|
.attr("width", size.width) |
|
.attr("height", size.height) |
|
.style("fill", "#EEEEEE"); |
|
|
|
vis.select("svg.container") |
|
.attr("top", 0) |
|
.attr("left", 0) |
|
.attr("width", size.width) |
|
.attr("height", size.height) |
|
.attr("viewBox", "0 0 "+size.width+" "+size.height); |
|
|
|
vis.select("svg.linebox") |
|
.attr("top", 0) |
|
.attr("left", 0) |
|
.attr("width", size.width) |
|
.attr("height", size.height) |
|
.attr("viewBox", "0 0 "+size.width+" "+size.height); |
|
|
|
if (graph.title) { |
|
vis.select("text.title") |
|
.attr("x", size.width/2) |
|
.attr("dy","-1em"); |
|
} |
|
|
|
if (graph.xlabel) { |
|
vis.select("text.xlabel") |
|
.attr("x", size.width/2) |
|
.attr("y", size.height); |
|
} |
|
|
|
if (graph.ylabel) { |
|
vis.select("text.ylabel") |
|
.attr("transform","translate(" + -50 + " " + size.height/2+") rotate(-90)"); |
|
} |
|
|
|
vis.selectAll("g.x").remove(); |
|
vis.selectAll("g.y").remove(); |
|
|
|
} else { |
|
|
|
vis = d3.select(potentialchart).append("svg:svg") |
|
.attr("width", cx) |
|
.attr("height", cy) |
|
.append("svg:g") |
|
.attr("transform", "translate(" + padding.left + "," + padding.top + ")"); |
|
|
|
plot = vis.append("svg:rect") |
|
.attr("class", "plot") |
|
.attr("width", size.width) |
|
.attr("height", size.height) |
|
.style("fill", "#EEEEEE") |
|
.attr("pointer-events", "all") |
|
.call(d3.behavior.zoom().on("zoom", redraw)) |
|
.on("mousedown", function() { |
|
if (d3.event.altKey) { |
|
points.push(selected = dragged = d3.svg.mouse(vis.node())); |
|
update(); |
|
d3.event.preventDefault(); |
|
d3.event.stopPropagation(); |
|
} |
|
}); |
|
|
|
vis.append("svg:svg") |
|
.attr("class", "linebox") |
|
.attr("top", 0) |
|
.attr("left", 0) |
|
.attr("width", size.width) |
|
.attr("height", size.height) |
|
.attr("viewBox", "0 0 "+size.width+" "+size.height) |
|
.append("svg:path") |
|
.attr("class", "line") |
|
.attr("d", line(graph.lennard_jones_potential)) |
|
|
|
// add Chart Title |
|
if (graph.title) { |
|
vis.append("svg:text") |
|
.attr("class", "title") |
|
.text(graph.title) |
|
.attr("x", size.width/2) |
|
.attr("dy","-1em") |
|
.style("text-anchor","middle"); |
|
} |
|
|
|
// Add the x-axis label |
|
if (graph.xlabel) { |
|
vis.append("svg:text") |
|
.attr("class", "xlabel") |
|
.text(graph.xlabel) |
|
.attr("x", size.width/2) |
|
.attr("y", size.height) |
|
.attr("dy","2.4em") |
|
.style("text-anchor","middle"); |
|
} |
|
|
|
// add y-axis label |
|
if (graph.ylabel) { |
|
vis.append("svg:g") |
|
.append("svg:text") |
|
.attr("class", "ylabel") |
|
.text( graph.ylabel) |
|
.style("text-anchor","middle") |
|
.attr("transform","translate(" + -50 + " " + size.height/2+") rotate(-90)"); |
|
} |
|
} |
|
redraw() |
|
} |
|
|
|
function redraw() { |
|
if (d3.event && d3.event.transform && isNaN(downx) && isNaN(downy)) { |
|
d3.event.transform(x, y); |
|
}; |
|
|
|
var fx = x.tickFormat(10), |
|
fy = y.tickFormat(10); |
|
|
|
// Regenerate x-ticks… |
|
var gx = vis.selectAll("g.x") |
|
.data(x.ticks(10), String) |
|
.attr("transform", tx); |
|
|
|
gx.select("text") |
|
.text(fx); |
|
|
|
var gxe = gx.enter().insert("svg:g", "a") |
|
.attr("class", "x") |
|
.attr("transform", tx); |
|
|
|
gxe.append("svg:line") |
|
.attr("stroke", stroke) |
|
.attr("y1", 0) |
|
.attr("y2", size.height); |
|
|
|
gxe.append("svg:text") |
|
.attr("y", size.height) |
|
.attr("dy", "1em") |
|
.attr("text-anchor", "middle") |
|
.text(fx) |
|
.on("mouseover", function(d) { d3.select(this).style("font-weight", "bold");}) |
|
.on("mouseout", function(d) { d3.select(this).style("font-weight", "normal");}) |
|
.on("mousedown", function(d) { |
|
var p = d3.svg.mouse(vis[0][0]); |
|
downx = x.invert(p[0]); |
|
downscalex = null; |
|
downscalex = x.copy(); |
|
// d3.behavior.zoom().off("zoom", redraw); |
|
}); |
|
|
|
|
|
gx.exit().remove(); |
|
|
|
// Regenerate y-ticks… |
|
var gy = vis.selectAll("g.y") |
|
.data(y.ticks(10), String) |
|
.attr("transform", ty); |
|
|
|
gy.select("text") |
|
.text(fy); |
|
|
|
var gye = gy.enter().insert("svg:g", "a") |
|
.attr("class", "y") |
|
.attr("transform", ty) |
|
.attr("background-fill", "#FFEEB6"); |
|
|
|
gye.append("svg:line") |
|
.attr("stroke", stroke) |
|
.attr("x1", 0) |
|
.attr("x2", size.width); |
|
|
|
gye.append("svg:text") |
|
.attr("x", -3) |
|
.attr("dy", ".35em") |
|
.attr("text-anchor", "end") |
|
.text(fy) |
|
.on("mouseover", function(d) { d3.select(this).style("font-weight", "bold");}) |
|
.on("mouseout", function(d) { d3.select(this).style("font-weight", "normal");}) |
|
.on("mousedown", function(d) { |
|
var p = d3.svg.mouse(vis[0][0]); |
|
downy = y.invert(p[1]); |
|
downscaley = y.copy(); |
|
// d3.behavior.zoom().off("zoom", redraw); |
|
}); |
|
|
|
gy.exit().remove(); |
|
update(); |
|
|
|
} |
|
|
|
// |
|
// draw the data |
|
// |
|
function update() { |
|
var epsilon_circle = vis.selectAll("circle") |
|
.data(graph.coefficients, function(d) { return d }); |
|
|
|
var lines = vis.select("path").attr("d", line(graph.lennard_jones_potential)), |
|
x_extent = x.domain()[1] - x.domain()[0]; |
|
|
|
epsilon_circle.enter().append("svg:circle") |
|
.attr("class", function(d) { return d === selected ? "selected" : null; }) |
|
.attr("cx", function(d) { return x(d.x); }) |
|
.attr("cy", function(d) { return y(d.y); }) |
|
.attr("r", 8.0) |
|
.on("mousedown", function(d) { |
|
if (d.coefficient == "epsilon") { |
|
d.x = graph.r_min; |
|
} else { |
|
d.y = 0 |
|
} |
|
selected = dragged = d; |
|
update(); |
|
}); |
|
|
|
epsilon_circle |
|
.attr("class", function(d) { return d === selected ? "selected" : null; }) |
|
.attr("cx", function(d) { return x(d.x); }) |
|
.attr("cy", function(d) { return y(d.y); }); |
|
|
|
epsilon_circle.exit().remove(); |
|
|
|
if (d3.event && d3.event.keyCode) { |
|
d3.event.preventDefault(); |
|
d3.event.stopPropagation(); |
|
} |
|
} |
|
|
|
function mousemove() { |
|
if (!dragged) return; |
|
var m = d3.svg.mouse(vis.node()), |
|
newx, newy; |
|
if (dragged.coefficient == "epsilon") { |
|
newx = graph.r_min; |
|
newy = y.invert(Math.max(0, Math.min(size.height, m[1]))); |
|
if (newy > 0) { newy = 0 }; |
|
if (newy < -2) { newy = -2 }; |
|
update_epsilon(newy); |
|
} else { |
|
newy = 0; |
|
newx = x.invert(Math.max(0, Math.min(size.width, m[0]))); |
|
if (newx < 1.1) { newx = 1.1 }; |
|
if (newx > 5.0) { newx = 5.0 }; |
|
update_sigma(newx); |
|
graph.coefficients[0].x = graph.r_min; |
|
} |
|
update_molecule_radius(); |
|
model.resolve_collisions(molecules); |
|
model.tick(); |
|
dragged.x = newx; |
|
dragged.y = newy; |
|
update(); |
|
} |
|
|
|
function mouseup() { |
|
if (!dragged) return; |
|
mousemove(); |
|
dragged = null; |
|
} |
|
|
|
// |
|
// Potential Chart axis scaling |
|
// |
|
// attach the mousemove and mouseup to the body |
|
// in case one wanders off the axis line |
|
d3.select(potentialchart) |
|
.on("mousemove", function(d) { |
|
var p = d3.svg.mouse(vis[0][0]); |
|
if (!isNaN(downx)) { |
|
var rupx = downscalex.invert(p[0]), |
|
xaxis1 = downscalex.domain()[0], |
|
xaxis2 = downscalex.domain()[1], |
|
xextent = xaxis2 - xaxis1; |
|
if (rupx !== 0) { |
|
var changex, dragx_factor, new_domain; |
|
dragx_factor = xextent/downx; |
|
changex = 1 + (downx / rupx - 1) * (xextent/(downx-xaxis1))/dragx_factor; |
|
new_domain = [xaxis1, xaxis1 + (xextent * changex)]; |
|
x.domain(new_domain); |
|
redraw(); |
|
} |
|
d3.event.preventDefault(); |
|
d3.event.stopPropagation(); |
|
} |
|
if (!isNaN(downy)) { |
|
var rupy = downscaley.invert(p[1]), |
|
yaxis1 = downscaley.domain()[1], |
|
yaxis2 = downscaley.domain()[0], |
|
yextent = yaxis2 - yaxis1; |
|
if (rupy !== 0) { |
|
var changey, dragy_factor, new_range; |
|
dragy_factor = yextent/downy; |
|
changey = 1 - (rupy / downy - 1) * (yextent/(downy-yaxis1))/dragy_factor; |
|
new_range = [yaxis1 + (yextent * changey), yaxis1]; |
|
y.domain(new_range); |
|
redraw(); |
|
} |
|
d3.event.preventDefault(); |
|
d3.event.stopPropagation(); |
|
} |
|
}) |
|
.on("mouseup", function(d) { |
|
if (!isNaN(downx)) { |
|
redraw(); |
|
downx = Math.NaN; |
|
d3.event.preventDefault(); |
|
d3.event.stopPropagation(); |
|
} |
|
if (!isNaN(downy)) { |
|
redraw(); |
|
downy = Math.NaN; |
|
d3.event.preventDefault(); |
|
d3.event.stopPropagation(); |
|
} |
|
}); |
|
|
|
// |
|
// |
|
// |
|
|
|
document.onwebkitfullscreenchange = setupScreen; |
|
window.onresize = setupScreen; |
|
|
|
setupScreen(); |
|
|
|
// |
|
// |
|
// |
|
|
|
var links = [], |
|
molecules; |
|
|
|
function generate_molecules(num) { |
|
var radius = graph.r_min * mol_rmin_radius_factor, |
|
px, py, x, y; |
|
|
|
molecules = d3.range(num).map(function(i) { |
|
px = Math.random() * mc_graph.xdomain * 0.8 + mc_graph.xdomain * 0.1, |
|
py = Math.random() * mc_graph.ydomain * 0.8 + mc_graph.ydomain * 0.1, |
|
x = px + Math.random() * temperature/100 - temperature/200, |
|
y = py + Math.random() * temperature/100 - temperature/200; |
|
|
|
return { |
|
index: i, |
|
radius: radius, |
|
px: px, |
|
py: py, |
|
x: x, |
|
y: y, |
|
vx: x - px, |
|
vy: y - py, |
|
ax: 0, |
|
ay: 0 |
|
} |
|
}); |
|
} |
|
|
|
function update_molecule_radius() { |
|
var r = graph.r_min * mol_rmin_radius_factor; |
|
molecules.forEach(function(m) { m.radius = r }); |
|
mc_container.selectAll("circle") |
|
.data(molecules) |
|
.attr("r", function(d) { return mc_x(d.radius) }); |
|
mc_container.selectAll("text") |
|
.attr("font-size", mc_x(r * 1.3) ); |
|
} |
|
|
|
// |
|
// |
|
// |
|
|
|
var model; |
|
|
|
modelReset(); |
|
// model.stop(); |
|
|
|
function model_setup() { |
|
generate_molecules(mol_number); |
|
model = modeler.layout.model() |
|
.size([mc_graph.xdomain, mc_graph.ydomain]) |
|
.gravity(0.0) |
|
.charge(-5) |
|
.ljforce(ljpotential) |
|
.linkStrength(0.0) |
|
.linkDistance(0.0) |
|
.theta(0.8) |
|
.friction(1.0) |
|
.nodes(molecules) |
|
.start(); |
|
model.resolve_collisions(molecules); |
|
} |
|
|
|
var particle, label, labelEnter, tail; |
|
|
|
function setup_particle() { |
|
mc_container.selectAll("circle").remove(); |
|
mc_container.selectAll("g").remove(); |
|
particle = mc_container.selectAll("circle") |
|
.data(molecules) |
|
.enter().append("svg:circle") |
|
.attr("r", function(d) { return mc_x(d.radius); }) |
|
.attr("cx", function(d) { return mc_x(d.x); }) |
|
.attr("cy", function(d) { return mc_y(d.y); }) |
|
.style("fill", function(d, i) { return "#2ca02c"; }) |
|
.call(model.drag); |
|
|
|
var font_size = mc_x(graph.r_min * mol_rmin_radius_factor * 1.3); |
|
|
|
label = mc_container.selectAll("g.label") |
|
.data(molecules); |
|
|
|
labelEnter = label.enter().append("svg:g") |
|
.attr("class", "label") |
|
.attr("transform", function(d) { |
|
return "translate(" + mc_x(d.x) + "," + mc_y(d.y) + ")"; |
|
}); |
|
|
|
labelEnter.append("svg:line") |
|
.attr("class", "index") |
|
.attr("class", "line") |
|
.attr("x2", function(d) { return d.vx }) |
|
.attr("y2", function(d) { return d.vy }) |
|
.attr("stroke", "#ccc"); |
|
|
|
labelEnter.append("svg:text") |
|
.attr("class", "index") |
|
.attr("font-size", font_size) |
|
.attr("x", 0) |
|
.attr("y", "0.31em") |
|
.text(function(d) { return d.index; }); |
|
} |
|
|
|
|
|
model.on("tick", function(e) { |
|
|
|
particle.attr("cx", function(d) { return mc_x(d.x); }) |
|
.attr("cy", function(d) { return mc_y(d.y); }); |
|
|
|
label.attr("transform", function(d) { |
|
return "translate(" + mc_x(d.x) + "," + mc_y(d.y) + ")"; |
|
}); |
|
|
|
}); |
|
|
|
|
|
function modelController() { |
|
for(i = 0; i < this.elements.length; i++) { |
|
if (this.elements[i].checked) { run_mode = this.elements[i].value; } |
|
} |
|
switch(run_mode) { |
|
case "stop": |
|
modelStop(); |
|
break; |
|
case "step": |
|
modelStep(); |
|
break; |
|
case "go": |
|
modelGo(); |
|
break; |
|
case "reset": |
|
modelReset(); |
|
break; |
|
} |
|
} |
|
|
|
model_controls.onchange = modelController; |
|
|
|
function modelStop() { |
|
model.stop(); |
|
model_controls_inputs[0].checked = true; |
|
} |
|
|
|
function modelStep() { |
|
model.stop(); |
|
model.tick(); |
|
model_controls_inputs[0].checked = true; |
|
} |
|
|
|
function modelGo() { |
|
model.resume(); |
|
} |
|
|
|
function modelReset() { |
|
model_setup(); |
|
model.stop(); |
|
setup_particle(); |
|
// select_temperature.value = 5; |
|
// temperature = 5; |
|
model.temperature(temperature); |
|
model.tick(); |
|
model_controls_inputs[0].checked = true; |
|
} |
|
|
|
d3.select(window) |
|
.on("mousemove", mousemove) |
|
.on("mouseup", mouseup); |
|
|
|
</script> |
|
|
|
</body> |
|
</html> |