---
title: "Writing metro maps"
description: Step-by-step guide to the nf-metro input format — lines, stations, sections, and directives.
---

import Metro from "@components/Metro.astro";

nf-metro turns text descriptions of pipelines into metro-map-style diagrams. Input files use a subset of [Mermaid](https://mermaid.js.org/) `graph LR` syntax, extended with `%%metro` directives for colors, sections, and layout control.

This guide builds up the format step by step, starting from a flat list of stations and finishing with a multi-section pipeline that fans out, changes direction, and reconverges.

## 1. Stations, lines, and edges

The simplest metro map needs three things: **lines** (colored routes), **stations** (pipeline steps), and **edges** (connections that carry lines between stations).

<Metro src="examples/guide/01_minimal.mmd" />

A few things to notice:

- **`%%metro line:`** defines a route as `id | Display Name | #hexcolor` with an optional fourth field for style (`solid`, `dashed`, or `dotted`). Every edge must reference one of these IDs.
- **`graph LR`** starts the Mermaid graph. nf-metro always uses left-to-right flow at the top level.
- **Stations** use Mermaid node syntax: `node_id[Label]`.
- **Edges** carry a line ID: `source -->|line_id| target`. An edge can carry multiple lines at once: `a -->|line1,line2| b`.

:::tip[Declare each station on its own line]
Give every station one labelled line of its own (`fastqc[FastQC]`) and use **bare ids** in edges (`fastqc -->|qc| multiqc`). Because most stations are shared by several edges, this keeps each label in exactly one place, and it gives `%%metro` directives (`file:`, `marker:`, `off_track:`, ...) a clear node to anchor to. nf-metro also accepts inline-shaped endpoints (`fastqc[FastQC] -->|qc| multiqc[MultiQC]`) for Mermaid compatibility, but avoid them in committed maps: a station can only carry its inline label on one of its edges, so mixing the two styles fragments where a label lives.
:::

Without sections, all stations sit on a single track. That works for simple pipelines, but real workflows have logical groupings.

## 2. Grouping stations into sections

Sections wrap related stations in visual boxes using Mermaid `subgraph` blocks. This makes the diagram easier to read and lets the layout engine route lines between groups automatically.

<Metro src="examples/guide/02_sections.mmd" />

There is one important rule: **edges between stations in different sections must go outside all `subgraph`/`end` blocks.** The three inter-section edges at the bottom of the file connect Pre-processing to Analysis and Reporting.

nf-metro places sections on a grid automatically based on their dependencies. It also creates port connections at section boundaries and junction stations where lines diverge.

### Packing sections into a shared column

When you place sections by hand with `%%metro grid: <section> | <col>,<row>`, each cell normally holds one section, and every cell in a column is as wide as the column's widest section. To pack two short sections into the horizontal space a single wide section would take, name several comma-separated sections in one cell:

```
%%metro grid: gatk, variant_calling | 1,0
%%metro grid: realign, reporting     | 1,1
```

The named sections share that cell and pack side-by-side along the flow axis (right-to-left for an `RL` row). The cell is as wide as its members combined, and the column is as wide as its widest cell, so a short+long pair in one row lines up top-to-bottom with a long+short pair packed into the same column below it:

<Metro src="examples/topologies/multi_section_cell.mmd" mmd={false} />

This keeps a folded pipeline compact and frees the cleared corner (here the bottom-left) for the legend. Each member keeps its own direction, ports, and internal layout; only their placement is shared.

## 3. Fan-out and fan-in

When lines diverge from a shared section into separate analysis paths and then reconverge, nf-metro stacks the target sections vertically and routes each line to its destination:

<Metro src="examples/guide/03_fan_out.mmd" />

Each line takes a different route through its own analysis section, then all three reconverge at annotation. The layout engine handles junction creation, vertical stacking, and routing automatically. You don't need to specify any positions or port sides.

### Same-line convergence (fan-in merge)

When optional processing steps mean the **same line** can reach a destination from multiple sources, nf-metro consolidates the overlapping routes. One bypass carries the full path (the "trunk"), and closer sources drop down to join it:

<Metro src="examples/guide/03b_fan_in_merge.mmd" />

The key pattern: every section sends `main` not just to the next step, but to **all** subsequent sections. This creates convergent same-line edges at the sink's entry port. The layout engine detects this and routes a single trunk bypass from the farthest source, with branches dropping down to join it from intermediate sections.

## 4. Section directions

By default every section flows left-to-right (`LR`). You can change a section's internal flow direction with `%%metro direction:` to create more compact or visually interesting layouts.

This example adds a top-to-bottom (`TB`) section that acts as a vertical connector between the fan-out analysis paths and the final reporting section:

<Metro src="examples/guide/04_directions.mmd" />

The Post-processing section flows top-to-bottom, collecting the RNA and DNA lines from the sections above and below, then handing them off horizontally to Reporting. The only change from a normal section is the single `%%metro direction: TB` directive.

The available directions are:

- **`LR`** (default) -- left to right
- **`TB`** -- top to bottom, useful for vertical connector sections
- **`RL`** -- right to left, used automatically by the layout engine for serpentine folds in long pipelines

## 5. File input and output icons

Real pipeline diagrams benefit from showing where data enters and leaves. The `%%metro file:` directive marks a station as a file terminus, rendering it as a document icon instead of a regular station marker.

Two things are needed:

1. A **`%%metro file:`** directive at the top of the file, mapping a station ID to a label:

   ```metro
   %%metro file: reads_in | FASTQ
   %%metro file: report_out | HTML
   ```

2. A **blank terminus station** (`[ ]`) inside a section, whose ID matches the directive:

   ```text
   reads_in[ ]
   ```

The blank label tells nf-metro to render the document icon (with the label from the directive) instead of a pill-shaped station. Connect it to the pipeline with normal edges like any other station.

<Metro src="examples/guide/05_file_icons.mmd" />

The FASTQ icon at the start of the Analysis section shows the pipeline input. The HTML icon at the end of Reporting shows where the QC report is written. Common labels include FASTQ, BAM, VCF, HTML, and CSV, but you can use any short string.

For a complex real-world example using file icons, see [`examples/rnaseq_sections.mmd`](https://github.com/seqeralabs/nf-metro/blob/main/examples/rnaseq_sections.mmd).

### Paired/multiple files

When a station represents paired input files (e.g. paired-end FASTQ reads), use `%%metro files:` instead of `%%metro file:`. This renders a stacked-documents icon to visually distinguish it from a single file:

```metro
%%metro files: reads_in | FASTQ
```

<Metro src="examples/guide/05c_files_icon.mmd" mmd={false} />

### Folder icons

For stations that represent a directory of output files rather than a single file, use `%%metro dir:`:

```metro
%%metro dir: results_out | Results
```

<Metro src="examples/guide/05d_folder_icon.mmd" mmd={false} />

All three directives (`file:`, `files:`, `dir:`) work the same way: pair a station ID with a label, give the station a blank label (`[ ]`), and connect it with normal edges. The only difference is the rendered icon shape.

### Naming an icon

The label inside an icon is meant for a short type chip (e.g. `CSV`, `FASTQ`). To attach a human-readable name without overlapping the chip, add an optional third field to the directive:

```metro
%%metro file: samples_in | CSV | Samples
%%metro file: contrasts_in | YAML | Contrasts
```

The name is rendered as a caption directly below the icon. If multiple labels are listed (`FASTQ, BAM`), the same name applies to all of them.

### Banner labels

To make a format stand out, add `banner` as a fourth field to a `file:` or `files:` directive. The format label then renders as bold white text on a dark strip across the lower part of the icon, transit-map "format chip" style, while the white document stays visible:

```metro
%%metro files: aln_out | BAM | Alignments | banner
```

<Metro src="examples/guide/05f_banner_labels.mmd" mmd={false} />

Any `name` caption (third field) still renders below the icon, so `| <name> | banner` keeps the caption and `| | banner` applies the strip with no caption. `banner` is not supported on `dir:` (folder) icons.

## 6. Per-station markers

By default every station is drawn as a uniform pill. The `%%metro marker:` directive overrides one station's marker so it can encode a tool attribute - mandatory vs optional, hardware-accelerated, expanded in another diagram - through its shape and fill:

```metro
%%metro marker: node_id | shape, fill
```

- **`shape`** is `circle` (fully rounded), `square` (sharp corners), or `pill` (a flat-edged capsule running along the line, handy for flagging a step whose detail is shown in a separate diagram). Every shape still spans the line bundle, so it covers all the lines passing through the station.
- **`fill`** is `open` (a hollow marker in the background colour), `solid` (the default station fill), or any literal colour - a name (`red`) or hex (`#4CAF50`).

`shape` defaults to `circle` and `fill` to `solid`, so `%%metro marker: node_id |` gives a solid circle. The directive may appear before or after the node is defined.

To explain the markers, add a key below the line legend with `%%metro marker_legend:`, one row per shape/fill combination:

```metro
%%metro marker_legend: shape, fill | Caption
```

Here a two-line variant-calling pipeline uses square (mandatory), open-circle (optional), pill (expanded elsewhere) and coloured-square (hardware-accelerated) markers, with a matching key:

<Metro src="examples/marker_styles.mmd" />

Stations with no `%%metro marker:` keep the default pill, so the feature is entirely opt-in.

## 7. Hidden stations

Sometimes you need a branching or merging point in the graph that doesn't represent a real pipeline step. For example, lines might diverge at a point where no tool is actually run. Adding a visible station there clutters the diagram with a meaningless marker.

**Any station whose ID starts with `_` (underscore) is hidden.** It participates in layout and routing (lines pass through it), but no marker or label is rendered.

Here is a pipeline with a visible `branch` station that serves only as a fork point:

<Metro src="examples/guide/06a_without_hidden.mmd" />

The "Branch" station is real in the graph but meaningless in the pipeline. Renaming it to `_branch` hides it:

```metro ins={2,5,6,9}
    subgraph processing [Processing]
        _branch
        align[Alignment]
        ...
        _branch -->|dna,rna| align
        _branch -->|prot| search
    end

    validate -->|dna,rna,prot| _branch
```

<Metro src="examples/guide/06b_with_hidden.mmd" mmd={false} />

The lines still fork at the same point, but there is no marker or label. This gives you fine control over where splits happen without adding a fake step to the diagram.

Use `--debug` to see hidden stations as dashed circles: `nf-metro render --debug pipeline.mmd -o debug.svg`

## 8. Putting it all together

The nf-core/rnaseq example at [`examples/rnaseq_auto.mmd`](https://github.com/seqeralabs/nf-metro/blob/main/examples/rnaseq_auto.mmd) combines all of these patterns in a real-world pipeline:

<Metro src="examples/rnaseq_auto.mmd" mmd={false} />

Five analysis routes share preprocessing, fan out to different aligners, reconverge at post-processing (a `TB` section), and fold back through QC (an `RL` section that creates a serpentine return path). The layout engine infers section directions, grid positions, and port sides automatically from the graph topology.

The nf-core/variantbenchmarking example at [`examples/variantbenchmarking_auto.mmd`](https://github.com/seqeralabs/nf-metro/blob/main/examples/variantbenchmarking_auto.mmd) shows a different topology pattern - seven lines converging at a benchmarking section, with the layout engine automatically splitting into two rows:

<Metro src="examples/variantbenchmarking_auto.mmd" mmd={false} />

See the [Gallery](/nf-metro/gallery/) for more rendered examples.

---

## Directive reference

### Global directives

These go at the top of the file, before `graph LR`.

| Directive                                                                  | Description                                                                                                                                                                                                                                                                                                                                                                                            |
| -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `%%metro title: <text>`                                                    | Map title                                                                                                                                                                                                                                                                                                                                                                                              |
| `%%metro caption: <text>`                                                  | Free-text caption or attribution line rendered bottom-left of the map (e.g. `Adapted from Author et al., Journal (Year)`).                                                                                                                                                                                                                                                                             |
| `%%metro logo: <path>`                                                     | Logo image, bundled into the legend (or top-left if there is no legend). The path is resolved relative to the `.mmd` file's directory; an unresolvable path is an error.                                                                                                                                                                                                                               |
| `%%metro logo_scale: <factor>`                                             | Scale the logo within the legend block (`1.0` = default auto-size). Values above 1 grow the legend box to contain the logo.                                                                                                                                                                                                                                                                            |
| `%%metro style: <name>`                                                    | Theme: `dark` (default, the nfcore theme) or `light`. Selects the render theme unless `--theme` is passed.                                                                                                                                                                                                                                                                                             |
| `%%metro line: <id> \| <name> \| <color> [\| <style>]`                     | Define a metro line. Optional style: `solid` (default), `dashed`, or `dotted`                                                                                                                                                                                                                                                                                                                          |
| `%%metro grid: <sections> \| <col>,<row>[,<rowspan>[,<colspan>]]`          | Pin a section to a grid position. The first field may name several comma-separated sections; they share one cell and pack side-by-side along the flow axis, so a short+long pair aligns top-to-bottom with a long+short pair in the same column below it.                                                                                                                                              |
| `%%metro legend: <position>`                                               | Position the legend (and its embedded logo). Keyword: `tl`, `tr`, `bl`, `br`, `bottom`, `right`, or `none` (a bare keyword auto-relocates if it would overlap a section or route). Add `\| canvas` to anchor the keyword to the canvas margin, or `\| <dx>,<dy>` to nudge it; both pin the block exactly (warning on overlap rather than relocating). Use `<x>,<y>` for absolute top-left coordinates. |
| `%%metro line_order: <strategy>`                                           | Line ordering for track assignment: `definition` (default, preserves `.mmd` order) or `span` (longest-spanning lines get inner tracks)                                                                                                                                                                                                                                                                 |
| `%%metro diamond_style: <mode>`                                            | Fork-join (diamond) layout: `straight` (default) keeps the top branch on the main track; `symmetric` fans the branches evenly                                                                                                                                                                                                                                                                          |
| `%%metro fold_threshold: <columns>`                                        | Max station-columns a section row may reach before the auto-layout wraps it onto the next row (default 15). Raise it to keep a long horizontal trunk of sections on a single row.                                                                                                                                                                                                                      |
| `%%metro track_gap: <pixels>`                                              | Visual gap between adjacent line strokes in a bundle (0–3 px, edge to edge). `0` means lines touch with no gap between them; default is 1 px (the built-in 4 px centre-to-centre minus the 3 px stroke). CLI equivalent `--track-gap`.                                                                                                                                                                 |
| `%%metro x_spacing: <pixels>`                                              | Horizontal spacing between layers (default: auto - widened from 60 only when wide labels would collide)                                                                                                                                                                                                                                                                                                |
| `%%metro y_spacing: <pixels>`                                              | Vertical spacing between tracks (default: auto - derived from the map's content)                                                                                                                                                                                                                                                                                                                       |
| `%%metro section_x_gap: <pixels>`                                          | Horizontal gap between sections (default: 50)                                                                                                                                                                                                                                                                                                                                                          |
| `%%metro section_y_gap: <pixels>`                                          | Vertical gap between sections (default: 50)                                                                                                                                                                                                                                                                                                                                                            |
| `%%metro label_angle: <degrees>`                                           | Station-label angle (0 = horizontal). Overrides the theme default                                                                                                                                                                                                                                                                                                                                      |
| `%%metro font_scale: <factor>`                                             | Scale every text size and the label-width metrics that drive layout spacing (`1.0` = default)                                                                                                                                                                                                                                                                                                          |
| `%%metro legend_logo_gap: <pixels>`                                        | Horizontal gap between the logo and the legend entries                                                                                                                                                                                                                                                                                                                                                 |
| `%%metro width: <pixels>`                                                  | Output width in pixels (default: auto from content)                                                                                                                                                                                                                                                                                                                                                    |
| `%%metro height: <pixels>`                                                 | Output height in pixels (default: auto from content)                                                                                                                                                                                                                                                                                                                                                   |
| `%%metro animate: true`                                                    | Add animated balls traveling along the metro lines                                                                                                                                                                                                                                                                                                                                                     |
| `%%metro directional: true`                                                | Draw static chevrons along each route pointing in the flow direction (source to target). Off by default; CLI equivalent `--directional`. Marker size, spacing, opacity, and colour are theme knobs (`directional_marker_*`).                                                                                                                                                                           |
| `%%metro strict: true`                                                     | Treat a layout-invariant violation on the rendered geometry (e.g. a station pushed outside its section box) as an error that aborts the render, instead of a warning. Off by default; CLI equivalent `--strict`. See [When a layout is broken](#when-a-layout-is-broken).                                                                                                                              |
| `%%metro file: <station> \| <label> [\| <name>] [\| banner]`               | Mark a station as a file terminus with a document icon. Optional `name` renders as a caption below the icon; optional `banner` draws the label on a dark strip across the icon.                                                                                                                                                                                                                        |
| `%%metro files: <station> \| <label> [\| <name>] [\| banner]`              | Mark a station with a stacked-documents icon (e.g. paired files). Optional `name` caption; optional `banner` strip.                                                                                                                                                                                                                                                                                    |
| `%%metro dir: <station> \| <label> [\| <name>]`                            | Mark a station with a folder icon (e.g. output directory). Optional `name` caption.                                                                                                                                                                                                                                                                                                                    |
| `%%metro off_track: <station>[, <station>...]`                             | Lift the listed stations above the section's main track, anchored to their consumer (inputs) or producer (output artefacts) (see below)                                                                                                                                                                                                                                                                |
| `%%metro compact_offsets: true`                                            | Compact line offsets within stations (see below)                                                                                                                                                                                                                                                                                                                                                       |
| `%%metro center_ports: true`                                               | Centre inter-section ports on the shorter of the two connected sections, so lines enter/exit at the visual midpoint.                                                                                                                                                                                                                                                                                   |
| `%%metro line_spread: <mode>[ \| <id>...]`                                 | How lines sharing a station relate vertically (see below). `<mode>` is `bundle` (default), `centered`, or `rails`. The bare form sets the graph default; `<mode> \| sectionA, sectionB` overrides those sections.                                                                                                                                                                                      |
| `%%metro interchange: <node> \| <rail-1 lines> \| <rail-2 lines> [\| ...]` | Render a shared step as a cross-track interchange instead of a convergence point (see below). Each pipe-group is one rail (comma-separated lines bundle on it). Auto-layout infers this for fully-parallel lanes, so the directive is only needed to pin a grouping.                                                                                                                                   |
| `%%metro legend_min_height: <pixels>`                                      | Minimum legend content height in pixels (useful for single-line maps where the logo would otherwise be tiny)                                                                                                                                                                                                                                                                                           |
| `%%metro process: <station> \| <regex>`                                    | Tie a station to the Nextflow process(es) it represents, for live progress (see [Live progress](/nf-metro/live/)). The regex matches the fully-qualified process name; repeat the directive to attach several patterns to one station. Pure metadata - it never affects the rendered map.                                                                                                              |
| `%%metro auto_process: <bool>`                                             | Give every station with no explicit `process:` directive its own id as a default process pattern, so a map whose station ids already name their processes lights up live with no per-station mapping (see [Live progress](/nf-metro/live/)). Off by default; CLI equivalent `--auto-process`.                                                                                                          |
| `%%metro process_scope: <prefix>`                                          | Common fully-qualified-name prefix shared by the pipeline's processes (e.g. `NFCORE_RNASEQ:RNASEQ`). Each `process:` value is then the tail under this scope, matched literally and tolerant of intermediate subworkflow nesting (see [Live progress](/nf-metro/live/)). CLI equivalent `--process-scope`.                                                                                             |
| `%%metro manifest: <bool>`                                                 | Embed the machine-readable data manifest (the `<metadata>` block and per-node `data-node-*` attributes) in the SVG. On by default; `%%metro manifest: false` (or `--no-manifest`) emits the drawn map only.                                                                                                                                                                                            |

**Compact offsets.** By default, each line reserves a fixed vertical slot across the whole map based on its declaration order. If you define three lines, every station that carries even one of them is sized to fit all three. This keeps bundles visually consistent but wastes space when most stations only carry one or two lines.

With `%%metro compact_offsets: true`, stations are only as wide as the lines actually passing through them. A station where one line enters and a different line exits renders as a dot (zero offset) rather than a pill. This works well for maps with few lines but many stations, like the [variantbenchmarking](https://github.com/seqeralabs/nf-metro/blob/main/examples/variantbenchmarking.mmd) example.

**Off-track inputs.** Pipelines often have reference or auxiliary inputs (a FASTA, a GTF, a known-variants VCF) that feed _into_ a processing step partway through a section rather than flowing along the main route. By default such an input station would claim a line-track slot on the trunk, pushing the layout around. List its station ID in `%%metro off_track:` and nf-metro lifts it above the section's main track, dropping it down into its consumer:

```metro {3}
%%metro file: ref_in | FASTA | Reference
%%metro file: gtf_in | GTF | Annotation
%%metro off_track: ref_in, gtf_in
```

This pairs naturally with the `file:` / `files:` / `dir:` icon directives - the lifted stations are usually file terminals. The [`off_track_convergence`](https://github.com/seqeralabs/nf-metro/blob/main/examples/topologies/off_track_convergence.mmd) topology and the [differentialabundance](https://github.com/seqeralabs/nf-metro/blob/main/examples/differentialabundance.mmd) example both use it.

**Off-track outputs.** The same directive works for file _artefacts_ written part-way through a section (a `bam`/`cram` dumped after a mapping step, say). A producer-fed sink - a station with an incoming edge from an on-track step and no on-track consumer - is anchored above its **producer** rather than the section top, so the artefact hangs off the trunk right where it is written:

```metro {2}
%%metro file: bam_mapped | BAM
%%metro off_track: bam_mapped
```

The [off_track_outputs](https://github.com/seqeralabs/nf-metro/blob/main/examples/off_track_outputs.mmd) example hangs several such artefacts above a pre-processing trunk.

**Track gap.** `%%metro track_gap: <px>` sets the visual gap between adjacent line strokes in a bundle - the empty space between their edges, not their centres. The default is 1 px (the built-in 4 px centre-to-centre minus the 3 px nfcore stroke). Set it to `0` to bring the strokes flush against each other with no gap, or increase it for more breathing room between co-running lines:

<Metro src="examples/guide/07_track_gap.mmd" />

Valid range is 0–3; values above 3 cause routing problems on complex maps and are rejected.

**Line spread.** `%%metro line_spread:` controls how lines that share a station relate to each other vertically. It has three modes:

- **`bundle`** (the default) merges every line sharing a station onto a single trunk track; a line that detours to its own station dips off the trunk and back. Line base-tracks stack downward from the first line, so the shared trunk sits at the top and detours cascade below it.
- **`centered`** also merges lines onto one trunk, but balances that bundle about the midline: the shared trunk sits on the vertical centre and each line's exclusive stations distribute symmetrically above and below it, instead of the top-anchored downward cascade.
- **`rails`** keeps co-travelling lines on separate parallel rails rather than bundling them onto a trunk. Each line gets a fixed, evenly-spaced horizontal rail, and a station several lines _pass through_ renders as the classic metro interchange: a white circle on each rail the station uses, joined by a straight connector segment (the nf-core/sarek "Example analysis pathways" subway idiom). Lines converge only at a genuine single-node fan-in/out - e.g. a file terminus all lines reach - where the rails ease together with 45-degree diagonals. Station labels alternate above and below the rails so dense runs stay readable.

The bare directive sets the graph-wide default:

```metro
%%metro line_spread: rails
```

Append `| <section>, ...` to override individual sections, so one map can mix modes - a `bundle` trunk feeding a `rails` analysis panel, say:

```metro {2}
%%metro line_spread: centered
%%metro line_spread: rails | pathways
```

Here every section defaults to `centered` while `pathways` is laid out as parallel rails; ordinary section placement positions both. The [`line_spread`](https://github.com/seqeralabs/nf-metro/blob/main/examples/line_spread.mmd) example shows all three modes in one map via per-section overrides. For `rails`, inter-section edges into or out of a rail section are not yet supported - a rail section should be self-contained.

**Cross-track interchanges.** Sometimes a single step is shared by lines that otherwise run as separate parallel lanes - a tumour and a normal lane both running MarkDuplicates, say - but the lanes never actually merge. In `bundle` mode each lane has to dip off its track to touch that shared node and dip back, pinching the lines together at a point that isn't really a join. An _interchange_ renders the shared step the way a real metro map would: each lane stays straight on its own track, and the step is drawn as a connector (a knob on each rail joined by a link bar) spanning them.

Unlike `line_spread: rails`, this is per-node and works in ordinary `bundle`/`centered` layout - only the one shared step becomes an interchange; everything else stays as it was. Internally the node is expanded into one ordinary sub-station per rail, so the normal layout engine keeps each lane straight and routes it; only the glyph is special.

Auto-layout infers an interchange automatically wherever the lanes are _fully parallel_ - every line through the node has its own predecessor and its own successor, so converging them buys nothing. You only need the directive to pin a specific rail grouping (e.g. bundling two lines onto one rail), or to force an interchange where lines share a neighbour:

```metro
%%metro interchange: markduplicates | tumor | normal
```

The lanes are listed one rail per pipe-group; commas bundle several lines onto the same rail. Auto-detection deliberately abstains when two lines share a predecessor or successor (e.g. two callers feeding one merge): there the convergence is doing real work, so it is left alone. It also abstains when another lane's rail would fall between the interchange's rails, since the connector bar would then cut across that lane's stations. Interchanges are skipped inside `rails` sections, which already lay every line on its own rail. The [cross_track_interchange](https://github.com/seqeralabs/nf-metro/blob/main/examples/cross_track_interchange.mmd) example shows a shared MarkDuplicates step across parallel tumour/normal lanes.

### Section directives

These go inside `subgraph` blocks.

| Directive                          | Description                                              |
| ---------------------------------- | -------------------------------------------------------- |
| `%%metro entry: <side> \| <lines>` | Entry port hint. Sides: `left`, `right`, `top`, `bottom` |
| `%%metro exit: <side> \| <lines>`  | Exit port hint. Sides: `left`, `right`, `top`, `bottom`  |
| `%%metro direction: <dir>`         | Internal flow direction: `LR`, `RL`, or `TB`             |

Entry/exit hints tell the layout engine which side of the section box lines should enter or leave from. Most of the time you can **omit these entirely** and let the auto-layout engine figure it out. They are useful when you want lines to exit from different sides of the same section (e.g., right for some lines, bottom for others).

## CLI flags and directive precedence

Every layout and render option can be set two ways: a `%%metro` directive in the file, or the matching `nf-metro render` CLI flag. They share one precedence rule:

> **CLI flag (when passed) → `%%metro` directive → built-in default.**

Set the directive in your committed `.mmd` so the map reproduces from the file alone, and reach for the flag only to tweak a single render without editing the file. The two planes always use the same name (the flag is the kebab-cased directive); a directive and its flag are defined from one registry, so they cannot drift apart.

| Directive            | CLI flag                                     | Default           |
| -------------------- | -------------------------------------------- | ----------------- |
| `title:`             | `--title`                                    | (none)            |
| `style:`             | `--theme`                                    | `nfcore`          |
| `logo:`              | `--logo`                                     | (none)            |
| `track_gap:`         | `--track-gap`                                | 1 (4 px c-to-c)   |
| `x_spacing:`         | `--x-spacing`                                | auto              |
| `y_spacing:`         | `--y-spacing`                                | auto              |
| `section_x_gap:`     | `--section-x-gap`                            | 50                |
| `section_y_gap:`     | `--section-y-gap`                            | 50                |
| `fold_threshold:`    | `--fold-threshold`                           | auto (15)         |
| `diamond_style:`     | `--diamond-style`                            | `straight`        |
| `line_order:`        | `--line-order`                               | `definition`      |
| `center_ports:`      | `--center-ports` / `--no-center-ports`       | false             |
| `compact_offsets:`   | `--compact-offsets` / `--no-compact-offsets` | false             |
| `line_spread:`       | `--line-spread`                              | `bundle`          |
| `label_angle:`       | `--label-angle`                              | theme default (0) |
| `font_scale:`        | `--font-scale`                               | 1.0               |
| `logo_scale:`        | `--logo-scale`                               | 1.0               |
| `legend:`            | `--legend`                                   | auto              |
| `legend_min_height:` | `--legend-min-height`                        | 0                 |
| `legend_logo_gap:`   | `--legend-logo-gap`                          | auto              |
| `width:`             | `--width`                                    | auto              |
| `height:`            | `--height`                                   | auto              |
| `animate:`           | `--animate` / `--no-animate`                 | off               |
| `strict:`            | `--strict` / `--no-strict`                   | off               |

`--output`, `--format`, `--from-nextflow`, and `--debug` have no directive: they select the output target or a diagnostic overlay rather than describing the diagram.

## When a layout is broken

Some directive combinations leave the layout engine no clean way to place a
map - for example, an internally left-to-right section whose only ports are on
the top and bottom edges has no flow-aligned edge to anchor its row, so its
stations end up outside their own section box. nf-metro renders such a map
**best-effort and prints a warning to stderr** naming the problem, rather than
refusing to produce anything:

```console
$ nf-metro render broken.mmd -o broken.svg
... the settled layout violates Tier-A invariants the renderer is about to draw ...
Rendered 12 stations, 11 edges, 1 lines -> broken.svg
```

Pass `--strict` (or add `%%metro strict: true`) to turn that warning into a
hard error with a non-zero exit, so a broken map fails your build instead of
shipping a visibly-wrong diagram:

```console
$ nf-metro render broken.mmd -o broken.svg --strict
Error: the settled layout violates Tier-A invariants ...
```

`--strict` has the same meaning for `nf-metro validate --with-layout`, which
checks a map without rendering it.

## Bridge glyphs

When two distinct lines cross at a point that is neither a shared station nor a
merge/junction, nf-metro automatically draws a **bridge glyph**: a short gap in
the under-route where it passes beneath the over-route. This disambiguates a
crossing from an interchange (where a gap would mean the lines genuinely share a
node).

Bridge glyphs are computed automatically - there is no directive to enable them.
If your diagram has a visual crossing that you do not expect, check whether the
two lines genuinely share an endpoint. If they should converge, connect them
with a shared station; if the crossing is unavoidable layout-wise, the bridge
glyph is the correct rendering.

:::tip[Tips]

- **Start without sections.** Get your stations and line routing right first, then wrap groups in `subgraph` blocks.
- **Omit entry/exit hints.** The auto-layout engine infers them correctly in most cases. Only add hints when you need multi-side exits or want to override the default.
- **Use `--debug`** to see the layout internals (see below).
- **Use `nf-metro validate`** to catch errors before rendering.
- **Use `nf-metro info`** to inspect the parsed structure (sections, lines, stations, edges).

:::

## Debug mode

Add `--debug` to any render command to overlay layout internals on the diagram:

```bash frame="terminal"
nf-metro render --debug pipeline.mmd -o debug.svg
```

<Metro src="examples/rnaseq_auto.mmd" debug mmd={false} />

The overlay shows:

| Element             | Appearance           | What it tells you                                                         |
| ------------------- | -------------------- | ------------------------------------------------------------------------- |
| **Entry ports**     | Green diamonds       | Where lines enter a section, with port ID and side                        |
| **Exit ports**      | Red diamonds         | Where lines leave a section, with port ID and side                        |
| **Hidden stations** | Dashed circles       | Branch points created with `[hidden]` - invisible in normal rendering     |
| **Edge waypoints**  | Small filled circles | Intermediate routing points along each edge path                          |
| **Grid lines**      | Yellow dashed lines  | Boundaries between grid columns and rows, labeled with column/row indices |

This is useful for diagnosing routing issues, understanding why lines take a particular path, or verifying that port sides and grid positions are what you expect.
