Geophysics_day2 / script.js
cwadayi's picture
Create script.js
835dae2 verified
// Interactive Travel-Time Curve
const ttCanvas = document.getElementById('travelTimeCanvas');
const ttCtx = ttCanvas.getContext('2d');
let receiverX = 100;
let isDragging = false;
const Vp = 200; // P-wave velocity m/s
const Vs = 120; // S-wave velocity m/s
const Vr = 100; // Rayleigh-wave velocity m/s
function drawTravelTime() {
const w = ttCanvas.width;
const h = ttCanvas.height;
ttCtx.clearRect(0, 0, w, h);
// Part 1: Survey Diagram (Top half)
const groundY = h * 0.2;
ttCtx.fillStyle = '#8b4513';
ttCtx.fillRect(0, groundY, w, h*0.3);
ttCtx.fillStyle = '#add8e6';
ttCtx.fillRect(0, 0, w, groundY);
// Source
ttCtx.fillStyle = 'red';
ttCtx.beginPath();
ttCtx.arc(50, groundY, 10, 0, Math.PI * 2);
ttCtx.fill();
ttCtx.fillText('Source', 40, groundY - 15);
// Receiver
ttCtx.fillStyle = 'blue';
ttCtx.fillRect(receiverX - 5, groundY - 20, 10, 20);
ttCtx.fillText('Receiver', receiverX - 25, groundY - 25);
// Part 2: Travel-Time Graph (Bottom half)
const graphOriginX = 50;
const graphOriginY = h * 0.5;
const graphHeight = h * 0.45;
const graphWidth = w - 60;
// Axes
ttCtx.strokeStyle = 'black';
ttCtx.beginPath();
ttCtx.moveTo(graphOriginX, graphOriginY);
ttCtx.lineTo(graphOriginX, graphOriginY + graphHeight);
ttCtx.lineTo(graphOriginX + graphWidth, graphOriginY + graphHeight);
ttCtx.stroke();
ttCtx.fillText('Time (T)', graphOriginX - 40, graphOriginY + graphHeight / 2);
ttCtx.fillText('Distance (X)', graphOriginX + graphWidth / 2, graphOriginY + graphHeight + 20);
// Plot lines
const plotX = receiverX - 50;
const maxDist = w - 100;
function plotWave(v, color) {
ttCtx.strokeStyle = color;
ttCtx.lineWidth = 2;
ttCtx.beginPath();
ttCtx.moveTo(graphOriginX, graphOriginY + graphHeight);
// Slope = 1/V. T = X/V. Y_pixel = T * scale_Y. X_pixel = X
// We invert T axis, so T=0 is at the top
let endT = maxDist / v;
let scaleY = graphHeight / (maxDist/Vr); // Scale based on slowest wave
let endY = graphOriginY + graphHeight - (endT * scaleY);
let endX = graphOriginX + maxDist;
// The line in the graph is T vs X, so Y axis is T.
// Y = origin + H - (T*scale)
// T = X/V
// Y = origin + H - (X/V * scaleY)
ttCtx.moveTo(graphOriginX, graphOriginY + graphHeight);
ttCtx.lineTo(graphOriginX+maxDist, graphOriginY+graphHeight - (maxDist/v * scaleY) );
ttCtx.stroke();
// Plot point for current receiver
let currentT = plotX / v;
let pointY = graphOriginY + graphHeight - (currentT * scaleY);
ttCtx.fillStyle = color;
ttCtx.beginPath();
ttCtx.arc(graphOriginX + plotX, pointY, 5, 0, Math.PI * 2);
ttCtx.fill();
}
plotWave(Vp, '#d90429');
plotWave(Vs, '#0077b6');
plotWave(Vr, '#2d6a4f');
ttCtx.lineWidth = 1;
}
ttCanvas.addEventListener('mousedown', (e) => {
isDragging = true;
});
ttCanvas.addEventListener('mouseup', () => {
isDragging = false;
});
ttCanvas.addEventListener('mouseleave', () => {
isDragging = false;
});
ttCanvas.addEventListener('mousemove', (e) => {
if (isDragging) {
const rect = ttCanvas.getBoundingClientRect();
let x = e.clientX - rect.left;
if (x > 50 && x < ttCanvas.width - 50) {
receiverX = x;
drawTravelTime();
}
}
});
// Interactive Refraction
const refCanvas = document.getElementById('refractionCanvas');
const refCtx = refCanvas.getContext('2d');
const slider = document.getElementById('v2-slider');
const v2ValueSpan = document.getElementById('v2-value');
const V1 = 1000;
function drawRefraction() {
const w = refCanvas.width;
const h = refCanvas.height;
refCtx.clearRect(0, 0, w, h);
const V2 = slider.value;
v2ValueSpan.textContent = V2;
const interfaceY = h / 2;
// Layers
refCtx.fillStyle = '#f0e68c'; // Layer 1
refCtx.fillRect(0, 0, w, interfaceY);
refCtx.fillStyle = '#d2b48c'; // Layer 2
refCtx.fillRect(0, interfaceY, w, h);
refCtx.fillText(`V₁ = ${V1} m/s`, 10, 20);
refCtx.fillText(`V₂ = ${V2} m/s`, 10, interfaceY + 20);
// Incident Ray
const source = { x: 100, y: 0 };
const incidentPoint = { x: 250, y: interfaceY };
const angle1 = Math.atan((incidentPoint.x - source.x) / incidentPoint.y);
refCtx.strokeStyle = 'black';
refCtx.lineWidth = 2;
refCtx.beginPath();
refCtx.moveTo(source.x, source.y);
refCtx.lineTo(incidentPoint.x, incidentPoint.y);
refCtx.stroke();
// Refracted Ray (Snell's Law: sin(theta1)/V1 = sin(theta2)/V2)
const sin_theta2 = (V2 / V1) * Math.sin(angle1);
if (sin_theta2 < 1) { // No critical refraction yet
const angle2 = Math.asin(sin_theta2);
const endX = incidentPoint.x + (h / 2) * Math.tan(angle2);
const endY = h;
refCtx.beginPath();
refCtx.moveTo(incidentPoint.x, incidentPoint.y);
refCtx.lineTo(endX, endY);
refCtx.stroke();
// Draw angles
refCtx.strokeStyle = 'rgba(0,0,0,0.5)';
refCtx.lineWidth = 1;
refCtx.beginPath();
refCtx.moveTo(incidentPoint.x, incidentPoint.y-30);
refCtx.lineTo(incidentPoint.x, incidentPoint.y+30);
refCtx.stroke();
refCtx.beginPath();
refCtx.arc(incidentPoint.x, incidentPoint.y, 20, Math.PI/2 - angle1, Math.PI/2);
refCtx.stroke();
refCtx.fillText('θ₁', incidentPoint.x - 25, incidentPoint.y-5);
refCtx.beginPath();
refCtx.arc(incidentPoint.x, incidentPoint.y, 20, Math.PI/2, Math.PI/2 + angle2);
refCtx.stroke();
refCtx.fillText('θ₂', incidentPoint.x - 25, incidentPoint.y+20);
} else { // Critical refraction
refCtx.beginPath();
refCtx.moveTo(incidentPoint.x, incidentPoint.y);
refCtx.lineTo(w, incidentPoint.y);
refCtx.stroke();
refCtx.fillStyle = 'red';
refCtx.fillText('Critical Refraction!', incidentPoint.x + 10, incidentPoint.y-10);
}
}
slider.addEventListener('input', drawRefraction);
// Initial draws
window.onload = () => {
drawTravelTime();
drawRefraction();
};