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 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 centerColor = color(maxR)
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
let dy = 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,0.01)"
context.font = '14px san-serif';
let lines = []
let p;
for(var i = 0; i < particles.length; i++) {
p = particles[i]
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;
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) {
touching = true;
p.r -= radiusDecay
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) {
p.angle += Math.PI * 0.01
}
p.r += radiusDecay
if(p.r < minR) p.r = minR
if(p.r > maxR) p.r = maxR
p.vx = p.dx * p.ivx * p.speed * Math.cos(p.angle)
p.vy = p.dy * p.ivy * p.speed * Math.sin(p.angle)
p.x += p.vx
p.y += p.vy
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()
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()
}
lines.sort(function(a,b) {
return b[2] - a[2]
})
context.lineCap = "round"
lines.forEach(function(line) {
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.lineWidth = linew(l)
context.beginPath()
context.moveTo(p0.x, p0.y)
context.lineTo(p1.x, p1.y)
context.stroke()
})
yield context.canvas;
}
}