├── .gitignore
├── Makefile
├── README.md
├── bower.json
├── component.json
├── demo.html
├── demo
├── index.jade
└── index.js
├── dist
├── horizontal-grid-packing.css
└── horizontal-grid-packing.js
├── lib
├── pack.css
└── pack.js
└── package.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled source #
2 | ###################
3 | *.com
4 | *.class
5 | *.dll
6 | *.exe
7 | *.o
8 | *.so
9 |
10 | # Packages #
11 | ############
12 | # it's better to unpack these files and commit the raw source
13 | # git has its own built in compression methods
14 | *.7z
15 | *.dmg
16 | *.gz
17 | *.iso
18 | *.jar
19 | *.rar
20 | *.tar
21 | *.zip
22 |
23 | # Logs and databases #
24 | ######################
25 | *.log
26 | *.sql
27 | *.sqlite
28 |
29 | # OS generated files #
30 | ######################
31 | .DS_Store*
32 | ehthumbs.db
33 | Icon?
34 | Thumbs.db
35 |
36 | # Node.js #
37 | ###########
38 | lib-cov
39 | *.seed
40 | *.log
41 | *.csv
42 | *.dat
43 | *.out
44 | *.pid
45 | *.gz
46 |
47 | pids
48 | logs
49 | results
50 |
51 | node_modules
52 | npm-debug.log
53 |
54 | # Git #
55 | #######
56 | *.orig
57 | *.BASE.*
58 | *.BACKUP.*
59 | *.LOCAL.*
60 | *.REMOTE.*
61 |
62 | # Components #
63 | ##############
64 |
65 | /build
66 | /components
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | build: component dist
2 |
3 | lint:
4 | @jshint lib
5 |
6 | clean:
7 | @rm -rf components
8 |
9 | component:
10 | @component install --dev
11 | @component build --dev
12 |
13 | dist:
14 | @component build \
15 | --standalone HorizontalGridPacking \
16 | --out dist \
17 | --name horizontal-grid-packing
18 |
19 | demo: npm component
20 | @node demo
21 |
22 | npm:
23 | @npm i
24 |
25 | .PHONY: build lint clean dist component demo npm
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Horizontal grid packing
2 |
3 | Packs grids into rows so that each row expands to the full container width.
4 | Row heights are dynamically adjusted.
5 | Similar to [Masonry](https://github.com/desandro/masonry), but the space taken by all the grids will always be a rectangle without any jagged edges.
6 | By being horizontal, users view the grid linearly (scan left to right, top to bottom) kind of like a comic book.
7 |
8 | Demo: http://jonathanong.github.io/horizontal-grid-packing/
9 |
10 | See:
11 |
12 | - https://github.com/math-utils/linear-partition
13 | - https://news.ycombinator.com/item?id=6198400
14 | - http://www.crispymtn.com/stories/the-algorithm-for-a-perfectly-balanced-photo-gallery
15 |
16 | ## Install
17 |
18 | If you use [component](https://github.com/component/component), the dependencies are handled for you.
19 | If you use [bower](https://github.com/bower/bower), the dependencies are packaged together.
20 |
21 | ```bash
22 | component install jonathanong/horizontal-grid-packing
23 | bower install horizontal-grid-packing
24 | ```
25 |
26 | ## API
27 |
28 | ### Layout
29 |
30 | The HTML must strictly be a single container whose children are strictly grid elements.
31 |
32 | ```html
33 |
34 |
35 |
36 |
37 |
38 |
39 | ```
40 |
41 | This library assumes you know the aspect ratio of each grid element.
42 | Each element should either have a `data-aspect-ratio` attribute (width/height) or both `data-width` and `data-height` attributes.
43 | If you do not know these attributes, use a library such as [imagesloaded](https://github.com/desandro/imagesloaded) to calculate the dimensions before using this library.
44 | This library will not attempt to figure out the dimensions of each grid element.
45 |
46 | ### Pack(container [, options])
47 |
48 | Returns a new instance of `Pack`.
49 |
50 | ```js
51 | var pack = new HorizontalGridPacking(container, options)
52 | ```
53 |
54 | `new` is optional.
55 | `container` is the element that contains all the grids.
56 | The `options` are:
57 |
58 | - `height` - Target row height in pixels.
59 | `120` by default.
60 | - `padding` - Padding between each grid in pixels.
61 | `0` by default.
62 |
63 | Each of these options can be changed as an attribute of `pack`:
64 |
65 | ```js
66 | // Change the target height
67 | pack.height = Math.round(window.outerHeight / 5)
68 | // Change the padding
69 | pack.padding = 5
70 | // Recalculate the grid
71 | pack.reload()
72 | ```
73 |
74 | Other options you may be interested are:
75 |
76 | - `width` - the width of the grid in pixels.
77 | You should change this when `container`'s width changes.
78 |
79 | ### pack.reload()
80 |
81 | Recalculates the grid.
82 | Specifically, you would want to use this when `container` is resized:
83 |
84 | ```js
85 | window.addEventListener('resize', function () {
86 | pack.width = container.clientWidth
87 | pack.height = Math.round(window.outerHeight / Math.PI)
88 | pack.reload()
89 | })
90 | ```
91 |
92 | ### pack.destroy()
93 |
94 | Destroys the grid and returns `container` to its original state.
95 |
96 | ### pack.create()
97 |
98 | Creates the grid.
99 | This is called by default.
100 | You should only use this if the grid has been previously destroyed.
101 |
102 | ### pack.append(DocumentFragment || elements)
103 |
104 | Append elements to the current grid.
105 | Could either be a `DocumentFragment` instance whose child nodes are `elements`,
106 | or an array-like variable of grid `elements`.
107 | Appends using `DocumentFragment`, so don't worry about reflows.
108 |
109 | ## Compatibility
110 |
111 | IE9+ (Pull requests welcome for IE8)
112 |
113 | ## License
114 |
115 | The MIT License (MIT)
116 |
117 | Copyright (c) 2013 Jonathan Ong me@jongleberry.com
118 |
119 | Permission is hereby granted, free of charge, to any person obtaining a copy
120 | of this software and associated documentation files (the "Software"), to deal
121 | in the Software without restriction, including without limitation the rights
122 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
123 | copies of the Software, and to permit persons to whom the Software is
124 | furnished to do so, subject to the following conditions:
125 |
126 | The above copyright notice and this permission notice shall be included in
127 | all copies or substantial portions of the Software.
128 |
129 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
130 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
131 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
132 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
133 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
134 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
135 | THE SOFTWARE.
136 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "horizontal-grid-packing",
3 | "description": "Horizontal grid packing",
4 | "version": "0.1.4",
5 | "main": [
6 | "dist/horizontal-grid-packing.js",
7 | "dist/horizontal-grid-packing.css"
8 | ]
9 | }
--------------------------------------------------------------------------------
/component.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "horizontal-grid-packing",
3 | "description": "Horizontal grid packing",
4 | "version": "0.1.4",
5 | "scripts": [
6 | "lib/pack.js"
7 | ],
8 | "styles": [
9 | "lib/pack.css"
10 | ],
11 | "main": "lib/pack.js",
12 | "dependencies": {
13 | "component/classes": "*",
14 | "the-swerve/linear-partitioning": "*"
15 | },
16 | "development": {
17 | "component/raf": "*"
18 | },
19 | "license": "MIT",
20 | "repo": "jonathanong/horizontal-grid-packing"
21 | }
--------------------------------------------------------------------------------
/demo.html:
--------------------------------------------------------------------------------
1 | Horizontal Grid Packing Example
--------------------------------------------------------------------------------
/demo/index.jade:
--------------------------------------------------------------------------------
1 | doctype html
2 | html(lang="en")
3 | head
4 | meta(name='viewport', content='width=device-width, initial-scale=1')
5 | title Horizontal Grid Packing Example
6 | style.
7 | html,
8 | body {
9 | margin: 0;
10 | padding: 0;
11 | }
12 |
13 | div {
14 | margin: 5px;
15 | }
16 |
17 | img {
18 | transition: all 0.3s linear;
19 | }
20 | link(rel="stylesheet", href="./build/build.css")
21 | script(src='./build/build.js')
22 | body
23 | .horizontal-grid-packing
24 | for image in images
25 | img(
26 | src = 'http://i.imgur.com/' + image.hash + 'l' + image.ext,
27 | data-width = image.width,
28 | data-height = image.height
29 | )
30 |
31 | script.
32 | var Pack = require('horizontal-grid-packing')
33 | var container = document.querySelector('.horizontal-grid-packing')
34 | var imagesHTML = container.innerHTML
35 | var pack = Pack(container, {
36 | height: height(),
37 | padding: 5
38 | })
39 |
40 | var raf = require('component-raf')
41 | var pending = false
42 | window.addEventListener('resize', function () {
43 | if (pending) return
44 | pending = true
45 | raf(reload)
46 | })
47 |
48 | function reload() {
49 | pack.width = container.clientWidth
50 | pack.height = height()
51 | pack.reload()
52 | pending = false
53 | }
54 |
55 | function append() {
56 | var frag = document.createElement('div')
57 | frag.innerHTML = imagesHTML
58 | pack.append(frag.children)
59 | }
60 |
61 | function height() {
62 | return Math.max(Math.round(window.outerHeight / Math.PI), 120)
63 | }
--------------------------------------------------------------------------------
/demo/index.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs')
2 | var path = require('path')
3 | var request = require('request')
4 | var jade = require('jade')
5 |
6 | var string = fs.readFileSync(path.join(__dirname, 'index.jade'), 'utf8')
7 | var template = jade.compile(string)
8 |
9 | request('http://imgur.com/r/emmawatson.json', function (err, res, body) {
10 | if (err)
11 | throw err
12 |
13 | fs.writeFileSync(
14 | path.join(__dirname, '..', 'demo.html'),
15 | template({
16 | images: JSON.parse(body).data
17 | })
18 | )
19 |
20 | process.exit()
21 | })
--------------------------------------------------------------------------------
/dist/horizontal-grid-packing.css:
--------------------------------------------------------------------------------
1 | .hgp {
2 | position: relative;
3 | visibility: hidden;
4 | overflow: hidden;
5 | }
--------------------------------------------------------------------------------
/dist/horizontal-grid-packing.js:
--------------------------------------------------------------------------------
1 | ;(function(){
2 |
3 | /**
4 | * Require the given path.
5 | *
6 | * @param {String} path
7 | * @return {Object} exports
8 | * @api public
9 | */
10 |
11 | function require(path, parent, orig) {
12 | var resolved = require.resolve(path);
13 |
14 | // lookup failed
15 | if (null == resolved) {
16 | orig = orig || path;
17 | parent = parent || 'root';
18 | var err = new Error('Failed to require "' + orig + '" from "' + parent + '"');
19 | err.path = orig;
20 | err.parent = parent;
21 | err.require = true;
22 | throw err;
23 | }
24 |
25 | var module = require.modules[resolved];
26 |
27 | // perform real require()
28 | // by invoking the module's
29 | // registered function
30 | if (!module.exports) {
31 | module.exports = {};
32 | module.client = module.component = true;
33 | module.call(this, module.exports, require.relative(resolved), module);
34 | }
35 |
36 | return module.exports;
37 | }
38 |
39 | /**
40 | * Registered modules.
41 | */
42 |
43 | require.modules = {};
44 |
45 | /**
46 | * Registered aliases.
47 | */
48 |
49 | require.aliases = {};
50 |
51 | /**
52 | * Resolve `path`.
53 | *
54 | * Lookup:
55 | *
56 | * - PATH/index.js
57 | * - PATH.js
58 | * - PATH
59 | *
60 | * @param {String} path
61 | * @return {String} path or null
62 | * @api private
63 | */
64 |
65 | require.resolve = function(path) {
66 | if (path.charAt(0) === '/') path = path.slice(1);
67 |
68 | var paths = [
69 | path,
70 | path + '.js',
71 | path + '.json',
72 | path + '/index.js',
73 | path + '/index.json'
74 | ];
75 |
76 | for (var i = 0; i < paths.length; i++) {
77 | var path = paths[i];
78 | if (require.modules.hasOwnProperty(path)) return path;
79 | if (require.aliases.hasOwnProperty(path)) return require.aliases[path];
80 | }
81 | };
82 |
83 | /**
84 | * Normalize `path` relative to the current path.
85 | *
86 | * @param {String} curr
87 | * @param {String} path
88 | * @return {String}
89 | * @api private
90 | */
91 |
92 | require.normalize = function(curr, path) {
93 | var segs = [];
94 |
95 | if ('.' != path.charAt(0)) return path;
96 |
97 | curr = curr.split('/');
98 | path = path.split('/');
99 |
100 | for (var i = 0; i < path.length; ++i) {
101 | if ('..' == path[i]) {
102 | curr.pop();
103 | } else if ('.' != path[i] && '' != path[i]) {
104 | segs.push(path[i]);
105 | }
106 | }
107 |
108 | return curr.concat(segs).join('/');
109 | };
110 |
111 | /**
112 | * Register module at `path` with callback `definition`.
113 | *
114 | * @param {String} path
115 | * @param {Function} definition
116 | * @api private
117 | */
118 |
119 | require.register = function(path, definition) {
120 | require.modules[path] = definition;
121 | };
122 |
123 | /**
124 | * Alias a module definition.
125 | *
126 | * @param {String} from
127 | * @param {String} to
128 | * @api private
129 | */
130 |
131 | require.alias = function(from, to) {
132 | if (!require.modules.hasOwnProperty(from)) {
133 | throw new Error('Failed to alias "' + from + '", it does not exist');
134 | }
135 | require.aliases[to] = from;
136 | };
137 |
138 | /**
139 | * Return a require function relative to the `parent` path.
140 | *
141 | * @param {String} parent
142 | * @return {Function}
143 | * @api private
144 | */
145 |
146 | require.relative = function(parent) {
147 | var p = require.normalize(parent, '..');
148 |
149 | /**
150 | * lastIndexOf helper.
151 | */
152 |
153 | function lastIndexOf(arr, obj) {
154 | var i = arr.length;
155 | while (i--) {
156 | if (arr[i] === obj) return i;
157 | }
158 | return -1;
159 | }
160 |
161 | /**
162 | * The relative require() itself.
163 | */
164 |
165 | function localRequire(path) {
166 | var resolved = localRequire.resolve(path);
167 | return require(resolved, parent, path);
168 | }
169 |
170 | /**
171 | * Resolve relative to the parent.
172 | */
173 |
174 | localRequire.resolve = function(path) {
175 | var c = path.charAt(0);
176 | if ('/' == c) return path.slice(1);
177 | if ('.' == c) return require.normalize(p, path);
178 |
179 | // resolve deps by returning
180 | // the dep in the nearest "deps"
181 | // directory
182 | var segs = parent.split('/');
183 | var i = lastIndexOf(segs, 'deps') + 1;
184 | if (!i) i = 0;
185 | path = segs.slice(0, i + 1).join('/') + '/deps/' + path;
186 | return path;
187 | };
188 |
189 | /**
190 | * Check if module is defined at `path`.
191 | */
192 |
193 | localRequire.exists = function(path) {
194 | return require.modules.hasOwnProperty(localRequire.resolve(path));
195 | };
196 |
197 | return localRequire;
198 | };
199 | require.register("component-indexof/index.js", function(exports, require, module){
200 | module.exports = function(arr, obj){
201 | if (arr.indexOf) return arr.indexOf(obj);
202 | for (var i = 0; i < arr.length; ++i) {
203 | if (arr[i] === obj) return i;
204 | }
205 | return -1;
206 | };
207 | });
208 | require.register("component-classes/index.js", function(exports, require, module){
209 | /**
210 | * Module dependencies.
211 | */
212 |
213 | var index = require('indexof');
214 |
215 | /**
216 | * Whitespace regexp.
217 | */
218 |
219 | var re = /\s+/;
220 |
221 | /**
222 | * toString reference.
223 | */
224 |
225 | var toString = Object.prototype.toString;
226 |
227 | /**
228 | * Wrap `el` in a `ClassList`.
229 | *
230 | * @param {Element} el
231 | * @return {ClassList}
232 | * @api public
233 | */
234 |
235 | module.exports = function(el){
236 | return new ClassList(el);
237 | };
238 |
239 | /**
240 | * Initialize a new ClassList for `el`.
241 | *
242 | * @param {Element} el
243 | * @api private
244 | */
245 |
246 | function ClassList(el) {
247 | if (!el) throw new Error('A DOM element reference is required');
248 | this.el = el;
249 | this.list = el.classList;
250 | }
251 |
252 | /**
253 | * Add class `name` if not already present.
254 | *
255 | * @param {String} name
256 | * @return {ClassList}
257 | * @api public
258 | */
259 |
260 | ClassList.prototype.add = function(name){
261 | // classList
262 | if (this.list) {
263 | this.list.add(name);
264 | return this;
265 | }
266 |
267 | // fallback
268 | var arr = this.array();
269 | var i = index(arr, name);
270 | if (!~i) arr.push(name);
271 | this.el.className = arr.join(' ');
272 | return this;
273 | };
274 |
275 | /**
276 | * Remove class `name` when present, or
277 | * pass a regular expression to remove
278 | * any which match.
279 | *
280 | * @param {String|RegExp} name
281 | * @return {ClassList}
282 | * @api public
283 | */
284 |
285 | ClassList.prototype.remove = function(name){
286 | if ('[object RegExp]' == toString.call(name)) {
287 | return this.removeMatching(name);
288 | }
289 |
290 | // classList
291 | if (this.list) {
292 | this.list.remove(name);
293 | return this;
294 | }
295 |
296 | // fallback
297 | var arr = this.array();
298 | var i = index(arr, name);
299 | if (~i) arr.splice(i, 1);
300 | this.el.className = arr.join(' ');
301 | return this;
302 | };
303 |
304 | /**
305 | * Remove all classes matching `re`.
306 | *
307 | * @param {RegExp} re
308 | * @return {ClassList}
309 | * @api private
310 | */
311 |
312 | ClassList.prototype.removeMatching = function(re){
313 | var arr = this.array();
314 | for (var i = 0; i < arr.length; i++) {
315 | if (re.test(arr[i])) {
316 | this.remove(arr[i]);
317 | }
318 | }
319 | return this;
320 | };
321 |
322 | /**
323 | * Toggle class `name`, can force state via `force`.
324 | *
325 | * For browsers that support classList, but do not support `force` yet,
326 | * the mistake will be detected and corrected.
327 | *
328 | * @param {String} name
329 | * @param {Boolean} force
330 | * @return {ClassList}
331 | * @api public
332 | */
333 |
334 | ClassList.prototype.toggle = function(name, force){
335 | // classList
336 | if (this.list) {
337 | if ("undefined" !== typeof force) {
338 | if (force !== this.list.toggle(name, force)) {
339 | this.list.toggle(name); // toggle again to correct
340 | }
341 | } else {
342 | this.list.toggle(name);
343 | }
344 | return this;
345 | }
346 |
347 | // fallback
348 | if ("undefined" !== typeof force) {
349 | if (!force) {
350 | this.remove(name);
351 | } else {
352 | this.add(name);
353 | }
354 | } else {
355 | if (this.has(name)) {
356 | this.remove(name);
357 | } else {
358 | this.add(name);
359 | }
360 | }
361 |
362 | return this;
363 | };
364 |
365 | /**
366 | * Return an array of classes.
367 | *
368 | * @return {Array}
369 | * @api public
370 | */
371 |
372 | ClassList.prototype.array = function(){
373 | var str = this.el.className.replace(/^\s+|\s+$/g, '');
374 | var arr = str.split(re);
375 | if ('' === arr[0]) arr.shift();
376 | return arr;
377 | };
378 |
379 | /**
380 | * Check if class `name` is present.
381 | *
382 | * @param {String} name
383 | * @return {ClassList}
384 | * @api public
385 | */
386 |
387 | ClassList.prototype.has =
388 | ClassList.prototype.contains = function(name){
389 | return this.list
390 | ? this.list.contains(name)
391 | : !! ~index(this.array(), name);
392 | };
393 |
394 | });
395 | require.register("component-is-document-fragment/index.js", function(exports, require, module){
396 | var toString = Object.prototype.toString;
397 |
398 | module.exports = function isDocumentFragment(element) {
399 | return element
400 | && toString.call(element) === '[object DocumentFragment]'
401 | }
402 | });
403 | require.register("the-swerve-linear-partitioning/linear-partitioning.js", function(exports, require, module){
404 | // Explanation: http://www8.cs.umu.se/kurser/TDBAfl/VT06/algorithms/BOOK/BOOK2/NODE45.HTM
405 |
406 | // Partition seq into k buckets
407 |
408 |
409 | var partition = function (seq, k) {
410 |
411 | if (k === 0) return [];
412 | if (k === 1) return [seq];
413 |
414 | if (k >= seq.length) {
415 | // return the lists of each single element in sequence, plus empty lists for any extra buckets.
416 | var repeated = [];
417 | for (var q = 0; q < k - seq.length; ++q) repeated.push([]);
418 | return seq.map(function(x) { return [x]; }).concat(repeated);
419 | }
420 |
421 | var sequence = seq.slice(0);
422 | var dividers = [];
423 | var sums = prefix_sums(sequence, k);
424 | var conds = boundary_conditions(sequence, k, sums);
425 |
426 | // evaluate main recurrence
427 | for(var i = 2; i <= sequence.length; ++i) {
428 | for(var j = 2; j <= k; ++j) {
429 |
430 | conds[i][j] = undefined;
431 |
432 | for(var x = 1; x < i; ++x) {
433 |
434 | var s = Math.max(conds[x][j-1], sums[i] - sums[x]);
435 | dividers[i] = dividers[i] || []; // Initialize a new row in the dividers matrix (unless it's already initialized).
436 |
437 | // Continue to find the cost of the largest range in the optimal partition.
438 | if (conds[i][j] === undefined || conds[i][j] > s) {
439 | conds[i][j] = s;
440 | dividers[i][j] = x;
441 | }
442 |
443 | }
444 | }
445 | }
446 |
447 | return(reconstruct_partition(sequence, dividers, k));
448 | };
449 |
450 | /* Work our way back up through the dividers, referencing each divider that we
451 | * saved given a value for k and a length of seq, using each divider to make
452 | * the partitions. */
453 | var reconstruct_partition = function(seq, dividers, k) {
454 | var partitions = [];
455 |
456 | while (k > 1) {
457 | if (dividers[seq.length]) {
458 | var divider = dividers[seq.length][k];
459 | var part = seq.splice(divider);
460 | partitions.unshift(part);
461 | }
462 | --k;
463 | }
464 |
465 | partitions.unshift(seq);
466 |
467 | return partitions;
468 | };
469 |
470 | /*
471 | Given a list of numbers of length n, loop through it with index 'i'
472 | Make each element the sum of all the numbers from 0...i
473 | For example, given [1,2,3,4,5]
474 | The prefix sums are [1,3,6,10,15]
475 | */
476 | var prefix_sums = function(seq) {
477 |
478 | var sums = [0];
479 |
480 | for(var i = 1; i <= seq.length; ++i) {
481 | sums[i] = sums[i - 1] + seq[i - 1];
482 | }
483 |
484 | return sums;
485 | };
486 |
487 | /* This matrix holds the maximum sums over all the ranges given the length of
488 | * seq and the number of buckets (k) */
489 | var boundary_conditions = function(seq, k, sums) {
490 | var conds = [];
491 |
492 | for(var i = 1; i <= seq.length; ++i) {
493 | conds[i] = [];
494 | conds[i][1] = sums[i];
495 | }
496 |
497 | for(var j = 1; j <= k; ++j) {
498 | conds[1][j] = seq[0];
499 | }
500 |
501 | return conds;
502 | };
503 |
504 | module.exports = partition;
505 |
506 | });
507 | require.register("horizontal-grid-packing/lib/pack.js", function(exports, require, module){
508 | var isFragment = require('is-document-fragment')
509 | var part = require('linear-partitioning')
510 | var classes = require('classes')
511 |
512 | module.exports = Pack
513 |
514 | function Pack(container, options) {
515 | if (!(this instanceof Pack))
516 | return new Pack(container, options)
517 |
518 | options = options || {}
519 |
520 | this.container = container
521 | this.isFragment = isFragment(container)
522 | this.classes = !this.isFragment && classes(container)
523 | this.images = slice(container.childNodes).filter(isElement)
524 | this.top = options.top || 0
525 | this.width = options.width || container.clientWidth
526 | this.height = options.height || 120
527 | this.padding = options.padding || 0
528 |
529 | this.create()
530 | }
531 |
532 | Pack.prototype.append = function (images) {
533 | var fragment
534 |
535 | if (isFragment(images)) {
536 | fragment = images
537 | images = slice(fragment.childNodes)
538 | } else {
539 | fragment = document.createDocumentFragment()
540 | images = slice(images)
541 | images.forEach(function (image) {
542 | if (image.parentNode)
543 | image.parentNode.removeChild(image)
544 |
545 | fragment.appendChild(image)
546 | })
547 | }
548 |
549 | var subpack = new Pack(fragment, {
550 | top: this.totalheight + this.padding,
551 | width: this.width,
552 | height: this.height,
553 | padding: this.padding
554 | })
555 |
556 | this.totalheight = subpack.totalheight
557 | this.images = this.images.concat(images.filter(isElement))
558 | this.mirror = this.mirror.concat(subpack.mirror)
559 |
560 | var container = this.container
561 | container.appendChild(fragment)
562 | container.style.height = this.totalheight + 'px'
563 | }
564 |
565 | Pack.prototype.destroy = function () {
566 | this.images.forEach(unsetStyle)
567 | this.mirror = null
568 |
569 | if (this.isFragment)
570 | return
571 |
572 | var style = this.container.style
573 | style.visibility =
574 | style.height = ''
575 | this.classes.remove('hgp')
576 | }
577 |
578 | Pack.prototype.reload = function () {
579 | this.container.style.visibility = 'hidden'
580 | this.create()
581 | }
582 |
583 | Pack.prototype.create = function () {
584 | var index = 0
585 | var ratios = this.calculateAspectRatios()
586 | var container = this.container
587 | var mirrors = this.mirror = []
588 |
589 | part(ratios, Math.max(Math.min(
590 | Math.floor(ratios.reduce(add, 0) * this.height / this.width),
591 | ratios.length
592 | ), 1)).forEach(function (x) {
593 | index += this.createRow(index, x.length)
594 | }, this)
595 |
596 | var lastmirror = mirrors[mirrors.length - 1]
597 | this.totalheight = lastmirror.top + lastmirror.height
598 | this.images.forEach(positionAbsolute)
599 |
600 | if (this.isFragment)
601 | return
602 |
603 | this.classes.add('hgp')
604 |
605 | var style = container.style
606 | style.height = this.totalheight + 'px'
607 | style.visibility = 'visible'
608 | }
609 |
610 | Pack.prototype.createRow = function (index, count) {
611 | var mirror = this.mirror
612 | var padding = this.padding
613 | var images = this.images.slice(index, index + count)
614 | var height = this.calculateRowHeight(images)
615 |
616 | var row = {
617 | index: index,
618 | count: count,
619 | height: height
620 | }
621 |
622 | var imagemirrors = row.images = []
623 | var lastrow = mirror[mirror.length - 1]
624 | var top = row.top = lastrow
625 | ? (lastrow.top + lastrow.height + padding)
626 | : (this.top || 0)
627 |
628 | images.forEach(function (image, i) {
629 | var lastimage = i && imagemirrors[i - 1]
630 | var left = lastimage
631 | ? lastimage.right + padding
632 | : 0
633 | var width = Math.round(height * image.aspectRatio)
634 |
635 | var style = image.style
636 | style.left = left + 'px'
637 | style.top = top + 'px'
638 | style.height = height + 'px'
639 | style.width = width + 'px'
640 |
641 | imagemirrors.push({
642 | left: left,
643 | width: width,
644 | right: left + width,
645 | image: image
646 | })
647 | })
648 |
649 | mirror.push(row)
650 |
651 | return count
652 | }
653 |
654 | Pack.prototype.calculateRowHeight = function (images) {
655 | return Math.ceil(
656 | (this.width - (this.padding * (images.length - 1))) /
657 | images.map(getAspectRatio).reduce(add, 0)
658 | )
659 | }
660 |
661 | Pack.prototype.calculateAspectRatios = function () {
662 | return this.images.map(calculateAspectRatio)
663 | }
664 |
665 | function positionAbsolute(image) {
666 | image.style.position = 'absolute'
667 | }
668 |
669 | function unsetStyle(image) {
670 | var style = image.style
671 | style.width =
672 | style.height =
673 | style.top =
674 | style.left =
675 | style.position = ''
676 | }
677 |
678 | function calculateAspectRatio(image) {
679 | return image.aspectRatio || (image.aspectRatio =
680 | parseFloat(image.getAttribute('data-aspect-ratio')) ||
681 | parseInt(image.getAttribute('data-width'), 10) /
682 | parseInt(image.getAttribute('data-height'), 10)
683 | )
684 | }
685 |
686 | function getAspectRatio(x) {
687 | return x.aspectRatio
688 | }
689 |
690 | function slice(x) {
691 | return [].slice.call(x, 0)
692 | }
693 |
694 | function add(a, b) {
695 | return a + b
696 | }
697 |
698 | function isElement(el) {
699 | return el.nodeType && el.nodeType === Node.ELEMENT_NODE
700 | }
701 |
702 | });
703 |
704 |
705 |
706 | require.alias("component-classes/index.js", "horizontal-grid-packing/deps/classes/index.js");
707 | require.alias("component-classes/index.js", "classes/index.js");
708 | require.alias("component-indexof/index.js", "component-classes/deps/indexof/index.js");
709 |
710 | require.alias("component-is-document-fragment/index.js", "horizontal-grid-packing/deps/is-document-fragment/index.js");
711 | require.alias("component-is-document-fragment/index.js", "is-document-fragment/index.js");
712 |
713 | require.alias("the-swerve-linear-partitioning/linear-partitioning.js", "horizontal-grid-packing/deps/linear-partitioning/linear-partitioning.js");
714 | require.alias("the-swerve-linear-partitioning/linear-partitioning.js", "horizontal-grid-packing/deps/linear-partitioning/index.js");
715 | require.alias("the-swerve-linear-partitioning/linear-partitioning.js", "linear-partitioning/index.js");
716 | require.alias("the-swerve-linear-partitioning/linear-partitioning.js", "the-swerve-linear-partitioning/index.js");
717 | require.alias("horizontal-grid-packing/lib/pack.js", "horizontal-grid-packing/index.js");if (typeof exports == "object") {
718 | module.exports = require("horizontal-grid-packing");
719 | } else if (typeof define == "function" && define.amd) {
720 | define(function(){ return require("horizontal-grid-packing"); });
721 | } else {
722 | this["HorizontalGridPacking"] = require("horizontal-grid-packing");
723 | }})();
--------------------------------------------------------------------------------
/lib/pack.css:
--------------------------------------------------------------------------------
1 | .hgp {
2 | position: relative;
3 | visibility: hidden;
4 | overflow: hidden;
5 | }
--------------------------------------------------------------------------------
/lib/pack.js:
--------------------------------------------------------------------------------
1 | /* jshint browser: true */
2 |
3 | var part = require('linear-partitioning')
4 | var classes = require('classes')
5 |
6 | module.exports = Pack
7 |
8 | function Pack(container, options) {
9 | if (!(this instanceof Pack))
10 | return new Pack(container, options)
11 |
12 | options = options || {}
13 |
14 | this.container = container
15 | this.isFragment = container.nodeType === 11
16 | this.classes = !this.isFragment && classes(container)
17 | this.images = slice(container.childNodes).filter(isElement)
18 | this.top = options.top || 0
19 | this.width = options.width || container.clientWidth
20 | this.height = options.height || 120
21 | this.padding = options.padding || 0
22 |
23 | this.create()
24 | }
25 |
26 | Pack.prototype.append = function (images) {
27 | var fragment
28 |
29 | if (images.nodeType === 11) {
30 | fragment = images
31 | images = slice(fragment.childNodes)
32 | } else {
33 | fragment = document.createDocumentFragment()
34 | images = slice(images)
35 | images.forEach(function (image) {
36 | if (image.parentNode)
37 | image.parentNode.removeChild(image)
38 |
39 | fragment.appendChild(image)
40 | })
41 | }
42 |
43 | var subpack = new Pack(fragment, {
44 | top: this.totalheight + this.padding,
45 | width: this.width,
46 | height: this.height,
47 | padding: this.padding
48 | })
49 |
50 | this.totalheight = subpack.totalheight
51 | this.images = this.images.concat(images.filter(isElement))
52 | this.mirror = this.mirror.concat(subpack.mirror)
53 |
54 | var container = this.container
55 | container.appendChild(fragment)
56 | container.style.height = this.totalheight + 'px'
57 | }
58 |
59 | Pack.prototype.destroy = function () {
60 | this.images.forEach(unsetStyle)
61 | this.mirror = null
62 |
63 | if (this.isFragment) return
64 |
65 | var style = this.container.style
66 | style.visibility =
67 | style.height = ''
68 | this.classes.remove('hgp')
69 | }
70 |
71 | Pack.prototype.reload = function () {
72 | this.container.style.visibility = 'hidden'
73 | this.create()
74 | }
75 |
76 | Pack.prototype.create = function () {
77 | var index = 0
78 | var ratios = this.calculateAspectRatios()
79 | var container = this.container
80 | var mirrors = this.mirror = []
81 |
82 | part(ratios, Math.max(Math.min(
83 | Math.floor(ratios.reduce(add, 0) * this.height / this.width),
84 | ratios.length
85 | ), 1)).forEach(function (x) {
86 | index += this.createRow(index, x.length)
87 | }, this)
88 |
89 | var lastmirror = mirrors[mirrors.length - 1]
90 | this.totalheight = lastmirror.top + lastmirror.height
91 | this.images.forEach(positionAbsolute)
92 |
93 | if (this.isFragment) return
94 |
95 | this.classes.add('hgp')
96 |
97 | var style = container.style
98 | style.height = this.totalheight + 'px'
99 | style.visibility = 'visible'
100 | }
101 |
102 | Pack.prototype.createRow = function (index, count) {
103 | var mirror = this.mirror
104 | var padding = this.padding
105 | var images = this.images.slice(index, index + count)
106 | var height = this.calculateRowHeight(images)
107 |
108 | var row = {
109 | index: index,
110 | count: count,
111 | height: height
112 | }
113 |
114 | var imagemirrors = row.images = []
115 | var lastrow = mirror[mirror.length - 1]
116 | var top = row.top = lastrow
117 | ? (lastrow.top + lastrow.height + padding)
118 | : (this.top || 0)
119 |
120 | images.forEach(function (image, i) {
121 | var lastimage = i && imagemirrors[i - 1]
122 | var left = lastimage
123 | ? lastimage.right + padding
124 | : 0
125 | var width = Math.round(height * image.aspectRatio)
126 |
127 | var style = image.style
128 | style.left = left + 'px'
129 | style.top = top + 'px'
130 | style.height = height + 'px'
131 | style.width = width + 'px'
132 |
133 | imagemirrors.push({
134 | left: left,
135 | width: width,
136 | right: left + width,
137 | image: image
138 | })
139 | })
140 |
141 | mirror.push(row)
142 |
143 | return count
144 | }
145 |
146 | Pack.prototype.calculateRowHeight = function (images) {
147 | return Math.ceil(
148 | (this.width - (this.padding * (images.length - 1))) /
149 | images.map(getAspectRatio).reduce(add, 0)
150 | )
151 | }
152 |
153 | Pack.prototype.calculateAspectRatios = function () {
154 | return this.images.map(calculateAspectRatio)
155 | }
156 |
157 | function positionAbsolute(image) {
158 | image.style.position = 'absolute'
159 | }
160 |
161 | function unsetStyle(image) {
162 | var style = image.style
163 | style.width =
164 | style.height =
165 | style.top =
166 | style.left =
167 | style.position = ''
168 | }
169 |
170 | function calculateAspectRatio(image) {
171 | return image.aspectRatio || (image.aspectRatio =
172 | parseFloat(image.getAttribute('data-aspect-ratio')) ||
173 | parseInt(image.getAttribute('data-width'), 10) /
174 | parseInt(image.getAttribute('data-height'), 10)
175 | )
176 | }
177 |
178 | function getAspectRatio(x) {
179 | return x.aspectRatio
180 | }
181 |
182 | function slice(x) {
183 | return [].slice.call(x, 0)
184 | }
185 |
186 | function add(a, b) {
187 | return a + b
188 | }
189 |
190 | function isElement(el) {
191 | return el.nodeType && el.nodeType === Node.ELEMENT_NODE
192 | }
193 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "horizontal-grid-packing",
3 | "description": "Horizontal grid packing",
4 | "version": "0.1.4",
5 | "author": {
6 | "name": "Jonathan Ong",
7 | "email": "me@jongleberry.com",
8 | "url": "http://jongleberry.com",
9 | "twitter": "https://twitter.com/jongleberry"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "https://github.com/jonathanong/horizontal-grid-packing.git"
14 | },
15 | "bugs": {
16 | "url": "https://github.com/jonathanong/horizontal-grid-packing/issues",
17 | "email": "me@jongleberry.com"
18 | },
19 | "license": "MIT",
20 | "devDependencies": {
21 | "jade": "*",
22 | "request": "*"
23 | }
24 | }
--------------------------------------------------------------------------------