Engineering Journal
Table Formatter
Table Formatter

Transpose as Linear Algebra: What Actually Happens When You Flip a Table

2026-05-11

TLDR

Flipping an HTML table matrix ($(i, j) \rightarrow (j, i)$) fails when cells contain colspan or rowspan attributes because DOM nodes visually span multiple spatial coordinates. Swapping span attributes (newRowspan = oldColspan) and tracking placed origins via a visited Set prevents duplicate element insertions during transposition.
Transposition StageSpatial TransformationSpan Value HandlingUnique Insertion Guard
Naive Matrix SwapSwaps indices $(r, c) \rightarrow (c, r)$Leaves colspan/rowspan intactDuplicates/corrupts spanned cells
Visual Grid TransposeMapped Coordinate SwapSwaps rowspan $\leftrightarrow$ colspan100% Guarded via visited Set

Transposing raw DOM nodes breaks layouts because merged spans occupy multiple coordinate slots

In linear algebra, transposing a matrix maps every element at position $(i, j)$ to $(j, i)$. In flat 2D arrays, this is accomplished with nested loops.

HTML tables with merged cells break naive loops. A cell with colspan="3" occupies one DOM node but fills three visual coordinate slots. Transposing the array directly places duplicate DOM nodes across multiple rows or drops spanned positions altogether.


Swapping colspans with rowspans and filtering duplicates via a visited set preserves structural integrity

We resolved merged cell transposition by mapping visual coordinates first, swapping span dimensions, and tracking inserted coordinates:

   ORIGINAL MATRIX WITH SPANS (3x3 Grid):
   ┌───────────┬───────┐
   │ 0,0       │ 0,2   │  <-- 0,0 has Colspan=2
   ├───────┬───┴───────┤
   │ 1,0   │ 1,1       │  <-- 1,1 has Rowspan=2
   ├───────┤           │
   │ 2,0   │           │
   └───────┴───────────┘

TRANSPOSED MATRIX SWAPPING ROWSPAN/COLSPAN: ┌───────┬───────┬───────┐ │ 0,0 │ 0,1 │ 0,2 │ <-- 0,0 now has Rowspan=2 │ ├───────┼───────┤ │ │ 1,1 │ │ <-- 1,1 now has Colspan=2 ├───────┤ │ │ │ 2,0 └───────┴───────┘ └───────────────────────┘

Here is the transposition logic accounting for spanned cells:

// Transpose visual grid matrix while swapping span parameters
export function transposeTableWithMapper(mapper) {
  const transposedGrid = [];

for (let c = 0; c < mapper.maxCols; c++) { transposedGrid[c] = []; for (let r = 0; r < mapper.maxRows; r++) { transposedGrid[c][r] = mapper.grid[r]?.[c] || null; } }

const visitedCoordinates = new Set(); const resultRows = [];

transposedGrid.forEach((row, rowIndex) => { const rowCells = []; row.forEach((cellData, colIndex) => { const coordKey = ${rowIndex},${colIndex}; if (visitedCoordinates.has(coordKey) || !cellData?.isOrigin) return;

// Swap span attributes: original colspan becomes new rowspan const newRowspan = cellData.colspan; const newColspan = cellData.rowspan;

// Mark occupied spanned coordinates as visited for (let r = 0; r < newRowspan; r++) { for (let c = 0; c < newColspan; c++) { visitedCoordinates.add(${rowIndex + r},${colIndex + c}); } }

rowCells.push({ element: cellData.element, newRowspan, newColspan }); }); resultRows.push(rowCells); });

return resultRows; }

Swapping span values ensures horizontal header spans transform into vertical side spans, keeping the table structure intact after matrix inversion.

Rule of thumb: Swap colspan and rowspan attributes during table transposition to maintain spatial proportions.
Read this post in the full Engineering Journal →