[Math] Algorithm to get the maximum size of n rectangles that fit into a rectangle with a given width and height

algorithms

I have the same problem like this guy here, except that I need to change the algorithm posted there to calculate rectangles instead of squares, because I use this to calculate a grid of icons (square images) but with text underneath them.

Is this possible?

I tried to implement his algorithm and just substract the height of the text, but that didn't worked.

PS: My implementation in JavaScript

// Used to subtract margin and border
var squareExtraWidth = $(this).outerWidth(true) - $(this).find('a > img').width();
// Used also to subtract the text height
var squareExtraHeight = $(this).outerHeight(true) - $(this).find('a > img').height();

var hw = containerHeight / containerWidth;
var wh = containerWidth / containerHeight;

var rows = Math.ceil(Math.sqrt(squareCount * hw));
var columns = Math.ceil(Math.sqrt(squareCount * wh));

var sx;
if (Math.floor(rows * wh) * rows < squareCount) {
  sx = containerWidth / Math.ceil(rows * wh);
}
else {
  sx = containerHeight / rows;
}
sx -= squareExtraHeight;

var sy;
if (Math.floor(columns * hw) * columns < squareCount) {
  sy = containerHeight / Math.ceil(columns * hw);
}
else {
  sy = containerWidth / columns;
}
sy -= squareExtraWidth;

var squareDimension = Math.floor(Math.max(sx, sy));

Best Answer

Here's a somewhat inelegant but correct solution to this. It works by trying all combinations of rows and columns

function getBestFit(containerWidth, containerHeight, numRects, aspectRatio) {
  let best = { area: 0, cols: 0, rows: 0, width: 0, height: 0 };

  // For each combination of rows + cols that can fit
  // the number of rectangles, place them and see the area.
  for (let cols = numRects; cols > 0; cols--) {
    const rows = Math.ceil(numRects / cols);
    const hScale = containerWidth / (cols * aspectRatio);
    const vScale = containerHeight / rows;
    let width;
    let height;

    // Determine which axis is the constraint.
    if (hScale <= vScale) {
      width = containerWidth / cols;
      height = width / aspectRatio;
    } else {
      height = containerHeight / rows;
      width = height * aspectRatio;
    }
    const area = width * height;
    if (area > best.area) {
      best = {area, width, height, rows, cols};
    }
  }
  return best;
}

```

If you're using npm, you can find it under rect-scaler.