/****************************************************************************** This is a demo page to experiment with binary tree based algorithms for packing blocks into a single 2 dimensional bin. See individual .js files for descriptions of each algorithm: * packer.js - simple algorithm for a fixed width/height bin * packer.growing.js - complex algorithm that grows automatically TODO ==== * step by step animated render to watch packing in action (and help debug) * optimization - mark branches as "full" to avoid walking them * optimization - dont bother with nodes that are less than some threshold w/h (2? 5?) *******************************************************************************/ Demo = { init: function() { Demo.el = { examples: $('#examples'), blocks: $('#blocks'), canvas: $('#canvas')[0], size: $('#size'), sort: $('#sort'), color: $('#color'), ratio: $('#ratio'), nofit: $('#nofit') }; if (!Demo.el.canvas.getContext) // no support for canvas return false; Demo.el.draw = Demo.el.canvas.getContext("2d"); Demo.el.blocks.val(Demo.blocks.serialize(Demo.blocks.examples.current())); Demo.el.blocks.change(Demo.run); Demo.el.size.change(Demo.run); Demo.el.sort.change(Demo.run); Demo.el.color.change(Demo.run); Demo.el.examples.change(Demo.blocks.examples.change); Demo.run(); Demo.el.blocks.keypress(function(ev) { if (ev.which == 13) Demo.run(); // run on while entering block information }); }, //--------------------------------------------------------------------------- run: function() { var blocks = Demo.blocks.deserialize(Demo.el.blocks.val()); var packer = Demo.packer(); Demo.sort.now(blocks); packer.fit(blocks); Demo.canvas.reset(packer.root.w, packer.root.h); Demo.canvas.blocks(blocks); Demo.canvas.boundary(packer.root); Demo.report(blocks, packer.root.w, packer.root.h); }, //--------------------------------------------------------------------------- packer: function() { var size = Demo.el.size.val(); if (size == 'automatic') { return new GrowingPacker(); } else { var dims = size.split("x"); return new Packer(parseInt(dims[0]), parseInt(dims[1])); } }, //--------------------------------------------------------------------------- report: function(blocks, w, h) { var fit = 0, nofit = [], block, n, len = blocks.length; for (n = 0 ; n < len ; n++) { block = blocks[n]; if (block.fit) fit = fit + block.area; else nofit.push("" + block.w + "x" + block.h); } Demo.el.ratio.text(Math.round(100 * fit / (w * h))); Demo.el.nofit.html("Did not fit (" + nofit.length + ") :
" + nofit.join(", ")).toggle(nofit.length > 0); }, //--------------------------------------------------------------------------- sort: { random : function (a,b) { return Math.random() - 0.5; }, w : function (a,b) { return b.w - a.w; }, h : function (a,b) { return b.h - a.h; }, a : function (a,b) { return b.area - a.area; }, max : function (a,b) { return Math.max(b.w, b.h) - Math.max(a.w, a.h); }, min : function (a,b) { return Math.min(b.w, b.h) - Math.min(a.w, a.h); }, height : function (a,b) { return Demo.sort.msort(a, b, ['h', 'w']); }, width : function (a,b) { return Demo.sort.msort(a, b, ['w', 'h']); }, area : function (a,b) { return Demo.sort.msort(a, b, ['a', 'h', 'w']); }, maxside : function (a,b) { return Demo.sort.msort(a, b, ['max', 'min', 'h', 'w']); }, msort: function(a, b, criteria) { /* sort by multiple criteria */ var diff, n; for (n = 0 ; n < criteria.length ; n++) { diff = Demo.sort[criteria[n]](a,b); if (diff != 0) return diff; } return 0; }, now: function(blocks) { var sort = Demo.el.sort.val(); if (sort != 'none') blocks.sort(Demo.sort[sort]); } }, //--------------------------------------------------------------------------- canvas: { reset: function(width, height) { Demo.el.canvas.width = width + 1; // add 1 because we draw boundaries offset by 0.5 in order to pixel align and get crisp boundaries Demo.el.canvas.height = height + 1; // (ditto) Demo.el.draw.clearRect(0, 0, Demo.el.canvas.width, Demo.el.canvas.height); }, rect: function(x, y, w, h, color) { Demo.el.draw.fillStyle = color; Demo.el.draw.fillRect(x + 0.5, y + 0.5, w, h); }, stroke: function(x, y, w, h) { Demo.el.draw.strokeRect(x + 0.5, y + 0.5, w, h); }, blocks: function(blocks) { var n, block; for (n = 0 ; n < blocks.length ; n++) { block = blocks[n]; if (block.fit) Demo.canvas.rect(block.fit.x, block.fit.y, block.w, block.h, Demo.color(n)); } }, boundary: function(node) { if (node) { Demo.canvas.stroke(node.x, node.y, node.w, node.h); Demo.canvas.boundary(node.down); Demo.canvas.boundary(node.right); } } }, //--------------------------------------------------------------------------- blocks: { examples: { simple: [ { w: 500, h: 200, num: 1 }, { w: 250, h: 200, num: 1 }, { w: 50, h: 50, num: 20 } ], square: [ { w: 50, h: 50, num: 100 } ], power2: [ { w: 2, h: 2, num: 256 }, { w: 4, h: 4, num: 128 }, { w: 8, h: 8, num: 64 }, { w: 16, h: 16, num: 32 }, { w: 32, h: 32, num: 16 }, { w: 64, h: 64, num: 8 }, { w: 128, h: 128, num: 4 }, { w: 256, h: 256, num: 2 } ], tall: [ { w: 50, h: 400, num: 2 }, { w: 50, h: 300, num: 5 }, { w: 50, h: 200, num: 10 }, { w: 50, h: 100, num: 20 }, { w: 50, h: 50, num: 40 } ], wide: [ { w: 400, h: 50, num: 2 }, { w: 300, h: 50, num: 5 }, { w: 200, h: 50, num: 10 }, { w: 100, h: 50, num: 20 }, { w: 50, h: 50, num: 40 } ], tallwide: [ /* alternate tall then wide */ { w: 400, h: 100 }, { w: 100, h: 400 }, { w: 400, h: 100 }, { w: 100, h: 400 }, { w: 400, h: 100 }, { w: 100, h: 400 } ], oddeven: [ /* both odd and even sizes leaves little areas of whitespace */ { w: 50, h: 50, num: 20 }, { w: 47, h: 31, num: 20 }, { w: 23, h: 17, num: 20 }, { w: 109, h: 42, num: 20 }, { w: 42, h: 109, num: 20 }, { w: 17, h: 33, num: 20 }, ], complex: [ {w: 100, h: 100, num: 3}, {w: 60, h: 60, num: 3}, {w: 50, h: 20, num: 20}, {w: 20, h: 50, num: 20}, {w: 250, h: 250, num: 1}, {w: 250, h: 100, num: 1}, {w: 100, h: 250, num: 1}, {w: 400, h: 80, num: 1}, {w: 80, h: 400, num: 1}, {w: 10, h: 10, num: 100}, {w: 5, h: 5, num: 500} ], current: function() { return Demo.blocks.examples[Demo.el.examples.val()]; }, change: function() { Demo.el.blocks.val(Demo.blocks.serialize(Demo.blocks.examples.current())); Demo.run(); } }, deserialize: function(val) { var i, j, block, blocks = val.split("\n"), result = []; for(i = 0 ; i < blocks.length ; i++) { block = blocks[i].split("x"); if (block.length >= 2) result.push({w: parseInt(block[0]), h: parseInt(block[1]), num: (block.length == 2 ? 1 : parseInt(block[2])) }); } var expanded = []; for(i = 0 ; i < result.length ; i++) { for(j = 0 ; j < result[i].num ; j++) expanded.push({w: result[i].w, h: result[i].h, area: result[i].w * result[i].h}); } return expanded; }, serialize: function(blocks) { var i, block, str = ""; for(i = 0; i < blocks.length ; i++) { block = blocks[i]; str = str + block.w + "x" + block.h + (block.num > 1 ? "x" + block.num : "") + "\n"; } return str; } }, //--------------------------------------------------------------------------- colors: { pastel: [ "#FFF7A5", "#FFA5E0", "#A5B3FF", "#BFFFA5", "#FFCBA5" ], basic: [ "silver", "gray", "red", "maroon", "yellow", "olive", "lime", "green", "aqua", "teal", "blue", "navy", "fuchsia", "purple" ], gray: [ "#111", "#222", "#333", "#444", "#555", "#666", "#777", "#888", "#999", "#AAA", "#BBB", "#CCC", "#DDD", "#EEE" ], vintage: [ "#EFD279", "#95CBE9", "#024769", "#AFD775", "#2C5700", "#DE9D7F", "#7F9DDE", "#00572C", "#75D7AF", "#694702", "#E9CB95", "#79D2EF" ], solarized: [ "#b58900", "#cb4b16", "#dc322f", "#d33682", "#6c71c4", "#268bd2", "#2aa198", "#859900" ], none: [ "transparent" ] }, color: function(n) { var cols = Demo.colors[Demo.el.color.val()]; return cols[n % cols.length]; } //--------------------------------------------------------------------------- } $(Demo.init);