<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1650 1170" width="1650" height="1170" version="1.1" >
<!--
Johan Karlsson, 2020
https://twitter.com/DonKarlssonSan
-->
</svg>
html, body {
height: 100%;
margin: 0;
cursor: pointer;
background-color: #223;
}
svg {
position: absolute;
width: 100%;
height: 100%;
}
/*
Johan Karlsson, 2020
https://twitter.com/DonKarlssonSan
MIT License, see Details View
Circle packing algorithm from Daniel Shiffman:
https://www.youtube.com/watch?v=QHEQuoIKgNE
*/
class Circle {
constructor(x, y) {
this.x = x;
this.y = y;
this.r = 8;
this.done = false;
}
draw(groupElement) {
drawSpiral(this.x, this.y, this.r, groupElement);
}
}
let svg;
let w = 1650;
let h = 1170;
let circles;
function setup() {
svg = document.querySelector("svg");
document.addEventListener("click", draw);
document.addEventListener("keydown", onKeyDown);
}
function createSvgElement(elementName) {
const svgNs = "http://www.w3.org/2000/svg";
return document.createElementNS(svgNs, elementName);
}
function onKeyDown (e) {
if(e.code === "KeyD") {
download();
}
}
function download() {
let svgDoc = svg.outerHTML;
let filename = "spiral-packing.svg";
let element = document.createElement("a");
element.setAttribute("href", "data:image/svg+xml;charset=utf-8," + encodeURIComponent(svgDoc));
element.setAttribute("download", filename);
element.style.display = "none";
document.body.appendChild(element);
element.addEventListener("click", e => e.stopPropagation());
element.click();
document.body.removeChild(element);
}
function dist(x1, y1, x2, y2) {
return Math.hypot(x1 - x2, y1 - y2);
}
function addCircles() {
let nrOfTries = 0;
let wasAdded;
let margin = 0.15;
do {
wasAdded = false;
let x = (1 - margin * 2) * Math.random() * w + w * margin;
let y = (1 - margin * 2) * Math.random() * h + h * margin;
if(validPos(x, y)) {
wasAdded = true;
let c = new Circle(x, y);
circles.push(c);
}
nrOfTries++;
} while (!wasAdded && nrOfTries < 50)
}
function validPos(x, y) {
for(let i = 0; i < circles.length; i++) {
let current = circles[i];
let d = dist(x, y, current.x, current.y);
if(d - 16 < current.r) {
return false;
}
}
return true;
}
function canGrow(circle) {
for(let i = 0; i < circles.length; i++) {
let current = circles[i];
if(circle !== current) {
let d = dist(circle.x, circle.y, current.x, current.y);
if(d - 4 <= circle.r + current.r) {
return false;
}
}
}
return true;
}
function resetCircles() {
circles = [];
}
function packCircles() {
let nrOfTries = w * h / 400;
for(let i = 0; i < nrOfTries; i++) {
if(i % 2 === 0) {
addCircles();
}
circles.filter(c => !c.done).forEach(c => {
if(canGrow(c)) {
c.r += 2;
} else {
c.done = true;
}
});
}
}
function drawSpiral(x0, y0, r, groupElement) {
let nrOfPoints = r * 10;
let nrOfTurns = r / 11;
let points = [];
for(let i = 0; i < nrOfPoints; i++) {
let angle = Math.PI * 2 * i / nrOfPoints * nrOfTurns;
let x = Math.cos(angle) * r * i / nrOfPoints + x0;
let y = Math.sin(angle) * r * i / nrOfPoints + y0;
points.push([x, y]);
}
let path = createSvgElement("path");
let commands = convertPointsToCommands(points);
path.setAttribute("d", commands);
groupElement.appendChild(path);
}
function convertPointsToCommands(points) {
let commands = [];
commands.push(`M ${points[0]}`);
for(let i = 1; i < points.length; i++) {
commands.push(`L ${points[i]}`);
}
return commands.join(" ");
}
function drawCircles(groupElement) {
circles.forEach(circle => circle.draw(groupElement));
}
function draw() {
console.clear();
let group = document.querySelector("#container");
if(group) {
group.remove();
}
group = createSvgElement("g");
group.setAttribute("id", "container");
group.setAttribute("fill", "none");
group.setAttribute("stroke", "white");
group.setAttribute("stroke-linecap", "round");
group.setAttribute("stroke-linejoin", "round");
resetCircles();
packCircles();
drawCircles(group);
let logo = new Logo(w - 90, h - 70, "white");
logo.draw(group);
svg.appendChild(group);
}
setup();
draw();
This Pen doesn't use any external CSS resources.