Skip to content

snorpey/circlepacker

 
 

Repository files navigation

circlepacker

downloads

npm install circlepacker

what is it?

circlepacker demo

a circlepacking algorithm that executes in a webworker, so it doesn't clog the ui thread of your browser.

how to use it

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.

reference

CirclePacker(options)

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()

pack(options)

CirclePacker(options)

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

addCircles(circles)

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

addCircle(circle)

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

setBounds(bounds)

update bounds. a bounds object should have a width and a height.

packer.setBounds({ width: 200, height: 300 });

back to reference

setTarget(position)

updates the target position. a position object should have x and y values.

packer.setTarget({ x: 21, y: 29 });

back to reference

setCenteringPasses(number)

updates number of centering passes. should be an integer >= 1. high values can impact performance.

packer.setCenteringPasses(3);

back to reference

setCollisionPasses(number)

updates number of collision passes. should be an integer >= 1. high values can impact performance.

packer.setCollisionPasses(3);

back to reference

setCorrectionPasses(number)

updates number of correction passes. should be an integer >= 0. high values can impact performance.

packer.setCorrectionPasses(3);

back to reference

setDamping(number)

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

setCenterPull(boolean)

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

setCalculateOverlap(boolean)

set calculate overlap. if set to false, overlap information will not be returned on the onMove callback.

packer.setCalculateOverlap(true);

back to reference

update()

starts calculation. useful if animationLoop was set to false.

packer.update();

back to reference

dragStart(circleId)

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

drag(circleId, position)

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

dragEnd(circleId)

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

pinCircle(circleId)

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

unpinCircle(circleId)

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

setCircleRadius(circleId, number)

change the radius of a circle.

packer.setCircleRadius('circle2', 20);

back to reference

setCircleCenterPull(circleId, boolean)

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

destroy()

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

pack(options)

shorthand for new CirclePacker({animationLoop: false})

returns a promise

import { pack } from 'circlepacker';
const { updatedCircles, target, overlappingCircles } = await pack(packerOptions);

back to reference

development

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.

license

mit

credits

Mario Gonzalez <mariogonzalez@gmail.com> Georg Fischer <hi@snorpey.com>

large parts of the circle packing algirithm are based on the CirclePackingJS repo by @onedayitwillmake (mit licensed)

missing something?

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!

most importantly

thank you for taking a look at this repo. and reading the readme file until the end. have a great day :)

About

circle packing algorithm in javascript using webworkers

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • JavaScript 100.0%