Published
Edited
Apr 19, 2020
Insert cell
Insert cell
Insert cell
canvas = {
const n = 50;
const minR = 80;
const maxR = 100;
const radiusDecay = 0.1
const color = d3.scaleSequential([maxR + 35, minR - 25], d3.interpolateCool)
// const color = d3.scaleSequential([maxR, minR], d3.interpolateTurbo)
const linew = d3.scaleLinear()
.domain([minR, maxR])
.range([3, 1])
const opacity = d3.scaleLinear()
.domain([minR, maxR])
.range([0.2, 0.01])
const circleColor = "rgba(25, 205, 58, 1)"
// const circleColor = color(maxR)//"rgba(25, 25, 25, 0.17)"
const centerColor = color(maxR)//"rgba(255, 255, 255, 0.7)"
const height = Math.ceil(width * screen.height / screen.width);
const margin = 20
let w = width - margin * 2
let x0 = margin
let h = height - margin * 2
let y0 = margin
const context = DOM.context2d(width, height);
context.strokeLinecap = "round"
context.globalCompositeOperation = "luminosity"
context.globalAlpha = 0.01;
const particles = Array.from({length: n}, (d,i) => {
let angle = Math.PI * 2 * i / n
let speed = 0.9
let dx = 1//Math.random() < 0.5 ? -1 : 1;
let dy = 1//Math.random() < 0.5 ? -1 : 1;
let ivx = 1 + Math.random()
let ivy = 1 + Math.random()
return {
x: margin + Math.random() * w,
y: margin + Math.random() * h,
dx: dx,
dy: dy,
ivx: ivx,
ivy: ivy,
vx: (dx * ivx * speed) * Math.cos(angle),
vy: (dy * ivy * speed) * Math.sin(angle),

r: (maxR - minR) * Math.random() + minR,
speed: speed,
angle: angle
}
});
context.canvas.style.background = "#111";

context.strokeStyle = "red";
while (true) {
const quadtree = d3.quadtree()
.x(d => d.x)
.y(d => d.y)
.addAll(particles);
// context.fillStyle = "rgba(5,5,5,1)"
context.fillStyle = "rgba(5,5,5,0.01)"
// context.fillStyle = "rgba(255,255,255,0.07)"
// context.fillRect(0,0, width, height)
// context.fillStyle = "blue";
context.font = '14px san-serif';
let lines = []
let p;
for(var i = 0; i < particles.length; i++) {
p = particles[i]

// Constrain to surface
if(p.x > x0 + w) {
if( Math.cos(p.angle) >= 0) {
p.dx = -1
} else {
p.dx = 1
}
}
if(p.y > y0 + h) {
if( Math.sin(p.angle) >= 0) {
p.dy = -1
} else {
p.dy = 1
}
}
if(p.x < x0) {
if( Math.cos(p.angle) >= 0) {
p.dx = 1
} else {
p.dx = -1
}
}
if(p.y < y0) {
if( Math.sin(p.angle) >= 0) {
p.dy = 1
} else {
p.dy = -1
}
}

let nx1 = p.x - p.r,
nx2 = p.x + p.r,
ny1 = p.y - p.r,
ny2 = p.y + p.r;
let touching = false;
// visit each squares in the quadtree
// x1 y1 x2 y2 constitues the coordinates of the square
// we want to check if each square is a leaf node (has data prop)
p.l = 0;
quadtree.visit((visited, x1, y1, x2, y2) => {
if (visited.data && (visited.data !== p)) {
let x = p.x - visited.data.x,
y = p.y - visited.data.y,
l = Math.sqrt(x * x + y * y),
r = p.r + visited.data.r;
if (l < r) { // if quadtree leaf and input node collides
// Change direction while touching another Element
touching = true;
// get direction from other point to me
// which we have here as x and y
// add a fraction of direction to p.x and p.y
// p.x += .01 * x
// p.y += .01 * y
p.r -= radiusDecay
// Process 4
lines.push([p, visited.data, l, r])
if(l > p.l) p.l = l;

}
}
return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
});
if(touching) {
// Change direction while touching another Element
p.angle += Math.PI * 0.01
// p.speed += 0.015
// p.r -= 1
}
p.r += radiusDecay
if(p.r < minR) p.r = minR
if(p.r > maxR) p.r = maxR
// p.speed -= 0.005
// let maxspeed = 5
// if(p.speed > maxspeed) p.speed = maxspeed
// if(p.speed < -maxspeed) p.speed = -maxspeed

p.vx = p.dx * p.ivx * p.speed * Math.cos(p.angle)
p.vy = p.dy * p.ivy * p.speed * Math.sin(p.angle)
// Move in a straight line
p.x += p.vx
p.y += p.vy
// drawing the outlines of the circles
context.strokeStyle = p.l ? color(p.l) : circleColor
context.lineWidth = 20
context.beginPath()
context.arc(p.x, p.y, 2*p.r/3 - 2*minR/3, 0, Math.PI*2)
context.stroke()
context.strokeStyle = p.l ? color(p.l) : circleColor
context.lineWidth = 2
context.beginPath()
context.arc(p.x, p.y, p.r/3, 0, Math.PI*2)
context.stroke()
// drawing the outlines of the circles
context.strokeStyle = p.l ? color(p.l) : centerColor
context.lineWidth = 1
context.beginPath()
context.arc(p.x, p.y, minR - 1, 0, Math.PI*2)
context.stroke()
// debuging with text
// context.fillText("p.x: " + p.x, p.x, p.y)
// context.fillText("p.y: " + p.y, p.x, p.y + 12)
// context.fillText("x0: " + (x0 + w), p.x - 70, p.y)
// context.fillText("y0: " + (y0 + h), p.x - 70, p.y + 12)
// context.fillText("cos: " + Math.floor(10 * Math.cos(p.angle)), p.x + 50, p.y)
// context.fillText("sin: " + Math.floor(10 * Math.sin(p.angle)), p.x + 50, p.y + 12)
// context.fillText("p.x: " + p.x, p.x + 30, p.y)
// context.fillText("p.y: " + p.y, p.x + 30, p.y + 12)
}
lines.sort(function(a,b) {
return b[2] - a[2]
})
context.lineCap = "round"
lines.forEach(function(line) {
// Process 4
// color by distance
let p0 = line[0]
let p1 = line[1]
let l = line[2]
let c = d3.rgb(color(l))
let o = opacity(l)
context.strokeStyle = `rgba(${c.r}, ${c.g}, ${c.b}, ${o})`
// context.strokeStyle = `rgba(128,128,128, ${o})`
context.lineWidth = linew(l)
// draw a line between centers
context.beginPath()
context.moveTo(p0.x, p0.y)
context.lineTo(p1.x, p1.y)
context.stroke()
})
// particles.forEach(function(p) {
// // drawing the outlines of the circles
// context.strokeStyle = circleColor
// context.lineWidth = 2
// context.beginPath()
// context.arc(p.x, p.y, p.r, 0, Math.PI*2)
// context.stroke()
// // drawing the outlines of the circles
// context.strokeStyle = centerColor
// context.lineWidth = 1
// context.beginPath()
// context.arc(p.x, p.y, 3, 0, Math.PI*2)
// context.stroke()
// })
yield context.canvas;
}
}
Insert cell
Insert cell
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