Empresas
Empregos
  • Sobre nós
  • Soluções
    • Publicação de vagas
      Publique sua vaga e receba candidatos qualificados em 48h.
    • Avaliações de candidatos
      Mais de 500 testes técnicos e psicológicos, mais anti-fraude.
    • Headhunting
      Busca executiva personalizada do início ao fim.
    • Folha de Pagamento + EOR
      Dispersão de folha e EOR em mais de 15 países da LATAM.
  • Preços
  • Empregos

0

285
Visualizações
How to calculate row- and colspans dynamically?

I'm using a table component from a UI framework that generates me the following headers

enter image description here

based on this configuration

(final result)

[
  [
    {
      "title": "Section 1",
      "rowspan": "4",
      "colspan": "1"
    },
    {
      "title": "Section 2",
      "rowspan": "1",
      "colspan": "2"
    },
    {
      "title": "Section 3",
      "rowspan": "1",
      "colspan": "3"
    },
    {
      "title": "Section 4",
      "rowspan": "1",
      "colspan": "1"
    }
  ],
  [
    {
      "title": "Section 2.1",
      "rowspan": "3",
      "colspan": "1"
    },
    {
      "title": "Section 2.2",
      "rowspan": "3",
      "colspan": "1"
    },
    {
      "title": "Section 3.1",
      "rowspan": "3",
      "colspan": "1"
    },
    {
      "title": "Section 3.2",
      "rowspan": "1",
      "colspan": "2"
    },
    {
      "title": "Section 4.1",
      "rowspan": "1",
      "colspan": "1"
    }
  ],
  [
    {
      "title": "Section 3.2.1",
      "rowspan": "2",
      "colspan": "1"
    },
    {
      "title": "Section 3.2.2",
      "rowspan": "2",
      "colspan": "1"
    },
    {
      "title": "Section 4.1.1",
      "rowspan": "1",
      "colspan": "1"
    }
  ],
  [
    {
      "title": "Section 4.1.1.1",
      "rowspan": "1",
      "colspan": "1"
    }
  ]
]

I want to simplify the configuration. There should be just a tree like structure and I want to calculate the row- and colspans dynamically

(starting point)

[
  {
    "title": "Section 1"
  },
  {
    "title": "Section 2",
    "children": [
      {
        "title": "Section 2.1"
      },
      {
        "title": "Section 2.2"
      }
    ]
  },
  {
    "title": "Section 3",
    "children": [
      {
        "title": "Section 3.1"
      },
      {
        "title": "Section 3.2",
        "children": [
          {
            "title": "Section 3.2.1"
          },
          {
            "title": "Section 3.2.2"
          }
        ]
      }
    ]
  },
  {
    "title": "Section 4",
    "children": [
      {
        "title": "Section 4.1",
        "children": [
          {
            "title": "Section 4.1.1",
            "children": [
              {
                "title": "Section 4.1.1.1"
              }
            ]
          }
        ]
      }
    ]
  }
]

I thought about how to map the starting point array to the final result array. These are my assumptions:

  • Traverse the array recursively
  • Calculate the colspan (width) like so
    • if there are no children => 1
    • if there are any children => sum of colspans from all children of the next depth
  • Calculate the rowspan (height)
    • if there are no children => maximum depth from the whole tree - current depth
    • if there are any children => 1

My current approach

const configuredHeaders = [{
    title: "Section 1"
},
{
    title: "Section 2",
    children: [{
            title: "Section 2.1"
        },
        {
            title: "Section 2.2"
        }
    ]
},
{
    title: "Section 3",
    children: [{
            title: "Section 3.1"
        },
        {
            title: "Section 3.2",
            children: [{
                    title: "Section 3.2.1"
                },
                {
                    title: "Section 3.2.2"
                }
            ]
        }
    ]
},
{
    title: "Section 4",
    children: [{
        title: "Section 4.1",
        children: [{
            title: "Section 4.1.1",
            children: [{
                title: "Section 4.1.1.1"
            }]
        }]
    }]
}];

const finalHeaders = [];

traverseColumnHeaders(configuredHeaders, -1); // first depth should start with 0

function traverseColumnHeaders(headers, parentDepth) {
    headers.forEach(header => {
        header.currentDepth = parentDepth + 1;
        
        if (header.children) {
            traverseColumnHeaders(header.children, header.currentDepth);
            
            header.colspan = header.children.reduce((currentSum, child) => currentSum + child.colspan, 0); // sum of child colspans
            header.rowspan = 1;
        } else {
            header.colspan = 1;

            // header.rowspan =     ... maximum depth - header.currentDepth ...
        }

        addHeaderToFinalHeaders(header);
    });
}

