PgBiel / typst-tablex

More powerful and customizable tables in Typst
MIT License
370 stars 12 forks source link

`map-cols` overrides cell fill of `map-rows` #64

Closed lkoehl closed 12 months ago

lkoehl commented 12 months ago

I would like to color all cells in columns but the the header row should have a different color.

Sadly map-cols overrides any fill from map-rows. Maybe it would be possible to specify to order of execution, because in this case I would like map-rows to override map-cols.

#tablex(
  columns: (1fr,) * 9,
  align: center + horizon,
  auto-vlines: true,
  inset: 8pt,
  stroke: 0.5pt,
  header-rows: 1,
  (), (), (), vlinex(stroke: 2pt), (), (), vlinex(stroke: 2pt),
  map-cells: cell => {
    set text(11pt)
    strong(cell.content)
  },
  map-cols: (col, cells) => cells.map(c =>
    if c == none {
      c
    } else {
      (..c, fill: if calc.rem(col, 3) == 0 { blue.lighten(50%) } else { if calc.rem(col, 3) == 1 { green.lighten(50%)} else { red.lighten(50%) } })
    }
  ),
  map-rows: (row, cells) => cells.map(c =>
    if c == none {
      c
    } else {
      (..c, fill: if row == 0 { black.lighten(87%) })
    }
  ),

  [*Zeichen*], [*Dezimal*], [*Binär*],[*Zeichen*], [*Dezimal*], [*Binär*],[*Zeichen*], [*Dezimal*], [*Binär*],
  [a], [97], [1100001], [j], [106], [1101010], [s], [115], [1110011],
  [b], [98], [1100010], [k], [107], [1101011], [t], [116], [1110100], 
  [c], [99], [1100011], [l], [108], [1101100], [u], [117], [1110101], 
  [d], [100], [1100100], [m], [109], [1101101], [v], [118], [1110110], 
  [e], [101], [1100101], [n], [110], [1101110], [w], [119], [1110111], 
  [f], [102], [1100110], [o], [111], [1101111], [x], [120], [1111000], 
  [g], [103], [1100111], [p], [112], [1110000], [y], [121], [1111001], 
  [h], [104], [1101000], [q], [113], [1110001], [z], [122], [1110010], 
  [i], [105], [1101001], [r], [114], [1110010], [#text(8pt,[Leerzeichen])], [32], [0100000],  
)

SCR-20230919-rnhw

PgBiel commented 12 months ago

This isn't really an intended use-case for map-cols and map-rows, as you're changing fill based on info from both columns and rows. There are two main alternatives here:

  1. Using the tablex fill parameter as a function, as per the docs:

    fill: Color with which to fill cells' backgrounds. Defaults to none, or no fill. Must be either a color, such as blue; an array of colors (one for each column in the table - if there are more columns than colors, they will alternate); or a function (column, row) => color (to customize for each individual cell).

So, in your example, you'd have something like:

#import "@preview/tablex:0.0.5": tablex, vlinex

#set page(width: 400pt)
#tablex(
  columns: (1fr,) * 9,
  align: center + horizon,
  auto-vlines: true,
  inset: 8pt,
  stroke: 0.5pt,
  header-rows: 1,
  (), (), (), vlinex(stroke: 2pt), (), (), vlinex(stroke: 2pt),
  map-cells: cell => {
    set text(11pt)
    strong(cell.content)
  },

  fill: (col, row) => if row == 0 {
     black.lighten(87%)
  } else if calc.rem(col, 3) == 0 {
     blue.lighten(50%)
  } else if calc.rem(col, 3) == 1 {
     green.lighten(50%)
  } else {
     red.lighten(50%)
  },
  [*Zeichen*], [*Dezimal*], [*Binär*],[*Zeichen*], [*Dezimal*], [*Binär*],[*Zeichen*], [*Dezimal*], [*Binär*],
  [a], [97], [1100001], [j], [106], [1101010], [s], [115], [1110011],
  [b], [98], [1100010], [k], [107], [1101011], [t], [116], [1110100], 
)

output

  1. If you depend on more than just the cell's coordinates, you can use map-cells for this purpose as well. Here, it would look like this:
#import "@preview/tablex:0.0.5": tablex, vlinex

#set page(width: 400pt)
#tablex(
  columns: (1fr,) * 9,
  align: center + horizon,
  auto-vlines: true,
  inset: 8pt,
  stroke: 0.5pt,
  header-rows: 1,
  (), (), (), vlinex(stroke: 2pt), (), (), vlinex(stroke: 2pt),
  map-cells: cell => {
    // !!! dont discard the cell !!!
    // override its content attribute
    // instead of returning the content,
    // to ensure rowspans colspans etc are kept
    cell.content = {
      set text(11pt)
      strong(cell.content)
    }
    let row = cell.y
    let col = cell.x
    cell.fill = if row == 0 {
       black.lighten(87%)
    } else if calc.rem(col, 3) == 0 {
       blue.lighten(50%)
    } else if calc.rem(col, 3) == 1 {
       green.lighten(50%)
    } else {
       red.lighten(50%)
    }

    cell
  },
  [*Zeichen*], [*Dezimal*], [*Binär*],[*Zeichen*], [*Dezimal*], [*Binär*],[*Zeichen*], [*Dezimal*], [*Binär*],
  [a], [97], [1100001], [j], [106], [1101010], [s], [115], [1110011],
  [b], [98], [1100010], [k], [107], [1101011], [t], [116], [1110100], 
)

output

PgBiel commented 12 months ago

Of course, if we find a more concrete example where controlling the order of map cols and map rows is actually needed, it could be considered, but in this case I think it'd be unnecessary. I'll be closing this issue as I think this is solved, but feel free to say if you'd like further explanation about something.

Also btw, I suggest not mapping cells to content, as you'd lose any properties you may override for individual cells. That is, don't return strong(cell.content) on map-cells, but rather do cell.content = strong(cell.content) (or, in this case, cell.content = { set text(11pt); strong(cell.content); } to include the set rule as well) and then return cell, so that other cell properties aren't discarded, which could lead to very hard to debug errors (e.g. trying to use a rowspanx or colspanx but it not working because rowspan/colspan properties were discarded on cell mapping).