Skip to content

Instantly share code, notes, and snippets.

@mtaptich
Last active December 31, 2015 20:58
Show Gist options
  • Save mtaptich/91d0d735e99017a303b8 to your computer and use it in GitHub Desktop.
Save mtaptich/91d0d735e99017a303b8 to your computer and use it in GitHub Desktop.
Combustion Reaction
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
width: 1024px;
margin-top: 0;
margin: auto;
font-family: "Lato", "PT Serif", serif;
color: #222222;
padding: 0;
font-weight: 300;
line-height: 33px;
-webkit-font-smoothing: antialiased;
}
text {
pointer-events: none;
}
.O2{
fill: #2980b9;
}
.C{
fill: #000;
}
.H{
fill: red;
}
.view{
cursor:pointer;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var margin = { top: 100, right: 20, bottom: 20, left: 20 },
width = 960 - margin.left - margin.right,
height = 540 - margin.top - margin.bottom,
nodes = [], nodesByName = {}, links = [], combust = false,
sub = ['₀','₁','₂','₃','₄','₅','₆','₇','₈','₉','₁₀','₁₀','₁₁','₁₂','₁₃','₁₄','₁₅','₁₆','₁₇','₁₈','₁₉','₂₀'],
translate_speed = 1500;
var color = d3.scale.category20();
var force = d3.layout.force()
.charge(-140)
.gravity(0)
.alpha(0.8)
.linkDistance(function(d){
if (d.id[0]=='o'){
return 25
} else{
return 5
}
})
.size([width, height]);
var svg = d3.select('body').append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.on('click', combustion)
.append('g')
.attr('class', 'view')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
var equation = svg.append('text')
.attr('x', width/2)
.attr('y', -30)
.style('text-anchor', 'middle')
.style('font-size', '70')
.text('equation')
svg.append('text')
.attr('x', width)
.attr('y', height+9)
.style('text-anchor', 'end')
.style('font-size', '10')
.text('Click to initiate combustion.')
function add(fullname) {
var name = fullname.split(',')[0];
if (!nodesByName[name]) {
var node = {"name":name, "links":[], 'rebondC': 0, 'rebondh2': false, 'isCO2': false}
nodesByName[name] = node;
nodes.push(node);
return node;
}
else
return nodesByName[name];
}
function O2(molecules){
for (var i = 0; i < molecules; i++) {
links.push({"source": add('O2 OA'+i), "target": add('O2 OB'+i), "value": 1, 'id': 'oo o'+i })
links.push({"source": add('O2 OB'+i), "target": add('O2 OB'+i), "value": 1, 'id': '_o o'+i })
};
}
function hydrocarbon(carbons){
var z = (carbons) + (carbons *2 +2)/4;
O2(z);
// update equation
equation.text('C'+sub[carbons]+'H'+sub[(carbons *2 +2)]+' + '+z+' O₂')
var cc = 0, hh = 0, i = 1
while(cc<carbons) {
if (i==1){
links.push({"source": add('C CA'+i), "target": add('H HB'+(i-2)), "value": 1, 'id': 'h'+hh})
links.push({"source": add('C CA'+i), "target": add('H HB'+(i)), "value": 1, 'id': 'h'+(hh+1)})
links.push({"source": add('C CA'+i), "target": add('H HB'+(i-1)), "value": 1, 'id': 'h'+(hh+2)})
cc++
hh = hh + 3
} else if (cc<carbons-1){
links.push({"source": add('C CA'+i), "target": add('H HB'+(i)), "value": 1, 'id': 'h'+hh})
links.push({"source": add('C CA'+i), "target": add('H HB'+(i-1)), "value": 1, 'id': 'h'+(hh+1)})
links.push({"source": add('C CA'+i), "target": add('C CA'+(i-3)), "value": 1, 'id': 'cc c'+cc})
cc++
hh = hh + 2;
} else{
links.push({"source": add('C CA'+i), "target": add('H HB'+(i-2)), "value": 1, 'id': 'h'+hh})
links.push({"source": add('C CA'+i), "target": add('H HB'+(i)), "value": 1, 'id':'h'+(hh+1) })
links.push({"source": add('C CA'+i), "target": add('H HB'+(i-1)), "value": 1, 'id': 'h'+(hh+2)})
links.push({"source": add('C CA'+i), "target": add('C CA'+(i-3)), "value": 1, 'id':'cc c'+cc })
hh = hh + 3
cc++
}
i = i+3
};
}
function getUnbondedC(){
var carbons = d3.selectAll('.C').data()
for (var i = 0; i < carbons.length; i++) {
if(carbons[i].rebondC < 2){
d3.selectAll('.C').data()[i].rebondC++
return carbons[i]
}
};
return false
}
function getUnbondedH(){
var hydrogens = d3.selectAll('.H').data()
for (var i = 0; i < hydrogens.length; i++) {
if(!hydrogens[i].rebondh2){
d3.selectAll('.H').data()[i].rebondh2 = true
return hydrogens[i]
}
};
return false
}
function endall(transition, callback){
if (transition.size() == 0){ callback()}
var n = 0;
transition
.each(function(){ ++n;})
.each('end', function(){
if (!--n) callback.apply(this, arguments)
})
}
function regenerate(){
d3.selectAll('.node').transition().delay(translate_speed*3).duration(translate_speed).style('opacity', '0').transition()
.call(endall,function(){
d3.selectAll('.node').remove();
d3.select('svg').style('pointer-events', 'auto');
fuels();
combust = false;
})
}
function combustion(){
// diable clicks
d3.select('svg').style('pointer-events', 'none');
// update grouping
combust = true;
var carbons = 0, hydrogens = 0
nodes.forEach(function(d){
if (d.name[0]=='C') carbons++
if (d.name[0]=='H') hydrogens++
})
// update equation
equation.text(carbons+' CO₂ + '+hydrogens/2+' H₂O')
var bonds = force.links().length
for (var i = 0; i < bonds; i++) {
var link = force.links().shift();
if (link.source.name[0]=='O'){
// make carbon dioxide
var target = getUnbondedC();
if (target) {
target.isCO2 = true;
link.source.isCO2 = true;
link.target = target;
force.links().push(link);
} else{
// make water
var target = getUnbondedH();
link.target = target;
force.links().push(link);
var target = getUnbondedH();
var newlink = {'source':link.source, 'target': target, 'value': 1, 'id': 'o'};
force.links().push(newlink);
};
}
};
force.start()
regenerate()
}
function fuels(){
var carbons = [3, 5, 7, 9],
position = Math.floor(Math.random()*4),
next = carbons[position];
nodes = [], nodesByName = {}, links = [];
hydrocarbon(next)
force
.nodes(nodes)
.links(links)
.start();
var node = svg.selectAll(".node")
.data(nodes).enter()
var atom = node.append("circle")
.attr("class", function(d,i){ return "node "+d.name })
.attr("r", function(d){
if (d.name[0]=='O'){
return 16
} else if (d.name[0]=='C'){
return 12
} else{
return 5
}
})
.call(force.drag);
var atomlabel = node.append('text')
.attr("class", function(d,i){ return "node "+d.name })
.text(function(d){ return d.name[0]})
.attr('dy', '0.35em')
.style('text-anchor', 'middle')
.style('fill', '#fff')
.style("font-size", function(d){
if (d.name[0]=='O'){
return 16
} else if (d.name[0]=='C'){
return 12
} else{
return 5
}
})
force.on("tick", function(e) {
if (!combust){
var k = 0.08 * e.alpha;
nodes.forEach(function(o) {
if (o.name[0] == 'O'){
o.y += (height/2 - o.y) * k;
o.x += (width*2/3 - o.x) * k;
} else{
o.y += (height/2 - o.y) * k;
o.x += (width/3 - o.x) * k;
}
});
atom.data(nodes);
}else{
var k = 0.08 * e.alpha;
nodes.forEach(function(o) {
if (!o.isCO2){
o.y += (height/2 - o.y) * k;
o.x += (width*2/3 - o.x) * k;
} else{
o.y += (height/2 - o.y) * k;
o.x += (width/3 - o.x) * k;
}
});
atom.data(nodes);
}
atom
.attr("cx", function(d) {
var radius = d3.select(this).attr('r')
return d.x = Math.max(radius, Math.min(width - radius, d.x));
})
.attr("cy", function(d) {
var radius = d3.select(this).attr('r')
return d.y = Math.max(radius, Math.min(height - radius, d.y))
});
atomlabel
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; });
});
}
fuels()
d3.select(self.frameElement).style("height", (height + margin.top + margin.bottom) + "px");
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment