library(shiny) library(bslib) library(mrgsolve) library(dplyr) library(ggplot2) library(tidyr) species_cfg <- tibble::tribble( ~species, ~label, ~bw_default, ~dose_min, ~dose_max, ~dose_default, ~route_default, ~interval_default, ~followup_default, ~Rtot, ~KDEG_R, ~KINT, ~KA, ~F1, ~Vc, ~CLm, ~QKID, ~VK, ~VKVAS, ~GFR, ~FUK, ~FUGFR, ~KASSK, ~KDISK, ~KASSL, ~KDISL, ~VHEP, ~FESC, ~KDEGE, ~KCLE, ~KONAPP, ~RISCTOT, ~KCMPLX, ~KDEGC, ~KDEGM, ~SMAX, ~SC50, "rat", "Rat", 0.25, 0.1, 30.0, 10.0, "SC", 168, 28, 513, 2.4, 2.4, 0.13, 0.9, 0.027, 0.017, 0.37, 0.0022, 0.000132, 0.168, 0.04, 1.0, 2.9, 0.004, 0.00035, 0.0036, 0.00272, 0.01, 0.028, 1.32, 1.4e5, 30, 0.042, 0.01, 0.3, 10, 6.1, "monkey", "Monkey", 3.50, 0.1, 30.0, 10.0, "SC", 336, 28, 339, 2.4, 2.4, 0.056,0.9, 0.60, 1.30, 3.8, 0.0244, 0.0015, 1.138, 0.04, 1.0, 2.9, 0.004, 0.00035, 0.0036, 0.0602, 0.01, 0.013, 1.32, 1.4e5, 30, 0.042, 0.01, 0.3, 10, 6.1, "human", "Human", 70.0, 0.35, 5.0, 2.5, "SC", 672, 56, 340, 1.9, 1.9, 0.036,0.9, 2.8, 3.7, 23.5, 0.296, 0.0182, 7.20, 0.04, 1.0, 2.9, 0.004, 0.00035, 0.0036, 0.69, 0.01, 0.007, 1.32, 1.4e5, 30, 0.042, 0.01, 0.3, 10, 6.1 ) species_notes <- list( rat = paste( "Validated against rat plasma, liver, kidney, RISC-loaded siRNA, and ALAS1 knockdown data.", "Paper scenarios include 10 mg/kg IV and 1 to 10 mg/kg SC." ), monkey = paste( "Validated against monkey plasma and liver PK.", "Paper scenarios include 10 mg/kg IV and 1, 5, and 10 mg/kg SC." ), human = paste( "Validated against human single-dose plasma PK after 0.35 to 5 mg/kg SC.", "Human tissue and PD panels are mechanistic exploratory outputs, not directly clinically validated in this paper." ) ) param_cols <- c( "Rtot", "KDEG_R", "KINT", "KA", "F1", "Vc", "CLm", "QKID", "VK", "VKVAS", "GFR", "FUK", "FUGFR", "KASSK", "KDISK", "KASSL", "KDISL", "VHEP", "FESC", "KDEGE", "KCLE", "KONAPP", "RISCTOT", "KCMPLX", "KDEGC", "KDEGM", "SMAX", "SC50" ) get_cfg <- function(species_key) { species_cfg |> dplyr::filter(.data$species == .env$species_key) |> dplyr::slice(1) } calc_auc <- function(time, conc) { if (length(time) < 2 || length(conc) < 2) return(NA_real_) sum(diff(time) * (head(conc, -1) + tail(conc, -1)) / 2, na.rm = TRUE) } estimate_half_life <- function(d, start_time) { terminal <- d |> dplyr::filter(.data$time >= start_time, .data$CP_ngml > 1e-6) if (nrow(terminal) < 4) return(NA_real_) fit <- try(stats::lm(log(CP_ngml) ~ time, data = terminal), silent = TRUE) if (inherits(fit, "try-error")) return(NA_real_) slope <- coef(fit)[[2]] if (!is.finite(slope) || slope >= 0) return(NA_real_) log(2) / (-slope) } model_code <- ' $PARAM @annotated KA : 0.036 : SC absorption rate constant (1/h) F1 : 0.90 : SC bioavailability fraction Vc : 2.80 : Central volume (L) CLm : 3.70 : Formation and elimination clearance for metabolite (L/h) Rtot : 340 : Baseline liver ASGPR density (nM) KDEG_R : 1.90 : ASGPR degradation rate constant (1/h) KINT : 1.90 : Tris-GalNAc receptor complex internalization rate constant (1/h) KOFF : 1.32 : Tris-GalNAc dissociation rate constant (1/h) KD : 27.7 : Tris-GalNAc equilibrium dissociation constant (nM) QKID : 23.5 : Kidney plasma flow (L/h) VK : 0.296 : Kidney interstitial and cellular volume (L) VKVAS : 0.0182 : Kidney vascular plasma volume (L) GFR : 7.20 : Glomerular filtration rate (L/h) FUK : 0.04 : Exchangeable free fraction in kidney tissue FUGFR : 1.0 : Fraction subject to GFR KASSK : 2.90 : Kidney deep-pool association rate constant (1/h) KDISK : 0.004 : Kidney deep-pool dissociation rate constant (1/h) KASSL : 0.00035 : Liver deep-pool association rate constant (1/h) KDISL : 0.0036 : Liver deep-pool dissociation rate constant (1/h) VHEP : 0.69 : Hepatocyte cellular volume (L) FESC : 0.01 : Endosomal escape fraction KDEGE : 0.007 : Endosomal degradation rate constant (1/h) KCLE : 1.32 : GalNAc cleavage rate constant (1/h) KONAPP : 1.4e5 : Apparent association rate constant of siRNA to RISC (nM^-1 h^-1) RISCTOT : 30 : Total hepatic RISC concentration (nM) KCMPLX : 0.042 : RISC-loaded siRNA degradation rate constant (1/h) KDEGC : 0.01 : Cytosolic siRNA degradation rate constant (1/h) KDEGM : 0.30 : Target mRNA degradation rate constant (1/h) SMAX : 10 : Maximal stimulation of target mRNA degradation SC50 : 6.1 : RISC-bound siRNA concentration at half maximal stimulation (ng/g) MRNA0 : 100 : Baseline target mRNA (% baseline) FRACHEP : 0.66 : Fraction of liver that is hepatocytes FRACCELL: 0.80 : Fraction of hepatocyte volume that is cellular SCALEBL : 1.82 : Kidney whole blood correction factor MWG : 16300 : Molecular weight of givosiran and metabolite (g/mol) MWSIRNA : 15120 : Molecular weight of cleaved siRNA (g/mol) MWAS : 7560 : Molecular weight of antisense strand on RISC (g/mol) $CMT @annotated DEPOT : SC dosing depot (nmol) CENT : Parent central amount (nmol) MET : Metabolite central amount (nmol) RF : Free ASGPR in liver (nM) RC : Parent-ASGPR complex (nM) RCM : Metabolite-ASGPR complex (nM) ALIV : Internalized parent-ASGPR complex in liver (nmol) ALIV_M : Internalized metabolite-ASGPR complex in liver (nmol) ALIV_ENDO : Free endosomal parent siRNA (nmol) ALIV_ENDO_M: Free endosomal metabolite siRNA (nmol) ADEEP_L : Liver endosomal parent bound pool (nmol) ADEEP_L_M : Liver endosomal metabolite bound pool (nmol) CF_CYT : Free cytoplasmic parent siRNA (nM) CF_CYT_M : Free cytoplasmic metabolite siRNA (nM) RISC : RISC-loaded siRNA (nM) CK_VAS : Kidney vascular parent concentration (nM) CK_VAS_M : Kidney vascular metabolite concentration (nM) CK : Kidney tissue parent concentration (nM) CK_M : Kidney tissue metabolite concentration (nM) ADEEP_K : Kidney parent bound pool (nmol) ADEEP_K_M : Kidney metabolite bound pool (nmol) MRNA : Target mRNA (% baseline) $MAIN RF_0 = Rtot; MRNA_0 = MRNA0; $ODE double KON = KOFF / KD; double Cp = CENT / Vc; double Cpm = MET / Vc; double RISC_ngg = RISC * MWAS / 1000.0; dxdt_DEPOT = -KA * DEPOT; dxdt_CENT = KA * F1 * DEPOT - KON * Cp * RF * Vc + KOFF * RC * Vc - CLm * Cp - QKID * Cp + QKID * CK_VAS; dxdt_MET = CLm * Cp - KON * Cpm * RF * Vc + KOFF * RCM * Vc - QKID * Cpm + QKID * CK_VAS_M - CLm * Cpm; dxdt_RF = KDEG_R * Rtot - KDEG_R * RF - KON * Cp * RF + KOFF * RC - KON * Cpm * RF + KOFF * RCM; dxdt_RC = KON * Cp * RF - KOFF * RC - KINT * RC; dxdt_RCM = KON * Cpm * RF - KOFF * RCM - KINT * RCM; dxdt_ALIV = KINT * RC * Vc - KCLE * ALIV; dxdt_ALIV_M = KINT * RCM * Vc - KCLE * ALIV_M; dxdt_ALIV_ENDO = KCLE * ALIV - KDEGE * ALIV_ENDO - FESC * KDEGE * ALIV_ENDO - KASSL * ALIV_ENDO + KDISL * ADEEP_L; dxdt_ALIV_ENDO_M = KCLE * ALIV_M - KDEGE * ALIV_ENDO_M - FESC * KDEGE * ALIV_ENDO_M - KASSL * ALIV_ENDO_M + KDISL * ADEEP_L_M; dxdt_ADEEP_L = KASSL * ALIV_ENDO - KDISL * ADEEP_L; dxdt_ADEEP_L_M = KASSL * ALIV_ENDO_M - KDISL * ADEEP_L_M; dxdt_CF_CYT = FESC * KDEGE * ALIV_ENDO / VHEP - KDEGC * CF_CYT - KONAPP * CF_CYT * (RISCTOT - RISC); dxdt_CF_CYT_M = FESC * KDEGE * ALIV_ENDO_M / VHEP - KDEGC * CF_CYT_M - KONAPP * CF_CYT_M * (RISCTOT - RISC); dxdt_RISC = KONAPP * (CF_CYT + CF_CYT_M) * (RISCTOT - RISC) - KCMPLX * RISC; dxdt_CK_VAS = QKID * (Cp - CK_VAS) / VKVAS + QKID * (FUK * CK - CK_VAS) / VKVAS - GFR * FUGFR * CK_VAS / VKVAS; dxdt_CK_VAS_M = QKID * (Cpm - CK_VAS_M) / VKVAS + QKID * (FUK * CK_M - CK_VAS_M) / VKVAS - GFR * FUGFR * CK_VAS_M / VKVAS; dxdt_CK = QKID * (CK_VAS - FUK * CK) / VK - KASSK * FUK * CK + KDISK * ADEEP_K / VK; dxdt_CK_M = QKID * (CK_VAS_M - FUK * CK_M) / VK - KASSK * FUK * CK_M + KDISK * ADEEP_K_M / VK; dxdt_ADEEP_K = KASSK * FUK * CK * VK - KDISK * ADEEP_K; dxdt_ADEEP_K_M = KASSK * FUK * CK_M * VK - KDISK * ADEEP_K_M; dxdt_MRNA = KDEGM * MRNA0 - KDEGM * (1.0 + SMAX * RISC_ngg / (SC50 + RISC_ngg)) * MRNA; $TABLE double CP_ngml = Cp * MWG / 1000.0; double CM_ngml = Cpm * MWG / 1000.0; double LIVER_ngg = ((ALIV * MWG) + ((ADEEP_L + ALIV_ENDO + CF_CYT * VHEP) * MWSIRNA)) / (VHEP * 1000.0 * FRACHEP * FRACCELL); double LIVERM_ngg = ((ALIV_M * MWG) + ((ADEEP_L_M + ALIV_ENDO_M + CF_CYT_M * VHEP) * MWSIRNA)) / (VHEP * 1000.0 * FRACHEP * FRACCELL); double KIDNEY_ngg = ((CK * VK + ADEEP_K) * MWG) / ((VKVAS * SCALEBL + VK) * 1000.0); double KIDNEYM_ngg = ((CK_M * VK + ADEEP_K_M) * MWG) / ((VKVAS * SCALEBL + VK) * 1000.0); double RISC_ngg_out = RISC * MWAS / 1000.0; double ASGPR_free_pct = 100.0 * RF / Rtot; double ASGPR_bound_pct = 100.0 * (RC + RCM) / (Rtot + 1e-9); double MRNA_pct = MRNA; $CAPTURE @annotated CP_ngml : Parent plasma concentration (ng/mL) CM_ngml : Metabolite plasma concentration (ng/mL) LIVER_ngg : Parent liver concentration (ng/g) LIVERM_ngg : Metabolite liver concentration (ng/g) KIDNEY_ngg : Parent kidney concentration (ng/g) KIDNEYM_ngg : Metabolite kidney concentration (ng/g) RISC_ngg_out : RISC-loaded siRNA (ng/g) ASGPR_free_pct : Free ASGPR (% baseline) ASGPR_bound_pct: Bound ASGPR complex (% baseline) MRNA_pct : Target mRNA (% baseline) ' mod <- mcode("givosiran_translational_pkpd", model_code, quiet = TRUE) app_theme <- bs_theme( version = 5, bootswatch = "flatly", primary = "#8b5cf6" ) |> bs_add_rules(" .metric-card { background: #f8f9fa; border-radius: 8px; padding: 15px; margin: 5px; text-align: center; border: 1px solid #dee2e6; } .metric-value { font-size: 24px; font-weight: bold; color: #2c3e50; } .metric-label { font-size: 12px; color: #7f8c8d; } .metric-success .metric-value { color: #10b981; } .metric-warning .metric-value { color: #f59e0b; } .metric-primary .metric-value { color: #8b5cf6; } .metric-info .metric-value { color: #0dcaf0; } .metric-danger .metric-value { color: #ef4444; } .ref-box { background: #f0f4ff; border-left: 4px solid #8b5cf6; padding: 12px 16px; border-radius: 4px; margin-top: 10px; font-size: 13px; } .ref-box a { color: #8b5cf6; } .note-box { background: #f8fafc; border: 1px solid #e2e8f0; border-left: 4px solid #8b5cf6; border-radius: 8px; padding: 12px 14px; margin-bottom: 12px; color: #334155; font-size: 13px; } ") ui <- page_sidebar( title = "Givosiran Translational PBPK-PD Simulator", theme = app_theme, sidebar = sidebar( title = "Simulation Settings", width = 340, accordion( open = c("Scenario", "Dosing"), accordion_panel( "Scenario", selectInput("species", "Species", choices = c("Human" = "human", "Monkey" = "monkey", "Rat" = "rat"), selected = "human"), selectInput("route", "Route", choices = c("Subcutaneous" = "SC", "Intravenous" = "IV"), selected = "SC"), sliderInput("bw", "Body weight (kg)", min = 40, max = 120, value = 70, step = 5) ), accordion_panel( "Dosing", sliderInput("dose_mgkg", "Dose (mg/kg)", min = 0.35, max = 5, value = 2.5, step = 0.05), numericInput("n_doses", "Number of doses", value = 1, min = 1, max = 12), numericInput("interval_h", "Dosing interval (h)", value = 672, min = 4, max = 1440), numericInput("followup_days", "Follow-up after last dose (days)", value = 56, min = 2, max = 180) ), accordion_panel( "Display", checkboxInput("show_metabolite", "Overlay metabolite in plasma plot", value = TRUE), checkboxInput("log_scale", "Log scale for concentration plots", value = FALSE) ) ) ), uiOutput("validation_note"), layout_column_wrap( width = 1/4, fill = FALSE, div(class = "metric-card metric-success", div(class = "metric-value", textOutput("cmax")), div(class = "metric-label", "Parent Cmax (ng/mL)")), div(class = "metric-card metric-warning", div(class = "metric-value", textOutput("ctrough")), div(class = "metric-label", "Parent Ctrough (ng/mL)")), div(class = "metric-card metric-primary", div(class = "metric-value", textOutput("auc")), div(class = "metric-label", "Parent AUC (ng·h/mL)")), div(class = "metric-card metric-info", div(class = "metric-value", textOutput("thalf")), div(class = "metric-label", "Apparent t½ (h)")), div(class = "metric-card metric-primary", div(class = "metric-value", textOutput("liver_cmax")), div(class = "metric-label", "Liver Cmax (ng/g)")), div(class = "metric-card metric-danger", div(class = "metric-value", textOutput("mrna_nadir")), div(class = "metric-label", "ALAS1 nadir (% baseline)")) ), card( card_header("Plasma parent and metabolite PK"), full_screen = TRUE, plotOutput("plasma_plot", height = "500px") ), card( card_header("Tissue distribution and mechanism"), full_screen = TRUE, plotOutput("mechanism_plot", height = "560px") ), navset_card_underline( title = "Drug Information", nav_panel( "Model Information", markdown("\n## Mechanistic translational GalNAc-siRNA model\n\n**Drug:** Givosiran (GalNAc-siRNA targeting ALAS1) \n**Paper:** Ayyar VS, Song D. *J Pharm Sci.* 2024;113:176-190. \n**Structural idea:** competitive ASGPR-mediated liver uptake, kidney redistribution, endosomal sequestration and release, RISC loading, and indirect-response ALAS1 mRNA knockdown.\n\n### Implemented compartments\n- SC depot and central plasma parent drug\n- Plasma active metabolite\n- Free and bound hepatic ASGPR\n- Internalized liver pools, endosomal deep pools, cytoplasmic siRNA, hepatic RISC\n- Kidney vascular, tissue, and deep retention pools\n- ALAS1 mRNA turnover response\n\n### Key extracted parameters\n- **KD for GalNAc-ASGPR:** 27.7 nM\n- **koff:** 1.32 h^-1\n- **Human calibrated values:** ka 0.036 h^-1, Vc 2.8 L, kint 1.9 h^-1, CLm 3.7 L/h\n- **Rat values:** ka 0.13 h^-1, Vc 0.027 L, CLm 0.017 L/h\n- **Monkey values:** ka 0.056 h^-1, Vc 0.6 L, CLm 1.3 L/h\n- **RISC potency for ALAS1 knockdown:** SC50 6.1 ng/g\n\n### What is validated here\n- **Rat:** plasma, liver, kidney, RISC-loaded siRNA, ALAS1 knockdown\n- **Monkey:** plasma and liver PK\n- **Human:** plasma PK after 0.35 to 5 mg/kg SC\n\n### Important interpretation note\nHuman tissue and PD outputs in this simulator are mechanistic exploratory projections. The paper directly validates human plasma PK, not human liver biopsy or human ALAS1 tissue response.\n ")), nav_panel( "References", div(class = "ref-box", tags$h5("Key references"), tags$ol( tags$li("Ayyar VS, Song D. Mechanistic Pharmacokinetics and Pharmacodynamics of GalNAc-siRNA: Translational Model Involving Competitive Receptor-Mediated Disposition and RISC-Dependent Gene Silencing Applied to Givosiran. Journal of Pharmaceutical Sciences. 2024;113:176-190."), tags$li("Ayyar VS et al. Prior GalNAc-siRNA mechanistic work used for receptor density and pulse-chase assumptions."), tags$li("Sardh E et al. Human givosiran phase 1 plasma PK data used in the translational step.") ), tags$h5("Therapeutic context"), tags$ul( tags$li(tags$strong("Class:"), " GalNAc-conjugated siRNA"), tags$li(tags$strong("Target:"), " ALAS1"), tags$li(tags$strong("Indication:"), " Acute hepatic porphyria"), tags$li(tags$strong("Routes in paper:"), " SC and IV preclinical, SC human"), tags$li(tags$strong("Mechanistic focus:"), " receptor occupancy, liver delivery, kidney redistribution, RISC-dependent gene silencing") ) ) ) ), div(style = "text-align: center; padding: 20px; margin-top: 30px; border-top: 1px solid #e9ecef; color: #6c757d; font-size: 12px;", "Powered by ", tags$a(href = "https://www.pkpdbuilder.com", target = "_blank", style = "color: #8b5cf6; font-weight: 500;", "PKPDBuilder.com"), " • Built by Sunny ☀️ (Husain Attarwala's AI Assistant)", br(), tags$span(style = "font-size: 10px;", "For research and educational purposes only. Not for clinical decision-making.")) ) server <- function(input, output, session) { observeEvent(input$species, { cfg <- get_cfg(input$species) if (input$species == "human") { updateSliderInput(session, "bw", min = 40, max = 120, value = cfg$bw_default, step = 5) updateSliderInput(session, "dose_mgkg", min = cfg$dose_min, max = cfg$dose_max, value = cfg$dose_default, step = 0.05) } else if (input$species == "monkey") { updateSliderInput(session, "bw", min = 2, max = 8, value = cfg$bw_default, step = 0.25) updateSliderInput(session, "dose_mgkg", min = cfg$dose_min, max = cfg$dose_max, value = cfg$dose_default, step = 0.1) } else { updateSliderInput(session, "bw", min = 0.15, max = 0.5, value = cfg$bw_default, step = 0.01) updateSliderInput(session, "dose_mgkg", min = cfg$dose_min, max = cfg$dose_max, value = cfg$dose_default, step = 0.1) } updateSelectInput(session, "route", selected = cfg$route_default) updateNumericInput(session, "interval_h", value = cfg$interval_default) updateNumericInput(session, "followup_days", value = cfg$followup_default) }, ignoreInit = FALSE) output$validation_note <- renderUI({ cfg <- get_cfg(input$species) tags$div( class = "note-box", tags$strong(paste0(cfg$label, " validation note: ")), species_notes[[input$species]] ) }) sim_data <- reactive({ cfg <- get_cfg(input$species) params <- cfg |> dplyr::select(dplyr::all_of(param_cols)) |> as.list() total_end <- max(24, (input$n_doses - 1) * input$interval_h + input$followup_days * 24) delta <- dplyr::case_when( total_end <= 120 ~ 0.1, total_end <= 480 ~ 0.25, total_end <= 1440 ~ 1, TRUE ~ 4 ) dose_mg <- input$dose_mgkg * input$bw dose_nmol <- dose_mg * 1e6 / 16300 ev1 <- ev( amt = dose_nmol, cmt = if (input$route == "SC") "DEPOT" else "CENT", ii = input$interval_h, addl = max(0, input$n_doses - 1) ) out <- mod %>% param(params) %>% ev(ev1) %>% mrgsim(end = total_end, delta = delta, atol = 1e-10, rtol = 1e-8) %>% as.data.frame() |> mutate(time_day = time / 24) shiny::req(nrow(out) > 0) out }) metric_window <- reactive({ d <- sim_data() total_end <- max(d$time) start_time <- if (input$n_doses > 1) max(0, total_end - input$interval_h) else 0 d |> dplyr::filter(.data$time >= start_time) }) output$cmax <- renderText({ d <- metric_window() sprintf("%.1f", max(d$CP_ngml, na.rm = TRUE)) }) output$ctrough <- renderText({ d <- metric_window() |> dplyr::filter(.data$time > min(.data$time)) if (nrow(d) == 0) return("NA") sprintf("%.1f", min(d$CP_ngml, na.rm = TRUE)) }) output$auc <- renderText({ d <- metric_window() auc <- calc_auc(d$time, d$CP_ngml) if (!is.finite(auc)) return("NA") sprintf("%.0f", auc) }) output$thalf <- renderText({ d <- metric_window() start_fit <- min(d$time) + 0.5 * diff(range(d$time)) th <- estimate_half_life(d, start_fit) if (!is.finite(th)) return("NA") sprintf("%.1f", th) }) output$liver_cmax <- renderText({ d <- sim_data() sprintf("%.1f", max(d$LIVER_ngg, na.rm = TRUE)) }) output$mrna_nadir <- renderText({ d <- sim_data() sprintf("%.1f", min(d$MRNA_pct, na.rm = TRUE)) }) output$plasma_plot <- renderPlot({ d <- sim_data() |> dplyr::select(time_day, CP_ngml, CM_ngml) |> tidyr::pivot_longer(cols = c(CP_ngml, CM_ngml), names_to = "analyte", values_to = "value") |> dplyr::mutate(analyte = dplyr::recode(.data$analyte, CP_ngml = "Parent givosiran", CM_ngml = "AS(N-1)3\'0 metabolite" )) if (!input$show_metabolite) { d <- d |> dplyr::filter(.data$analyte == "Parent givosiran") } d_plot <- if (input$log_scale) { d |> dplyr::filter(.data$value > 0.001) } else { d } p <- ggplot(d_plot, aes(x = time_day, y = value, color = analyte)) + geom_line(linewidth = 0.9) + scale_color_manual(values = c("Parent givosiran" = "#8b5cf6", "AS(N-1)3'0 metabolite" = "#10b981")) + labs( x = "Time (days)", y = "Plasma concentration (ng/mL)", color = NULL, title = paste0(get_cfg(input$species)$label, ": ", input$dose_mgkg, " mg/kg ", input$route, if (input$n_doses > 1) paste0(" x ", input$n_doses, " doses") else " single dose") ) + theme_minimal(base_size = 14) + theme(legend.position = "top") if (input$log_scale) p <- p + scale_y_log10() p }) output$mechanism_plot <- renderPlot({ d <- sim_data() |> dplyr::select(time_day, LIVER_ngg, KIDNEY_ngg, RISC_ngg_out, MRNA_pct, ASGPR_free_pct) |> tidyr::pivot_longer( cols = c(LIVER_ngg, KIDNEY_ngg, RISC_ngg_out, MRNA_pct, ASGPR_free_pct), names_to = "panel", values_to = "value" ) |> dplyr::mutate( panel = dplyr::recode(.data$panel, LIVER_ngg = "Liver total givosiran (ng/g)", KIDNEY_ngg = "Kidney total givosiran (ng/g)", RISC_ngg_out = "RISC-loaded siRNA (ng/g)", MRNA_pct = "ALAS1 mRNA (% baseline)", ASGPR_free_pct = "Free ASGPR (% baseline)" ) ) p <- ggplot(d, aes(x = time_day, y = value)) + geom_line(color = "#8b5cf6", linewidth = 0.8) + facet_wrap(~panel, scales = "free_y", ncol = 2) + labs( x = "Time (days)", y = NULL, title = "Mechanistic tissue and PD outputs" ) + theme_minimal(base_size = 13) p }) } shinyApp(ui = ui, server = server)