Architecture & Design

System architecture, module design, and dependency management
Authors
Affiliation

Nicholas Boehler

University of Toronto Mississauga

Hai-Ying Mary Cheng

University of Toronto Mississauga

Published

December 18, 2025

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

  1. Modular Architecture: Code organized into self-contained, testable modules with clear boundaries
  2. Box Module System: Explicit dependency management via box::use() (not library())
  3. Convention Over Configuration: Standardized directory structure (app/, tests/, renv/)
  4. Reproducibility First: Environment locking via renv and Dockerfile
  5. 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

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

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
WarningModule Development Notes

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()" | less

10.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.0

Dockerfile 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.0

15.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: 60

16.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