Question

I have an array of 50 objects, each width a different width and height. My objective is to create panels to hold this objects and keep them in order, but this panel cannot exceed a width of 300 pixels or a height of 600 pixels, so I have to fit as many objects as I can within this panel and if they don't fit then I need to create a new panel until there are no more items to append.
So for example if I have an item with the dimensions of 150x75, then I could only place another object of the same dimensions, and if the next item does not have this dimensions then I create an empty object to take up these empty space. An example can be found at Codepen.io Now this works as long as the height of each item is 75px, but the problem arises when the height exceeds this. I have been trying to solve this problem for a few days now, but this particular task seems to throw of the entire design. Here is my logic to check if the item fits:

layout.prototype.createPanel = function(){
//create a new panel
var panel = '<div style="float:left;margin:5px;border:2px solid black;min-width:'+this.WIDTH+'px;min-height:'+this.HEIGHT+'px;">';
var h = 0;
//as long as the panel height does not exceed the constant height
while(h < this.HEIGHT){
    //this.commands += h+'<br>';
    var w = 0;
    panel += '<div>'; //create a new row
    //as long as the panel width does not exceed the constant width
    while(w < this.WIDTH){
        var el = this.items[0]; //grab the first item for checking purposes
        var fits = false;
        //if the current column is the first one, then just check if the item fits vertically
        if(w < this.int_width){
            //as long as the item's height plus the current height of this panel is less than the static height, then it fits
            fits = h + el.height <= this.HEIGHT ? true : false;
        }
        //else check if it fits horizontal and vertically
        else{
            if(this.fitsHorizontal(w,el.width) && h + el.height <= this.HEIGHT){
                fits = true;
            }
            else{
                fits = false;
            }
        }
        //if the item fits, remove it from the array and append it to the panel
        if(fits){
            el = this.items.shift();
            panel += el.html;
            w+= el.width;   //update the width
            this.desired_height = el.height;    //update the new desired height     

            //if this shifted item was the last one then terminate execution to avoid any infinite loops
            if(this.items.length == 0){
                this.has_items = false;
                break;
            }
        }
        //else create an empty element to fill up the space needed
        else{
            panel += '<div style="float:left;width:'+(this.WIDTH - w)+'px;height:'+this.desired_height+'px;background-color:#ccc;margin:1px;"></div>';
            el.height = this.int_height;
            break;
        }
    }
    panel += '</div>'; //close the row

    //if this shifted item was the last one then terminate execution to avoid any infinite loops
    if(!this.has_items){
        this.has_items = false;
        break;
    }
    h+=this.desired_height;
}
panel += '</div>'; //close the panel
return panel;
}
layout.prototype.fitsHorizontal = function(current_width,item_width){
//if the current width plus the item width is less than the constant width them the item fits horizontal
if(current_width + item_width <= this.WIDTH){
    return true;
}
return false;
}
layout.prototype.fitsVertical = function(current_height,item_height){
if(current_height + item_height <= this.HEIGHT && item_height == this.desired_height){
    return true;
}
return false;
}

Is there anything wrong with the logic? I now I could solve it if I reorder the items, but they have to stay in order. The full code can be found ad Codepen.io

Was it helpful?

Solution

UPDATE

About your code, I can't understand what is the problem because it is not clear. I would like to give you some tips:
-When you need to work with someone (like here if you need help, or in a project, etc), you should share the same identical things that you know.
-In this case, where the language is not a specification, you should abstract the code as much as you can, pseudo-code is a good begin.
-you can describes the problems you met, from some example but also from images and schemes.
-If you add a specific codification to represent the problem you should respect that codification.

It is not really clear how your code works because it seems an hybrid between js and a pseudo-code. For what I can understand, the first logic error it seems to be related to the space division (rows columns): for example if you have 4 elements 1x1 (rows x columns) they will fill 1 row but, if you have 1 element 1x1,1 element 2x1 (rows x columns), 1 element 1x1 (rows x columns) and another element 2x1 (rows x columns), they complete a row, but you will also find some pieces in the row below, and you doesn't seems to registers this kind of behaviour in your code.

eg

I hope to have understood well, here the jsfiddle and here the code:

<!DOCTYPE html>
<head>
<style>
.pad div{
    margin:1px;
}
.pad{
    background-color:#FFF;
    border:#000 1px solid;
}
</style>
<script src="http://code.jquery.com/jquery-1.9.1.js"></script>
<script src="http://code.jquery.com/ui/1.10.3/jquery-ui.js"></script>
<body>
<script>
var pieceN=50;
var pieces = [];
const multip = 75;
var actualmaxheight=0;
const h=600;
const w=300;
const maxrows = 5;
const maxcols = 4;
var id="";
var actualcontent=0;
$('body').append('<div id="content'+actualcontent+'" class="pad" style="'+
                        'height:'+(h+4)+'px;width:'+(w+4)+'px"></div>');
