Skip to content

Instantly share code, notes, and snippets.

@morisono
Forked from gouldingken/Readme.md
Created October 21, 2025 07:19
Show Gist options
  • Save morisono/5f67f58d066df48c7e658ddb320d4d0d to your computer and use it in GitHub Desktop.
Save morisono/5f67f58d066df48c7e658ddb320d4d0d to your computer and use it in GitHub Desktop.

Revisions

  1. @gouldingken gouldingken revised this gist Sep 17, 2015. 1 changed file with 7 additions and 1 deletion.
    8 changes: 7 additions & 1 deletion Readme.md
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,10 @@
    Make a circle circle pack to fill any arbitrary shape (loaded from an SVG file) inspired by http://mathematica.stackexchange.com/questions/40334/generating-visually-pleasing-circle-packs

    Live example:
    http://bl.ocks.org/gouldingken/8d0b7a05b0b0156da3b8
    http://bl.ocks.org/gouldingken/8d0b7a05b0b0156da3b8

    Gist:
    https://gist.github.com/gouldingken/8d0b7a05b0b0156da3b8

    The same approach using squares instead of circles:
    http://bl.ocks.org/gouldingken/25df41d631a3557d80b7
  2. @gouldingken gouldingken revised this gist Jul 6, 2015. 1 changed file with 4 additions and 0 deletions.
    4 changes: 4 additions & 0 deletions Readme.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,4 @@
    Make a circle circle pack to fill any arbitrary shape (loaded from an SVG file) inspired by http://mathematica.stackexchange.com/questions/40334/generating-visually-pleasing-circle-packs

    Live example:
    http://bl.ocks.org/gouldingken/8d0b7a05b0b0156da3b8
  3. @gouldingken gouldingken revised this gist Jul 6, 2015. 2 changed files with 2 additions and 3 deletions.
    2 changes: 1 addition & 1 deletion circlePackShape.js
    Original file line number Diff line number Diff line change
    @@ -104,7 +104,7 @@ $(document).ready(function () {
    var $canvas = $('<canvas>').attr(_canvasProps).appendTo('body');
    var $canvas2 = $('<canvas>').attr(_canvasProps).appendTo('body');
    var ctx = $canvas[0].getContext('2d');
    _drawSvg(ctx, 'data/note.svg', function() {
    _drawSvg(ctx, 'note.svg', function() {
    var imgData = ctx.getImageData(0, 0, _canvasProps.width, _canvasProps.height);
    _placeCircles(imgData);
    _drawCircles($canvas2[0].getContext('2d'));
    3 changes: 1 addition & 2 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -4,9 +4,8 @@
    <meta charset="UTF-8">
    <title>Shape Circles</title>
    <script src="https://code.jquery.com/jquery-2.1.3.min.js"></script>
    <link rel="stylesheet" href="styles/main.css"/>
    </head>
    <body>
    <script src="js/main.js"></script>
    <script src="circlePackShape.js"></script>
    </body>
    </html>
  4. @gouldingken gouldingken revised this gist Jul 3, 2015. 1 changed file with 12 additions and 0 deletions.
    12 changes: 12 additions & 0 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,12 @@
    <!DOCTYPE html>
    <html>
    <head lang="en">
    <meta charset="UTF-8">
    <title>Shape Circles</title>
    <script src="https://code.jquery.com/jquery-2.1.3.min.js"></script>
    <link rel="stylesheet" href="styles/main.css"/>
    </head>
    <body>
    <script src="js/main.js"></script>
    </body>
    </html>
  5. @gouldingken gouldingken revised this gist Jul 3, 2015. 1 changed file with 14 additions and 0 deletions.
    14 changes: 14 additions & 0 deletions note.svg
    Loading
    Sorry, something went wrong. Reload?
    Sorry, we cannot display this file.
    Sorry, this file is invalid so it cannot be displayed.
  6. @gouldingken gouldingken renamed this gist Jul 3, 2015. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  7. @gouldingken gouldingken created this gist Jul 3, 2015.
    113 changes: 113 additions & 0 deletions main.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,113 @@
    var _canvasProps = {width: 300, height: 300};
    var _options = {spacing: 1, numCircles: 1000, minSize: 1, maxSize: 10, higherAccuracy: false};
    var _placedCirclesArr = [];

    var _isFilled = function (imgData, imageWidth, x, y) {
    x = Math.round(x);
    y = Math.round(y);
    var a = imgData.data[((imageWidth * y) + x) * 4 + 3];
    return a > 0;
    };

    var _isCircleInside = function (imgData, imageWidth, x, y, r) {
    //if (!_isFilled(imgData, imageWidth, x, y)) return false;
    //--use 4 points around circle as good enough approximation
    if (!_isFilled(imgData, imageWidth, x, y - r)) return false;
    if (!_isFilled(imgData, imageWidth, x, y + r)) return false;
    if (!_isFilled(imgData, imageWidth, x + r, y)) return false;
    if (!_isFilled(imgData, imageWidth, x - r, y)) return false;
    if (_options.higherAccuracy) {
    //--use another 4 points between the others as better approximation
    var o = Math.cos(Math.PI / 4);
    if (!_isFilled(imgData, imageWidth, x + o, y + o)) return false;
    if (!_isFilled(imgData, imageWidth, x - o, y + o)) return false;
    if (!_isFilled(imgData, imageWidth, x - o, y - o)) return false;
    if (!_isFilled(imgData, imageWidth, x + o, y - o)) return false;
    }
    return true;
    };

    var _touchesPlacedCircle = function (x, y, r) {
    return _placedCirclesArr.some(function (circle) {
    return _dist(x, y, circle.x, circle.y) < circle.size + r + _options.spacing;//return true immediately if any match
    });
    };

    var _dist = function (x1, y1, x2, y2) {
    var a = x1 - x2;
    var b = y1 - y2;
    return Math.sqrt(a * a + b * b);
    };

    var _placeCircles = function (imgData) {
    var i = _circles.length;
    _placedCirclesArr = [];
    while (i > 0) {
    i--;
    var circle = _circles[i];
    var safety = 1000;
    while (!circle.x && safety-- > 0) {
    var x = Math.random() * _canvasProps.width;
    var y = Math.random() * _canvasProps.height;
    if (_isCircleInside(imgData, _canvasProps.width, x, y, circle.size)) {
    if (!_touchesPlacedCircle(x, y, circle.size)) {
    circle.x = x;
    circle.y = y;
    _placedCirclesArr.push(circle);
    }
    }
    }
    }
    };

    var _makeCircles = function () {
    var circles = [];
    for (var i = 0; i < _options.numCircles; i++) {
    var circle = {
    color: _colors[Math.round(Math.random() * _colors.length)],
    size: _options.minSize + Math.random() * Math.random() * (_options.maxSize - _options.minSize) //do random twice to prefer more smaller ones
    };
    circles.push(circle);
    }
    circles.sort(function (a, b) {
    return a.size - b.size;
    });
    return circles;
    };

    var _drawCircles = function (ctx) {
    ctx.save();
    $.each(_circles, function (i, circle) {
    ctx.fillStyle = circle.color;
    ctx.beginPath();
    ctx.arc(circle.x, circle.y, circle.size, 0, 2 * Math.PI);
    ctx.closePath();
    ctx.fill()
    });

    ctx.restore();
    };

    var _drawSvg = function (ctx, path, callback) {
    var img = new Image(ctx);
    img.onload = function () {
    ctx.drawImage(img, 0, 0);
    callback();
    };
    img.src = path;
    };

    var _colors = ['#993300', '#a5c916', '#00AA66', '#FF9900'];
    var _circles = _makeCircles();

    $(document).ready(function () {
    var $canvas = $('<canvas>').attr(_canvasProps).appendTo('body');
    var $canvas2 = $('<canvas>').attr(_canvasProps).appendTo('body');
    var ctx = $canvas[0].getContext('2d');
    _drawSvg(ctx, 'data/note.svg', function() {
    var imgData = ctx.getImageData(0, 0, _canvasProps.width, _canvasProps.height);
    _placeCircles(imgData);
    _drawCircles($canvas2[0].getContext('2d'));
    });

    });