Overview
Ninetails includes an interactive Shiny dashboard for exploring analysis results. The dashboard provides six tabs for classification overview, residue composition, poly(A) length distribution, raw signal visualization, configurable report download, and package information.
The dashboard supports two usage modes:
- Single-sample mode — provide file paths directly; all tabs are available when classification and residue data are supplied.
- Multi-sample mode — provide a YAML configuration file pointing to multiple samples; enables comparative analysis across samples and experimental groups.
Requirements
The dashboard requires several optional packages. Install them before first use:
install.packages(c("shiny", "plotly", "htmltools", "DT", "base64enc"))
# For multi-sample mode (YAML config):
install.packages("yaml")
# For rug density plots (position distribution):
install.packages("cowplot")The Signal Viewer tab additionally requires a working Python
environment with the pod5 package, accessible via
reticulate. See the installation
guide for details.
Quick start
Single-sample mode
The simplest way to launch the dashboard with results from a single ninetails run:
ninetails::launch_signal_browser(
summary_file = "/path/to/dorado_summary.txt",
pod5_dir = "/path/to/pod5/",
class_file = "/path/to/read_classes.txt",
residue_file = "/path/to/nonadenosine_residues.txt"
)All four arguments are optional. When class_file and
residue_file are provided, the Classification, Residues,
and Poly(A) length tabs become active. When summary_file
and pod5_dir are provided, the Signal Viewer tab enables
raw signal visualization.
If only signal data paths are provided (no class_file),
only the Signal Viewer tab is functional:
ninetails::launch_signal_browser(
summary_file = "/path/to/dorado_summary.txt",
pod5_dir = "/path/to/pod5/"
)Multi-sample mode
For comparative analysis across multiple samples and experimental conditions, prepare a YAML configuration file and launch with:
ninetails::launch_signal_browser(
config = "/path/to/config.yml"
)YAML configuration file
The YAML configuration file defines samples, experimental groups, and
file paths. Each sample entry must include class_path and
residue_path (paths to ninetails output files). The
dorado_summary and pod5_dir fields are
optional and only needed for the Signal Viewer tab.
Format
samples:
KO_rep1:
sample_name: KO_rep1
group: KO
class_path: /path/to/KO_rep1/read_classes.txt
residue_path: /path/to/KO_rep1/nonadenosine_residues.txt
dorado_summary: /path/to/KO_rep1/dorado_summary.txt # optional
pod5_dir: /path/to/KO_rep1/pod5/ # optional
KO_rep2:
sample_name: KO_rep2
group: KO
class_path: /path/to/KO_rep2/read_classes.txt
residue_path: /path/to/KO_rep2/nonadenosine_residues.txt
WT_rep1:
sample_name: WT_rep1
group: WT
class_path: /path/to/WT_rep1/read_classes.txt
residue_path: /path/to/WT_rep1/nonadenosine_residues.txt
dorado_summary: /path/to/WT_rep1/dorado_summary.txt
pod5_dir: /path/to/WT_rep1/pod5/Field reference
| Field | Required | Description |
|---|---|---|
sample_name |
Yes | Display name used in plots and legends |
group |
Yes | Experimental condition for grouping |
class_path |
Yes | Path to read_classes.txt from ninetails |
residue_path |
Yes | Path to nonadenosine_residues.txt from ninetails |
dorado_summary |
No | Path to Dorado summary file (for Signal Viewer) |
pod5_dir |
No | Path to POD5 file directory (for Signal Viewer) |
A config template is included in the package:
system.file("extdata", "config_template.yml", package = "ninetails")Dashboard tabs
Classification
The first tab provides an overview of the analysis results.
Value boxes at the top display five metrics in a row of equal-width cards, each with a colored border and matching label: number of samples analyzed, number of transcripts found, total read count, number of blank reads, and number of decorated reads.
Read Classification plot shows the distribution of read categories (decorated, blank, unclassified). Three views are available via the “Classification view” dropdown:
- Summary (N) — simplified view with main categories
- Detailed (R) — expanded view with all comment codes (YAY, MPU, MAU, IRL, BAC, UNM)
- Decorated (A) — decorated reads only
Non-A Abundance plot shows the frequency of reads
containing one, two, or three or more separate non-adenosine residues
per read (plot_nonA_abundance()).
When the browser window is wide enough (≥1200px), both plots are displayed side-by-side in a responsive flex layout. On narrower screens they stack vertically.
Sidebar controls:
-
Grouping variable — choose between
sample_nameorgroup(detected dynamically from the data) -
Filter by transcript — server-side searchable dropdown for
transcript-level filtering (uses
symbolif annotated, otherwisecontig) - Show as frequency — toggle between raw counts and proportions
- Show descriptions — toggle explanatory text below each plot, including classification code definitions (YAY, MPU, MAU, IRL, BAC, UNM)
Residues
The Residues tab provides three views of non-adenosine residue composition.
Residue Counts plot shows the distribution of C, G,
and U residues (plot_residue_counts()). Toggle between
counting by read (each read counted once per residue type) or by residue
(total occurrences).
Non-A Position Distribution displays rug density
plots showing where non-adenosine residues are located along the poly(A)
tail relative to tail length (plot_rug_density()). One row
of three plots (C, G, U) is generated per sample. To keep the display
readable and rendering fast, each residue type is subsampled to a
maximum of 1,000 points per sample (without replacement). If fewer than
1,000 points exist for a given residue type in a sample, all available
points are plotted. If a residue type is absent from a sample, a “No X
residues” placeholder is shown.
Summary Table (when merged data is available)
provides per-transcript statistics including read counts, non-A counts
and hits, and poly(A) length metrics (summarize_nonA()).
The table is searchable and sortable via DataTables.
Sidebar controls:
-
Grouping variable —
sample_nameorgroup - Filter by transcript — transcript-level filter
- Show as frequency — counts vs proportions
- Count by read — toggle between by-read and by-residue counting
- Max tail length (rug plot) — controls rug density plot x-axis
- Show descriptions — toggle explanatory text below plots
Poly(A) length
The Poly(A) length tab shows poly(A) tail length distributions across
samples or groups (plot_tail_distribution()).
Sidebar controls:
-
Grouping variable —
sample_nameorgroup - Filter by transcript — transcript-level filter
- Select condition(s) — multi-select dropdown to plot only specific conditions (samples or groups). All conditions are selected by default. Remove items to compare subsets; changes take effect immediately.
- Central tendency — overlay mean, median, mode, or none
- Max tail length — upper limit for the x-axis (slider, 0–500)
- Normalized density — toggle between raw density and normalized density
- Color palette — choose from 8 built-in palettes: kanto, johto, hoenn, sinnoh, hisui, unova, kalos, alola
- Reset all filters — restores every control to its default value
- Show descriptions — toggle explanatory text below the plot
Signal Viewer
The Signal Viewer tab provides interactive visualization of raw
nanopore signals from POD5 files. It requires
dorado_summary and pod5_dir paths (either from
the launcher or from the YAML config).
In multi-sample mode, a dropdown at the top selects which sample’s signals to browse. In single-sample mode, paths are loaded automatically from the launcher arguments.
Two sub-tabs are available:
Static Viewer — renders two ggplot2 plots:
- Entire Signal — full-length squiggle with colored segments (adapter in blue, poly(A) in orange, transcript in black)
- Poly(A) Region — zoomed view centered on the poly(A) tail with ±250 positions flanking
Both plots show vertical dashed lines marking the 5’ (red) and 3’ (navy) boundaries of the poly(A) tail. When non-A residue data is available, semi-transparent rectangles highlight the estimated positions of detected modifications, colored by residue type (C, G, U) with a letter label at the top.
Dynamic Explorer — renders an interactive Plotly chart of the full signal with zoom, pan, and hover capabilities. Non-A highlights are shown as translucent rectangles. Double-click to reset the view.
Signal Viewer sidebar controls:
- Minimum poly(A) length — filter reads by tail length
- Decoration status — filter by comment code (YAY, MPU, MAU, etc.)
- Non-A residue type — show only reads with a specific residue (C, G, U)
- Alignment genome — filter by reference (if available in summary)
- Mapping quality range — MAPQ slider filter
- Select Read ID — server-side searchable dropdown with Previous/Next navigation buttons
Download
The Download tab provides a configurable report generator. The report is a self-contained HTML file with all plots embedded as base64-encoded PNG images, suitable for sharing or archiving without external dependencies.
Report sections (each controlled by a checkbox):
- Classification plot — global read classification summary
- Non-A abundance — frequency of reads with 1, 2, 3+ non-A residues
- Residue frequency — distribution of C, G, U residues
- Rug density plots — per-sample positional distribution of non-A residues with automatic downsampling to 1,000 points and annotations indicating whether downsampling was applied
- Poly(A) length distribution — density plot with configurable central tendency
- Example signal plots — for each sample with POD5 access, 5 randomly selected reads per category (blank, decorated-C, decorated-G, decorated-U) are rendered as full-width tail range plots with non-A overlay. If a category has fewer than 5 reads, all available reads are shown. Categories absent from a sample display a placeholder message.
Plot settings (apply to all poly(A) plots in the report):
- Central tendency — mean, median, mode, or none (annotated in the report)
- Max poly(A) length — upper x-axis limit
- Color palette — palette applied to poly(A) density curves
Per-transcript sections (optional, up to 3 transcripts):
Select up to three transcripts for detailed sub-reports. Each transcript section includes its own classification, residue frequency, and poly(A) length distribution plots, filtered to reads mapping to that transcript.
All plots in the report include descriptive annotations explaining what is shown, consistent with the “Show descriptions” text available in the interactive tabs.
About
The About tab displays the ninetails logo, package version, full citation (Gumińska et al., Nat Commun 2025), links to the GitHub repository, Wiki documentation, pkgdown website, and Zenodo DOI, as well as the IIMCB logo, laboratory information (Laboratory of RNA Biology, ERA Chairs Group), and developer contact details.
Annotated data
When the input data has been annotated with
annotate_with_biomart() (adding symbol and
Ensembl transcript ID columns), the dashboard automatically uses
symbol as the transcript label in all “Filter by
transcript” dropdowns. Without annotation, the raw contig
column is used instead.
This applies to both single-sample and multi-sample modes. Annotation should be performed before launching the dashboard:
# After running the ninetails pipeline:
class_data_annotated <- ninetails::annotate_with_biomart(
class_data,
species = "mmusculus"
)
residue_data_annotated <- ninetails::annotate_with_biomart(
residue_data,
species = "mmusculus"
)
# Save annotated data, then point the dashboard to the annotated filesLauncher reference
?ninetails::launch_signal_browser| Argument | Type | Description |
|---|---|---|
config |
character | Path to YAML config (multi-sample mode) |
summary_file |
character | Path to Dorado summary file |
pod5_dir |
character | Path to POD5 directory |
class_file |
character | Path to read_classes.txt
|
residue_file |
character | Path to nonadenosine_residues.txt
|
... |
Additional arguments passed to shiny::runApp()
|
When config is provided, single-sample arguments are
ignored. All arguments are optional; the dashboard adapts its active
tabs based on which data is available.
Additional shiny::runApp() arguments can be passed
through ...:
# Custom port and host
ninetails::launch_signal_browser(
config = "config.yml",
port = 8080,
host = "0.0.0.0"
)
# Suppress browser launch
ninetails::launch_signal_browser(
config = "config.yml",
launch.browser = FALSE
)Static assets
The dashboard expects three image files in
inst/app/www/:
-
logo.png— ninetails logo (copy fromman/figures/logo.png) -
favicon.ico— browser tab icon (copy frompkgdown/favicon/) -
IIMCB_logo.png— IIMCB institute logo (displayed in the About tab)
Deploying to Shiny Server
The dashboard can be deployed to Shiny Server (Open Source or Pro) for shared access over a network. A deployment wrapper is included in the package that handles data loading from a YAML configuration file and delegates rendering to the installed ninetails app.
Setup
# 1. Copy the deployment wrapper to the Shiny Server app directory
deploy_dir <- "/srv/shiny-server/ninetails"
dir.create(deploy_dir, recursive = TRUE, showWarnings = FALSE)
file.copy(
system.file("deployment", "app.R", package = "ninetails"),
file.path(deploy_dir, "app.R"),
overwrite = TRUE
)
# 2. Copy your YAML configuration
file.copy("config.yml", file.path(deploy_dir, "config.yml"))
# 3. Symlink static assets from the installed package
file.symlink(
system.file("app", "www", package = "ninetails"),
file.path(deploy_dir, "www")
)Configuring the deployment wrapper
Before launching, edit the top of the deployed app.R to
set two variables:
-
config_path— path to the YAML configuration file (default:config.ymlin the same directory asapp.R) -
PYTHON_PATH— path to the Python binary that has thepod5module installed. Required for the Signal Viewer tab. Set toNULLif signal visualization is not needed.
# In /srv/shiny-server/ninetails/app.R:
config_path <- file.path(getwd(), "config.yml")
PYTHON_PATH <- "/home/user/miniconda3/envs/r-reticulate/bin/python"Common Python path examples:
| Environment | Typical path |
|---|---|
| System Python | /usr/bin/python3 |
| Conda env | /home/user/miniconda3/envs/r-reticulate/bin/python |
| virtualenv | /home/user/.virtualenvs/ninetails/bin/python |
| pyenv | /home/user/.pyenv/versions/3.11.0/bin/python |
You can find the correct path by running which python in
the environment where pod5 is installed.
Directory structure
/srv/shiny-server/ninetails/
├── app.R # deployment wrapper (from inst/deployment/)
├── config.yml # your YAML configuration
└── www/ # symlink to inst/app/www/ in the installed package
├── logo.png
├── favicon.ico
└── IIMCB_logo.png
Requirements
- ninetails and all Suggests dependencies must be installed
system-wide (accessible to the
shinyuser) - All data file paths in
config.ymlmust be readable by theshinyuser - For the Signal Viewer tab:
-
PYTHON_PATHmust be set in the deployment wrapper - The specified Python environment must have the
pod5module installed (pip install pod5) - The
shinyuser must have execute permissions on the Python binary and read access to the POD5 files
-
After placing the files, restart Shiny Server:
The dashboard will be accessible at
http://your-server:3838/ninetails/.
Note: When you update the ninetails package, the deployed app picks up changes automatically (it sources from the installed package at runtime). To change the dataset, edit
config.ymland restart Shiny Server. To change the Python environment, editPYTHON_PATHinapp.Rand restart.
Troubleshooting
Rug density plots are empty or show an error
plot_rug_density() requires the cowplot
package. Install it with install.packages("cowplot").
Download button produces an error The report
generation requires base64enc for embedding plots as base64
images. Install with install.packages("base64enc").
Signal Viewer shows “No POD5 file found” Ensure the
pod5_dir path points to the directory containing
.pod5 files (not a parent directory). The app searches
recursively within that directory.
Signal Viewer shows “Extraction failed” The Python
pod5 package must be installed and accessible via
reticulate. Check with:
reticulate::py_module_available("pod5")Classification or Residue plots are blank If using
single-sample mode, make sure you provided class_file
and/or residue_file. These are optional arguments — without
them, the corresponding tabs show placeholder messages.
“Column more doesn’t exist” in Non-A Abundance
plot This occurs when no reads have 3 or more non-A residues.
Update plot_nonA_abundance() to the latest version which
handles missing columns after pivot.
Y-axis shows scientific notation The dashboard
disables scientific notation globally with
options(scipen = 999) at startup. If you still see
scientific notation, ensure you are running the latest version of the
app.
