Skip to content

Instantly share code, notes, and snippets.

@chemplexity
Last active August 29, 2015 14:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chemplexity/2f6a6195cbe89d10f5f4 to your computer and use it in GitHub Desktop.
Save chemplexity/2f6a6195cbe89d10f5f4 to your computer and use it in GitHub Desktop.
Interactive Mass Spectrometer
<html>
<head>
<style>
body {
margin:10;
padding:10;
}
canvas {
background-color: white;
}
p {
font-family: "Verdana";
font-size: 14px;
}
div#strength {
position:absolute;
width:300px;
height:100px;
left:50px;
top:210px;
}
</style>
</head>
<body>
<div id="strength">
<table>
<tr><td align="right"><p>Acceleration Voltage</p></td><td>&nbsp</td>
<td align="middle"><input type="range" min="2.4" max="4.2" step="0.01" onchange="voltageSlider(this.value)"/></td></tr>
<tr><td align="right"><p>Ion Optics</p></td><td>&nbsp</td>
<td align="middle"><input type="range" min="-0.9" max="2.1" step="0.01" onchange="opticsSlider(this.value)"/></td></tr>
<tr><td align="right"><p>Magnetic Sector</p></td><td>&nbsp</td>
<td align="middle"><input type="range" min="-200" max="-50" step="0.01" onchange="magnetSlider(this.value)"/></td></tr>
</table>
</div>
<canvas></canvas>
</body>
</html>
<script>
"use strict";
// Global Variables
var maxParticles = 2500,
particleSize = 3,
emissionRate = 9,
objectSize = 5,
fieldValue = -160;
// Counter Variables
var counterA = 0,
counterB = 0,
counterC = 0,
counterIons = 0;
// Position Variables
var startX = 40,
startY = 40,
cableX = startX+125,
cableY = startY+120;
// Text Variables
var maxDisplay = 0,
maxIon = 0;
// Canvas Properties
var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');
canvas.width = 400;
canvas.height = 400;
// Particle Object
function Particle(point, velocity, acceleration) {
this.position = point || new Vector(0, 0);
this.velocity = velocity || new Vector(0, 0);
this.acceleration = acceleration || new Vector(0, 0);
}
// Magnetic Field Calculations
Particle.prototype.submitToFields = function (fields) {
// Particle Acceleration
var totalAccelerationX = 0;
var totalAccelerationY = 0;
// Calculations
for (var i = 0; i < fields.length; i++) {
var field = fields[i];
// Distance to Magnet
var vectorX = field.position.x - this.position.x;
var vectorY = field.position.y - this.position.y;
// Force of Magnet
var force = field.mass / Math.pow(vectorX * vectorX + vectorY * vectorY, 1.5);
totalAccelerationX += vectorX * force;
totalAccelerationY += vectorY * force;
}
// Update Acceleration
this.acceleration = new Vector(totalAccelerationX, totalAccelerationY);
};
// Particle Properties
Particle.prototype.move = function () {
this.velocity.add(this.acceleration);
this.position.add(this.velocity);
};
// Field Position
function Field(point, mass) {
this.position = point;
this.setMass(mass);
}
// Field Properties
Field.prototype.setMass = function(mass) {
this.mass = mass || 10;
this.drawColor = mass < 0 ? "#f00" : "#0f0";
}
// Emitter Properties
function Vector(x, y) {this.x = x || 0; this.y = y || 0;}
Vector.prototype.add = function(vector) {
this.x += vector.x;
this.y += vector.y;}
Vector.prototype.getMagnitude = function () {
return Math.sqrt(this.x * this.x + this.y * this.y)}
Vector.prototype.getAngle = function () {
return Math.atan2(this.y, this.x)}
Vector.fromAngle = function (angle, magnitude) {
return new Vector(magnitude * Math.cos(angle), magnitude * Math.sin(angle))}
// Create Emitter
function Emitter(point, velocity, spread) {
// Emitter Properties
this.position = point;
this.velocity = velocity;
this.spread = spread || Math.PI / 360;
this.drawColor = "#202020";
}
// Update Emitter
Emitter.prototype.emitParticle = function() {
// Particle Properties
var angle = this.velocity.getAngle() + this.spread - (Math.random() * this.spread * 3.5);
var initial = this.velocity.getMagnitude();
var magnitude = initial + (Math.random() * initial * 0.08);
var position = new Vector(this.position.x, this.position.y);
var velocity = Vector.fromAngle(angle, magnitude);
return new Particle(position,velocity);
}
// Create Particle
function addNewParticles() {
// Stop Filter
if (particles.length > maxParticles) return;
// Emitter
for (var i = 0; i < emitters.length; i++) {
// Particle Array
for (var j = 0; j < emissionRate; j++) {
particles.push(emitters[i].emitParticle());
}
}
}
// Particle Animation
function plotParticles(boundsX, boundsY) {
// Particle Array
var currentParticles = [];
// Particle Movement
for (var i = 0; i < particles.length; i++) {
var particle = particles[i];
var pos = particle.position;
// Out of Bounds
if (pos.x < 0 || pos.x > boundsX || pos.y < 0 || pos.y > boundsY) continue;
// Update Detector
if (pos.x <= startX+290 && pos.x >= startX+281 && pos.y >= startY+55 && pos.y <= startY+55+7) {
counterA += 1;
};
// Particle Trajectory
particle.submitToFields(fields);
particle.move();
currentParticles.push(particle);
}
// Particle Array
particles = currentParticles;
}
// Draw Particles
function drawParticles() {
// Particle Color
ctx.fillStyle = "rgba(10, 70, 250, 0.25)";
// Particle Properties
for (var i = 0; i < particles.length; i++) {
var position = particles[i].position;
ctx.fillRect(position.x, position.y, particleSize, particleSize);
}
}
// Draw Fields
function drawCircle(object) {
//Fields
ctx.fillStyle = object.drawColor;
ctx.beginPath();
ctx.arc(object.position.x, object.position.y, objectSize, 0, Math.PI * 3);
ctx.closePath();
ctx.fill();
}
// Draw Detector
function drawDetector() {
// Save Canvas
ctx.save();
// Position
var x = ctx.width,
y = ctx.height,
detectorX = 280,
detectorY = 135,
deg = 33;
// Detector
ctx.beginPath();
ctx.translate(x, y);
ctx.rotate(deg * Math.PI / 180);
ctx.rect(startX+detectorX, startY-detectorY-2.5, 15, 5.5);
ctx.fillStyle = "rgba(0, 0, 0, 0.9)"
ctx.fill();
// Top Line
ctx.beginPath();
ctx.translate(x, y);
ctx.rotate(0 * Math.PI / 180);
ctx.rect(startX+detectorX-7, startY-detectorY-5, 22, 3);
ctx.fillStyle = "rgba(0, 0, 0, 0.5)"
ctx.fill();
// Bottom Line
ctx.beginPath();
ctx.translate(x, y);
ctx.rotate(0 * Math.PI / 180);
ctx.rect(startX+detectorX-7, startY-detectorY+3, 22, 3);
ctx.fillStyle = "rgba(0, 0, 0, 0.5)"
ctx.fill();
// Restor Canvas
ctx.restore();
// Cable Position
var fromX = startX+detectorX+4;
var fromY = startY+detectorY-70.5;
var toX = startX+125;
var toY = startY+120;
var cp1X = startX+220;
var cp1Y = startY+200;
var cp2X = startX+230;
var cp2Y = startY+20;
// Cable
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(fromX, fromY);
ctx.bezierCurveTo(cp1X, cp1Y, cp2X, cp2Y, toX, toY);
ctx.stroke();
ctx.closePath();
}
// Draw Computer
function drawComputer() {
// Computer Position
var computerX = startX+123;
var computerY = startY+80;
var detectorX = 310;
var detectorY = 130;
var fromX = startX+detectorX+2;
var fromY = startY+detectorY-64;
var toX = startX+125;
var toY = startY+120;
var cp1X = startX+220;
var cp1Y = startY+200;
var cp2X = startX+250;
var cp2Y = startY+20;
// Computer Screen
ctx.beginPath();
ctx.rect(computerX-80, computerY, 50, 40);
ctx.strokeStyle = "rgba(0, 0, 0, 0.7)"
ctx.lineWidth = 4;
ctx.stroke();
// Computer Stand
ctx.beginPath();
ctx.rect(computerX-65, computerY+45, 20, 2);
ctx.strokeStyle = "rgba(0, 0, 0, 0.7)"
ctx.lineWidth = 2;
ctx.stroke();
// Computer Base
ctx.beginPath();
ctx.rect(computerX-20, computerY, 20, 47);
ctx.strokeStyle = "rgba(0, 0, 0, 0.7)"
ctx.lineWidth = 4;
ctx.stroke();
// Computer Button Bottom
ctx.beginPath();
ctx.rect(computerX-10.5, computerY + 34, 2, 4);
ctx.strokeStyle = powerColor;
ctx.lineWidth = 2.5;
ctx.stroke();
// Computer Button Top
ctx.beginPath();
ctx.rect(computerX-13.2, computerY + 7, 7, 0.5);
ctx.strokeStyle = "rgba(0, 0, 0, 0.7)"
ctx.lineWidth = 2;
ctx.stroke();
// Computer Button Middle
ctx.beginPath();
ctx.rect(computerX-14, computerY + 12, 8.5, 0.5);
ctx.strokeStyle = "rgba(0, 0, 0, 0.7)"
ctx.lineWidth = 2;
ctx.stroke();
// Ions/Second
ctx.font = '7pt Verdana';
ctx.textAlign = 'center';
ctx.fillStyle = 'black';
ctx.fillText("ions/sec", computerX-55, computerY+32);
}
// Update Computer
function updateComputer() {
// Computer Position
var computerX = startX+123;
var computerY = startY+80;
var count = 0;
if (maxDisplay == 0) {count = counterIons}
else if (maxDisplay == 1) {count = maxIon}
// Ions/Second
ctx.font = '10pt Verdana';
ctx.textAlign = 'center';
ctx.fillStyle = 'black';
ctx.fillText(count.toString(), computerX-55, computerY+20);
}
// Timer
function timedCount() {
// Average Ions (over 3 seconds)
counterIons = Math.round((counterA + counterB + counterC) / 3);
// Pause
setTimeout(function(){timedCount()}, 1000);
// Update Text
updateComputer();
// Update Counter
counterC = counterB;
counterB = counterA;
counterA = 0;
//Update Max
if (counterIons > maxIon) {maxIon = counterIons}
}
// Intialize Timer
timedCount();
// Draw Mass Spectrometer
function drawMS() {
// Emitter
ctx.beginPath();
ctx.rect(startX-10, startY-1, 24, 9);
ctx.fillStyle = "rgba(0, 0, 0, 0.4)"
ctx.fill();
ctx.lineWidth = 0.5;
ctx.stroke();
// Magnetic Sector
var sectorX1 = startX + 150;
var sectorX2 = sectorX1;
var sectorY1 = startY + 175;
var sectorY2 = sectorY1 - 20;
var sectorLine = 7;
var radius = 190;
var startAngle = 1.5 * Math.PI;
var endAngle = 1.67 * Math.PI;
var counterClockwise = false;
// Top Arch
ctx.beginPath();
ctx.arc(sectorX1, sectorY1, radius, startAngle, endAngle, counterClockwise);
ctx.strokeStyle = "rgba(20, 20, 20, 0.7)"
ctx.lineWidth = sectorLine;
ctx.stroke();
// Bottom Arch
ctx.beginPath();
ctx.arc(sectorX2, sectorY2, radius-(0.3 * radius), startAngle, endAngle, counterClockwise);
ctx.strokeStyle = "rgba(20, 20, 20, 0.7)"
ctx.lineWidth = sectorLine;
ctx.stroke();
// Top Line
ctx.beginPath();
ctx.rect(startX+42.5, startY+14, 80, 6.0);
ctx.fillStyle = "rgba(10, 10, 10, 0.35)"
ctx.lineWidth = 0.75;
ctx.strokeStyle = "rgba(0, 0, 0, 0.4)"
ctx.stroke();
ctx.fill();
// Bottom Line
ctx.beginPath();
ctx.rect(startX+42.5, startY-13, 80, 6.0);
ctx.fillStyle = "rgba(10, 10, 10, 0.35)"
ctx.fill();
ctx.strokeStyle = "rgba(0, 0, 0, 0.4)"
ctx.lineWidth = 0.75;
ctx.stroke();
}
// Magnetic Sector Slider
function magnetSlider(force) {
fields[0].mass = -force;
}
// Ion Optics Slider
function opticsSlider(force) {
// Sharpen Condition
//if (force < 0.5 && force > 0) {force = 0.1}
emitters[0].spread = (Math.PI / 360) * force * force
emitters[1].spread = (Math.PI / 360) * force * force
}
// Acceleration Voltage Slider
function voltageSlider(force) {
emitters[0].velocity.x = force - 0.1
emitters[1].velocity.x = force
}
// Particle Array
var particles = [];
// Position
var midX = canvas.width / 2;
var midY = canvas.height / 2;
// Emitters
var emitters = [new Emitter(new Vector(midX - 150, startY + 2), Vector.fromAngle(-0.0, 3.3)),
new Emitter(new Vector(midX - 150, startY + 2), Vector.fromAngle(-0.0, 3.4))];
// Fields //50 27 50 45 130 -10
var fields = [new Field(new Vector(midX + 34, startY + 45), -fieldValue)];
// Animation
function loop() {
clear();
update();
draw();
queue();
}
// Clear
function clear() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
// Update
function update() {
addNewParticles();
plotParticles(canvas.width, canvas.height/2);
}
// Draw
function draw() {
drawParticles();
drawDetector();
drawMS();
drawComputer();
updateComputer();
//fields.forEach(drawCircle);
//emitters.forEach(drawCircle);
}
// Wait
function queue() {
window.requestAnimationFrame(loop);
}
// Start
loop();
// Mouse Coordinates
function getMousePos(canvas, evt) {
// Boundary
var rect = canvas.getBoundingClientRect();
// XY Values
return {x: evt.clientX - rect.left,
y: evt.clientY - rect.top};
}
// Mouse Position
var mousePos = []
// Position Listener
canvas.addEventListener('mousemove', function(evt) {
mousePos = getMousePos(canvas, evt)
if (mousePos.x >= startX+43 && mousePos.x <= startX+43+50 &&
mousePos.y >= startY+80 && mousePos.y <= startY+80+40) {
maxDisplay = 1}
else {maxDisplay = 0}
}, false);
// Computer Screen Position
var screen = {left: startX+43, top: startY+80, right: startX+43+50, bottom: startY+80+40};
var toggleLow = 0,
togglePower = 0,
maxParticlesLast = maxParticles,
emissionRateLast = emissionRate,
powerColor = "";
if (togglePower == 0) {powerColor = "rgba(20, 230, 20, 0.9)"}
else if(togglePower == 1) {powerColor = "rgba(230, 20, 20, 0.9)"}
canvas.addEventListener('click', function(){
if (mousePos.x >= startX+43 && mousePos.x <= startX+43+50 &&
mousePos.y >= startY+80 && mousePos.y <= startY+80+40 &&
toggleLow == 0) {
toggleLow = 1
togglePower = 0
maxParticles = 500
emissionRate = 2
powerColor = "rgba(20, 230, 20, 0.9)"}
else if (mousePos.x >= startX+43 && mousePos.x <= startX+43+50 &&
mousePos.y >= startY+80 && mousePos.y <= startY+80+40 &&
toggleLow == 1) {
toggleLow = 0
togglePower = 0
maxParticles = 2500
emissionRate = 9
powerColor = "rgba(20, 230, 20, 0.9)"
}
else if (mousePos.x >= startX+123-20 && mousePos.x <= startX+123-20+20 &&
mousePos.y >= startY+80 && mousePos.y <= startY+80+47 &&
togglePower == 0) {
togglePower = 1
maxParticlesLast = maxParticles
maxParticles = 0
emissionRateLast = emissionRate
emissionRate = 0
powerColor = "rgba(230, 20, 20, 0.9)"
}
else if (mousePos.x >= startX+123-20 && mousePos.x <= startX+123-20+20 &&
mousePos.y >= startY+80 && mousePos.y <= startY+80+47 &&
togglePower == 1) {
togglePower = 0
maxParticles = maxParticlesLast
emissionRate = emissionRateLast
powerColor = "rgba(20, 230, 20, 0.9)"
}
});
// Misc. Code
function misc(){
// Text
ctx.font = '10pt Verdana';
ctx.textAlign = 'center';
ctx.fillStyle = 'black';
ctx.fillText('Source', 30, textY);
// Computer Color
ctx.beginPath();
ctx.rect(computerX-80, computerY, 50, 40);
ctx.fillStyle = "rgba(62, 219, 67, 0.7";
ctx.fill();
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment