How to display the header line rotated by 45 degrees like in Excel?

Hi everyone,

I don’t know if that’s possible (yet) in typst but I’m trying to make a template for my students grades and I’d like to have the headers rotated by 45 degrees (like in Excel) to have a render like that:

I can rotate the text but I have hard time have the same “Excel look”. Here’s my code :

#table(
  columns: 4,
  table.cell(inset:15pt)[#rotate(-45deg)[Students]],table.cell(inset:15pt)[#rotate(-45deg)[Maths]],table.cell(inset:15pt)[#rotate(-45deg)[Physics]],table.cell(inset:15pt)[#rotate(-45deg)[French]],
  [Name 1],[],[],[],
  [Name 2],[],[],[],
  [Name 3],[],[],[],
)

Anyone might have an idea or actually already made it before ? Or is there any package I could use to have that render?

Thanks for your help!

I’m not sure if a table should ever look like that. :sweat_smile:

But if you really like (or need) it, here you go:
#let header-cell-frame(angle, height, width, stroke) = polygon(
  stroke: stroke,
  (height * calc.tan(angle - 90deg), 0pt),
  (0pt, 100%),
  (width, 100%),
  (height * calc.tan(angle - 90deg) + width, 0pt),
)

#let header-cell-body(angle, body) = rotate(
  angle,
  reflow: true,
  place(horizon + left, dx: 1em, body)
)

#let header-cell(angle, height, width, stroke, body) = table.cell(
  inset: 0pt,
  block(
    height: height,
    width: width,
    {
      place(bottom + left, header-cell-frame(angle, height, width, stroke))
      place(bottom + left, dx: width / 2, header-cell-body(angle, body))
    }
  )
)

#let show-rotated-header(angle, height, columns: auto) = it => context {
  if "label" in it.fields() and it.label == <rotated-header> { return it }
  let (header, ..children) = it.children
  let column-widths = if columns == auto {
    header.children.map(cell => measure(cell).width + 2 * it.inset)
  } else {
    columns
  }

  [
    #table(
      columns: column-widths,
      stroke: (x, y) => if y == 0 { none } else { it.stroke },
      table.header(
        ..column-widths.zip(header.children).map(
          ((width, child)) => header-cell(angle, height, width, it.stroke, child.body)
        )
      ),
      ..children
    ) <rotated-header>
  ]
}

#show table: show-rotated-header(-45deg, 2cm)

#table(
  columns: 4,
  table.header([*Students*], "Maths", "Physics", "French"),
  [Name 1],[],[],[],
  [Name 2],[],[],[],
  [Name 3],[],[],[],
)
The table will look like this

How does this work?

The rule show-rotated-header() will recreate the table with a new header. The label <rotated-header> is used to ensure that the show rule is only applied once.

By default the new table will use the column widths of the initial table without the rotated header. You can however also specify the column widths manually. The stroke in the header row is hidden since these cells will get a custom frame.

Each header cell is composed of a polygon to draw the angled frame, and the initial body of the cell rotated by the same angle. The polygons use the same stroke as the table.

Possible changes/improvements

  • Find the maximum width in each column for the automatic column-widths. Right now the widths of the cells in the initial header are used.
  • Set the height to the maximum height of the rotated header cells.
  • Transfer all fields from the initial table to the new table. Right now only columns and stroke are inherited.
  • Implement this as a function instead of a show rule if you don’t want to style every table in your document like this. If you need to customize the column widths for each table separately, this will definitely be the cleaner solution.
3 Likes

Wow thank you so much for your quick reply, It’s exactly what I needed!

It works like a charm and I also like how you described it.

And I’ll follow your advice, I’ll create a function instead of a rule because I’ll need different kind of tables for my document.