function addHeaderToFinalHeaders({ currentDepth, title, rowspan, colspan }) {
    const depthExistsInFinalHeaders = currentDepth in finalHeaders;
    
    if (!depthExistsInFinalHeaders) {
        finalHeaders[currentDepth] = [];
    }

    finalHeaders[currentDepth].push({ title, rowspan, colspan });
}

console.log(finalHeaders);

This one looks quite good to me because I only need to traverse the collection once. But as you can see some rowspan fields are not calculated yet because I don't know the maximum depth at this point.

To solve this I could initialize a global variable, search for the maximum depth like so

(I took the solution from here Getting the depth of a tree data structure in a simpler way for now)

function getMaximumDepth(headers) {
    return getDepth(headers) -1;
};

function getDepth(headers) {
    return 1 + Math.max(0, ...headers.map(({ children = [] }) => getDepth(children)));
}

const maximumDepth = getMaximumDepth(configuredHeaders);

and calculate

header.rowspan = maximumDepth - header.currentDepth

but now I have to traverse the structure twice. Is there a way to optimize my solution?

over 4 years ago · Santiago Trujillo
1 Respostas
Responde à pergunta

0

I believe there is no reasonable way to do what you want with only a single iteration. One of the premises of what you're trying to accomplish is that cells must span the rows to the depth of the tree. So that is knowledge that must exist as a prerequisite while traversing the array to transform it into the finalHeaders form.

As I get older I find that I'd rather optimize for code clarity rather than for some execution concern. Obviously I'm not ignoring efficiency as a concern, but even phones are exceeding fast at looping through small datasets. But I have to live with and maintain the code. I'll be cursing myself in 6 months if I write some obtuse code that is "efficient" and I have to spend 45 minutes trying to remember how it even works.

Besides, your goal to only traverse the structure once is already impossible. To determine colspan you are using reduce which is itself iterating over all the children. Plus any display code will be iterating over the data. So there's already a lot of iteration going on. I'd council against trying to come up with some clever way to accomplish this in a single iteration at the cost of code clarity.

The following is a working example. It is essentially what you've outlined in your question with maybe some small tweaks by me but no substantial changes to the logic:

const configuredHeaders = [{
    title: "Section 1"
  },
  {
    title: "Section 2",
    children: [{
        title: "Section 2.1"
      },
      {
        title: "Section 2.2"
      }
    ]
  },
  {
    title: "Section 3",
    children: [{
        title: "Section 3.1"
      },
      {
        title: "Section 3.2",
        children: [{
            title: "Section 3.2.1"
          },
          {
            title: "Section 3.2.2"
          }
        ]
      }
    ]
  },
  {
    title: "Section 4",
    children: [{
      title: "Section 4.1",
      children: [{
        title: "Section 4.1.1",
        children: [{
          title: "Section 4.1.1.1"
        }]
      }]
    }]
  }
];

const maximumDepth = getMaximumDepth(configuredHeaders);
const finalHeaders = [];

traverseColumnHeaders(configuredHeaders);

function traverseColumnHeaders(headers, depth = 0) {
  headers.forEach(header => {
    header.depth = depth;

    if (header.children) {
      traverseColumnHeaders(header.children, depth + 1);
      header.colspan = header.children.reduce((currentSum, child) => currentSum + child.colspan, 0); // sum of child colspans
      header.rowspan = 1;
    } else {
      header.colspan = 1;
      header.rowspan = maximumDepth - depth;
    }

    addHeaderToFinalHeaders(header);
  });
}

function addHeaderToFinalHeaders({depth, title, rowspan, colspan}) {
  if (!finalHeaders[depth]) finalHeaders[depth] = [];
  finalHeaders[depth].push({title, rowspan, colspan});
}

function getMaximumDepth(headers) {
  return getDepth(headers) - 1;

  function getDepth(headers) {
    return 1 + Math.max(0, ...headers.map(({children = []}) => getDepth(children)));
  }
}

//console.log(finalHeaders);
document.body.insertAdjacentHTML('beforeend', table(finalHeaders));

function table(headers) {
  return `
<table>
    <thead>
        ${headers.map(header => `
        <tr>${header.map(cell => `<th colspan="${cell.colspan}" rowspan="${cell.rowspan}">${cell.title}</th>`).join('')}</tr>
        `).join('')}
    </thead>
</table>`;
}
table, th {
  border: 1px solid black;
}

over 4 years ago · Santiago Trujillo Relatório
Responde à pergunta
Encontrar trabalhos remotos

Descubra a nova forma de encontrar um emprego!

melhores empregos
Principais categorias de trabalho
Empresas
Postar vaga Preços Comercial
Jurídico
Termos e Condições Política de privacidade
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomende algumas ofertas para mim
Preciso de ajuda