packery
Version:
bin-packing layout library
171 lines (140 loc) • 4.22 kB
JavaScript
/**
* Packer
* bin-packing algorithm
*/
( function( window ) {
;
// -------------------------- Packer -------------------------- //
function packerDefinition( Rect ) {
/**
* @param {Number} width
* @param {Number} height
* @param {String} sortDirection
* topLeft for vertical, leftTop for horizontal
*/
function Packer( width, height, sortDirection ) {
this.width = width || 0;
this.height = height || 0;
this.sortDirection = sortDirection || 'downwardLeftToRight';
this.reset();
}
Packer.prototype.reset = function() {
this.spaces = [];
this.newSpaces = [];
var initialSpace = new Rect({
x: 0,
y: 0,
width: this.width,
height: this.height
});
this.spaces.push( initialSpace );
// set sorter
this.sorter = sorters[ this.sortDirection ] || sorters.downwardLeftToRight;
};
// change x and y of rect to fit with in Packer's available spaces
Packer.prototype.pack = function( rect ) {
for ( var i=0, len = this.spaces.length; i < len; i++ ) {
var space = this.spaces[i];
if ( space.canFit( rect ) ) {
this.placeInSpace( rect, space );
break;
}
}
};
Packer.prototype.placeInSpace = function( rect, space ) {
// place rect in space
rect.x = space.x;
rect.y = space.y;
this.placed( rect );
};
// update spaces with placed rect
Packer.prototype.placed = function( rect ) {
// update spaces
var revisedSpaces = [];
for ( var i=0, len = this.spaces.length; i < len; i++ ) {
var space = this.spaces[i];
var newSpaces = space.getMaximalFreeRects( rect );
// add either the original space or the new spaces to the revised spaces
if ( newSpaces ) {
revisedSpaces.push.apply( revisedSpaces, newSpaces );
} else {
revisedSpaces.push( space );
}
}
this.spaces = revisedSpaces;
this.mergeSortSpaces();
};
Packer.prototype.mergeSortSpaces = function() {
// remove redundant spaces
Packer.mergeRects( this.spaces );
this.spaces.sort( this.sorter );
};
// add a space back
Packer.prototype.addSpace = function( rect ) {
this.spaces.push( rect );
this.mergeSortSpaces();
};
// -------------------------- utility functions -------------------------- //
/**
* Remove redundant rectangle from array of rectangles
* @param {Array} rects: an array of Rects
* @returns {Array} rects: an array of Rects
**/
Packer.mergeRects = function( rects ) {
for ( var i=0, len = rects.length; i < len; i++ ) {
var rect = rects[i];
// skip over this rect if it was already removed
if ( !rect ) {
continue;
}
// clone rects we're testing, remove this rect
var compareRects = rects.slice(0);
// do not compare with self
compareRects.splice( i, 1 );
// compare this rect with others
var removedCount = 0;
for ( var j=0, jLen = compareRects.length; j < jLen; j++ ) {
var compareRect = compareRects[j];
// if this rect contains another,
// remove that rect from test collection
var indexAdjust = i > j ? 0 : 1;
if ( rect.contains( compareRect ) ) {
// console.log( 'current test rects:' + testRects.length, testRects );
// console.log( i, j, indexAdjust, rect, compareRect );
rects.splice( j + indexAdjust - removedCount, 1 );
removedCount++;
}
}
}
return rects;
};
// -------------------------- sorters -------------------------- //
// functions for sorting rects in order
var sorters = {
// top down, then left to right
downwardLeftToRight: function( a, b ) {
return a.y - b.y || a.x - b.x;
},
// left to right, then top down
rightwardTopToBottom: function( a, b ) {
return a.x - b.x || a.y - b.y;
}
};
// -------------------------- -------------------------- //
return Packer;
}
// -------------------------- transport -------------------------- //
if ( typeof define === 'function' && define.amd ) {
// AMD
define( [ './rect' ], packerDefinition );
} else if ( typeof exports === 'object' ) {
// CommonJS
module.exports = packerDefinition(
require('./rect')
);
} else {
// browser global
var Packery = window.Packery = window.Packery || {};
Packery.Packer = packerDefinition( Packery.Rect );
}
})( window );