graph TD
subgraph "Entry Points"
A["app.R"]
B["main.R"]
end
subgraph "UI Layer (app/view/)"
C["sample_input"]
D["plot_settings"]
E["filters_ui"]
F["main_panel"]
G["variant_modal"]
H["export_ui"]
end
subgraph "Logic Layer (app/logic/)"
I["data_manager"]
J["plot_gds"]
K["plot_sv"]
L["plot_cnv"]
M["validators"]
N["error_handler"]
O["variant_modal_helpers"]
end
subgraph "Style Layer (app/styles/)"
P["layout"]
end
A -->|rhino::app| B
B -->|module$server| C
B -->|module$server| D
B -->|module$server| E
B -->|module$server| F
B -->|module$server| H
F -->|uses| I
F -->|uses| J
F -->|uses| K
F -->|uses| L
I -->|calls| M
J -->|calls| N
K -->|calls| N
L -->|calls| N
G -->|uses| O
B -->|uses| P
style A fill:#fff3e0
style B fill:#fff3e0
style C fill:#e8f5e9
style D fill:#e8f5e9
style E fill:#e8f5e9
style F fill:#e8f5e9
style G fill:#e8f5e9
style H fill:#e8f5e9
style I fill:#fff3e0
style J fill:#fff3e0
style K fill:#fff3e0
style L fill:#fff3e0
style M fill:#e1f5ff
style N fill:#ffebee
style O fill:#fff3e0
style P fill:#f3e5f5
Architecture & Design
1 Rhinoverse Architecture
IMPACT-VIS is built using the Rhino framework developed by Appsilon. Rhino provides an opinionated, production-ready structure for R Shiny applications following the Rhinoverse philosophy: scalable, modular, reproducible, and professionally-engineered.
1.1 Core Rhino Tenets
- Modular Architecture: Code organized into self-contained, testable modules with clear boundaries
- Box Module System: Explicit dependency management via
box::use()(notlibrary()) - Convention Over Configuration: Standardized directory structure (
app/,tests/,renv/) - Reproducibility First: Environment locking via
renvandDockerfile - Developer Experience: Integrated testing, hot-reloading, and IDE support
1.2 Rhino vs. Traditional Shiny
| Aspect | Traditional Shiny | Rhino |
|---|---|---|
| Module System | shiny::moduleServer() only |
Shiny + Box (R-level modules) |
| Imports | library(), implicit globals |
box::use(), explicit dependencies |
| File Organization | ui.R, server.R, misc files |
Strict app/{view,logic,styles}/ structure |
| Testing | Manual or framework-agnostic | Built-in testthat integration |
| Dependency Lock | Manual or package-specific | renv.lock + Dockerfile |
Reference: Appsilon Rhino Documentation
2 Project Structure
IMPACT-VIS/
├── app.R # Entry point (calls rhino::app())
├── config.yml # Application configuration
├── dependencies.R # Package dependency declaration
├── renv.lock # Locked package versions
├── rhino.yml # Rhino configuration
├── app/
│ ├── main.R # Top-level UI/server orchestration
│ ├── view/ # UI modules (Shiny frontend)
│ │ ├── sample_input.R
│ │ ├── plot_settings.R
│ │ ├── filters_ui.R
│ │ ├── main_panel.R
│ │ ├── variant_modal.R
│ │ ├── sv_variant_modal.R
│ │ ├── cnv_variant_modal.R
│ │ ├── export_ui.R
│ │ ├── tooltip_ui.R
│ │ ├── header_panel.R
│ │ └── __init__.R
│ ├── logic/ # Business logic (data processing)
│ │ ├── data_manager.R
│ │ ├── plot_gds.R
│ │ ├── plot_sv.R
│ │ ├── plot_cnv.R
│ │ ├── validators.R
│ │ ├── sample_loader.R
│ │ ├── error_handler.R
│ │ ├── variant_modal_helpers.R
│ │ └── __init__.R
│ ├── styles/ # Layout and theme styling
│ │ ├── layout.R
│ │ ├── main.scss
│ │ └── _layout.scss
│ ├── js/ # JavaScript utilities
│ ├── static/ # Static assets (images, etc.)
│ ├── data/ # Sample data files (GDS, TSV, TXT)
│ └── __init__.R
├── tests/
│ ├── testthat.R
│ ├── testthat/
│ │ ├── test-load-gds.R
│ │ ├── test-load-sv.R
│ │ ├── test-validators.R
│ │ └── fixtures/
│ └── cypress/ # E2E tests
├── vignettes/
│ └── quick-start.Rmd
├── docs/ # Documentation (Quarto)
├── .github/workflows/
│ ├── rhino-test.yml
│ └── publish-quarto.yml
├── renv/
│ ├── activate.R
│ └── library/
├── Dockerfile
└── README.md
3 Box Module System
IMPACT-VIS uses Box for R-level modularity—think of it as an R package system embedded within your application. Each .R file is a self-contained module with explicit imports and exports, enabling true separation of concerns and preventing namespace pollution.
3.1 Why Box Instead of library()?
Problems with library(): - Pollutes global namespace with all functions - No explicit dependency declaration - Circular dependencies possible - IDE tooling limited
Box Benefits: - Only imported functions are available in scope - Dependencies explicitly declared at file top - Prevents naming conflicts - Full IDE support (autocomplete, go-to-definition) - Easier refactoring and dead-code elimination
3.2 Module Pattern: View vs. Logic
IMPACT-VIS strictly separates UI modules (app/view/) and logic modules (app/logic/). This separation is enforced by Rhino conventions.
3.2.1 Logic Modules (app/logic/*.R)
These are pure business logic with no Shiny dependencies. They export functions for data processing, validation, and plotting.
# app/logic/data_manager.R
box::use(
SeqArray,
readr,
fs,
)
box::use(
app/logic/validators,
)
# Private function (not exported)
.apply_gds_filters <- function(gds, filters) {
# On-disk filtering logic
}
#' @export
load_gds_data <- function(gds_path, num_variants = 500, filters = list()) {
gds <- SeqArray$seqOpen(gds_path, allow.duplicate = TRUE)
on.exit(SeqArray$seqClose(gds), add = TRUE)
validation_result <- validators$validate_gds_file(gds_path)
if (!validation_result$is_valid) {
warning("GDS validation failed")
return(NULL)
}
# Apply on-disk filters
.apply_gds_filters(gds, filters)
# Extract and return data
}Key Pattern: - All external functions are explicitly imported - Private functions prefixed with . - Only exported functions use #' @export - Functions are stateless (no side effects beyond return value) - Suitable for unit testing in isolation
3.2.2 UI Modules (app/view/*.R)
These define the Shiny user interface and reactive server logic. Each exports ui() and server() functions following the Shiny moduleServer pattern.
# app/view/sample_input.R
box::use(
shiny[moduleServer, NS, reactive, observeEvent],
shiny.fluent[Stack, Dropdown, PrimaryButton],
)
box::use(
app/logic/sample_loader,
)
#' @export
ui <- function(id) {
ns <- NS(id)
Stack(
Dropdown.shinyInput(
id = ns("sample_picker"),
label = "Select Sample",
options = list()
),
PrimaryButton.shinyInput(
id = ns("load"),
text = "Load Sample"
)
)
}
#' @export
server <- function(id) {
moduleServer(id, function(input, output, session) {
# Return reactive list for downstream modules
reactive(list(
selected_sample = reactive(input$sample_picker),
files = reactive({
sample_loader$expected_sample_paths(input$sample_picker)
})
))
})
}Key Pattern: - Both ui() and server() are exported - server() returns a reactive list or reactive value - Downstream modules receive this reactive output as input - UI depends on shiny.fluent (not base Shiny)
3.3 Importing Modules
Direct Import (for ≤8 functions):
# Preferred for small APIs
box::use(
app/logic/data_manager[load_gds_data, load_sv_data, load_cnv_data],
)
# Use directly
data <- load_gds_data(path)Module-Qualified Import (for larger APIs):
# Use when importing >8 functions
box::use(
app/logic/data_manager,
)
# Use with module prefix
data <- data_manager$load_gds_data(path)Current IMPACT-VIS Module APIs:
| Module | Functions | Import Style |
|---|---|---|
data_manager |
4 exported | Module-qualified |
plot_gds |
2-3 exported | Module-qualified |
sample_loader |
4 exported | Direct |
validators |
4 exported | Direct |
error_handler |
3 exported | Direct |
variant_modal_helpers |
1 fn + 1 constant | Direct |
plot_combine |
1 exported | Direct |
3.4 Module Structure in app/main.R
The orchestration happens in app/main.R, which composes all UI and logic modules:
# app/main.R
box::use(
shiny[reactive, observe, observeEvent],
)
box::use(
app/view/sample_input,
app/view/plot_settings,
app/view/main_panel,
app/view/variant_modal,
)
box::use(
app/logic/data_manager[load_gds_data],
app/logic/plot_gds,
app/logic/error_handler,
)
#' @export
ui <- function() {
# Compose all UI modules
tagList(
sample_input$ui("sample_input"),
plot_settings$ui("plot_settings"),
main_panel$ui("main_panel"),
variant_modal$ui("variant_modal")
)
}
#' @export
server <- function(input, output, session) {
# Instantiate modules and wire reactives
# 1. Sample selection returns reactive list
sample_api <- sample_input$server("sample_input")
# 2. Plot settings depends on sample selection
settings_api <- plot_settings$server("plot_settings", sample_api)
# 3. Main panel uses both above
main_panel$server(
"main_panel",
sample_api,
settings_api,
data_loader = load_gds_data
)
# 4. Variant modal depends on selected variant
variant_modal$server("variant_modal", main_panel_api)
}Module Wiring Pattern: - Each module server returns a reactive object (list, value, or function) - Downstream modules receive upstream reactives as dependencies - Reactives automatically unwrap when used (no need for ()) - Data flows unidirectionally: UI → Logic → Plotting → Display
4 Dependency Graph
5 Reactive Programming
IMPACT-VIS uses Shiny’s reactive framework with careful dependency management:
5.1 Reactive Data Flow
sequenceDiagram
participant User
participant UI as Shiny UI
participant React as Reactive Env
participant Logic as Box Modules
participant Plot as Plotting
User->>UI: Select sample
UI->>React: Update sample_input$selected
React->>Logic: Trigger data load
Logic->>Logic: Filter variants
Logic->>React: Return data
React->>Plot: Trigger plot update
Plot->>UI: Render plot object
UI->>User: Display plot
5.2 Reactive Pattern
# In app/main.R
# 1. Input reactive from UI module
sample_data <- sample_input$server("sample_input")
# Returns: reactive list {$gds_path, $sv_path, $cnv_path}
# 2. Derived reactive (depends on sample)
plot_settings <- plot_settings$server("plot_settings", sample_data)
# Returns: reactive list {$plot_type, $colors, $filters}
# 3. Computed reactive (depends on both above)
filtered_data <- reactive({
req(sample_data$gds_path()) # Ensure input is valid
data_manager$load_gds_data(
sample_data$gds_path(),
filters = plot_settings$active_filters(),
num_variants = 500
)
})
# 4. Observer (executes side effects)
observe({
data <- filtered_data() # Auto-triggers when dependencies change
if (is.null(data)) {
error_handler$show_error("Failed to load data")
} else {
output$main_plot <- renderPlotly({
plot_gds$plot_snv_scatter(data)
})
}
})6 Module Responsibilities & Development Status
6.1 UI Layer (app/view/)
| Module | Responsibility | Status | Notes |
|---|---|---|---|
sample_input |
Sample selection, file discovery | ✅ Stable | Core feature |
plot_settings |
Plot customization controls | ✅ Stable | Core feature |
filters_ui |
Variant filter controls | ✅ Stable | Core feature |
main_panel |
Central plot display area | ✅ Stable | Core feature |
variant_modal |
SNV detail modal | ✅ Stable | Core feature |
sv_variant_modal |
SV detail modal | ✅ Stable | Core feature |
cnv_variant_modal |
CNV detail modal | ✅ Stable | Core feature |
export_ui |
Export options | ✅ Stable | Core feature |
header_panel |
App header, title | ✅ Stable | Core feature |
tooltip_ui |
Hover tooltips | ✅ Stable | Core feature |
react.R |
Reactive orchestration helpers | 🟡 Development | Internal utilities |
6.2 Logic Layer (app/logic/)
| Module | Responsibility | Status | Dependencies | Notes |
|---|---|---|---|---|
data_manager |
Load GDS/TSV/TXT, on-disk filtering | ✅ Stable | SeqArray, readr, validators | Core data engine |
plot_gds |
SNV/Indel panel (bottom of IMPACT Plot) | ✅ Stable | ggplot2, plotly | High-frequency use |
plot_sv |
SV panel with 5 tracks (top of IMPACT Plot) | ✅ Stable | ggplot2, plotly | Conditional (SV data) |
plot_cnv |
CNV track (middle of IMPACT Plot) | ✅ Stable | ggplot2, plotly | Conditional (CNV data) |
plot_combine |
IMPACT Plot 3-panel composition | ✅ Stable | plotly | Synchronizes all 3 panels |
validators |
File format validation | ✅ Stable | SeqArray | Upstream of data_manager |
sample_loader |
Sample discovery, file lookup | ✅ Stable | fs, stringr, purrr | Sample initialization |
error_handler |
Error/warning/success messages | ✅ Stable | shinyjs | UI notifications |
variant_modal_helpers |
Variant annotation lookup | ✅ Stable | dplyr | Modal detail pages |
Under Active Development (react.R): Reactive orchestration utilities may change between releases. Do not depend on internal functions from this module.
Optional Data Types: Modules for SV (plot_sv) and CNV (plot_cnv) are optional—applications without corresponding data files function normally with graceful degradation.
Performance Critical: data_manager$load_gds_data() filters on-disk via SeqArray; changes here significantly impact startup time and memory usage.
6.3 Style Layer (app/styles/)
| Module | Responsibility |
|---|---|
layout |
Container styles, responsive layout, Fluent UI configuration |
7 Data Flow Diagram
graph TD
User["👤 User"]
UI["🎨 Shiny.Fluent UI<br/>(app/view)"]
React["⚡ Reactive Context<br/>(app/main.R)"]
Logic["🔧 Logic Modules<br/>(app/logic)"]
Data["💾 File I/O<br/>(GDS/TSV/TXT)"]
Plot["📊 Plotly Objects"]
Display["🖥️ Browser Display"]
User -->|Select Sample<br/>Adjust Filters| UI
UI -->|Input Event| React
React -->|Trigger Load| Logic
Logic -->|Query| Data
Data -->|Raw Variants| Logic
Logic -->|Validate &<br/>Transform| Logic
Logic -->|Return Data| React
React -->|Trigger Plot| Logic
Logic -->|ggplot2 →<br/>ggplotly| Plot
Plot -->|Render| Display
Display -->|Interactive<br/>Hover/Click| User
style User fill:#e1f5ff
style UI fill:#f3e5f5
style React fill:#fff3e0
style Logic fill:#e8f5e9
style Data fill:#ffebee
style Plot fill:#fce4ec
style Display fill:#e0f2f1
8 Reactive Module Wiring
The reactive flow in app/main.R orchestrates all module interactions:
# Core pattern: Each module returns reactives to downstream dependents
server <- function(input, output, session) {
# Level 1: Input modules (user selections)
sample_api <- sample_input$server("sample_input")
# Returns: list(gds_path = reactive, sv_path = reactive, cnv_path = reactive)
# Level 2: Settings modules (depend on level 1)
settings_api <- plot_settings$server("plot_settings", sample_api)
# Returns: list(plot_type = reactive, filters = reactive, colors = reactive)
# Level 3: Data computation (depends on level 1 & 2)
filtered_data <- reactive({
req(sample_api$gds_path())
data_manager$load_gds_data(
sample_api$gds_path(),
filters = settings_api$filters(),
num_variants = 500
)
})
# Level 4: Plotting (depends on level 3)
plot_object <- reactive({
req(filtered_data())
plot_gds$plot_snv_scatter(
data = filtered_data(),
colors = settings_api$colors()
)
})
# Level 5: Display (depends on level 4)
output$main_plot <- renderPlotly(plot_object())
}Dependency Rules: - Each reactive() depends on upstream reactives - Reactives auto-unwrap in expressions (no () needed inside reactive context) - Use req() to short-circuit if dependencies are NULL - Avoid circular dependencies (detected at runtime)
9 Error Handling
IMPACT-VIS implements layered error handling:
Layer 1: Validation
# In data_manager.R
validation_result <- validate_gds_file(gds_path)
if (!validation_result$is_valid) {
warning("GDS validation failed: ", validation_result$message)
return(NULL)
}Layer 2: Graceful Degradation
# In main_panel server
observe({
data <- filtered_data()
if (is.null(data)) {
# Render error message instead of plot
output$main_plot <- renderUI({
div(class = "alert alert-danger", "No data to display")
})
} else {
output$main_plot <- renderPlotly({...})
}
})Layer 3: User Notification
# In error_handler.R
show_error <- function(message) {
# Render modal dialog with error
shinyjs::addClass("body", "modal-active")
session$sendCustomMessage("show_error", message)
}10 Testing Box Modules
Rhino integrates testthat for unit testing of Box modules. Each logic module should have a corresponding test file in tests/testthat/.
10.1 Testing Exported Functions
# tests/testthat/test-data-manager.R
box::use(
app/logic/data_manager,
)
describe("data_manager$load_gds_data", {
it("loads GDS and returns data.frame", {
path <- "tests/testthat/fixtures/sample001_SNV_IMPACT.gds"
result <- data_manager$load_gds_data(path, num_variants = 100)
expect_s3_class(result, "data.frame")
expect_true(nrow(result) <= 100)
})
it("returns NULL for invalid path", {
result <- data_manager$load_gds_data("nonexistent.gds")
expect_null(result)
})
it("applies filters correctly", {
path <- "tests/testthat/fixtures/sample001_SNV_IMPACT.gds"
unfiltered <- data_manager$load_gds_data(path)
filtered <- data_manager$load_gds_data(
path,
filters = list(consequence_tier = "HIGH")
)
expect_true(nrow(filtered) <= nrow(unfiltered))
})
})10.2 Testing Non-Exported (Private) Functions
For testing internal functions (prefixed with .), access the module’s namespace:
# tests/testthat/test-validators.R
box::use(
app/logic/validators,
)
describe("validators internals", {
it("checks GDS structure correctly", {
# Get internal namespace
impl <- attr(validators, "namespace")
path <- "tests/testthat/fixtures/sample001_SNV_IMPACT.gds"
# Test private function
result <- impl$.check_gds_nodes(path)
expect_true(result$has_impact_annotation)
})
})10.3 Running Tests
# Run all tests
R -e "testthat::test_dir('tests/testthat')"
# Run specific test file
R -e "testthat::test_file('tests/testthat/test-data-manager.R')"
# Run with coverage report
R -e "covr::package_coverage()" | less10.4 Test Fixtures
Fixtures are stored in tests/testthat/fixtures/ and represent realistic (but anonymized) sample data:
sample001_SNV_IMPACT.gds— Complete valid GDS file (~10k variants)sample001_SV_IMPACT.tsv— AnnotSV-format SV file (~150 variants)sample001_CNV_IMPACT.txt— SCIP-format CNV file (~200 segments)
All fixtures are derived from actual IMPACT outputs but use generic sample IDs.
11 Error Handling & Resilience
IMPACT-VIS implements three-layer error handling to ensure graceful degradation and user-facing feedback:
11.1 Layer 1: Validation (Prevention)
Validation occurs at data entry—files are validated before loading:
# app/logic/data_manager.R
load_gds_data <- function(gds_path, ...) {
# Validate first
validation_result <- validators$validate_gds_file(gds_path)
if (!validation_result$is_valid) {
warning("GDS validation failed: ", validation_result$message)
return(NULL) # Signal failure to caller
}
# Proceed only if valid
...
}Contract: All loaders (load_gds_data, load_sv_data, load_cnv_data) return NULL with warning() on failure—never raise exceptions.
11.2 Layer 2: Graceful Degradation (Response)
If data fails to load, modules respond appropriately without crashing:
# app/view/main_panel.R
server <- function(id, sample_api, settings_api) {
moduleServer(id, function(input, output, session) {
filtered_data <- reactive({
data_manager$load_gds_data(...)
})
# Render error OR plot
output$main_plot <- renderPlotly({
data <- filtered_data()
if (is.null(data)) {
# Render error message instead of crashing
plot_ly() %>%
add_text(text = "No data available for selected sample",
textposition = "middle center",
showlegend = FALSE)
} else {
plot_gds$plot_snv_scatter(data)
}
})
})
}11.3 Layer 3: User Notification (Communication)
Errors are communicated to users via the error handler:
# app/logic/error_handler.R
show_error <- function(title, message) {
# Display modal with error details
shinyjs::runjs(sprintf(
"alert('%s: %s')", title, message
))
}
show_warning <- function(message) {
# Display inline warning banner
}
show_success <- function(message) {
# Display success toast
}Usage:
observe({
data <- filtered_data()
if (is.null(data)) {
error_handler$show_error(
"Data Loading Failed",
"Unable to read GDS file. Check file permissions."
)
} else {
error_handler$show_success(
paste("Loaded", nrow(data), "variants")
)
}
})11.4 File Access Constraints
IMPACT-VIS restricts file access to app/data/ for security:
# app/logic/sample_loader.R
get_sample_names <- function(dir = "app/data") {
# Enforce directory limit
if (!grepl("^app/data/?$", dir)) {
warning("Access denied: can only read from app/data/")
return(NULL)
}
# Scan for sample files
...
}12 Reproducibility & Environments
12.1 Local Development
Setup (one-time):
cd IMPACT-VIS
R -e "renv::restore(prompt = FALSE)"Run:
R -e "shiny::runApp('app')"Rationale: renv::restore() installs exact package versions from renv.lock, ensuring reproducibility across machines.
12.2 Docker Deployment
Build:
docker build -t impact-vis:1.0.0 .Run:
docker run -p 3838:3838 \
-e SHINY_LOG_LEVEL=INFO \
impact-vis:1.0.0Dockerfile Structure:
FROM r-base:4.3.0
RUN apt-get update && apt-get install -y \
libgdal-dev libgeos-dev libproj-dev \
pandoc-citeproc
COPY renv.lock /app/renv.lock
WORKDIR /app
RUN R -e "renv::restore(prompt = FALSE)"
COPY . /app
CMD ["R", "-e", "shiny::runApp('app')"]Rationale: Docker encapsulates OS, system libraries, R version, and all R packages. Guarantees reproducibility across any host.
12.3 Dependency Versioning
renv.lock is the source of truth for reproducibility:
{
"R": {
"Version": "4.3.0",
"Repositories": [{"Name": "CRAN", "URL": "..."}]
},
"Packages": {
"SeqArray": {
"Package": "SeqArray",
"Version": "1.42.0",
"Source": "Bioconductor",
"Hash": "..."
},
...
}
}Update Workflow:
# Install new package
R -e "install.packages('new_package')"
# Update lock file
R -e "renv::snapshot()"
# Commit changes
git add renv.lock
git commit -m "Update: added new_package v1.2.3"12.4 CI/CD Reproducibility
GitHub Actions in .github/workflows/rhino-test.yml:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: r-lib/actions/setup-r@v2
with:
r-version: '4.3.0'
- run: R -e "renv::restore(prompt = FALSE)"
- run: R -e "testthat::test_dir('tests/testthat')"Rationale: Every pull request runs in a clean environment with locked dependencies, ensuring changes don’t break reproducibility.
13 Performance Optimization
13.1 Memory Efficiency with SeqArray
The critical performance bottleneck is loading large GDS files:
# Bad: Loads entire GDS into memory
gds <- seqOpen("file.gds")
all_data <- seqGetData(gds, "") # Everything!
seqClose(gds)# Good: Filter on-disk, then extract
gds <- seqOpen("file.gds")
on.exit(seqClose(gds), add = TRUE)
# Apply filters BEFORE extraction
seqSetFilter(gds, variant.sel = which_variants, sample.sel = which_samples)
# Extract only filtered data
data <- seqGetData(gds, "genotype/@data")IMPACT-VIS Implementation: - data_manager$load_gds_data() filters on-disk via SeqArray - No intermediate matrices allocated - Memory usage scales with results, not input file size
13.2 Lazy Reactivity
Computations trigger only when dependencies change:
# This doesn't execute until someone reads `filtered_data()`
filtered_data <- reactive({
data_manager$load_gds_data(...) # Expensive operation
})
# UI only subscribes to what it displays
output$main_plot <- renderPlotly({
filtered_data() # Now it executes
})13.3 Vectorized Operations
All variants processed at once, never in loops:
# Bad: O(n) passes through data
for (variant in 1:nrow(data)) {
if (data$impact[variant] == "HIGH") { ... }
}
# Good: Single vectorized operation
high_impact <- data[data$impact == "HIGH", ]14 Configuration Management
config.yml provides environment-specific settings:
default:
sample_dir: "app/data"
max_variants: 1000
log_level: "INFO"
bravo_threshold: 0.01
development:
log_level: "DEBUG"
max_variants: 500
production:
sample_dir: "/data/samples"
max_variants: 5000
log_level: "WARN"Access in R:
box::use(config)
sample_dir <- config::get("sample_dir")15 Deployment Strategies
15.1 Local Development
# One-time setup
R -e "renv::restore(prompt = FALSE)"
# Daily development
R -e "shiny::runApp('app')"15.2 Docker for Production
# Build
docker build -t impact-vis:1.0.0 .
# Run with volume mount
docker run -p 3838:3838 \
-v /data/samples:/app/app/data \
-e SHINY_LOG_LEVEL=INFO \
impact-vis:1.0.015.3 Cloud Deployment
IMPACT-VIS can deploy to: - Shiny Server Pro: Enterprise self-hosted - ShinyApps.io: Rapid cloud deployment - Kubernetes: Scalable orchestration (via Docker)
16 Security & Access Control
16.1 Input Validation
All user inputs validated before processing: - File paths restricted to app/data/ - File types validated by extension - GDS structure validated before opening - Sample IDs sanitized to prevent path traversal
16.2 Resource Limits
# In config.yml
default:
max_upload_size_mb: 100
max_variants_per_query: 5000
session_timeout_minutes: 6016.3 Data Access
- Read-only: Application never modifies input files
- Isolated: Each session operates on separate data copy
- Logging: All file access logged to
app/data/IMPACT-VIS.log
17 Extending IMPACT-VIS
17.1 Adding a New Plotting Function
Step 1: Enhance a plotting module (example: improve SV track styling)
# app/logic/plot_sv.R (modify exported function)
#' @export
plot_sv_tracks <- function(data, chr_lengths, selected_chr = "All",
sv_types_to_show = c("DEL", "DUP", "INS", "INV", "BND")) {
# Filter by SV type
data <- data %>% filter(type %in% sv_types_to_show)
# Create five horizontal tracks with improved styling
ggplot(data, aes(x = start, y = track, color = type)) +
geom_segment(size = 2) +
scale_color_manual(values = sv_colors()) +
labs(title = "Structural Variants") %>%
ggplotly()
}Step 2: Update IMPACT Plot composition
# app/view/main_panel.R (reactive that combines all panels)
impact_plot <- reactive({
req(snv_data(), sv_data(), cnv_data())
snv_panel <- plot_gds$plot_snv_scatter(snv_data())
sv_panel <- plot_sv$plot_sv_tracks(sv_data())
cnv_panel <- plot_cnv$plot_cnv_track(cnv_data())
plot_combine$combine_plots(
sv_plot = sv_panel,
cnv_plot = cnv_panel,
gds = list(plot = snv_panel, y_range = c(0, 100)),
selected_chr = selected_chromosome()
)
})Step 3: Test the IMPACT Plot
# tests/testthat/test-plot-combine.R
test_that("IMPACT Plot renders all three panels", {
snv_data <- sample_gds_data()
sv_data <- sample_sv_data()
cnv_data <- sample_cnv_data()
snv_panel <- plot_gds$plot_snv_scatter(snv_data)
sv_panel <- plot_sv$plot_sv_tracks(sv_data)
cnv_panel <- plot_cnv$plot_cnv_track(cnv_data)
result <- plot_combine$combine_plots(
sv_plot = sv_panel,
cnv_plot = cnv_panel,
gds = list(plot = snv_panel, y_range = c(0, 100))
)
expect_s3_class(result, "plotly")
})17.2 Adding a New Data Filter
Step 1: Implement in validators
# app/logic/validators.R (add new filter function)
#' @export
apply_transcript_filter <- function(data, gene_list) {
data[data$gene %in% gene_list, ]
}Step 2: Wire in data_manager
# app/logic/data_manager.R
if (!is.null(filters$genes)) {
data <- validators$apply_transcript_filter(data, filters$genes)
}Step 3: Add UI control
# app/view/filters_ui.R
ui <- function(id) {
ns <- NS(id)
TagList(
ComboBox.shinyInput(
id = ns("genes"),
label = "Filter by Gene",
multiSelect = TRUE,
options = gene_options
)
)
}18 References
Document Version: 1.0.0
Last Updated: 2025-12-10