- circlepacker.min.js 23kb (6kb gzipped)
- circlepacker.js 72kb (13kb gzipped)
- circlepacker.esm.js 69kb (13kb gzipped)
- circlepacker.esm.min.js 23kb (6kb gzipped)
npm install circlepacker
a circlepacking algorithm that executes in a webworker, so it doesn't clog the ui thread of your browser.
the easiest way to use this library is to create a CirclePacker
instance and call it`s methods:
import { CirclePacker } from 'circlepacker';
const packer = new CirclePacker({
// see reference below for all available options
circles: [
{
id: 'circle1',
radius: 34,
position: { x: 32, y: 54 },
isPulledToCenter: true,
isPinned: false,
},
// ...
],
onMove(updatedCircles) {
// your render function goes here...
},
});
please note: circlepacker does not handle the rendering of circles. in merely calculates the circle positions. in the the examples folder, you'll find some demos that show you how to handle rendering.
addCircles(circles)
, addCircle(circle)
, setBounds(bounds)
, setTarget(position)
, setCenteringPasses(number)
, setCollisionPasses(number)
, setCorrectionPasses(number)
, setDamping(number)
, setCenterPull(boolean)
, update()
, dragStart(circleId)
, drag(circleId, position)
, dragEnd(circleId)
, pinCircle(circleId)
, unpinCircle(circleId)
, setCircleRadius(circleId, number)
, setCircleCenterPull(circleId, boolean)
destroy()
returns a new circlepacker instance. it accepts the following options:
const packerOptions = {
// the point that the circles should be attracted to
// OPTIONAL (but recommended)
target: { x: 50, y: 50 },
// the bounds of the area we want to draw the circles in
// OPTIONAL (but recommended)
bounds: { width: 100, height: 100 },
// the initial position and sizes of our circles
// it is possible to add more circles later
// each circle should have a unique id, a position and a radius
// REQUIRED
circles: [
{
id: 'circle1',
radius: 34,
position: { x: 32, y: 54 },
isPulledToCenter: true,
isPinned: false,
},
{
id: 'circle2',
radius: 64,
position: { x: 24, y: 42 },
isPulledToCenter: true,
isPinned: false,
},
{
id: 'circle3',
radius: 53,
position: { x: 23, y: 21 },
isPulledToCenter: true,
isPinned: false,
},
],
// true: continuous animation loop
// false: one-time animation
// not available in node or in the pack() shorthand.
// OPTIONAL. default: true
animationLoop: true,
// correctness of collision calculations.
// higher number means means longer time to calculate
// OPTIONAL
// default = 3
collisionPasses: 3,
// number of centering animations per frame.
// higher number means faster movement and longer time to calculate
// OPTIONAL
// default = 1
centeringPasses: 2,
// number of times per frame we try to remove overlapping circles.
// this is mostly relevant in cases where animationLoop === false.
// higher number means longer time to calculate
// OPTIONAL
// default = 3
correctionPasses: 3,
// return information about overlapping circles
// in the onMove() callback?
// can impact performance for high number of circles.
// OPTIONAL
// default = false
calculateOverlap: false,
// prevent using a web worker to calculate the results.
// OPTIONAL
// option is not available in node.
// default = false
noWorker: false,
// callback function for when movement started
// can get called multiple times
// not available in node or in the pack() shorthand.
// OPTIONAL
onMoveStart: function () {},
// callback function for updated circle positions
// can optionally also include information about
// overlapping circles and the attraction target
onMove: function (
updatedCirclePositions,
attractionTarget = undefined,
overlappingCircles = undefined
) {
// draw logic here...
},
// callback function for when movement ended
// can get called multiple times
// not available in node or in the pack() shorthand.
// OPTIONAL
onMoveEnd: function () {},
};
const packer = new CirclePacker(packerOptions);
back to reference
add an array of new circles. each circle should have a unique id
, a position
and a radius
.
packer.addCircles([
{
id: 'circle4',
radius: 21,
position: { x: 12, y: 27 },
isPulledToCenter: false,
isPinned: false,
},
{
id: 'circle5',
radius: 64,
position: { x: 14, y: 42 },
isPulledToCenter: false,
isPinned: false,
},
]);
back to reference
add a single new circle. a circle should have a unique id
, a position
and a radius
.
packer.addCircles({ id: 'circle6', radius: 21, position: { x: 12, y: 27 } });
back to reference
update bounds. a bounds object should have a width
and a height
.
packer.setBounds({ width: 200, height: 300 });
back to reference
updates the target position. a position object should have x
and y
values.
packer.setTarget({ x: 21, y: 29 });
back to reference
updates number of centering passes. should be an integer >= 1. high values can impact performance.
packer.setCenteringPasses(3);
back to reference
updates number of collision passes. should be an integer >= 1. high values can impact performance.
packer.setCollisionPasses(3);
back to reference
updates number of correction passes. should be an integer >= 0. high values can impact performance.
packer.setCorrectionPasses(3);
back to reference
set damping. this affects the movement speed of the circles. value should be a float between 0 and 1. the default value is 0.025
packer.setDamping(0.01);
back to reference
set center pull. if set to false
, circles collide, but are not pulled to the center. the default value is true
.
packer.setCenterPull(false);
back to reference
set calculate overlap. if set to false
, overlap information will not be returned on the onMove
callback.
packer.setCalculateOverlap(true);
back to reference
starts calculation. useful if animationLoop
was set to false
.
packer.update();
back to reference
indicate that we're about to start dragging this circle. this is usually called in a mousedown
or a touchstart
event handler.
packer.dragStart('circle2');
back to reference
update position of dragges circle. a position object should have x
and y
values. this is usually called in a mousemove
or a touchmove
event handler.
packer.drag('circle2', { x: 30, y: 45 });
back to reference
indicate that we're done dragging this circle. this is usually called in an mouseup
or a touchend
event handler.
packer.dragEnd('circle2');
back to reference
pin circle. this means that the circle is static and will not move. other circles will still be bounce off of it.
packer.pinCircle('circle2');
back to reference
unpin circle. this means that the circle is no longer static and will start colliding with other circles as normal.
packer.unpinCircle('circle2');
back to reference
change the radius of a circle.
packer.setCircleRadius('circle2', 20);
back to reference
change the isPulledToCenter
value of a circle. if it is set to false
, the circle is not pulled to the center. (it still collides with other circles).
packer.setCircleCenterPull('circle2', true);
back to reference
tell circlepacker instance we're done and won't be needing it again. it terminates the webworker. useful for lifecycle hooks in single page web apps.
packer.destroy();
back to reference
shorthand for new CirclePacker({animationLoop: false})
returns a promise
import { pack } from 'circlepacker';
const { updatedCircles, target, overlappingCircles } = await pack(packerOptions);
back to reference
the source code is located in the src
folder, the built source files are located in the dist
folder.
npm run build
will run the build script (build.mjs
) that compiles all files in the dist
folder.
npm run test
will run the tests on the src/
folder.
npm run test:all
will run the tests on the src/
and /dist
folders.
Mario Gonzalez <[email protected]> Georg Fischer <[email protected]>
large parts of the circle packing algirithm are based on the CirclePackingJS repo by @onedayitwillmake (mit licensed)
found a bug? missing a feature? instructions unclear? are you using this library in an interesting project? maybe open an issue or a pull request to let me know. thanks!
thank you for taking a look at this repo. and reading the readme file until the end. have a great day :)