Default result
#let dat = csv("test.csv")
#repr(dat)
#set table(align: center + horizon)
#table(columns: (3cm,) * 3, ..dat.flatten())
First attempt:
#let dat = csv("test.csv")
#set table(align: center + horizon)
#{
// show table.cell.where(y: 0, x: 1): set table.cell(colspan: 2)
// show table.cell.where(y: 2, x: 0): set table.cell(rowspan: 2)
let colspan-cells = ((0, 1),) // (y, x)
let rowspan-cells = ((2, 0),) // (y, x)
let data = dat
for ((y, x)) in colspan-cells {
data.at(y) = {
let row = data.at(y)
let _ = row.remove(x + 1)
row
}
}
for ((y, x)) in rowspan-cells {
data.at(y + 1) = {
let row = data.at(y + 1)
let _ = row.remove(x)
row
}
}
let bonus-coloring = (fill: green.transparentize(70%))
table(columns: (3cm,) * 3, ..data
.enumerate()
.map(((y, row)) => row.enumerate().map(((x, cell)) =>
if x == 1 and y == 0 {
table.cell(colspan: 2, cell, ..bonus-coloring)
} else if x == 0 and y == 2 {
table.cell(rowspan: 2, cell, ..bonus-coloring)
} else {
cell
}))
.flatten())
}
Second attempt:
#let dat = csv("test.csv")
#set table(align: center + horizon)
/// Removes elements which will be overwritten by a `colspan`/`rowspan`.
/// * `data` a 2D array of future cell content elements
/// * `colspan-cells` an array of tuples `(y, x, length)` describing each colspan cell
/// * `rowspan-cells` an array of tuples `(y, x, length)` describing each rowspan cell
/// Returns a prepared 2D `array`.
///
/// Note that length of each row can differ.
#let prepare-data(data, colspan-cells, rowspan-cells) = {
for ((y, x, length)) in colspan-cells {
data.at(y) = {
let row = data.at(y)
for i in range(length - 1) {
assert(
(x + 1) < row.len(),
message: "Column span out of bounds: " + repr((y, x, length)),
)
let _ = row.remove(x + 1)
}
row
}
}
for ((y, x, length)) in rowspan-cells {
for i in range(length - 1) {
data.at(y + 1 + i) = {
assert(
(y + 1 + i) < data.len(),
message: "Row span out of bounds: " + repr((y, x, length)),
)
let row = data.at(y + 1 + i)
let _ = row.remove(x)
row
}
}
}
data
}
/// Adds `colspan`/`rowspan` field to specified cells.
/// * `cells` a 2D array of cells (content)
/// * `colspan-cells` an array of tuples `(y, x, length)` describing each colspan cell
/// * `rowspan-cells` an array of tuples `(y, x, length)` describing each rowspan cell
/// Returns flatten `array` of patched cells.
///
/// Note that this is not tested for when cell already wrapped in `cell()`.
#let patch-span-cells(cells, colspan-cells, rowspan-cells) = {
let fill = (fill: green.transparentize(70%))
for ((y, x, length)) in colspan-cells {
cells.at(y).at(x) = table.cell(colspan: length, cells.at(y).at(x), ..fill)
}
for ((y, x, length)) in rowspan-cells {
cells.at(y).at(x) = table.cell(rowspan: length, cells.at(y).at(x), ..fill)
}
cells.flatten()
}
#{
// show table.cell.where(y: 0, x: 1): set table.cell(colspan: 2)
// show table.cell.where(y: 2, x: 0): set table.cell(rowspan: 2)
let colspan-cells = ((0, 1, 2),) // (y, x, length), min_length = 2 (no-op for "< 2")
let rowspan-cells = ((2, 0, 2),) // (y, x, length), min_length = 2 (no-op for "< 2")
let data = prepare-data(dat, colspan-cells, rowspan-cells)
table(
columns: (3cm,) * 3,
..patch-span-cells(data, colspan-cells, rowspan-cells),
)
}
You can merge or rewrite the 2 helper functions however you see fit. If you don’t need to do anything in between, then you don’t really need two of them. But they can also be easily merged by making a 3rd wrapper function:
#let patch-spans(data, colspan-cells, rowspan-cells) = {
patch-span-cells(
prepare-data(dat, colspan-cells, rowspan-cells),
colspan-cells,
rowspan-cells,
)
}
#table(
// A small bonus (can only use (the initial) unmodified
// 2D array here).
columns: dat.at(0).len(),
..patch-spans(dat, colspan-cells, rowspan-cells),
)