D3
OG
Old school D3 from simpler times
All examples
By author
By category
About
chemplexity
Full window
Github gist
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> </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> </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> </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>