UNPKG

packery

Version:

bin-packing layout library

171 lines (140 loc) 4.22 kB
/** * Packer * bin-packing algorithm */ ( function( window ) { 'use strict'; // -------------------------- 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 );