Spaces:
Sleeping
Sleeping
description: | | |
Not sure how to change table strokes? Need to rotate a table? This guide | |
explains all you need to know about tables in Typst. | |
# Table guide | |
Tables are a great way to present data to your readers in an easily readable, | |
compact, and organized manner. They are not only used for numerical values, but | |
also survey responses, task planning, schedules, and more. Because of this wide | |
set of possible applications, there is no single best way to lay out a table. | |
Instead, think about the data you want to highlight, your document's overarching | |
design, and ultimately how your table can best serve your readers. | |
Typst can help you with your tables by automating styling, importing data from | |
other applications, and more! This guide takes you through a few of the most | |
common questions you may have when adding a table to your document with Typst. | |
Feel free to skip to the section most relevant to you – we designed this guide | |
to be read out of order. | |
If you want to look up a detail of how tables work, you should also [check out | |
their reference page]($table). And if you are looking for a table of contents | |
rather than a normal table, the reference page of the [`outline` | |
function]($outline) is the right place to learn more. | |
## How to create a basic table? { #basic-tables } | |
In order to create a table in Typst, use the [`table` function]($table). For a | |
basic table, you need to tell the table function two things: | |
- The number of columns | |
- The content for each of the table cells | |
So, let's say you want to create a table with two columns describing the | |
ingredients for a cookie recipe: | |
```example | |
#table( | |
columns: 2, | |
[*Amount*], [*Ingredient*], | |
[360g], [Baking flour], | |
[250g], [Butter (room temp.)], | |
[150g], [Brown sugar], | |
[100g], [Cane sugar], | |
[100g], [70% cocoa chocolate], | |
[100g], [35-40% cocoa chocolate], | |
[2], [Eggs], | |
[Pinch], [Salt], | |
[Drizzle], [Vanilla extract], | |
) | |
``` | |
This example shows how to call, configure, and populate a table. Both the column | |
count and cell contents are passed to the table as arguments. The [argument | |
list]($function) is surrounded by round parentheses. In it, we first pass the | |
column count as a named argument. Then, we pass multiple [content | |
blocks]($content) as positional arguments. Each content block contains the | |
contents for a single cell. | |
To make the example more legible, we have placed two content block arguments on | |
each line, mimicking how they would appear in the table. You could also write | |
each cell on its own line. Typst does not care on which line you place the | |
arguments. Instead, Typst will place the content cells from left to right (or | |
right to left, if that is the writing direction of your language) and then from | |
top to bottom. It will automatically add enough rows to your table so that it | |
fits all of your content. | |
It is best to wrap the header row of your table in the [`table.header` | |
function]($table.header). This clarifies your intent and will also allow future | |
versions of Typst to make the output more accessible to users with a screen | |
reader: | |
```example | |
#table( | |
columns: 2, | |
table.header[*Amount*][*Ingredient*], | |
[360g], [Baking flour], | |
<<< // ... the remaining cells | |
>>> [250g], [Butter (room temp.)], | |
>>> [150g], [Brown sugar], | |
>>> [100g], [Cane sugar], | |
>>> [100g], [70% cocoa chocolate], | |
>>> [100g], [35-40% cocoa chocolate], | |
>>> [2], [Eggs], | |
>>> [Pinch], [Salt], | |
>>> [Drizzle], [Vanilla extract], | |
) | |
``` | |
You could also write a show rule that automatically [strongly | |
emphasizes]($strong) the contents of the first cells for all tables. This | |
quickly becomes useful if your document contains multiple tables! | |
```example | |
#show table.cell.where(y: 0): strong | |
#table( | |
columns: 2, | |
table.header[Amount][Ingredient], | |
[360g], [Baking flour], | |
<<< // ... the remaining cells | |
>>> [250g], [Butter (room temp.)], | |
>>> [150g], [Brown sugar], | |
>>> [100g], [Cane sugar], | |
>>> [100g], [70% cocoa chocolate], | |
>>> [100g], [35-40% cocoa chocolate], | |
>>> [2], [Eggs], | |
>>> [Pinch], [Salt], | |
>>> [Drizzle], [Vanilla extract], | |
) | |
``` | |
We are using a show rule with a selector for cell coordinates here instead of | |
applying our styles directly to `table.header`. This is due to a current | |
limitation of Typst that will be fixed in a future release. | |
Congratulations, you have created your first table! Now you can proceed to | |
[change column sizes](#column-sizes), [adjust the strokes](#strokes), [add | |
striped rows](#fills), and more! | |
## How to change the column sizes? { #column-sizes } | |
If you create a table and specify the number of columns, Typst will make each | |
column large enough to fit its largest cell. Often, you want something | |
different, for example, to make a table span the whole width of the page. You | |
can provide a list, specifying how wide you want each column to be, through the | |
`columns` argument. There are a few different ways to specify column widths: | |
- First, there is `{auto}`. This is the default behavior and tells Typst to grow | |
the column to fit its contents. If there is not enough space, Typst will try | |
its best to distribute the space among the `{auto}`-sized columns. | |
- [Lengths]($length) like `{6cm}`, `{0.7in}`, or `{120pt}`. As usual, you can | |
also use the font-dependent `em` unit. This is a multiple of your current font | |
size. It's useful if you want to size your table so that it always fits | |
about the same amount of text, independent of font size. | |
- A [ratio in percent]($ratio) such as `{40%}`. This will make the column take | |
up 40% of the total horizontal space available to the table, so either the | |
inner width of the page or the table's container. You can also mix ratios and | |
lengths into [relative lengths]($relative). Be mindful that even if you | |
specify a list of column widths that sum up to 100%, your table could still | |
become larger than its container. This is because there can be | |
[gutter]($table.gutter) between columns that is not included in the column | |
widths. If you want to make a table fill the page, the next option is often | |
very useful. | |
- A [fractional part of the free space]($fraction) using the `fr` unit, such as | |
`1fr`. This unit allows you to distribute the available space to columns. It | |
works as follows: First, Typst sums up the lengths of all columns that do not | |
use `fr`s. Then, it determines how much horizontal space is left. This | |
horizontal space then gets distributed to all columns denominated in `fr`s. | |
During this process, a `2fr` column will become twice as wide as a `1fr` | |
column. This is where the name comes from: The width of the column is its | |
fraction of the total fractionally sized columns. | |
Let's put this to use with a table that contains the dates, numbers, and | |
descriptions of some routine checks. The first two columns are `auto`-sized and | |
the last column is `1fr` wide as to fill the whole page. | |
```example | |
#table( | |
columns: (auto, auto, 1fr), | |
table.header[Date][°No][Description], | |
[24/01/03], [813], [Filtered participant pool], | |
[24/01/03], [477], [Transitioned to sec. regimen], | |
[24/01/11], [051], [Cycled treatment substrate], | |
) | |
``` | |
Here, we have passed our list of column lengths as an [array], enclosed in round | |
parentheses, with its elements separated by commas. The first two columns are | |
automatically sized, so that they take on the size of their content and the | |
third column is sized as `{1fr}` so that it fills up the remainder of the space | |
on the page. If you wanted to instead change the second column to be a bit more | |
spacious, you could replace its entry in the `columns` array with a value like | |
`{6em}`. | |
## How to caption and reference my table? { #captions-and-references } | |
A table is just as valuable as the information your readers draw from it. You | |
can enhance the effectiveness of both your prose and your table by making a | |
clear connection between the two with a cross-reference. Typst can help you with | |
automatic [references]($ref) and the [`figure` function]($figure). | |
Just like with images, wrapping a table in the `figure` function allows you to | |
add a caption and a label, so you can reference the figure elsewhere. Wrapping | |
your table in a figure also lets you use the figure's `placement` parameter to | |
float it to the top or bottom of a page. | |
Let's take a look at a captioned table and how to reference it in prose: | |
```example | |
>>> #set page(width: 14cm) | |
#show table.cell.where(y: 0): set text(weight: "bold") | |
#figure( | |
table( | |
columns: 4, | |
stroke: none, | |
table.header[Test Item][Specification][Test Result][Compliance], | |
[Voltage], [220V ± 5%], [218V], [Pass], | |
[Current], [5A ± 0.5A], [4.2A], [Fail], | |
), | |
caption: [Probe results for design A], | |
) <probe-a> | |
The results from @probe-a show that the design is not yet optimal. | |
We will show how its performance can be improved in this section. | |
``` | |
The example shows how to wrap a table in a figure, set a caption and a label, | |
and how to reference that label. We start by using the `figure` function. It | |
expects the contents of the figure as a positional argument. We just put the | |
table function call in its argument list, omitting the `#` character because it | |
is only needed when calling a function in markup mode. We also add the caption | |
as a named argument (above or below) the table. | |
After the figure call, we put a label in angle brackets (`[<probe-a>]`). This | |
tells Typst to remember this element and make it referenceable under this name | |
throughout your document. We can then reference it in prose by using the at sign | |
and the label name `[@probe-a]`. Typst will print a nicely formatted reference | |
and automatically update the label if the table's number changes. | |
## How to get a striped table? { #fills } | |
Many tables use striped rows or columns instead of strokes to differentiate | |
between rows and columns. This effect is often called _zebra stripes._ Tables | |
with zebra stripes are popular in Business and commercial Data Analytics | |
applications, while academic applications tend to use strokes instead. | |
To add zebra stripes to a table, we use the `table` function's `fill` argument. | |
It can take three kinds of arguments: | |
- A single color (this can also be a gradient or a tiling) to fill all cells | |
with. Because we want some cells to have another color, this is not useful if | |
we want to build zebra tables. | |
- An array with colors which Typst cycles through for each column. We can use an | |
array with two elements to get striped columns. | |
- A function that takes the horizontal coordinate `x` and the vertical | |
coordinate `y` of a cell and returns its fill. We can use this to create | |
horizontal stripes or [checkerboard patterns]($grid.cell). | |
Let's start with an example of a horizontally striped table: | |
```example | |
>>> #set page(width: 16cm) | |
#set text(font: "IBM Plex Sans") | |
// Medium bold table header. | |
#show table.cell.where(y: 0): set text(weight: "medium") | |
// Bold titles. | |
#show table.cell.where(x: 1): set text(weight: "bold") | |
// See the strokes section for details on this! | |
#let frame(stroke) = (x, y) => ( | |
left: if x > 0 { 0pt } else { stroke }, | |
right: stroke, | |
top: if y < 2 { stroke } else { 0pt }, | |
bottom: stroke, | |
) | |
#set table( | |
fill: (rgb("EAF2F5"), none), | |
stroke: frame(rgb("21222C")), | |
) | |
#table( | |
columns: (0.4fr, 1fr, 1fr, 1fr), | |
table.header[Month][Title][Author][Genre], | |
[January], [The Great Gatsby], [F. Scott Fitzgerald], [Classic], | |
[February], [To Kill a Mockingbird], [Harper Lee], [Drama], | |
[March], [1984], [George Orwell], [Dystopian], | |
[April], [The Catcher in the Rye], [J.D. Salinger], [Coming-of-Age], | |
) | |
``` | |
This example shows a book club reading list. The line `{fill: (rgb("EAF2F5"), | |
none)}` in `table`'s set rule is all that is needed to add striped columns. It | |
tells Typst to alternate between coloring columns with a light blue (in the | |
[`rgb`]($color.rgb) function call) and nothing (`{none}`). Note that we | |
extracted all of our styling from the `table` function call itself into set and | |
show rules, so that we can automatically reuse it for multiple tables. | |
Because setting the stripes itself is easy we also added some other styles to | |
make it look nice. The other code in the example provides a dark blue | |
[stroke](#stroke-functions) around the table and below the first line and | |
emboldens the first row and the column with the book title. See the | |
[strokes](#strokes) section for details on how we achieved this stroke | |
configuration. | |
Let's next take a look at how we can change only the set rule to achieve | |
horizontal stripes instead: | |
```example | |
>>> #set page(width: 16cm) | |
>>> #set text(font: "IBM Plex Sans") | |
>>> #show table.cell.where(x: 1): set text(weight: "medium") | |
>>> #show table.cell.where(y: 0): set text(weight: "bold") | |
>>> | |
>>> #let frame(stroke) = (x, y) => ( | |
>>> left: if x > 0 { 0pt } else { stroke }, | |
>>> right: stroke, | |
>>> top: if y < 2 { stroke } else { 0pt }, | |
>>> bottom: stroke, | |
>>> ) | |
>>> | |
#set table( | |
fill: (_, y) => if calc.odd(y) { rgb("EAF2F5") }, | |
stroke: frame(rgb("21222C")), | |
) | |
>>> | |
>>> #table( | |
>>> columns: (0.4fr, 1fr, 1fr, 1fr), | |
>>> | |
>>> table.header[Month][Title][Author][Genre], | |
>>> [January], [The Great Gatsby], | |
>>> [F. Scott Fitzgerald], [Classic], | |
>>> [February], [To Kill a Mockingbird], | |
>>> [Harper Lee], [Drama], | |
>>> [March], [1984], | |
>>> [George Orwell], [Dystopian], | |
>>> [April], [The Catcher in the Rye], | |
>>> [J.D. Salinger], [Coming-of-Age], | |
>>> ) | |
``` | |
We just need to replace the set rule from the previous example with this one and | |
get horizontal stripes instead. Here, we are passing a function to `fill`. It | |
discards the horizontal coordinate with an underscore and then checks if the | |
vertical coordinate `y` of the cell is odd. If so, the cell gets a light blue | |
fill, otherwise, no fill is returned. | |
Of course, you can make this function arbitrarily complex. For example, if you | |
want to stripe the rows with a light and darker shade of blue, you could do | |
something like this: | |
```example | |
>>> #set page(width: 16cm) | |
>>> #set text(font: "IBM Plex Sans") | |
>>> #show table.cell.where(x: 1): set text(weight: "medium") | |
>>> #show table.cell.where(y: 0): set text(weight: "bold") | |
>>> | |
>>> #let frame(stroke) = (x, y) => ( | |
>>> left: if x > 0 { 0pt } else { stroke }, | |
>>> right: stroke, | |
>>> top: if y < 2 { stroke } else { 0pt }, | |
>>> bottom: stroke, | |
>>> ) | |
>>> | |
#set table( | |
fill: (_, y) => (none, rgb("EAF2F5"), rgb("DDEAEF")).at(calc.rem(y, 3)), | |
stroke: frame(rgb("21222C")), | |
) | |
>>> | |
>>> #table( | |
>>> columns: (0.4fr, 1fr, 1fr, 1fr), | |
>>> | |
>>> table.header[Month][Title][Author][Genre], | |
>>> [January], [The Great Gatsby], | |
>>> [F. Scott Fitzgerald], [Classic], | |
>>> [February], [To Kill a Mockingbird], | |
>>> [Harper Lee], [Drama], | |
>>> [March], [1984], | |
>>> [George Orwell], [Dystopian], | |
>>> [April], [The Catcher in the Rye], | |
>>> [J.D. Salinger], [Coming-of-Age], | |
>>> ) | |
``` | |
This example shows an alternative approach to write our fill function. The | |
function uses an array with three colors and then cycles between its values for | |
each row by indexing the array with the remainder of `y` divided by 3. | |
Finally, here is a bonus example that uses the _stroke_ to achieve striped rows: | |
```example | |
>>> #set page(width: 16cm) | |
>>> #set text(font: "IBM Plex Sans") | |
>>> #show table.cell.where(x: 1): set text(weight: "medium") | |
>>> #show table.cell.where(y: 0): set text(weight: "bold") | |
>>> | |
>>> #let frame(stroke) = (x, y) => ( | |
>>> left: if x > 0 { 0pt } else { stroke }, | |
>>> right: stroke, | |
>>> top: if y < 2 { stroke } else { 0pt }, | |
>>> bottom: stroke, | |
>>> ) | |
>>> | |
#set table( | |
stroke: (x, y) => ( | |
y: 1pt, | |
left: if x > 0 { 0pt } else if calc.even(y) { 1pt }, | |
right: if calc.even(y) { 1pt }, | |
), | |
) | |
>>> | |
>>> #table( | |
>>> columns: (0.4fr, 1fr, 1fr, 1fr), | |
>>> | |
>>> table.header[Month][Title][Author][Genre], | |
>>> [January], [The Great Gatsby], | |
>>> [F. Scott Fitzgerald], [Classic], | |
>>> [February], [To Kill a Mockingbird], | |
>>> [Harper Lee], [Drama], | |
>>> [March], [1984], | |
>>> [George Orwell], [Dystopian], | |
>>> [April], [The Catcher in the Rye], | |
>>> [J.D. Salinger], [Coming-of-Age], | |
>>> ) | |
``` | |
### Manually overriding a cell's fill color { #fill-override } | |
Sometimes, the fill of a cell needs not to vary based on its position in the | |
table, but rather based on its contents. We can use the [`table.cell` | |
element]($table.cell) in the `table`'s parameter list to wrap a cell's content | |
and override its fill. | |
For example, here is a list of all German presidents, with the cell borders | |
colored in the color of their party. | |
```example | |
>>> #set page(width: 10cm) | |
#set text(font: "Roboto") | |
#let cdu(name) = ([CDU], table.cell(fill: black, text(fill: white, name))) | |
#let spd(name) = ([SPD], table.cell(fill: red, text(fill: white, name))) | |
#let fdp(name) = ([FDP], table.cell(fill: yellow, name)) | |
#table( | |
columns: (auto, auto, 1fr), | |
stroke: (x: none), | |
table.header[Tenure][Party][President], | |
[1949-1959], ..fdp[Theodor Heuss], | |
[1959-1969], ..cdu[Heinrich Lübke], | |
[1969-1974], ..spd[Gustav Heinemann], | |
[1974-1979], ..fdp[Walter Scheel], | |
[1979-1984], ..cdu[Karl Carstens], | |
[1984-1994], ..cdu[Richard von Weizsäcker], | |
[1994-1999], ..cdu[Roman Herzog], | |
[1999-2004], ..spd[Johannes Rau], | |
[2004-2010], ..cdu[Horst Köhler], | |
[2010-2012], ..cdu[Christian Wulff], | |
[2012-2017], [n/a], [Joachim Gauck], | |
[2017-], ..spd[Frank-Walter-Steinmeier], | |
) | |
``` | |
In this example, we make use of variables because there only have been a total | |
of three parties whose members have become president (and one unaffiliated | |
president). Their colors will repeat multiple times, so we store a function that | |
produces an array with their party's name and a table cell with that party's | |
color and the president's name (`cdu`, `spd`, and `fdp`). We then use these | |
functions in the `table` argument list instead of directly adding the name. We | |
use the [spread operator]($arguments/#spreading) `..` to turn the items of the | |
arrays into single cells. We could also write something like | |
`{[FDP], table.cell(fill: yellow)[Theodor Heuss]}` for each cell directly in the | |
`table`'s argument list, but that becomes unreadable, especially for the parties | |
whose colors are dark so that they require white text. We also delete vertical | |
strokes and set the font to Roboto. | |
The party column and the cell color in this example communicate redundant | |
information on purpose: Communicating important data using color only is a bad | |
accessibility practice. It disadvantages users with vision impairment and is in | |
violation of universal access standards, such as the | |
[WCAG 2.1 Success Criterion 1.4.1](https://www.w3.org/WAI/WCAG21/Understanding/use-of-color.html). | |
To improve this table, we added a column printing the party name. Alternatively, | |
you could have made sure to choose a color-blindness friendly palette and mark | |
up your cells with an additional label that screen readers can read out loud. | |
The latter feature is not currently supported by Typst, but will be added in a | |
future release. You can check how colors look for color-blind readers with | |
[this Chrome extension](https://chromewebstore.google.com/detail/colorblindly/floniaahmccleoclneebhhmnjgdfijgg), | |
[Photoshop](https://helpx.adobe.com/photoshop/using/proofing-colors.html), or | |
[GIMP](https://docs.gimp.org/2.10/en/gimp-display-filter-dialog.html). | |
## How to adjust the lines in a table? { #strokes } | |
By default, Typst adds strokes between each row and column of a table. You can | |
adjust these strokes in a variety of ways. Which one is the most practical, | |
depends on the modification you want to make and your intent: | |
- Do you want to style all tables in your document, irrespective of their size | |
and content? Use the `table` function's [stroke]($table.stroke) argument in a | |
set rule. | |
- Do you want to customize all lines in a single table? Use the `table` | |
function's [stroke]($table.stroke) argument when calling the table function. | |
- Do you want to change, add, or remove the stroke around a single cell? Use the | |
`table.cell` element in the argument list of your table call. | |
- Do you want to change, add, or remove a single horizontal or vertical stroke | |
in a single table? Use the [`table.hline`] and [`table.vline`] elements in the | |
argument list of your table call. | |
We will go over all of these options with examples next! First, we will tackle | |
the `table` function's [stroke]($table.stroke) argument. Here, you can adjust | |
both how the table's lines get drawn and configure which lines are drawn at all. | |
Let's start by modifying the color and thickness of the stroke: | |
```example | |
#table( | |
columns: 4, | |
stroke: 0.5pt + rgb("666675"), | |
[*Monday*], [11.5], [13.0], [4.0], | |
[*Tuesday*], [8.0], [14.5], [5.0], | |
[*Wednesday*], [9.0], [18.5], [13.0], | |
) | |
``` | |
This makes the table lines a bit less wide and uses a bluish gray. You can see | |
that we added a width in point to a color to achieve our customized stroke. This | |
addition yields a value of the [stroke type]($stroke). Alternatively, you can | |
use the dictionary representation for strokes which allows you to access | |
advanced features such as dashed lines. | |
The previous example showed how to use the stroke argument in the table | |
function's invocation. Alternatively, you can specify the stroke argument in the | |
`table`'s set rule. This will have exactly the same effect on all subsequent | |
`table` calls as if the stroke argument was specified in the argument list. This | |
is useful if you are writing a template or want to style your whole document. | |
```typ | |
// Renders the exact same as the last example | |
#set table(stroke: 0.5pt + rgb("666675")) | |
#table( | |
columns: 4, | |
[*Monday*], [11.5], [13.0], [4.0], | |
[*Tuesday*], [8.0], [14.5], [5.0], | |
[*Wednesday*], [9.0], [18.5], [13.0], | |
) | |
``` | |
For small tables, you sometimes want to suppress all strokes because they add | |
too much visual noise. To do this, just set the stroke argument to `{none}`: | |
```example | |
#table( | |
columns: 4, | |
stroke: none, | |
[*Monday*], [11.5], [13.0], [4.0], | |
[*Tuesday*], [8.0], [14.5], [5.0], | |
[*Wednesday*], [9.0], [18.5], [13.0], | |
) | |
``` | |
If you want more fine-grained control of where lines get placed in your table, | |
you can also pass a dictionary with the keys `top`, `left`, `right`, `bottom` | |
(controlling the respective cell sides), `x`, `y` (controlling vertical and | |
horizontal strokes), and `rest` (covers all strokes not styled by other | |
dictionary entries). All keys are optional; omitted keys will be treated as if | |
their value was the default value. For example, to get a table with only | |
horizontal lines, you can do this: | |
```example | |
#table( | |
columns: 2, | |
stroke: (x: none), | |
align: horizon, | |
[☒], [Close cabin door], | |
[☐], [Start engines], | |
[☐], [Radio tower], | |
[☐], [Push back], | |
) | |
``` | |
This turns off all vertical strokes and leaves the horizontal strokes in place. | |
To achieve the reverse effect (only horizontal strokes), set the stroke argument | |
to `{(y: none)}` instead. | |
[Further down in the guide](#stroke-functions), we cover how to use a function | |
in the stroke argument to customize all strokes individually. This is how you | |
achieve more complex stroking patterns. | |
### Adding individual lines in the table { #individual-lines } | |
If you want to add a single horizontal or vertical line in your table, for | |
example to separate a group of rows, you can use the [`table.hline`] and | |
[`table.vline`] elements for horizontal and vertical lines, respectively. Add | |
them to the argument list of the `table` function just like you would add | |
individual cells and a header. | |
Let's take a look at the following example from the reference: | |
```example | |
#set table.hline(stroke: 0.6pt) | |
#table( | |
stroke: none, | |
columns: (auto, 1fr), | |
// Morning schedule abridged. | |
[14:00], [Talk: Tracked Layout], | |
[15:00], [Talk: Automations], | |
[16:00], [Workshop: Tables], | |
table.hline(), | |
[19:00], [Day 1 Attendee Mixer], | |
) | |
``` | |
In this example, you can see that we have placed a call to `table.hline` between | |
the cells, producing a horizontal line at that spot. We also used a set rule on | |
the element to reduce its stroke width to make it fit better with the weight of | |
the font. | |
By default, Typst places horizontal and vertical lines after the current row or | |
column, depending on their position in the argument list. You can also manually | |
move them to a different position by adding the `y` (for `hline`) or `x` (for | |
`vline`) argument. For example, the code below would produce the same result: | |
```typ | |
#set table.hline(stroke: 0.6pt) | |
#table( | |
stroke: none, | |
columns: (auto, 1fr), | |
// Morning schedule abridged. | |
table.hline(y: 3), | |
[14:00], [Talk: Tracked Layout], | |
[15:00], [Talk: Automations], | |
[16:00], [Workshop: Tables], | |
[19:00], [Day 1 Attendee Mixer], | |
) | |
``` | |
Let's imagine you are working with a template that shows none of the table | |
strokes except for one between the first and second row. Now, since you have one | |
table that also has labels in the first column, you want to add an extra | |
vertical line to it. However, you do not want this vertical line to cross into | |
the top row. You can achieve this with the `start` argument: | |
```example | |
>>> #set page(width: 12cm) | |
>>> #show table.cell.where(y: 0): strong | |
>>> #set table(stroke: (_, y) => if y == 0 { (bottom: 1pt) }) | |
// Base template already configured tables, but we need some | |
// extra configuration for this table. | |
#{ | |
set table(align: (x, _) => if x == 0 { left } else { right }) | |
show table.cell.where(x: 0): smallcaps | |
table( | |
columns: (auto, 1fr, 1fr, 1fr), | |
table.vline(x: 1, start: 1), | |
table.header[Trainset][Top Speed][Length][Weight], | |
[TGV Réseau], [320 km/h], [200m], [383t], | |
[ICE 403], [330 km/h], [201m], [409t], | |
[Shinkansen N700], [300 km/h], [405m], [700t], | |
) | |
} | |
``` | |
In this example, we have added `table.vline` at the start of our positional | |
argument list. But because the line is not supposed to go to the left of the | |
first column, we specified the `x` argument as `{1}`. We also set the `start` | |
argument to `{1}` so that the line does only start after the first row. | |
The example also contains two more things: We use the align argument with a | |
function to right-align the data in all but the first column and use a show rule | |
to make the first column of table cells appear in small capitals. Because these | |
styles are specific to this one table, we put everything into a [code | |
block]($scripting/#blocks), so that the styling does not affect any further | |
tables. | |
### Overriding the strokes of a single cell { #stroke-override } | |
Imagine you want to change the stroke around a single cell. Maybe your cell is | |
very important and needs highlighting! For this scenario, there is the | |
[`table.cell` function]($table.cell). Instead of adding your content directly in | |
the argument list of the table, you wrap it in a `table.cell` call. Now, you can | |
use `table.cell`'s argument list to override the table properties, such as the | |
stroke, for this cell only. | |
Here's an example with a matrix of two of the Big Five personality factors, with | |
one intersection highlighted. | |
```example | |
>>> #set page(width: 16cm) | |
#table( | |
columns: 3, | |
stroke: (x: none), | |
[], [*High Neuroticism*], [*Low Neuroticism*], | |
[*High Agreeableness*], | |
table.cell(stroke: orange + 2pt)[ | |
_Sensitive_ \ Prone to emotional distress but very empathetic. | |
], | |
[_Compassionate_ \ Caring and stable, often seen as a supportive figure.], | |
[*Low Agreeableness*], | |
[_Contentious_ \ Competitive and easily agitated.], | |
[_Detached_ \ Independent and calm, may appear aloof.], | |
) | |
``` | |
Above, you can see that we used the `table.cell` element in the table's argument | |
list and passed the cell content to it. We have used its `stroke` argument to | |
set a wider orange stroke. Despite the fact that we disabled vertical strokes on | |
the table, the orange stroke appeared on all sides of the modified cell, showing | |
that the table's stroke configuration is overwritten. | |
### Complex document-wide stroke customization { #stroke-functions } | |
This section explains how to customize all lines at once in one or multiple | |
tables. This allows you to draw only the first horizontal line or omit the outer | |
lines, without knowing how many cells the table has. This is achieved by | |
providing a function to the table's `stroke` parameter. The function should | |
return a stroke given the zero-indexed x and y position of the current cell. You | |
should only need these functions if you are a template author, do not use a | |
template, or need to heavily customize your tables. Otherwise, your template | |
should set appropriate default table strokes. | |
For example, this is a set rule that draws all horizontal lines except for the | |
very first and last line. | |
```example | |
#show table.cell.where(x: 0): set text(style: "italic") | |
#show table.cell.where(y: 0): set text(style: "normal", weight: "bold") | |
#set table(stroke: (_, y) => if y > 0 { (top: 0.8pt) }) | |
#table( | |
columns: 3, | |
align: center + horizon, | |
table.header[Technique][Advantage][Drawback], | |
[Diegetic], [Immersive], [May be contrived], | |
[Extradiegetic], [Breaks immersion], [Obtrusive], | |
[Omitted], [Fosters engagement], [May fracture audience], | |
) | |
``` | |
In the set rule, we pass a function that receives two arguments, assigning the | |
vertical coordinate to `y` and discarding the horizontal coordinate. It then | |
returns a stroke dictionary with a `{0.8pt}` top stroke for all but the first | |
line. The cells in the first line instead implicitly receive `{none}` as the | |
return value. You can easily modify this function to just draw the inner | |
vertical lines instead as `{(x, _) => if x > 0 { (left: 0.8pt) }}`. | |
Let's try a few more stroking functions. The next function will only draw a line | |
below the first row: | |
```example | |
>>> #show table.cell: it => if it.x == 0 and it.y > 0 { | |
>>> set text(style: "italic") | |
>>> it | |
>>> } else { | |
>>> it | |
>>> } | |
>>> | |
>>> #show table.cell.where(y: 0): strong | |
#set table(stroke: (_, y) => if y == 0 { (bottom: 1pt) }) | |
<<< // Table as seen above | |
>>> #table( | |
>>> columns: 3, | |
>>> align: center + horizon, | |
>>> table.header[Technique][Advantage][Drawback], | |
>>> [Diegetic], [Immersive], [May be contrived], | |
>>> [Extradiegetic], [Breaks immersion], [Obtrusive], | |
>>> [Omitted], [Fosters engagement], [May fracture audience], | |
>>> ) | |
``` | |
If you understood the first example, it becomes obvious what happens here. We | |
check if we are in the first row. If so, we return a bottom stroke. Otherwise, | |
we'll return `{none}` implicitly. | |
The next example shows how to draw all but the outer lines: | |
```example | |
>>> #show table.cell: it => if it.x == 0 and it.y > 0 { | |
>>> set text(style: "italic") | |
>>> it | |
>>> } else { | |
>>> it | |
>>> } | |
>>> | |
>>> #show table.cell.where(y: 0): strong | |
#set table(stroke: (x, y) => ( | |
left: if x > 0 { 0.8pt }, | |
top: if y > 0 { 0.8pt }, | |
)) | |
<<< // Table as seen above | |
>>> #table( | |
>>> columns: 3, | |
>>> align: center + horizon, | |
>>> table.header[Technique][Advantage][Drawback], | |
>>> [Diegetic], [Immersive], [May be contrived], | |
>>> [Extradiegetic], [Breaks immersion], [Obtrusive], | |
>>> [Omitted], [Fosters engagement], [May fracture audience], | |
>>> ) | |
``` | |
This example uses both the `x` and `y` coordinates. It omits the left stroke in | |
the first column and the top stroke in the first row. The right and bottom lines | |
are not drawn. | |
Finally, here is a table that draws all lines except for the vertical lines in | |
the first row and horizontal lines in the table body. It looks a bit like a | |
calendar. | |
```example | |
>>> #show table.cell: it => if it.x == 0 and it.y > 0 { | |
>>> set text(style: "italic") | |
>>> it | |
>>> } else { | |
>>> it | |
>>> } | |
>>> | |
>>> #show table.cell.where(y: 0): strong | |
#set table(stroke: (x, y) => ( | |
left: if x == 0 or y > 0 { 1pt } else { 0pt }, | |
right: 1pt, | |
top: if y <= 1 { 1pt } else { 0pt }, | |
bottom: 1pt, | |
)) | |
<<< // Table as seen above | |
>>> #table( | |
>>> columns: 3, | |
>>> align: center + horizon, | |
>>> table.header[Technique][Advantage][Drawback], | |
>>> [Diegetic], [Immersive], [May be contrived], | |
>>> [Extradiegetic], [Breaks immersion], [Obtrusive], | |
>>> [Omitted], [Fosters engagement], [May fracture audience], | |
>>> ) | |
``` | |
This example is a bit more complex. We start by drawing all the strokes on the | |
right of the cells. But this means that we have drawn strokes in the top row, | |
too, and we don't need those! We use the fact that `left` will override `right` | |
and only draw the left line if we are not in the first row or if we are in the | |
first column. In all other cases, we explicitly remove the left line. Finally, | |
we draw the horizontal lines by first setting the bottom line and then for the | |
first two rows with the `top` key, suppressing all other top lines. The last | |
line appears because there is no `top` line that could suppress it. | |
### How to achieve a double line? { #double-stroke } | |
Typst does not yet have a native way to draw double strokes, but there are | |
multiple ways to emulate them, for example with [tilings]($tiling). We will | |
show a different workaround in this section: Table gutters. | |
Tables can space their cells apart using the `gutter` argument. When a gutter is | |
applied, a stroke is drawn on each of the now separated cells. We can | |
selectively add gutter between the rows or columns for which we want to draw a | |
double line. The `row-gutter` and `column-gutter` arguments allow us to do this. | |
They accept arrays of gutter values. Let's take a look at an example: | |
```example | |
#table( | |
columns: 3, | |
stroke: (x: none), | |
row-gutter: (2.2pt, auto), | |
table.header[Date][Exercise Type][Calories Burned], | |
[2023-03-15], [Swimming], [400], | |
[2023-03-17], [Weightlifting], [250], | |
[2023-03-18], [Yoga], [200], | |
) | |
``` | |
We can see that we used an array for `row-gutter` that specifies a `{2.2pt}` gap | |
between the first and second row. It then continues with `auto` (which is the | |
default, in this case `{0pt}` gutter) which will be the gutter between all other | |
rows, since it is the last entry in the array. | |
## How to align the contents of the cells in my table? { #alignment } | |
You can use multiple mechanisms to align the content in your table. You can | |
either use the `table` function's `align` argument to set the alignment for your | |
whole table (or use it in a set rule to set the alignment for tables throughout | |
your document) or the [`align`] function (or `table.cell`'s `align` argument) to | |
override the alignment of a single cell. | |
When using the `table` function's align argument, you can choose between three | |
methods to specify an [alignment]: | |
- Just specify a single alignment like `right` (aligns in the top-right corner) | |
or `center + horizon` (centers all cell content). This changes the alignment | |
of all cells. | |
- Provide an array. Typst will cycle through this array for each column. | |
- Provide a function that is passed the horizontal `x` and vertical `y` | |
coordinate of a cell and returns an alignment. | |
For example, this travel itinerary right-aligns the day column and left-aligns | |
everything else by providing an array in the `align` argument: | |
```example | |
>>> #set page(width: 12cm) | |
#set text(font: "IBM Plex Sans") | |
#show table.cell.where(y: 0): set text(weight: "bold") | |
#table( | |
columns: 4, | |
align: (right, left, left, left), | |
fill: (_, y) => if calc.odd(y) { green.lighten(90%) }, | |
stroke: none, | |
table.header[Day][Location][Hotel or Apartment][Activities], | |
[1], [Paris, France], [Hôtel de l'Europe], [Arrival, Evening River Cruise], | |
[2], [Paris, France], [Hôtel de l'Europe], [Louvre Museum, Eiffel Tower], | |
[3], [Lyon, France], [Lyon City Hotel], [City Tour, Local Cuisine Tasting], | |
[4], [Geneva, Switzerland], [Lakeview Inn], [Lake Geneva, Red Cross Museum], | |
[5], [Zermatt, Switzerland], [Alpine Lodge], [Visit Matterhorn, Skiing], | |
) | |
``` | |
However, this example does not yet look perfect — the header cells should be | |
bottom-aligned. Let's use a function instead to do so: | |
```example | |
>>> #set page(width: 12cm) | |
#set text(font: "IBM Plex Sans") | |
#show table.cell.where(y: 0): set text(weight: "bold") | |
#table( | |
columns: 4, | |
align: (x, y) => | |
if x == 0 { right } else { left } + | |
if y == 0 { bottom } else { top }, | |
fill: (_, y) => if calc.odd(y) { green.lighten(90%) }, | |
stroke: none, | |
table.header[Day][Location][Hotel or Apartment][Activities], | |
[1], [Paris, France], [Hôtel de l'Europe], [Arrival, Evening River Cruise], | |
[2], [Paris, France], [Hôtel de l'Europe], [Louvre Museum, Eiffel Tower], | |
<<< // ... remaining days omitted | |
>>> [3], [Lyon, France], [Lyon City Hotel], [City Tour, Local Cuisine Tasting], | |
>>> [4], [Geneva, Switzerland], [Lakeview Inn], [Lake Geneva, Red Cross Museum], | |
>>> [5], [Zermatt, Switzerland], [Alpine Lodge], [Visit Matterhorn, Skiing], | |
) | |
``` | |
In the function, we calculate a horizontal and vertical alignment based on | |
whether we are in the first column (`{x == 0}`) or the first row (`{y == 0}`). | |
We then make use of the fact that we can add horizontal and vertical alignments | |
with `+` to receive a single, two-dimensional alignment. | |
You can find an example of using `table.cell` to change a single cell's | |
alignment on [its reference page]($table.cell). | |
## How to merge cells? { #merge-cells } | |
When a table contains logical groupings or the same data in multiple adjacent | |
cells, merging multiple cells into a single, larger cell can be advantageous. | |
Another use case for cell groups are table headers with multiple rows: That way, | |
you can group for example a sales data table by quarter in the first row and by | |
months in the second row. | |
A merged cell spans multiple rows and/or columns. You can achieve it with the | |
[`table.cell`] function's `rowspan` and `colspan` arguments: Just specify how | |
many rows or columns you want your cell to span. | |
The example below contains an attendance calendar for an office with in-person | |
and remote days for each team member. To make the table more glanceable, we | |
merge adjacent cells with the same value: | |
```example | |
>>> #set page(width: 22cm) | |
#let ofi = [Office] | |
#let rem = [_Remote_] | |
#let lea = [*On leave*] | |
#show table.cell.where(y: 0): set text( | |
fill: white, | |
weight: "bold", | |
) | |
#table( | |
columns: 6 * (1fr,), | |
align: (x, y) => if x == 0 or y == 0 { left } else { center }, | |
stroke: (x, y) => ( | |
// Separate black cells with white strokes. | |
left: if y == 0 and x > 0 { white } else { black }, | |
rest: black, | |
), | |
fill: (_, y) => if y == 0 { black }, | |
table.header( | |
[Team member], | |
[Monday], | |
[Tuesday], | |
[Wednesday], | |
[Thursday], | |
[Friday] | |
), | |
[Evelyn Archer], | |
table.cell(colspan: 2, ofi), | |
table.cell(colspan: 2, rem), | |
ofi, | |
[Lila Montgomery], | |
table.cell(colspan: 5, lea), | |
[Nolan Pearce], | |
rem, | |
table.cell(colspan: 2, ofi), | |
rem, | |
ofi, | |
) | |
``` | |
In the example, we first define variables with "Office", "Remote", and "On | |
leave" so we don't have to write these labels out every time. We can then use | |
these variables in the table body either directly or in a `table.cell` call if | |
the team member spends multiple consecutive days in office, remote, or on leave. | |
The example also contains a black header (created with `table`'s `fill` | |
argument) with white strokes (`table`'s `stroke` argument) and white text (set | |
by the `table.cell` set rule). Finally, we align all the content of all table | |
cells in the body in the center. If you want to know more about the functions | |
passed to `align`, `stroke`, and `fill`, you can check out the sections on | |
[alignment], [strokes](#stroke-functions), and [striped | |
tables](#fills). | |
This table would be a great candidate for fully automated generation from an | |
external data source! Check out the [section about importing | |
data](#importing-data) to learn more about that. | |
## How to rotate a table? { #rotate-table } | |
When tables have many columns, a portrait paper orientation can quickly get | |
cramped. Hence, you'll sometimes want to switch your tables to landscape | |
orientation. There are two ways to accomplish this in Typst: | |
- If you want to rotate only the table but not the other content of the page and | |
the page itself, use the [`rotate` function]($rotate) with the `reflow` | |
argument set to `{true}`. | |
- If you want to rotate the whole page the table is on, you can use the [`page` | |
function]($page) with its `flipped` argument set to `{true}`. The header, | |
footer, and page number will now also appear on the long edge of the page. | |
This has the advantage that the table will appear right side up when read on a | |
computer, but it also means that a page in your document has different | |
dimensions than all the others, which can be jarring to your readers. | |
Below, we will demonstrate both techniques with a student grade book table. | |
First, we will rotate the table on the page. The example also places some text | |
on the right of the table. | |
```example | |
#set page("a5", columns: 2, numbering: "— 1 —") | |
>>> #set page(margin: auto) | |
#show table.cell.where(y: 0): set text(weight: "bold") | |
#rotate( | |
-90deg, | |
reflow: true, | |
table( | |
columns: (1fr,) + 5 * (auto,), | |
inset: (x: 0.6em,), | |
stroke: (_, y) => ( | |
x: 1pt, | |
top: if y <= 1 { 1pt } else { 0pt }, | |
bottom: 1pt, | |
), | |
align: (left, right, right, right, right, left), | |
table.header( | |
[Student Name], | |
[Assignment 1], [Assignment 2], | |
[Mid-term], [Final Exam], | |
[Total Grade], | |
), | |
[Jane Smith], [78%], [82%], [75%], [80%], [B], | |
[Alex Johnson], [90%], [95%], [94%], [96%], [A+], | |
[John Doe], [85%], [90%], [88%], [92%], [A], | |
[Maria Garcia], [88%], [84%], [89%], [85%], [B+], | |
[Zhang Wei], [93%], [89%], [90%], [91%], [A-], | |
[Marina Musterfrau], [96%], [91%], [74%], [69%], [B-], | |
), | |
) | |
#lorem(80) | |
``` | |
What we have here is a two-column document on ISO A5 paper with page numbers on | |
the bottom. The table has six columns and contains a few customizations to | |
[stroke](#strokes), alignment and spacing. But the most important part is that | |
the table is wrapped in a call to the `rotate` function with the `reflow` | |
argument being `{true}`. This will make the table rotate 90 degrees | |
counterclockwise. The reflow argument is needed so that the table's rotation | |
affects the layout. If it was omitted, Typst would lay out the page as if the | |
table was not rotated (`{true}` might become the default in the future). | |
The example also shows how to produce many columns of the same size: To the | |
initial `{1fr}` column, we add an array with five `{auto}` items that we | |
create by multiplying an array with one `{auto}` item by five. Note that arrays | |
with just one item need a trailing comma to distinguish them from merely | |
parenthesized expressions. | |
The second example shows how to rotate the whole page, so that the table stays | |
upright: | |
```example | |
#set page("a5", numbering: "— 1 —") | |
>>> #set page(margin: auto) | |
#show table.cell.where(y: 0): set text(weight: "bold") | |
#page(flipped: true)[ | |
#table( | |
columns: (1fr,) + 5 * (auto,), | |
inset: (x: 0.6em,), | |
stroke: (_, y) => ( | |
x: 1pt, | |
top: if y <= 1 { 1pt } else { 0pt }, | |
bottom: 1pt, | |
), | |
align: (left, right, right, right, right, left), | |
table.header( | |
[Student Name], | |
[Assignment 1], [Assignment 2], | |
[Mid-term], [Final Exam], | |
[Total Grade], | |
), | |
[Jane Smith], [78%], [82%], [75%], [80%], [B], | |
[Alex Johnson], [90%], [95%], [94%], [96%], [A+], | |
[John Doe], [85%], [90%], [88%], [92%], [A], | |
[Maria Garcia], [88%], [84%], [89%], [85%], [B+], | |
[Zhang Wei], [93%], [89%], [90%], [91%], [A-], | |
[Marina Musterfrau], [96%], [91%], [74%], [69%], [B-], | |
) | |
#pad(x: 15%, top: 1.5em)[ | |
= Winter 2023/24 results | |
#lorem(80) | |
] | |
] | |
``` | |
Here, we take the same table and the other content we want to set with it and | |
put it into a call to the [`page`] function while supplying `{true}` to the | |
`flipped` argument. This will instruct Typst to create new pages with width and | |
height swapped and place the contents of the function call onto a new page. | |
Notice how the page number is also on the long edge of the paper now. At the | |
bottom of the page, we use the [`pad`] function to constrain the width of the | |
paragraph to achieve a nice and legible line length. | |
## How to break a table across pages? { #table-across-pages } | |
It is best to contain a table on a single page. However, some tables just have | |
many rows, so breaking them across pages becomes unavoidable. Fortunately, Typst | |
supports breaking tables across pages out of the box. If you are using the | |
[`table.header`] and [`table.footer`] functions, their contents will be repeated | |
on each page as the first and last rows, respectively. If you want to disable | |
this behavior, you can set `repeat` to `{false}` on either of them. | |
If you have placed your table inside of a [figure], it becomes unable to break | |
across pages by default. However, you can change this behavior. Let's take a | |
look: | |
```example | |
#set page(width: 9cm, height: 6cm) | |
#show table.cell.where(y: 0): set text(weight: "bold") | |
#show figure: set block(breakable: true) | |
#figure( | |
caption: [Training regimen for Marathon], | |
table( | |
columns: 3, | |
fill: (_, y) => if y == 0 { gray.lighten(75%) }, | |
table.header[Week][Distance (km)][Time (hh:mm:ss)], | |
[1], [5], [00:30:00], | |
[2], [7], [00:45:00], | |
[3], [10], [01:00:00], | |
[4], [12], [01:10:00], | |
[5], [15], [01:25:00], | |
[6], [18], [01:40:00], | |
[7], [20], [01:50:00], | |
[8], [22], [02:00:00], | |
[...], [...], [...], | |
table.footer[_Goal_][_42.195_][_02:45:00_], | |
) | |
) | |
``` | |
A figure automatically produces a [block] which cannot break by default. | |
However, we can reconfigure the block of the figure using a show rule to make it | |
`breakable`. Now, the figure spans multiple pages with the headers and footers | |
repeating. | |
## How to import data into a table? { #importing-data } | |
Often, you need to put data that you obtained elsewhere into a table. Sometimes, | |
this is from Microsoft Excel or Google Sheets, sometimes it is from a dataset | |
on the web or from your experiment. Fortunately, Typst can load many [common | |
file formats]($category/data-loading), so you can use scripting to include their | |
data in a table. | |
The most common file format for tabular data is CSV. You can obtain a CSV file | |
from Excel by choosing "Save as" in the _File_ menu and choosing the file format | |
"CSV UTF-8 (Comma-delimited) (.csv)". Save the file and, if you are using the | |
web app, upload it to your project. | |
In our case, we will be building a table about Moore's Law. For this purpose, we | |
are using a statistic with [how many transistors the average microprocessor | |
consists of per year from Our World in | |
Data](https://ourworldindata.org/grapher/transistors-per-microprocessor). Let's | |
start by pressing the "Download" button to get a CSV file with the raw data. | |
Be sure to move the file to your project or somewhere Typst can see it, if you | |
are using the CLI. Once you did that, we can open the file to see how it is | |
structured: | |
```csv | |
Entity,Code,Year,Transistors per microprocessor | |
World,OWID_WRL,1971,2308.2417 | |
World,OWID_WRL,1972,3554.5222 | |
World,OWID_WRL,1974,6097.5625 | |
``` | |
The file starts with a header and contains four columns: Entity (which is to | |
whom the metric applies), Code, the year, and the number of transistors per | |
microprocessor. Only the last two columns change between each row, so we can | |
disregard "Entity" and "Code". | |
First, let's start by loading this file with the [`csv`] function. It accepts | |
the file name of the file we want to load as a string argument: | |
```typ | |
#let moore = csv("moore.csv") | |
``` | |
We have loaded our file (assuming we named it `moore.csv`) and [bound | |
it]($scripting/#bindings) to the new variable `moore`. This will not produce any | |
output, so there's nothing to see yet. If we want to examine what Typst loaded, | |
we can either hover the name of the variable in the web app or print some items | |
from the array: | |
```example | |
#let moore = csv("moore.csv") | |
#moore.slice(0, 3) | |
``` | |
With the arguments `{(0, 3)}`, the [`slice`]($array.slice) method returns the | |
first three items in the array (with the indices 0, 1, and 2). We can see that | |
each row is its own array with one item per cell. | |
Now, let's write a loop that will transform this data into an array of cells | |
that we can use with the table function. | |
```example | |
#let moore = csv("moore.csv") | |
#table( | |
columns: 2, | |
..for (.., year, count) in moore { | |
(year, count) | |
} | |
) | |
``` | |
The example above uses a for loop that iterates over the rows in our CSV file | |
and returns an array for each iteration. We use the for loop's | |
[destructuring]($scripting/#bindings) capability to discard all but the last two | |
items of each row. We then create a new array with just these two. Because Typst | |
will concatenate the array results of all the loop iterations, we get a | |
one-dimensional array in which the year column and the number of transistors | |
alternate. We can then insert the array as cells. For this we use the [spread | |
operator]($arguments/#spreading) (`..`). By prefixing an array, or, in our case | |
an expression that yields an array, with two dots, we tell Typst that the | |
array's items should be used as positional arguments. | |
Alternatively, we can also use the [`map`]($array.map), [`slice`]($array.slice), | |
and [`flatten`]($array.flatten) array methods to write this in a more functional | |
style: | |
```typ | |
#let moore = csv("moore.csv") | |
#table( | |
columns: moore.first().len(), | |
..moore.map(m => m.slice(2)).flatten(), | |
) | |
``` | |
This example renders the same as the previous one, but first uses the `map` | |
function to change each row of the data. We pass a function to map that gets run | |
on each row of the CSV and returns a new value to replace that row with. We use | |
it to discard the first two columns with `slice`. Then, we spread the data into | |
the `table` function. However, we need to pass a one-dimensional array and | |
`moore`'s value is two-dimensional (that means that each of its row values | |
contains an array with the cell data). That's why we call `flatten` which | |
converts it to a one-dimensional array. We also extract the number of columns | |
from the data itself. | |
Now that we have nice code for our table, we should try to also make the table | |
itself nice! The transistor counts go from millions in 1995 to trillions in 2021 | |
and changes are difficult to see with so many digits. We could try to present | |
our data logarithmically to make it more digestible: | |
```example | |
#let moore = csv("moore.csv") | |
#let moore-log = moore.slice(1).map(m => { | |
let (.., year, count) = m | |
let log = calc.log(float(count)) | |
let rounded = str(calc.round(log, digits: 2)) | |
(year, rounded) | |
}) | |
#show table.cell.where(x: 0): strong | |
#table( | |
columns: moore-log.first().len(), | |
align: right, | |
fill: (_, y) => if calc.odd(y) { rgb("D7D9E0") }, | |
stroke: none, | |
table.header[Year][Transistor count ($log_10$)], | |
table.hline(stroke: rgb("4D4C5B")), | |
..moore-log.flatten(), | |
) | |
``` | |
In this example, we first drop the header row from the data since we are adding | |
our own. Then, we discard all but the last two columns as above. We do this by | |
[destructuring]($scripting/#bindings) the array `m`, discarding all but the two | |
last items. We then convert the string in `count` to a floating point number, | |
calculate its logarithm and store it in the variable `log`. Finally, we round it | |
to two digits, convert it to a string, and store it in the variable `rounded`. | |
Then, we return an array with `year` and `rounded` that replaces the original | |
row. In our table, we have added our custom header that tells the reader that | |
we've applied a logarithm to the values. Then, we spread the flattened data as | |
above. | |
We also styled the table with [stripes](#fills), a | |
[horizontal line](#individual-lines) below the first row, [aligned](#alignment) | |
everything to the right, and emboldened the first column. Click on the links to | |
go to the relevant guide sections and see how it's done! | |
## What if I need the table function for something that isn't a table? { #table-and-grid } | |
Tabular layouts of content can be useful not only for matrices of closely | |
related data, like shown in the examples throughout this guide, but also for | |
presentational purposes. Typst differentiates between grids that are for layout | |
and presentational purposes only and tables, in which the arrangement of the | |
cells itself conveys information. | |
To make this difference clear to other software and allow templates to heavily | |
style tables, Typst has two functions for grid and table layout: | |
- The [`table`] function explained throughout this guide which is intended for | |
tabular data. | |
- The [`grid`] function which is intended for presentational purposes and page | |
layout. | |
Both elements work the same way and have the same arguments. You can apply | |
everything you have learned about tables in this guide to grids. There are only | |
three differences: | |
- You'll need to use the [`grid.cell`], [`grid.vline`], and [`grid.hline`] | |
elements instead of [`table.cell`], [`table.vline`], and [`table.hline`]. | |
- The grid has different defaults: It draws no strokes by default and has no | |
spacing (`inset`) inside of its cells. | |
- Elements like `figure` do not react to grids since they are supposed to have | |
no semantical bearing on the document structure. | |