diff --git a/Cargo.lock b/Cargo.lock index e05b4fe88..3b497b2da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2622,7 +2622,7 @@ dependencies = [ [[package]] name = "typst-dev-assets" version = "0.10.0" -source = "git+https://github.com/typst/typst-dev-assets?rev=c63ab46#c63ab467b6d2242b7993b81c1156b915486bcf02" +source = "git+https://github.com/typst/typst-dev-assets?rev=2eec695#2eec695af4b8b32c079e549b78c64ef7ff5d5fff" [[package]] name = "typst-docs" diff --git a/Cargo.toml b/Cargo.toml index 10b362845..519d06f10 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,7 @@ typst-svg = { path = "crates/typst-svg" } typst-syntax = { path = "crates/typst-syntax" } typst-timing = { path = "crates/typst-timing" } typst-assets = { git = "https://github.com/typst/typst-assets", rev = "4d1211a" } -typst-dev-assets = { git = "https://github.com/typst/typst-dev-assets", rev = "c63ab46" } +typst-dev-assets = { git = "https://github.com/typst/typst-dev-assets", rev = "2eec695" } az = "1.2" base64 = "0.22" bitflags = { version = "2", features = ["serde"] } diff --git a/crates/typst/src/model/table.rs b/crates/typst/src/model/table.rs index 8eaf55a9b..1b616a0bb 100644 --- a/crates/typst/src/model/table.rs +++ b/crates/typst/src/model/table.rs @@ -24,6 +24,9 @@ use crate::visualize::{Paint, Stroke}; /// /// Tables are used to arrange content in cells. Cells can contain arbitrary /// content, including multiple paragraphs and are specified in row-major order. +/// For a hands-on explanation of all the ways you can use and customize tables +/// in Typst, check out the [table guide]($guides/table-guide). +/// /// Because tables are just grids with different defaults for some cell /// properties (notably `stroke` and `inset`), refer to the [grid /// documentation]($grid) for more information on how to size the table tracks diff --git a/docs/guides/guide-for-latex-users.md b/docs/guides/guide-for-latex-users.md index 1e35f6b3b..8b2c267ff 100644 --- a/docs/guides/guide-for-latex-users.md +++ b/docs/guides/guide-for-latex-users.md @@ -10,8 +10,6 @@ out Typst. We will explore the main differences between these two systems from a user perspective. Although Typst is not built upon LaTeX and has a different syntax, you will learn how to use your LaTeX skills to get a head start. - - Just like LaTeX, Typst is a markup-based typesetting system: You compose your document in a text file and mark it up with commands and other syntax. Then, you use a compiler to typeset the source file into a PDF. However, Typst also diff --git a/docs/guides/tables.md b/docs/guides/tables.md new file mode 100644 index 000000000..df6f0ea79 --- /dev/null +++ b/docs/guides/tables.md @@ -0,0 +1,1343 @@ +--- +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](#striped-rows-and-columns), 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], +) + +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 (`[]`). 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 pattern) 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(x: 1): set text(weight: "medium") + +// Bold titles. +#show table.cell.where(y: 0): 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], [Obstrusive], + [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], [Obstrusive], +>>> [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], [Obstrusive], +>>> [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], [Obstrusive], +>>> [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 [patterns]($pattern). 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], [Hotel de L'Europe], [Arrival, Evening River Cruise], + [2], [Paris, France], [Hotel 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], [Hotel de L'Europe], [Arrival, Evening River Cruise], + [2], [Paris, France], [Hotel 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](#striped-rows-and-columns). + +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](#striped-rows-and-columns), 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. diff --git a/docs/guides/welcome.md b/docs/guides/welcome.md index 9714518bc..c5c927828 100644 --- a/docs/guides/welcome.md +++ b/docs/guides/welcome.md @@ -11,3 +11,4 @@ propose other topics for guides! ## List of Guides - [Guide for LaTeX users]($guides/guide-for-latex-users) - [Page setup guide]($guides/page-setup-guide) +- [Table guide]($guides/table-guide) diff --git a/docs/src/lib.rs b/docs/src/lib.rs index 53221381a..765e7c7ce 100644 --- a/docs/src/lib.rs +++ b/docs/src/lib.rs @@ -167,6 +167,7 @@ fn guide_pages(resolver: &dyn Resolver) -> PageModel { page.children = vec![ md_page(resolver, &base, load!("guides/guide-for-latex-users.md")), md_page(resolver, &base, load!("guides/page-setup.md")), + md_page(resolver, &base, load!("guides/tables.md")), ]; page }