Tips for enhancing the vertical-exclusive Masonry grid formula?

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);

Answer №1

Let's consider another method:

function calculateTotalHeight (elements) {
  return elements.reduce ((accumulator, {height}) => accumulator + height, 0);
}

function distributeGroups (groups, elements) {
  const sortedElements = elements.sort((a, b) => b.height - a.height);
  
  function recursiveDistribute([x, ...rest], [group, ...remainingGroups]) {
    if (x === undefined) {
      return [group, ...remainingGroups];
    } else {
      return recursiveDistribute(
        rest,
        [[...group, x], ...remainingGroups].sort((as, bs) => calculateTotalHeight(as) - calculateTotalHeight(bs))
      );
    }
  }

  return recursiveDistribute(sortedElements, Array(groups).fill().map(() => []));
}

const itemsList = [{id: 0, height: 100}, {id: 1, height: 200}, {id: 2, height: 250}, {id: 3, height: 110}, {id: 4, height: 50}, {id: 5, height: 160}, {id: 6, height: 70}, {id: 7, height: 90}];

console.log(distributeGroups(3, itemsList));
.as-console-wrapper {max-height: 100% !important; top: 0}

This solution prioritizes simplicity over optimizing efficiency. It solves the problem without delving into more complex algorithms like those for the Knapsack problem.

The process is straightforward. The calculateTotalHeight function adds up the heights of elements in an array, while the distributeGroups function takes the number of groups and the list of items to be distributed among them. It then calls the internal recursive function that sorts the elements by height and arranges them into the appropriate number of groups.

The recursion stops when there are no more items to distribute. Otherwise, it places the next item in the first group and continues recursively with the remaining items and updated group sizes.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the best way to eliminate the comma at the end of the final array

<% for(var i=0; i < 10; i++) { %> <a href="#"><%= related[i] %></a><% if(i !== 9) { %>, <% } %> <% } %> Displayed above is some code that includes a loop to display related items. The goal is to remove the comm ...

Encountering difficulty when trying to define the onComplete function in Conf.ts. A type error is occurring, stating that '(passed: any) => void' is not compatible with type '() => void'.ts(2322)'

I have been developing a custom Protractor - browserstack framework from the ground up. While implementing the onComplete function as outlined on the official site in conf.ts - // Code snippet to update test status on BrowserStack based on test assertion ...

Tips on how to exclude one key from validation and ensure that all other keys have a non-empty value

Currently, I am learning about array functions in JavaScript and have come across a solution that involves using Object.fromEntries. However, the dilemma is that in my Angular project, I am constrained by an outdated ES version which cannot be updated due ...

Can the value in a JavaScript object be updated dynamically when a button is clicked?

In my JavaScript code, there is an object named annualPlan. Whenever a user submits the HTML form for a specific month, I aim to update the value in the object for that particular month accordingly. For instance, if someone submits August 21 and 200, I w ...

Instructions for automatically sending SMS when there is a change in MySQL database data using PHP

Is it possible to trigger an SMS using Twillo as the gateway when there is a change in data in a MySQL database with PHP? ...

javascriptHow to specify the character set in a Data URI

In a UTF-8 page, I am implementing the following code: var data = "a\tb\tc\r\nd\te\tf"; window.location.href = "data:text/csv;charset=utf-8," + encodeURIComponent(data); This code is used to prompt the browser to download an ...

What is the best way to set the first option in a mat-select to be

My approach to date selection involves using 3 mat-select components for day, month, and year. You can view a demo of this setup here. In an attempt to improve the code, I decided to set the initial options as null by modifying the following lines: allDat ...

Snatching the lesson found within an iframe

Is it possible to obtain the id from an iframe using the following method? var iFrame = window.top.document.getElementById('window_<?php echo $_product->getId() ?>_content'); However, I am struggling to understand how to retrieve the c ...

I encounter difficulties when retrieving data in Next.js

Within a Next.js project, there is code provided that retrieves data from an external API endpoint and then passes it as props to a component called Services. This Services component utilizes the received data to dynamically render different sections of th ...

Can the Route be modified in Next.js?

I have developed search functionality that navigates to "/search/xxxx", but it is located in the header. Every time I perform a search, the route gets repeated and becomes "/search/search/xxx". Could you suggest any way other than usin ...

Eliminate JavaScript comments with Regex in PHP

Looking to reduce the size of HTML code with the help of PHP and Regex. Here is the minify function: public static function sanitize_output($buffer) { $search = array( '/ {2,}/', '/<!--.*?-->|\t|(?:\r?& ...

Gain access to PowerBI reports effortlessly without the need to input any credentials

Seeking guidance on calling Power BI reports from an ASP.NET C# web application while passing credentials, specifically without utilizing Azure AD. Access has been granted to certain users in the Power BI Service workspace with view permissions. We aim t ...

Steps to bring an image forward on a canvas to enable its onclick function

One of the challenges I'm facing involves an image of a pawn on a board. The image is set up with an onclick function that should trigger an alert when clicked. However, there is a canvas positioned above the image, which is interfering with the funct ...

Organize the array by property name and include a tally for each group

My current data structure looks like this: var data = [ { MainHeader: Header1, SubHeader: 'one'}, { MainHeader: Header1, SubHeader: 'two'}, { MainHeader: Header2, SubHeader: 'three'}, { MainHeader: Header2, SubHea ...

What is the best way to determine if a segment is crossing any line?

Within my code, I am managing a collection of lines stored in a std::list. Each line consists of 2 CPoint objects representing the start and end points. std::list<std::list<CPoint>> edges; I also have an additional line (comprised of 2 CPoint ...

The encodeURIComponent function does not provide an encoded URI as an output

Looking to develop a bookmarklet that adds the current page's URL to a specific pre-set URL. javascript:(function(){location.href='example.com/u='+encodeURIComponent(location.href)}()); Even though when I double encode the returned URL usin ...

The code in the head section is not running as expected

I've been exploring the possibilities of using lambda on AWS in combination with api gateway to create a contact form for a static S3 website, all inspired by this informative blog post: https://aws.amazon.com/blogs/architecture/create-dynamic-contact ...

The placement of term.js is always at the bottom of the body

Seeking help with JavaScript as a beginner, I am facing issues with placing an element in my project. Although I can easily open terminals and write to them, every time I create one, it gets appended at the end of the body. I referred to this example for ...

Nav Bar Toggle Button - Refuses to display the dropdown menus

My Django homepage features a Bootstrap navbar that, when resized, displays a toggle button. However, I am experiencing difficulty with dropdown functionality for the navbar items once the toggle button is activated. Can anyone provide guidance on how to a ...

Guide on placing stickers on an item in Three JS

Recently, I began experimenting with the three.js library and have a question about decals: I managed to create a sphere with a texture applied to it. Is there a way to overlay another texture on specific areas of the sphere without repeating the original ...