--- title: "Brachistochrone" description: | Lets create a double pendulum in Observable JS! date: 2025-05-09 categories: - Observable JS - Code - Math --- This is a bunch of text This is a bunch of text This is a bunch of text This is a bunch of text This is a bunch of text This is a bunch of text This is a bunch of text This is a bunch of text This is a bunch of text This is a bunch of text This is a bunch of text :::{.column-screen} ```{ojs} //| echo: false viewof canvas = { const height = 500; const canvas = html``; const ctx = canvas.getContext('2d'); let isDrawing = false; let lastX = 0; let lastY = 0; let pathLength = 0; let pathHistory = [{time: Date.now(), length: 0}]; // Drawing settings ctx.strokeStyle = '#000'; ctx.lineWidth = 2; ctx.lineCap = 'round'; ctx.lineJoin = 'round'; canvas.addEventListener('mousedown', (e) => { if (!isDrawing) { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.arc(300, 100, 5, 0, 2 * Math.PI); ctx.fillStyle = 'red'; ctx.fill(); ctx.arc(600, 300, 5, 0, 2 * Math.PI); ctx.fillStyle = 'red'; ctx.fill(); pathLength = 0; pathHistory = [{time: Date.now(), length: 0}]; canvas.value = {length: pathLength, history: pathHistory}; canvas.dispatchEvent(new CustomEvent("input")); } isDrawing = true; [lastX, lastY] = [e.offsetX, e.offsetY]; }); canvas.addEventListener('mousemove', (e) => { if (!isDrawing) return; // Calculate distance const distance = Math.sqrt( Math.pow(e.offsetX - lastX, 2) + Math.pow(e.offsetY - lastY, 2) ); const angle = Math.atan2(e.offsetY - lastY, e.offsetX - lastX) pathLength += distance; // Draw ctx.beginPath(); ctx.moveTo(lastX, lastY); ctx.lineTo(e.offsetX, e.offsetY); ctx.stroke(); [lastX, lastY] = [e.offsetX, e.offsetY]; // Update value and emit event pathHistory.push({time: Date.now(), length: pathLength}); canvas.value = {length: pathLength, history: pathHistory}; canvas.dispatchEvent(new CustomEvent("input")); }); canvas.addEventListener('mouseup', () => isDrawing = false); canvas.addEventListener('mouseout', () => isDrawing = false); // Initial value canvas.value = {length: 0, history: pathHistory}; return canvas; } ``` ```{ojs} //| echo: false md`Current path length: **${canvas.length?.toFixed(0) || 0} pixels**` ``` ```{ojs} //| echo: false import {Plot} from "@observablehq/plot" pathChart = Plot.plot({ width: width, height: 200, y: {label: "Path Length (pixels)"}, x: {label: "Time", type: "time"}, marks: [ Plot.line(canvas.history || [], { x: "time", y: "length", stroke: "steelblue", strokeWidth: 2 }) ] }) ``` This is a bunch of text This is a bunch of text This is a bunch of text This is a bunch of text This is a bunch of text This is a bunch of text This is a bunch of text This is a bunch of text This is a bunch of text This is a bunch of text This is a bunch of text