mirror of
https://gitlab.com/Anson-Projects/projects.git
synced 2025-06-15 22:46:48 +00:00
114 lines
3.1 KiB
Plaintext
114 lines
3.1 KiB
Plaintext
---
|
|
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`<canvas width=${width} height="${height}" style="border: 1px solid #ccc; cursor: crosshair;"></canvas>`;
|
|
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 |