Public
Edited
May 17, 2024
Insert cell
Insert cell
{
// Create the SVG container
const svg = d3.create("svg")
.attr("width", width)
.attr("height", width)
.style("border", "1px solid black");

const charge = { x: width / 2, y: width / 2 }; // Initial position of the charge
const dots = []; // Array to hold the dots emitted by the charge
const speed = 2; // Speed of the dots (pixels per frame)
const emissionInterval = 500; // Interval to emit dots (milliseconds)
const updateInterval = 30; // Interval to update the position of the dots (milliseconds)
const n = 20; // Number of fixed directions
const maxDots = 500; // Maximum number of dots to keep on screen
const maxChargeSpeed = speed * 0.9; // Maximum speed of the charge (90% of the speed of light)

let dragging = false;
let targetPosition = { x: charge.x, y: charge.y };
let lastUpdateTime = 0; // Time of the last mousemove event update

// Function to update the target position of the charge
function updateTargetPosition(event) {
const now = Date.now();
if (dragging && now - lastUpdateTime > updateInterval) {
targetPosition.x = d3.pointer(event)[0];
targetPosition.y = d3.pointer(event)[1];
lastUpdateTime = now;
}
}

// Function to move the charge towards the target position at a limited speed
function moveCharge() {
const dx = targetPosition.x - charge.x;
const dy = targetPosition.y - charge.y;
const distance = Math.sqrt(dx * dx + dy * dy);

if (distance > maxChargeSpeed) {
const scale = maxChargeSpeed / distance;
charge.x += dx * scale;
charge.y += dy * scale;
} else {
charge.x = targetPosition.x;
charge.y = targetPosition.y;
}
}

// Function to emit new dots in fixed directions with relativistic effects
function emitDots() {
console.log("Emitting dots");
const velocityMag = Math.sqrt(targetPosition.x * targetPosition.x + targetPosition.y * targetPosition.y);
const beta = velocityMag / speed; // Speed as a fraction of the speed of light
const gamma = 1 / Math.sqrt(1 - beta * beta); // Lorentz factor

for (let i = 0; i < n; i++) {
const angle = (2 * i * Math.PI) / n;

// Adjust angle for relativistic effects
const adjustedAngle = Math.atan2(Math.sin(angle), gamma * (Math.cos(angle) + beta));
const velocity = {
x: Math.cos(adjustedAngle) * speed,
y: Math.sin(adjustedAngle) * speed
};

const color = (i % 2 === 0) ? "blue" : "orange"; // Alternate colors for even and odd i
dots.push({ x: charge.x, y: charge.y, vx: velocity.x, vy: velocity.y, color: color, direction: i });
}

// Limit the number of dots to maxDots
while (dots.length > maxDots) {
dots.shift();
}
}

// Function to update the positions of the dots
function updateDots() {
console.log("Updating dots");
dots.forEach(dot => {
dot.x += dot.vx;
dot.y += dot.vy;
});

// Remove dots that are out of bounds
for (let i = dots.length - 1; i >= 0; i--) {
if (dots[i].x < 0 || dots[i].x > width || dots[i].y < 0 || dots[i].y > width) {
dots.splice(i, 1);
}
}
}

function draw() {
console.log("Drawing");
// Update the line paths
const lines = svg.selectAll(".line").data(d3.range(n));
lines.exit().remove();

lines.enter()
.append("path")
.attr("class", "line")
.merge(lines)
.attr("d", d => {
const points = dots.filter(dot => dot.direction === d);
if (points.length < 1) return null; // At least one point needed to draw a line
const path = d3.line()
.x(dot => dot.x)
.y(dot => dot.y);
return path([...points, charge]); // Connect all dots in the same direction to the charge
})
.attr("stroke", d => (d % 2 === 0) ? "blue" : "orange")
.attr("stroke-width", 1)
.attr("fill", "none");

// Update the circles
const circles = svg.selectAll(".dot").data(dots);
circles.exit().remove();

circles.enter()
.append("circle")
.attr("class", "dot")
.attr("r", 2)
.merge(circles)
.attr("cx", d => d.x)
.attr("cy", d => d.y)
.attr("fill", d => d.color);

// Update the charge
svg.select("#charge").remove();

svg.append("circle")
.attr("id", "charge")
.attr("cx", charge.x)
.attr("cy", charge.y)
.attr("r", 5)
.attr("fill", "red");
}

// Setup the dragging behavior
svg.on("mousedown", () => {
dragging = true;
targetPosition = { x: charge.x, y: charge.y };
}).on("mouseup", () => {
dragging = false;
}).on("mousemove", updateTargetPosition);

// Set up the interval for emitting dots
setInterval(() => {
emitDots();
}, emissionInterval);

// Set up the interval for updating the simulation
setInterval(() => {
if (dragging) {
moveCharge();
}
updateDots();
draw();
}, updateInterval);

return svg.node();
}

Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more