Skip to content

Embedding guide

This guide is for someone putting a rendered nf-metro map into their own page or application: a docs site, an internal dashboard, a pipeline run viewer. You do not need to read src/ to follow it. It covers how to produce an embed-friendly file, how to size and theme it from the host page, and how to drive it from live state (lighting up nodes as a job runs).

nf-metro renders two shapes, and the right one depends on what the host needs.

You wantUseWhy
A static picture (thumbnail, README, slide)renderSVGOne self-contained file; scales crisply; no scripts.
A live, interactive panel (pan/zoom, line filtering, hover)render --format htmlA self-contained page with the driver and styling already wired.
A progress overlay driven by your own appSVG + the manifestYou read the embedded manifest and draw your own status layer.

The SVG carries a machine-readable manifest and a stable data-* contract either way, so a static embed can later become an interactive one without re-rendering.

These flags shape the SVG for life inside someone else’s page. They apply to --format svg; the interactive HTML page already handles sizing, scoping, and chrome itself (see Interactive and progress embeds).

By default the <svg> carries fixed width/height attributes. With --responsive it emits only a viewBox (plus preserveAspectRatio), so the host sizes it with CSS:

Terminal window
nf-metro render pipeline.mmd -o pipeline.svg --responsive
.metro-map svg {
width: 100%;
height: auto;
}

Use this for any fluid layout. The viewBox stays 0 0 <width> <height>, so overlays built from the manifest still line up (see Progress overlays).

Font portability - --embed-font / --text-to-paths

Section titled “Font portability - --embed-font / --text-to-paths”

By default the SVG references a system font family, which renders differently (or falls back) on a host without that font. Two flags make it self-contained:

FlagWhat it doesKeeps selectable text?Trade-off
--embed-fontInlines a subset of Inter as a base64 @font-face block.Yes (and data-* on labels).Larger file.
--text-to-pathsConverts every glyph to a vector <path>.No.Smallest dependency surface; needs fonttools[woff].
Terminal window
nf-metro render pipeline.mmd -o pipeline.svg --embed-font # portable, still selectable
nf-metro render pipeline.mmd -o pipeline.svg --text-to-paths # zero font dependency

Prefer --embed-font when you want labels to stay selectable/searchable; --text-to-paths when the consumer is a strict renderer or you need pixel fidelity with no font handling at all.

--bare drops the title and the outer right padding so the canvas hugs the content, for a host that supplies its own frame and heading:

Terminal window
nf-metro render pipeline.mmd -o pipeline.svg --bare

The viewBox origin stays at 0 0 and coordinates stay absolute, so the manifest and any overlay still align. The attribution watermark is kept in bare mode (see Attribution).

Theming from the host - --nfm-* properties

Section titled “Theming from the host - --nfm-* properties”