id='content'+actualcontent;
actualcontent++;
//creating objects n times
for(var i=0; i<(pieceN); i++){
    color=(Math.floor(Math.random()*16777215)).toString(16);
    if(color.length==5){ color=color+'0'};
    if(color.length==4){ color=color+'00'};
    //for faster thing I'll use an already sorted array
    eit = Math.floor((Math.random()*maxrows)+1);
    wid = Math.floor((Math.random()*maxcols)+1);
    pieces.push({
        'height': eit*multip,
        'width': wid*multip,
        'rows': eit,
        'cols': wid,
        'color':'#'+color
    });
}
var copy=pieces;
var i =0;
//while i is less then the number of the objects -1
//the last element will be inserted afther this cycle
//it can be also inserted in the cylce, anyway it's only 1 loop
while(i<pieces.length-1){
    //if there is enough height for the element in the container
    if(h-actualmaxheight>=parseInt(pieces[i].height)){ 
        //the width fill te container width (insert)
        if(parseInt(pieces[i].width)==w){
            document.getElementById(id).innerHTML+=
                '<div id="'+pieces.length+'" style="'+
                'width:'+pieces[i].width+'px;'+
                'height:'+pieces[i].height+'px;'+
                'background-color:'+pieces[i].color+';'+
                'float:left"></div>';
            actualmaxheight+=parseInt(pieces[i].height);
        }else{//if the width don't fill te container
          //if this elem+nextone width fill the container width (insert x 2)
          if(((parseInt(pieces[i].width)+parseInt(pieces[i+1].width))==w) && (parseInt(pieces[i].height)==parseInt(pieces[i+1].height))){
              document.getElementById(id).innerHTML+=
                  '<div id="'+i+'" style="'+
                  'width:'+pieces[i].width+'px;'+
                  'height:'+pieces[i].height+'px;'+
                  'background-color:'+pieces[i].color+';'+
                  'float:left"></div>'+
                  '<div id="'+(i+1)+'" style="'+
                  'width:'+pieces[i+1].width+'px;'+
                  'height:'+pieces[i+1].height+'px;'+
                  'background-color:'+pieces[i+1].color+';'+
                  'float:left"></div>';
              actualmaxheight+=parseInt(pieces[i].height);
              i++;//because 2 inserted
          }else{
              //else we insert 1 elem + 1 filling element
              document.getElementById(id).innerHTML+=
                  '<div id="'+i+'" style="'+
                  'width:'+pieces[i].width+'px;'+
                  'height:'+pieces[i].height+'px;'+
                  'background-color:'+pieces[i].color+';'+
                  'float:left"></div>'+
                  '<div id="'+(pieces.length+100)+'" style="'+
                  'width:'+(w-pieces[i].width)+'px;'+
                  'height:'+pieces[i].height+'px;'+
                  'background-color:'+pieces[i].color+';'+
                  'float:left"></div>';
              actualmaxheight+=parseInt(pieces[i].height);
          }
        }
    i++;//we surely inserted an elem
    }else{
        //else the container have not enough height for this elem
        //fill the height with a black block and don't increase i
        //because we inserted nothing
        document.getElementById(id).innerHTML+=
            '<div id="'+(pieces.length+1000)+'" style="'+
            'width:'+w+'px;'+
            'height:'+(h-actualmaxheight)+'px;'+
            'background-color:#000000;'+
            'float:left;color:#FFFFFF">here to fill H</div>';
        $('body').append('<div id="content'+actualcontent+'" class="pad" style="'+
                        'height:'+(h+6+actualcontent*1.2)+'px;width:'+(w+4)+'px;color:#FFFFFF"></div>');
        id='content'+actualcontent++; //id of the container updated
        actualmaxheight=0;//reset height
    }
}
//insert last element
i=pieces.length-1;//get its array index
if(h-actualmaxheight>=pieces[i].height){//there is enough height in this container
    //the elem fill the width
    if(parseInt(pieces[i].width)==w){
      actualmaxheight+=pieces[i].height;
      document.getElementById(id).innerHTML+=
          '<div id="'+pieces.length+'" style="'+
          'width:'+pieces[i].width+'px;'+
          'height:'+pieces[i].height+'px;'+
          'background-color:'+pieces[i].color+';'+
          'float:left"></div>'+
          '<div id="'+(pieces.length+1000)+'" style="'+
          'width:'+w+'px;'+
          'height:'+(h-actualmaxheight)+'px;'+
          'background-color:#000000;color:#FFFFFF;'+
          'float:left">here to fill H</div>';
    }else{ //the elem don't fill the width
      actualmaxheight+=pieces[i].height;
      document.getElementById(id).innerHTML+=
          '<div id="'+pieces.length+'" style="'+
          'width:'+pieces[i].width+'px;'+
          'height:'+pieces[i].height+'px;'+
          'background-color:'+pieces[i].color+';'+
          'float:left"></div>'+
          '<div id="'+(pieces.length+10000)+'" style="'+
          'width:'+(w-pieces[i].width)+'px;'+
          'height:'+pieces[i].height+'px;'+
          'background-color:'+pieces[i].color+';'+
          'float:left"></div>'+
          '<div id="'+(pieces.length+1000)+'" style="'+
          'width:'+w+'px;'+
          'height:'+(h-actualmaxheight)+'px;'+
          'background-color:#000000;color:#FFFFFF;'+
          'float:left">here to fill H</div>';
    }
}else{//there is not enough height in this container
    if(parseInt(pieces[i].width)==w){//the elem fill the width
      //fill the height with a black block
      document.getElementById(id).innerHTML+=
          '<div id="'+(pieces.length+1000)+'" style="'+
          'width:'+pieces[i].width+'px;'+
          'height:'+(h-actualmaxheight)+'px;'+
          'background-color:#000000;color:#FFFFFF;'+
          'float:left">here to fill H</div>';
      //create another container and add the elem + black block to fill the height
      actualmaxheight=parseInt(pieces[i].height);
          $('body').append('<div id="content'+actualcontent+'" class="pad" style="'+
          'height:'+(h+4)+'px;width:'+(w+4)+'px">'+
          '<div id="'+pieces.length+'" style="'+
          'width:'+pieces[i].width+'px;'+
          'height:'+pieces[i].height+'px;'+
          'background-color:'+pieces[i].color+';'+
          'float:left"></div>'+
          '<div id="'+(pieces.length+1000)+'" style="'+
          'width:'+w+'px;'+
          'height:'+(h-actualmaxheight)+'px;'+
          'background-color:#000000;color:#FFFFFF;'+
          'float:left">here to fill H</div>'+
          '</div>');
    }else{//the elem don't fill the width
      //fill the height with a black block to fill the height
      document.getElementById(id).innerHTML+=
          '<div id="'+(pieces.length+1000)+'" style="'+
          'width:'+w+'px;'+
          'height:'+(h-actualmaxheight)+'px;'+
          'background-color:#000000;color:#FFFFFF;'+
          'float:left">here to fill H</div>';
          actualmaxheight=parseInt(pieces[i].height);
      //create another container and add the elem + 1 elem to fill the width 
      //+ 1 black block to fill the height
      $('body').append('<div id="content'+actualcontent+'" class="pad" style="'+
          'height:'+(h+4)+'px;width:'+(w+4)+'px">'+
          '<div id="'+pieces.length+'" style="'+
          'width:'+pieces[i].width+'px;'+
          'height:'+pieces[i].height+'px;'+
          'background-color:'+pieces[i].color+';'+
          'float:left"></div>'+
          '<div id="'+(pieces.length+100)+'" style="'+
          'width:'+(w-pieces[i].width)+'px;'+
          'height:'+pieces[i].height+'px;'+
          'background-color:'+pieces[i].color+';'+
          'float:left"></div>'+
          '<div id="'+(pieces.length+1000)+'" style="'+
          'width:'+w+'px;'+
          'height:'+(h-actualmaxheight)+'px;'+
          'background-color:#000000;color:#FFFFFF;'+
          'float:left">here to fill H</div>'+
          '</div>');
    }
}
/*
//print the elments
for(var i=0; i<(copy.length); i++){
    $('body').append('<div id="el'+i+'"'+ 
        'style="width:'+copy[i].width+'px;'+
        'height:'+copy[i].height+'px;'+
        'background-color:'+copy[i].color+';float:left"></div>'
    );
}
*/
</script>
</body>
</html>

