Convert a Nextflow DAG into a metro map
nf-metro converts the built-in directed acyclic graph (DAG) output from Nextflow into a metro map.
Generate a Nextflow DAG
Section titled “Generate a Nextflow DAG”Nextflow exports its pipeline DAG in Mermaid format:
nextflow run my_pipeline.nf -preview -with-dag dag.mmdThe -preview flag skips execution and generates only the DAG.
The resulting file uses the Nextflow flowchart TB Mermaid syntax, which nf-metro cannot render directly but can convert.
Direct rendering
Section titled “Direct rendering”The quickest way to get a metro map is to convert and render in one step with the --from-nextflow flag.
The examples that follow show the unedited output.
Flat pipeline (no subworkflows)
Section titled “Flat pipeline (no subworkflows)”A five-process pipeline with no subworkflows:
workflow { reads_ch = Channel.of(["sample1", [file("reads/s1_1.fq.gz"), file("reads/s1_2.fq.gz")]]) reference_ch = Channel.of(file("genome.fa"))
FASTQC(reads_ch) TRIM_READS(reads_ch) ALIGN(TRIM_READS.out, reference_ch.collect()) SORT_BAM(ALIGN.out) MULTIQC(FASTQC.out.zip.mix(SORT_BAM.out.map { it[1] }).collect())}nextflow run flat_pipeline.nf -preview -with-dag dag.mmdnf-metro render dag.mmd -o pipeline.svg --from-nextflowRendered map
With no subworkflows, everything lands in a single section and the converter assigns one main line following the longest path.
Output rarely comes out tidier than this.
Pipeline with subworkflows
Section titled “Pipeline with subworkflows”A pipeline with three subworkflows (Preprocess, Alignment, Quantification) plus a standalone MultiQC process:
workflow PREPROCESS { take: reads main: FASTQC(reads) TRIMGALORE(reads) emit: reads = TRIMGALORE.out.reads fastqc_zip = FASTQC.out.zip trim_log = TRIMGALORE.out.log}
workflow ALIGNMENT { take: reads; genome; gtf main: STAR_GENOMEGENERATE(genome, gtf) STAR_ALIGN(reads, STAR_GENOMEGENERATE.out.index.collect()) SAMTOOLS_SORT(STAR_ALIGN.out.bam) SAMTOOLS_INDEX(SAMTOOLS_SORT.out.bam) emit: bam = SAMTOOLS_SORT.out.bam star_log = STAR_ALIGN.out.log}
workflow QUANTIFICATION { take: bam; gtf main: SALMON_QUANT(bam, gtf) emit: results = SALMON_QUANT.out.results}
workflow { PREPROCESS(reads_ch) ALIGNMENT(PREPROCESS.out.reads, genome_ch, gtf_ch) QUANTIFICATION(ALIGNMENT.out.bam, gtf_ch) MULTIQC(/* all logs */)}nextflow run with_subworkflows.nf -preview -with-dag dag.mmdnf-metro render dag.mmd -o pipeline.svg --from-nextflowRendered map
The converter maps each subworkflow to a section and creates a Reporting section for the standalone MultiQC. It also detects bypass lines, meaning edges that skip sections, such as QC metrics going from Preprocess straight to Reporting. Dead-end processes such as Samtools Index get spur lines.
Variant calling pipeline (diamond pattern)
Section titled “Variant calling pipeline (diamond pattern)”A pipeline where two variant callers (GATK and DeepVariant) both receive input from the same alignment step and reconverge at BCFtools Stats:
workflow VARIANT_CALLING { take: bam; bai; genome main: bam_bai = bam.join(bai) GATK_HAPLOTYPECALLER(bam_bai, genome.collect()) DEEPVARIANT(bam_bai, genome.collect()) BCFTOOLS_STATS(GATK_HAPLOTYPECALLER.out.vcf.mix(DEEPVARIANT.out.vcf)) emit: stats = BCFTOOLS_STATS.out.stats}
workflow { PREPROCESS(reads_ch) ALIGNMENT(PREPROCESS.out.reads, genome_ch) VARIANT_CALLING(ALIGNMENT.out.bam, ALIGNMENT.out.bai, genome_ch) MULTIQC(/* all logs */)}nextflow run variant_calling.nf -preview -with-dag dag.mmdnf-metro render dag.mmd -o pipeline.svg --from-nextflowRendered map
The Variant Calling section fans out to the two callers and back in again without further editing.
Hand-tune the output
Section titled “Hand-tune the output”For anything beyond a toy pipeline, the two-step process gives better results.
Convert, edit the .mmd, then render:
nf-metro convert dag.mmd -o pipeline.mmd --title "My Pipeline"# edit pipeline.mmdnf-metro render pipeline.mmd -o pipeline.svgThe converter produces this for the variant calling pipeline:
%%metro title: Preprocess / Alignment / Variant Calling Pipeline%%metro style: nfcore%%metro line: main | Main | #2db572%%metro line: preprocess_reporting | Preprocess - Reporting | #0570b0
graph LR subgraph preprocess [Preprocess] fastqc([Fastqc]) fastp([Fastp]) end
subgraph alignment [Alignment] bwa_index([Bwa Index]) bwa_mem([Bwa Mem]) samtools_sort([Samtools Sort]) samtools_index([Samtools Index])
bwa_index -->|main| bwa_mem bwa_mem -->|main| samtools_sort samtools_sort -->|main| samtools_index end
subgraph variant_calling [Variant Calling] gatk_haplotypecaller([Gatk Haplotypeca]) deepvariant([Deepvariant]) bcftools_stats([Bcftools Stats])
gatk_haplotypecaller -->|main| bcftools_stats deepvariant -->|main| bcftools_stats end
subgraph reporting [Reporting] multiqc([Multiqc]) end
%% Inter-section edges bcftools_stats -->|main| multiqc fastp -->|main| bwa_mem samtools_sort -->|main| gatk_haplotypecaller samtools_sort -->|main| deepvariant samtools_index -->|main| gatk_haplotypecaller samtools_index -->|main| deepvariant fastqc -->|preprocess_reporting| multiqc fastp -->|preprocess_reporting| multiqcCleaning up the labels, renaming the bypass line, and adding a proper title gives this:
Mermaid source
%%metro title: Variant Calling Pipeline%%metro style: nfcore%%metro line: main | Main | #2db572%%metro line: qc | QC Reporting | #0570b0
graph LR subgraph preprocess [Pre-processing] fastqc[FastQC] fastp[FastP] end
subgraph alignment [Alignment] bwa_index[BWA Index] bwa_mem[BWA-MEM] samtools_sort[SAMtools Sort] samtools_index[SAMtools Index]
bwa_index -->|main| bwa_mem bwa_mem -->|main| samtools_sort samtools_sort -->|main| samtools_index end
subgraph variant_calling [Variant Calling] gatk[GATK HaplotypeCaller] deepvariant[DeepVariant] bcftools[BCFtools Stats]
gatk -->|main| bcftools deepvariant -->|main| bcftools end
subgraph reporting [Reporting] multiqc[MultiQC] end
%% Inter-section edges fastp -->|main| bwa_mem samtools_sort -->|main| gatk samtools_sort -->|main| deepvariant samtools_index -->|main| gatk samtools_index -->|main| deepvariant bcftools -->|main| multiqc fastqc -->|qc| multiqc fastp -->|qc| multiqcRendered map
The changes are small, but the diagram reads better for them: proper casing on labels (BWA-MEM, SAMtools, GATK HaplotypeCaller), a meaningful line name (“QC Reporting” rather than “Preprocess - Reporting”), and a clearer title.
See the Guide for the full .mmd format reference.
Add file icons
Section titled “Add file icons”Document icons mark where data enters and leaves a Nextflow pipeline diagram.
The %%metro file: directive pairs a station ID with a label.
When that station has a blank label ([ ]), it renders as a document icon instead of a pill-shaped station marker.
Starting from the hand-tuned variant calling example earlier, three things change:
-
Add
%%metro file:directives at the top of the file, one per file terminus:%%metro file: fastq_in | FASTQ%%metro file: ref_in | FASTA%%metro file: vcf_out | VCF%%metro file: report_out | HTML -
Add blank terminus stations (
[ ]) at the input and output points of your pipeline. The station ID must match the%%metro file:directive:fastq_in[ ] -
Connect them to the pipeline with normal edges:
fastq_in -->|main,qc| fastp
The full .mmd with file icons added:
Mermaid source
%%metro title: Variant Calling Pipeline%%metro style: nfcore%%metro file: fastq_in | FASTQ%%metro file: ref_in | FASTA%%metro file: vcf_out | VCF%%metro file: report_out | HTML%%metro line: main | Main | #2db572%%metro line: qc | QC Reporting | #0570b0
graph LR subgraph preprocess [Pre-processing] fastq_in[ ] fastqc[FastQC] fastp[FastP] fastq_in -->|main,qc| fastp fastq_in -->|qc| fastqc end
subgraph alignment [Alignment] ref_in[ ] bwa_index[BWA Index] bwa_mem[BWA-MEM] samtools_sort[SAMtools Sort] samtools_index[SAMtools Index]
ref_in -->|main| bwa_index bwa_index -->|main| bwa_mem bwa_mem -->|main| samtools_sort samtools_sort -->|main| samtools_index end
subgraph variant_calling [Variant Calling] gatk[GATK HaplotypeCaller] deepvariant[DeepVariant] bcftools[BCFtools Stats] vcf_out[ ]
gatk -->|main| bcftools deepvariant -->|main| bcftools bcftools -->|main| vcf_out end
subgraph reporting [Reporting] multiqc[MultiQC] report_out[ ] multiqc -->|qc| report_out end
%% Inter-section edges fastp -->|main| bwa_mem samtools_sort -->|main| gatk samtools_sort -->|main| deepvariant samtools_index -->|main| gatk samtools_index -->|main| deepvariant bcftools -->|qc| multiqc fastqc -->|qc| multiqc fastp -->|qc| multiqcRendered map
The FASTQ icon at the start of Pre-processing and the FASTA icon at the start of Alignment show where data enters the pipeline. The VCF icon at the end of Variant Calling and the HTML icon in Reporting show where the pipeline writes results. A reader who has never seen the pipeline can now tell what goes in and what comes out.
For a more complex example with multiple file icons, see the nf-core/rnaseq diagram at examples/rnaseq_sections.mmd, which uses FASTQ input icons and HTML report output icons across several sections.
Two more icon variants cover common Nextflow patterns:
%%metro files:renders a stacked-documents icon for paired-end reads or multi-file inputs, as in%%metro files: reads_in | FASTQ.%%metro dir:renders a folder icon for directory outputs such aspublishDirresults, as in%%metro dir: results_out | Results.
Add banner as a fourth field to a file: or files: directive to draw the format label on a dark strip across the icon.
Use it when the format should stand out, as in %%metro files: aln_out | BAM | Alignments | banner.
Converter internals
Section titled “Converter internals”The -with-dag output from Nextflow contains three types of node: processes (the pipeline steps), channels and values (data plumbing), and operators (Nextflow internals such as mix and collect).
The raw DAG for the flat pipeline example looks like this:
flowchart TB subgraph " " v0["Channel.of"] v1["Channel.of"] end v2(["FASTQC"]) v4(["TRIM_READS"]) v6(["ALIGN"]) v7(["SORT_BAM"]) v11(["MULTIQC"]) v5(( )) v8(( )) v0 --> v2 v0 --> v4 v1 --> v5 v4 --> v6 v5 --> v6 v6 --> v7 v7 --> v8 v2 --> v8 v8 --> v11The converter:
-
Drops non-process nodes. It removes channel nodes (
v0["Channel.of"]), value nodes (v1["Channel.of"]), and operator nodes (v5(( )),v8(( ))). It keeps only stadium-shaped process nodes such asv2(["FASTQC"]). -
Reconnects edges. It stitches edges that ran through a dropped node back together, turning
SORT_BAM --> v8 --> MULTIQCintoSORT_BAM --> MULTIQCandFASTQC --> v8 --> MULTIQCintoFASTQC --> MULTIQC. -
Breaks cycles. The nf-metro layout engine requires a DAG. A process graph that loops therefore cannot be drawn edge for edge. The converter removes a deterministic set of back edges, including any process that reaches itself. It reports every removed connection on stderr and lists them in a
%%comment block at the end of the output. You can then redraw a loop by hand rather than lose it silently.%% Feedback removed: the process graph loops back here, and nf-metro's%% layout requires a DAG, so these connections were left out of the map.%% Each one may run through channel or operator nodes rather than being%% a single declared edge.%% polish (Polish) -> assemble (Assemble) -
Maps subworkflows to sections. Nextflow subworkflows become nf-metro
subgraphsections. The converter groups processes that belong to no subworkflow into generated sections. -
Assigns metro lines. The longest path gets the
mainline. Edges that skip sections get their own bypass lines, and dead-end processes get spur lines. -
Cleans up labels.
SCREAMING_SNAKE_CASEbecomesTitle Case, and the converter abbreviates long names.
For this example, the result is:
%%metro title: Pipeline%%metro style: nfcore%%metro line: main | Main | #2db572
graph LR subgraph pipeline [Pipeline] fastqc([Fastqc]) trim_reads([Trim Reads]) align([Align]) sort_bam([Sort Bam]) multiqc([Multiqc])
fastqc -->|main| multiqc trim_reads -->|main| align align -->|main| sort_bam sort_bam -->|main| multiqc end