Chrome colors (background, title, labels, section boxes, legend) are emitted as CSS custom properties with the theme color as the fallback, e.g. fill: var(--nfm-bg, #2b2b2b). A host recolors the map without re-rendering by setting these on a wrapping element:

.metro-map {
--nfm-bg: #ffffff;
--nfm-title-color: #222;
--nfm-label-color: #333;
--nfm-section-fill: #f4f4f4;
--nfm-section-stroke: #ddd;
--nfm-section-label-color: #555;
--nfm-legend-bg: #fafafa;
--nfm-legend-text-color: #333;
}
PropertyRecolors
--nfm-bgBackground rectangle
--nfm-title-colorTitle text
--nfm-label-colorStation labels
--nfm-section-fill / --nfm-section-strokeSection box fill / border
--nfm-section-label-colorSection names and group labels
--nfm-legend-bg / --nfm-legend-text-colorLegend background / text

Line and route colors are not recolorable - they carry meaning, so they stay baked as presentation attributes.

Multiple maps on one page - --svg-class-prefix

Section titled “Multiple maps on one page - --svg-class-prefix”

Two inline SVGs on the same page share class names (nf-metro-station, …), so host CSS or the dark-mode block from one can bleed into the other. Give each a distinct prefix:

Terminal window
nf-metro render a.mmd -o a.svg --svg-class-prefix mapA
nf-metro render b.mmd -o b.svg --svg-class-prefix mapB

mapA-nf-metro-station, mapB-nf-metro-station, and so on stay independent. data-* attributes and the manifest element id are never prefixed, so the contract is unchanged.

When a theme has a transparent background, the SVG injects a @media (prefers-color-scheme: dark) block so labels stay readable on a dark host page. If your host manages its own theme and that media query fights it, suppress it:

Terminal window
nf-metro render pipeline.mmd -o pipeline.svg --no-dark-mode-css

Raster export (PNG) - --mode and --no-chrome-css

Section titled “Raster export (PNG) - --mode and --no-chrome-css”

Two independent settings control correct PNG output:

Palette (--mode) - always pass --mode light or --mode dark explicitly. Without it the default palette is used, which may not match your intent. It also pins color-scheme on the SVG root so CSS-aware rasterizers resolve light-dark() to the right values regardless of the host OS color scheme.

CSS variables (--no-chrome-css) - the --nfm-* properties above use CSS var(), which many rasterizers (including cairosvg) cannot parse and abort on. Add --no-chrome-css to bake the concrete theme colors instead (the map looks identical; you just lose live host recoloring):

Terminal window
nf-metro render pipeline.mmd -o pipeline.svg --no-chrome-css --mode light
python -c "import cairosvg; cairosvg.svg2png(url='pipeline.svg', write_to='pipeline.png', scale=2)"

For a CSS-custom-property-aware rasterizer (resvg, rsvg-convert, headless Chromium) skip --no-chrome-css - those tools resolve var() and light-dark() natively - but still pass --mode to pin the palette:

Terminal window
nf-metro render pipeline.mmd -o pipeline.svg --mode light
resvg pipeline.svg pipeline.png

Everything in an nf-metro SVG lives in one coordinate space: viewBox="0 0 w h" with no outer transform. That is what makes the host’s job simple:

  • Size the SVG with CSS (width: 100%; height: auto) - use --responsive so there are no fixed dimensions to override.
  • Stack a base render and an overlay by giving both the same viewBox and absolutely positioning them in the same box. Because coordinates are absolute and share the origin, a marker the overlay draws at a node’s manifest (x, y) lands exactly on that node.
<div class="metro-map" style="position: relative;">
<!-- base render, sized by CSS -->
<object data="pipeline.svg" type="image/svg+xml" style="width:100%;"></object>
<!-- overlay, same viewBox, on top -->
<svg
viewBox="0 0 1509 759"
style="position:absolute; inset:0; width:100%; pointer-events:none;"
>
<!-- status markers at manifest coordinates -->
</svg>
</div>

The manifest’s width/height fields give the exact viewBox to reuse.

The stable surface a host depends on is documented in one authoritative place each - this guide links to them rather than restating them:

  • Embed contract - the data-node-* / data-station-* / data-section-* attribute vocabulary and the driver API (attachMetroMap, highlightLine, selectNode, getManifest, …).
  • Data manifest - the manifest JSON schema, its version, the matching semantics (patterns → runtime names), and the overlay_svg helper.

The join key across all of it is the node id: it equals data-node-id on the drawn element and node.id in the manifest JSON.

The minimum to put a map on a page. Render a portable, fluid SVG and inline it:

Terminal window
nf-metro render pipeline.mmd -o pipeline.svg --responsive --embed-font
<div class="metro-map" style="max-width: 1000px;">
<!-- paste the contents of pipeline.svg here, or: -->
<object data="pipeline.svg" type="image/svg+xml" style="width:100%;"></object>
</div>

GitHub READMEs strip <script>, so a static SVG is the right choice there. Most static-site generators and wikis accept the inline SVG as-is.

render --format html produces a complete page - SVG, driver, and styling inlined, no network. Its Embed… modal offers an inline <div> snippet (keeps interactivity, no iframe), an iframe one-liner, and a static-SVG fallback. The page is already responsive and scopes each map independently, so the SVG-only sizing/namespacing flags above do not apply to it (the CLI warns if you pass them with --format html). Font portability does reach the inlined SVG, so an embeddable page can carry its own fonts:

Terminal window
nf-metro render pipeline.mmd --format html -o pipeline.html --embed-font

To wire the driver onto a page yourself (rather than copy the modal snippet), see the driver API and nf-metro embed-script.

To light up nodes as a pipeline runs, keep the base map static and redraw a thin overlay layer on each state change. The base SVG is the durable map; the overlay is a cheap, disposable status layer. The coordinate-space rules:

  • The base SVG and overlay share viewBox="0 0 w h" (origin 0 0).
  • The manifest’s width/height match the base render’s dimensions.
  • Each node’s x/y/r are absolute units in that space, so an overlay marker at (x, y) lands on the node.

The recipe is always the same three steps: read_manifest the committed SVG, match_node_ids each runtime event to a node, and redraw an overlay_svg() status layer over the base. The manifest tutorial, Light up a diagram as a job runs, walks it end to end in ~50 lines of Python (with the matching semantics and the node state model documented alongside it on the Data manifest page).

For a ready-made server that does exactly this for a live Nextflow run - no code to write - see Live progress.

The manifest schema and the driver contract are versioned independently, both 1.0 today. The stable surface keyed to those versions - the data-* attribute names, the manifest fields, the 0 0 w h coordinate rule, and the driver method names - and the major.minor rules for changing it are specified under Versioning on the Embed contract page. This surface is stable as of nf-metro 1.0: within a major version it only grows in backward-compatible ways, so consumers must ignore unknown fields. Pin to a specific nf-metro release only if you depend on the exact bytes of the output.