I have successfully implemented the core algorithm for the Vertical-only Masonry grid. This algorithm takes an array of items data and creates a required number of columns, where items are sorted to efficiently utilize the available space. Each column should ideally have a total sum of the height of the nested items that is as close as possible to the other columns.
Let's consider an example:
The source array of all unsorted items
// represents an array of data that will be used as a base to render UI elements
const items = [{
"id": 0,
"height": 100
},
{
"id": 1,
"height": 200
},
...
]
The goal is to receive a specific number of columns with each containing sorted items by height to utilize the most available space effectively. Here is how it would look like:
[
[
{
"id": 0,
"height": 100
},
...
],
...
]
The columns should have a total sum of heights as closely similar as possible: 1 col = 370 2 column = 340 3 column = 320
I have already implemented a solution but I am open to any suggestions or examples on how to improve it. You can find the full source code in this JSFiddle link.
Your ideas on enhancing this algorithm would be greatly appreciated!
// need to create X arrays sorted by height. Each array should contain ~equal height as much as possible
const requiredArrays = 3;
// represents an array of data that will be used as a base to render UI elements
const items = [{
"id": 0,
"height": 100
},
...
]
const cols = Array.from({
length: requiredArrays
}, () => []);
// it sorts the columns by least empty or smallest sum height and inserts items to optimize space utilization
function sorter(item) {
let lowest = Number.POSITIVE_INFINITY;
let highest = Number.NEGATIVE_INFINITY;
let tmp;
// the column where sum of its items is the lowest
let mostEmptyCol;
const colsDataWithTotalH = [];
cols.forEach(col => {
const totalH = col.reduce((acc, o) => acc + o.height, 0);
// calculates the items sum of the single columns
colsDataWithTotalH.push({
col: col,
totalH: totalH
})
})
// looking for the least empty column by height
for (var i = colsDataWithTotalH.length - 1; i >= 0; i--) {
const currentCoItem = colsDataWithTotalH[i];
tmp = currentCoItem.totalH;
if (tmp < lowest) {
lowest = tmp;
// lets assign the Col array into this var to use it in future
mostEmptyCol = currentCoItem.col;
};
if (tmp > highest) highest = tmp;
}
// fill the least empty column
mostEmptyCol.push(item)
}
items.forEach(item => {
const col = cols.find(o => {
return !o.length;
});
// at the start columns are empty so we should just push items into them
if (col) {
col.push(item);
} else {
// the columns contain the items so we need to run the sorter algorhytm
sorter(item);
}
});
console.log('Result', cols);