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

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more