I would like to clear some points

  1. Does the object dimensions are multiple of something or they are just random?
  2. Where you need to add a filling piece, near every pieces for which you can't find another piece with same height and 300 as sum of widths?
  3. an object can be fit with many object or less depending on how many object you want to search, this clearly increase the time needed.

Anyway to present something, I wrote this algorithm (jsfiddle) that can or cannot be useful, depending on what are you searching :

<!DOCTYPE html>
<head>
<style>
.pad div{
    margin:1px;
}
.pad{
    background-color:#FFF;
    border:#000 1px solid;
}
</style>
<script src="http://code.jquery.com/jquery-1.9.1.js"></script>
<script src="http://code.jquery.com/ui/1.10.3/jquery-ui.js"></script>
<body>
<script>
var pieceN=50;
var pieces = [];
var actualmaxheight=0;
var h=600;
var w=300;
var flag=0;

$('body').append('<div id="content0" class="pad" style="'+
                        'height:'+(h+4)+'px;width:'+(w+4)+'px"></div>');
//creating objects n times
for(var i=0; i<(pieceN); i++){
    color=(Math.floor(Math.random()*16777215)).toString(16);
    if(color.length==5){ color=color+'0'};
    //for faster thing I'll use an already sorted array
    pieces.push({
        'height': Math.floor((Math.random()*600)+1),
        'width':Math.floor((Math.random()*150)+1),
        'color':'#'+color
    });
}
var copy=pieces;
id='content0';
//for all the elements aren't placed(i remove every placed element)
for(i=0;i<pieces.length;i++){
    flag=0;
    k=0;
    //while k<pieces.length confront every piece[k] with piece[i]
    while(k<pieces.length){
        //if exist 2 piece with sum of width==w(300), the same height and the actual container have enough height, let's add them 
        if(parseInt(pieces[i].width)+parseInt(pieces[k].width)==w && pieces[i].height==pieces[k].height && ((h-actualmaxheight)>parseInt(pieces[i].height))){
            document.getElementById(id).innerHTML+='<div id="e'+(i+k)+'"'+ 
            'style="width:'+pieces[i].width+'px;'+
            'height:'+pieces[i].height+'px;'+
            'background-color:'+pieces[i].color+';float:left"></div>'+
            '<div id="e'+(k+i)+'2"'+ 
            'style="width:'+pieces[k].width+'px;'+
            'height:'+pieces[k].height+'px;'+
            'background-color:'+pieces[k].color+';float:left"></div>';
            actualmaxheight+=parseInt(pieces[i].height);
            //remove them from the array
            pieces = $.grep( pieces, function(n,index){
                return index != k;
            });
            pieces = $.grep( pieces, function(n,index){
                return index != i;
            });
            flag=1;
        }
        k++;
    }
    //if we inserted 0 elements in the while before, we have 2 reason
    //1) there aren't 2 elements which satisfy the rules
    //in this case we add the element with an element created to fill the empty space orizontal
    if(flag==0){
        for(j=0;j<pieces.length;j++){
            if((h-actualmaxheight)>parseInt(pieces[j].height)){
                document.getElementById(id).innerHTML+='<div id="e'+i+'"'+ 
                'style="width:'+pieces[j].width+'px;'+
                'height:'+pieces[j].height+'px;'+
                'background-color:'+pieces[j].color+';float:left"></div>'+
                '<div id="e'+i+'2"'+ 
                'style="width:'+(w-parseInt(pieces[j].width))+'px;'+
                'height:'+pieces[j].height+'px;'+
                'background-color:'+pieces[j].color+';float:left"></div>';
                actualmaxheight+=parseInt(pieces[j].height);
                pieces = $.grep( pieces, function(n,index){
                    return index != j;
                });
            }
        }
    }
    //2) there isn't an element which can be conained in the space remained in the container
    //in this case we add a black stripe to fill the space vertical
    if(actualmaxheight<h){
        document.getElementById(id).innerHTML+='<div id="e'+j+i*k+'"'+ 
        'style="width:300px;'+
        'height:'+(h-actualmaxheight)+'px;'+
        'background-color:#000000;float:left;color:#FFFFFF">i fill the space</div>';                
    }
    //then we create a new container to repeat until all the elements are placed
    $('body').append('<div class="pad" id="content'+j+'"'+
        'style="margin-top:10px;'+
        'height:'+(h+4)+'px;width:'+(w+4)+'px"></div>');
    id='content'+j;
    actualmaxheight=0;
    i=0;
}
//print the elments
for(var i=0; i<(copy.length); i++){
    $('body').append('<div id="el'+i+'"'+ 
        'style="width:'+copy[i].width+'px;'+
        'height:'+copy[i].height+'px;'+
        'background-color:'+copy[i].color+';float:left"></div>'
    );
}
</script>
</body>
</html>
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top