Spaces:
Sleeping
Sleeping
| library(shiny) | |
| library(DT) | |
| library(ggplot2) | |
| library(dplyr) | |
| library(reshape2) | |
| # ββ Helper functions ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| load_data <- function(file_path, expected_cols = NULL) { | |
| data <- read.csv(file_path, stringsAsFactors = FALSE) | |
| if (!is.null(expected_cols) && !all(expected_cols %in% colnames(data))) { | |
| stop(paste("Missing required columns. Expected:", paste(expected_cols, collapse = ", "))) | |
| } | |
| return(data) | |
| } | |
| process_kmat <- function(kmat_raw) { | |
| Kmat <- as.matrix(kmat_raw[, -1]) | |
| rownames(Kmat) <- trimws(kmat_raw[, 1]) | |
| colnames(Kmat) <- rownames(Kmat) | |
| Kmat[Kmat < 0] <- 1e-6 | |
| Kmat <- (Kmat + t(Kmat)) / 2 | |
| diag(Kmat) <- 0 | |
| return(Kmat) | |
| } | |
| relate_thinning <- function(K, criterion, threshold = 0.15, max_per_cluster = 5) { | |
| K[K < 0] <- 1e-6 | |
| diag(K) <- 0 | |
| K_binary <- K | |
| K_binary[K_binary > threshold] <- 1 | |
| K_binary[K_binary <= threshold] <- 0 | |
| size_init <- nrow(K_binary) | |
| for (i in 1:nrow(K_binary)) { | |
| index <- colSums(K_binary) | |
| index <- sort(index, decreasing = TRUE) | |
| index <- index[which(index > max_per_cluster)] | |
| if (length(index) == 0) break | |
| cluster_members <- names(which(K_binary[names(index)[1], ] == 1)) | |
| tmp <- criterion[match(cluster_members, criterion[, 1]), ] | |
| tmp <- tmp[order(tmp$Criterion, decreasing = TRUE), ] | |
| if (nrow(tmp) > max_per_cluster) { | |
| tmp_rm <- match(tmp[-c(1:max_per_cluster), 1], rownames(K_binary)) | |
| tmp_rm <- tmp_rm[!is.na(tmp_rm)] | |
| if (length(tmp_rm) > 0) K_binary <- K_binary[-tmp_rm, -tmp_rm] | |
| } | |
| } | |
| return(rownames(K_binary)) | |
| } | |
| generate_half_diallel <- function(parents, criterion, K) { | |
| n <- length(parents) | |
| if (n < 2) stop("Need at least 2 parents") | |
| total_crosses <- n * (n - 1) / 2 | |
| P1 <- character(total_crosses) | |
| P2 <- character(total_crosses) | |
| P1_Value <- numeric(total_crosses) | |
| P2_Value <- numeric(total_crosses) | |
| Mid_Parent_Value <- numeric(total_crosses) | |
| Kinship <- numeric(total_crosses) | |
| idx <- 1 | |
| for (i in 1:(n - 1)) { | |
| for (j in (i + 1):n) { | |
| P1[idx] <- parents[i] | |
| P2[idx] <- parents[j] | |
| P1_Value[idx] <- criterion$Criterion[criterion$Genotype == parents[i]] | |
| P2_Value[idx] <- criterion$Criterion[criterion$Genotype == parents[j]] | |
| Mid_Parent_Value[idx] <- (P1_Value[idx] + P2_Value[idx]) / 2 | |
| Kinship[idx] <- K[parents[i], parents[j]] | |
| idx <- idx + 1 | |
| } | |
| } | |
| crosses <- data.frame( | |
| Cross_ID = 1:total_crosses, | |
| Parent1 = P1, | |
| Parent2 = P2, | |
| P1_Criterion_Value = round(P1_Value, 4), | |
| P2_Criterion_Value = round(P2_Value, 4), | |
| Predicted_Offspring_Value = round(Mid_Parent_Value, 4), | |
| Kinship_Coefficient = round(Kinship, 6), | |
| stringsAsFactors = FALSE | |
| ) | |
| crosses$Relatedness_Category <- cut(crosses$Kinship_Coefficient, | |
| breaks = c(-Inf, 0.05, 0.125, 0.25, Inf), | |
| labels = c("Unrelated", "Distantly_Related", "Moderately_Related", "Closely_Related") | |
| ) | |
| q_breaks <- quantile(crosses$Predicted_Offspring_Value, probs = c(0, 0.25, 0.5, 0.75, 1)) | |
| if (length(unique(q_breaks)) < 5) { | |
| crosses$Performance_Category <- "Medium" | |
| } else { | |
| crosses$Performance_Category <- cut(crosses$Predicted_Offspring_Value, | |
| breaks = q_breaks, | |
| labels = c("Low", "Medium", "High", "Very_High"), | |
| include.lowest = TRUE | |
| ) | |
| } | |
| crosses <- crosses[order(crosses$Predicted_Offspring_Value, decreasing = TRUE), ] | |
| rownames(crosses) <- NULL | |
| crosses | |
| } | |
| # ββ UI βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| ui <- fluidPage( | |
| tags$head(tags$style(HTML(" | |
| :root { | |
| --accent: #58a6ff; | |
| --accent2: #3fb950; | |
| --accent3: #f78166; | |
| --surface: #161b22; | |
| --border: #30363d; | |
| --muted: #8b949e; | |
| } | |
| body { background: #0d1117 !important; color: #e6edf3 !important; font-family: 'Courier New', monospace; } | |
| .container-fluid { background: #0d1117; } | |
| .well { background: #161b22 !important; border-color: #30363d !important; } | |
| .nav-tabs > li > a { background: #0d1117; color: #8b949e; border-color: #30363d; font-family: 'Courier New', monospace; font-size: 0.85rem; } | |
| .nav-tabs > li.active > a, .nav-tabs > li.active > a:hover, .nav-tabs > li.active > a:focus { background: #161b22; color: #58a6ff; border-color: #30363d #30363d #161b22; } | |
| .nav-tabs > li > a:hover { background: #161b22; color: #e6edf3; border-color: #30363d; } | |
| .tab-content { background: #0d1117; border: 1px solid #30363d; border-top: none; padding: 16px; border-radius: 0 0 6px 6px; } | |
| select, input[type=number], input[type=text] { background: #0d1117 !important; color: #e6edf3 !important; border: 1px solid #30363d !important; border-radius: 4px !important; } | |
| .selectize-input { background: #0d1117 !important; color: #e6edf3 !important; border-color: #30363d !important; } | |
| .selectize-dropdown { background: #161b22 !important; color: #e6edf3 !important; border-color: #30363d !important; } | |
| .irs--shiny .irs-bar { background: #58a6ff; border-color: #58a6ff; } | |
| .irs--shiny .irs-handle { background: #58a6ff; border-color: #58a6ff; } | |
| .irs--shiny .irs-from, .irs--shiny .irs-to, .irs--shiny .irs-single { background: #58a6ff; } | |
| .irs-grid-text { color: #8b949e; } | |
| .app-header { | |
| background: linear-gradient(135deg, #161b22 0%, #0d1117 100%); | |
| border-bottom: 1px solid var(--border); | |
| padding: 24px 32px 20px; | |
| margin-bottom: 28px; | |
| } | |
| .app-title { font-family: 'Courier New', monospace; font-size: 1.6rem; font-weight: 700; color: var(--accent); letter-spacing: -0.5px; margin: 0; } | |
| .app-subtitle { color: var(--muted); font-size: 0.82rem; margin-top: 4px; letter-spacing: 0.5px; } | |
| .card-panel { | |
| background: var(--surface); | |
| border: 1px solid var(--border); | |
| border-radius: 8px; | |
| padding: 20px 24px; | |
| margin-bottom: 20px; | |
| } | |
| .card-title { font-family: 'Courier New', monospace; font-size: 0.78rem; font-weight: 600; color: var(--muted); letter-spacing: 1.5px; text-transform: uppercase; margin-bottom: 14px; border-bottom: 1px solid var(--border); padding-bottom: 10px; } | |
| .stat-box { background: #0d1117; border: 1px solid var(--border); border-radius: 6px; padding: 14px 18px; text-align: center; } | |
| .stat-num { font-family: 'Courier New', monospace; font-size: 2rem; font-weight: 700; color: var(--accent); } | |
| .stat-lab { font-size: 0.72rem; color: var(--muted); text-transform: uppercase; letter-spacing: 1px; margin-top: 2px; } | |
| .nav-tabs { border-bottom: 1px solid var(--border); } | |
| .nav-tabs .nav-link { color: var(--muted); border: none; padding: 10px 18px; font-size: 0.84rem; font-family: 'Courier New', monospace; } | |
| .nav-tabs .nav-link.active { background: var(--surface); color: var(--accent); border-bottom: 2px solid var(--accent); border-radius: 0; } | |
| .nav-tabs .nav-link:hover { color: #e6edf3; } | |
| .form-control, .selectize-input, .shiny-input-container input { | |
| background: #0d1117 !important; color: #e6edf3 !important; | |
| border: 1px solid var(--border) !important; border-radius: 6px !important; | |
| font-family: 'Courier New', monospace !important; font-size: 0.84rem !important; | |
| } | |
| .form-label, label { color: var(--muted); font-size: 0.78rem; letter-spacing: 0.5px; text-transform: uppercase; } | |
| .btn-primary { background: var(--accent); border-color: var(--accent); font-family: 'Courier New', monospace; font-size: 0.82rem; letter-spacing: 0.5px; color: #0d1117; font-weight: 700; } | |
| .btn-primary:hover { background: #79c0ff; border-color: #79c0ff; color: #0d1117; } | |
| .btn-success { background: var(--accent2); border-color: var(--accent2); font-family: 'Courier New', monospace; font-size: 0.82rem; color: #0d1117; font-weight: 700; } | |
| .badge-unrelated { background: #238636; } | |
| .badge-distant { background: #9e6a03; } | |
| .badge-moderate { background: #da3633; } | |
| table.dataTable { background: #0d1117; color: #e6edf3; border-color: var(--border); font-size: 0.82rem; } | |
| table.dataTable thead { background: var(--surface); color: var(--muted); } | |
| table.dataTable tbody tr:hover { background: var(--surface); } | |
| .dataTables_wrapper { color: var(--muted); } | |
| .dataTables_wrapper .dataTables_filter input, | |
| .dataTables_wrapper .dataTables_length select { background: #0d1117; color: #e6edf3; border: 1px solid var(--border); border-radius: 4px; } | |
| .dataTables_wrapper .dataTables_paginate .paginate_button.current, | |
| .dataTables_wrapper .dataTables_paginate .paginate_button.current:hover { background: var(--accent); border-color: var(--accent); color: white !important; border-radius: 4px; } | |
| .dataTables_wrapper .dataTables_paginate .paginate_button:hover { background: var(--surface); color: #e6edf3 !important; border-color: var(--border); border-radius: 4px; } | |
| .step-badge { display: inline-block; background: var(--accent); color: #0d1117; border-radius: 50%; width: 22px; height: 22px; text-align: center; line-height: 22px; font-size: 0.72rem; font-weight: 700; margin-right: 8px; } | |
| .alert-info-custom { background: rgba(88,166,255,0.08); border: 1px solid rgba(88,166,255,0.3); border-radius: 6px; padding: 12px 16px; color: #79c0ff; font-size: 0.82rem; } | |
| .alert-success-custom { background: rgba(63,185,80,0.08); border: 1px solid rgba(63,185,80,0.3); border-radius: 6px; padding: 12px 16px; color: var(--accent2); font-size: 0.82rem; } | |
| .alert-warn-custom { background: rgba(210,153,34,0.1); border: 1px solid rgba(210,153,34,0.3); border-radius: 6px; padding: 12px 16px; color: #d29922; font-size: 0.82rem; } | |
| .shiny-plot-output { border-radius: 6px; overflow: hidden; } | |
| hr { border-color: var(--border); } | |
| .progress { background: var(--border); } | |
| .progress-bar { background: var(--accent); } | |
| "))), | |
| # Header | |
| div(class = "app-header", | |
| div(class = "app-title", "β Half-Diallel Cross Optimizer"), | |
| div(class = "app-subtitle", "GENOMIC SELECTION Β· KINSHIP-AWARE CROSSING Β· BREEDING VALUE PREDICTION") | |
| ), | |
| div(style = "padding: 0 24px;", | |
| tabsetPanel( | |
| # ββ TAB 1: Upload & Configure ββ | |
| tabPanel("β Upload & Configure", | |
| br(), | |
| fluidRow( | |
| column(4, | |
| div(class = "card-panel", | |
| div(class = "card-title", "Step 1 β Load Data"), | |
| div(class = "alert-info-custom", style = "margin-bottom: 14px;", | |
| "Upload your criterion CSV (columns: Genotype, Criterion) and kinship matrix CSV." | |
| ), | |
| fileInput("criterion_file", "Criterion File (.csv)", accept = ".csv", | |
| buttonLabel = "Browse", placeholder = "criterion.csv"), | |
| fileInput("kmat_file", "Kinship Matrix File (.csv)", accept = ".csv", | |
| buttonLabel = "Browse", placeholder = "kmat.csv"), | |
| hr(), | |
| div(class = "card-title", "Step 2 β Thinning Parameters"), | |
| sliderInput("threshold", "Kinship Threshold", min = 0.05, max = 0.5, value = 0.15, step = 0.01), | |
| sliderInput("max_per_cluster", "Max per Cluster", min = 1, max = 20, value = 5, step = 1), | |
| hr(), | |
| actionButton("run_btn", "βΆ Run Analysis", class = "btn-primary btn-block", width = "100%"), | |
| br(), | |
| uiOutput("run_status") | |
| ) | |
| ), | |
| column(8, | |
| div(class = "card-panel", | |
| div(class = "card-title", "Data Preview & Validation"), | |
| uiOutput("data_validation"), | |
| fluidRow( | |
| column(6, tableOutput("criterion_preview")), | |
| column(6, uiOutput("kmat_preview")) | |
| ) | |
| ) | |
| ) | |
| ) | |
| ), | |
| # ββ TAB 2: Summary Dashboard ββ | |
| tabPanel("β‘ Summary Dashboard", | |
| br(), | |
| uiOutput("summary_stats_ui"), | |
| br(), | |
| fluidRow( | |
| column(6, | |
| div(class = "card-panel", | |
| div(class = "card-title", "Predicted Offspring Value Distribution"), | |
| plotOutput("hist_pov", height = "260px") | |
| ) | |
| ), | |
| column(6, | |
| div(class = "card-panel", | |
| div(class = "card-title", "Kinship Coefficient Distribution"), | |
| plotOutput("hist_kinship", height = "260px") | |
| ) | |
| ) | |
| ), | |
| fluidRow( | |
| column(6, | |
| div(class = "card-panel", | |
| div(class = "card-title", "Crosses by Relatedness Category"), | |
| plotOutput("bar_relatedness", height = "260px") | |
| ) | |
| ), | |
| column(6, | |
| div(class = "card-panel", | |
| div(class = "card-title", "Value vs Kinship Scatter"), | |
| plotOutput("scatter_vk", height = "260px") | |
| ) | |
| ) | |
| ) | |
| ), | |
| # ββ TAB 3: All Crosses ββ | |
| tabPanel("β’ All Crosses", | |
| br(), | |
| div(class = "card-panel", | |
| div(class = "card-title", "Half-Diallel Cross Table"), | |
| fluidRow( | |
| column(3, | |
| selectInput("filter_relatedness", "Filter Relatedness", | |
| choices = c("All", "Unrelated", "Distantly_Related", "Moderately_Related", "Closely_Related"), | |
| selected = "All") | |
| ), | |
| column(3, | |
| selectInput("filter_performance", "Filter Performance", | |
| choices = c("All", "Low", "Medium", "High", "Very_High"), | |
| selected = "All") | |
| ), | |
| column(3, | |
| numericInput("filter_kinship_max", "Max Kinship", value = 1, min = 0, max = 1, step = 0.01) | |
| ), | |
| column(3, style = "margin-top: 24px;", | |
| downloadButton("download_all", "β¬ Download CSV", class = "btn-success") | |
| ) | |
| ), | |
| DTOutput("crosses_table") | |
| ) | |
| ), | |
| # ββ TAB 4: Top Crosses ββ | |
| tabPanel("β£ Top Crosses", | |
| br(), | |
| fluidRow( | |
| column(6, | |
| div(class = "card-panel", | |
| div(class = "card-title", "Top 50 β Low Kinship (K < 0.125)"), | |
| DTOutput("top_low_kinship"), | |
| br(), | |
| downloadButton("dl_low_k", "β¬ Download", class = "btn-success btn-sm") | |
| ) | |
| ), | |
| column(6, | |
| div(class = "card-panel", | |
| div(class = "card-title", "Top 50 β Medium Kinship (0.125 β€ K < 0.25)"), | |
| DTOutput("top_med_kinship"), | |
| br(), | |
| downloadButton("dl_med_k", "β¬ Download", class = "btn-success btn-sm") | |
| ) | |
| ) | |
| ) | |
| ), | |
| # ββ TAB 5: Parent Summary ββ | |
| tabPanel("β€ Parent Summary", | |
| br(), | |
| fluidRow( | |
| column(5, | |
| div(class = "card-panel", | |
| div(class = "card-title", "Parent Usage & Criterion Values"), | |
| DTOutput("parent_table"), | |
| br(), | |
| downloadButton("dl_parents", "β¬ Download", class = "btn-success btn-sm") | |
| ) | |
| ), | |
| column(7, | |
| div(class = "card-panel", | |
| div(class = "card-title", "Parent Criterion Values (Ranked)"), | |
| plotOutput("parent_bar", height = "400px") | |
| ) | |
| ) | |
| ) | |
| ) | |
| ) | |
| ) | |
| ) | |
| # ββ Server ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| server <- function(input, output, session) { | |
| dark_theme <- function() { | |
| theme( | |
| plot.background = element_rect(fill = "#161b22", color = NA), | |
| panel.background = element_rect(fill = "#0d1117", color = NA), | |
| panel.grid.major = element_line(color = "#21262d", size = 0.4), | |
| panel.grid.minor = element_blank(), | |
| axis.text = element_text(color = "#8b949e", size = 9, family = "mono"), | |
| axis.title = element_text(color = "#8b949e", size = 9, family = "mono"), | |
| plot.title = element_text(color = "#e6edf3", size = 11, family = "mono", face = "bold"), | |
| legend.background = element_rect(fill = "#161b22"), | |
| legend.text = element_text(color = "#8b949e", size = 8), | |
| legend.title = element_text(color = "#8b949e", size = 8), | |
| strip.background = element_rect(fill = "#161b22"), | |
| strip.text = element_text(color = "#8b949e") | |
| ) | |
| } | |
| COLS <- c("Unrelated" = "#3fb950", "Distantly_Related" = "#d29922", | |
| "Moderately_Related" = "#f78166", "Closely_Related" = "#da3633") | |
| # ββ Reactive data ββ | |
| criterion_data <- reactive({ | |
| req(input$criterion_file) | |
| tryCatch(load_data(input$criterion_file$datapath, c("Genotype", "Criterion")), | |
| error = function(e) { showNotification(e$message, type = "error"); NULL }) | |
| }) | |
| kmat_data <- reactive({ | |
| req(input$kmat_file) | |
| tryCatch({ | |
| raw <- load_data(input$kmat_file$datapath) | |
| process_kmat(raw) | |
| }, error = function(e) { showNotification(e$message, type = "error"); NULL }) | |
| }) | |
| # ββ Validation UI ββ | |
| output$data_validation <- renderUI({ | |
| crit <- criterion_data() | |
| kmat <- kmat_data() | |
| if (is.null(crit) && is.null(kmat)) return(div(class = "alert-info-custom", "Upload files to begin validation.")) | |
| msgs <- list() | |
| if (!is.null(crit)) { | |
| crit$Genotype <- trimws(as.character(crit$Genotype)) | |
| msgs <- c(msgs, list(div(class = "alert-success-custom", paste("β Criterion loaded:", nrow(crit), "rows,", length(unique(crit$Genotype)), "unique genotypes")))) | |
| } | |
| if (!is.null(kmat)) { | |
| msgs <- c(msgs, list(div(class = "alert-success-custom", paste("β Kinship matrix loaded:", nrow(kmat), "Γ", ncol(kmat))))) | |
| } | |
| if (!is.null(crit) && !is.null(kmat)) { | |
| crit$Genotype <- trimws(as.character(crit$Genotype)) | |
| overlap <- sum(crit$Genotype %in% rownames(kmat)) | |
| cls <- if (overlap == 0) "alert-warn-custom" else "alert-success-custom" | |
| msgs <- c(msgs, list(div(class = cls, paste("Overlap:", overlap, "genotypes match between files")))) | |
| } | |
| do.call(tagList, msgs) | |
| }) | |
| output$criterion_preview <- renderTable({ | |
| req(criterion_data()) | |
| head(criterion_data(), 8) | |
| }, striped = TRUE, bordered = TRUE, hover = TRUE) | |
| output$kmat_preview <- renderUI({ | |
| req(kmat_data()) | |
| k <- kmat_data() | |
| div(p(style = "color: #8b949e; font-size: 0.8rem;", | |
| paste0("Matrix: ", nrow(k), " Γ ", ncol(k), " | Range: [", | |
| round(min(k), 4), ", ", round(max(k), 4), "]"))) | |
| }) | |
| # ββ Analysis reactive ββ | |
| analysis_results <- eventReactive(input$run_btn, { | |
| req(criterion_data(), kmat_data()) | |
| withProgress(message = "Running analysis...", value = 0, { | |
| crit <- criterion_data() | |
| crit$Genotype <- trimws(as.character(crit$Genotype)) | |
| kmat <- kmat_data() | |
| incProgress(0.1, detail = "Filtering genotypes...") | |
| crit_filtered <- crit[crit$Genotype %in% rownames(kmat), ] | |
| validate(need(nrow(crit_filtered) > 0, "No matching genotypes. Check genotype name formats.")) | |
| incProgress(0.3, detail = "Relatedness thinning...") | |
| keep <- tryCatch( | |
| relate_thinning(kmat, crit_filtered, input$threshold, input$max_per_cluster), | |
| error = function(e) { showNotification(paste("Thinning error:", e$message), type = "error"); NULL } | |
| ) | |
| req(keep) | |
| all_parents <- intersect(keep, rownames(kmat)) | |
| validate(need(length(all_parents) >= 2, "Need at least 2 parents after thinning.")) | |
| incProgress(0.5, detail = "Generating crosses...") | |
| crosses <- tryCatch( | |
| generate_half_diallel(all_parents, crit_filtered, kmat), | |
| error = function(e) { showNotification(paste("Cross error:", e$message), type = "error"); NULL } | |
| ) | |
| req(crosses) | |
| incProgress(0.85, detail = "Building summaries...") | |
| parent_usage <- data.frame( | |
| Genotype = all_parents, | |
| Times_Used = sapply(all_parents, function(x) sum(crosses$Parent1 == x | crosses$Parent2 == x)), | |
| Criterion_Value = sapply(all_parents, function(x) crit_filtered$Criterion[crit_filtered$Genotype == x]), | |
| stringsAsFactors = FALSE | |
| ) | |
| parent_usage <- parent_usage[order(parent_usage$Criterion_Value, decreasing = TRUE), ] | |
| incProgress(1) | |
| list(crosses = crosses, parents = all_parents, parent_usage = parent_usage, | |
| crit_filtered = crit_filtered, n_original = nrow(crit)) | |
| }) | |
| }) | |
| output$run_status <- renderUI({ | |
| res <- analysis_results() | |
| if (is.null(res)) return(NULL) | |
| div(class = "alert-success-custom", style = "margin-top: 10px;", | |
| paste0("β Complete: ", nrow(res$crosses), " crosses from ", length(res$parents), " parents")) | |
| }) | |
| # ββ Summary stats ββ | |
| output$summary_stats_ui <- renderUI({ | |
| req(analysis_results()) | |
| res <- analysis_results() | |
| cr <- res$crosses | |
| make_stat <- function(num, lab) { | |
| div(class = "stat-box", | |
| div(class = "stat-num", num), | |
| div(class = "stat-lab", lab) | |
| ) | |
| } | |
| div(class = "card-panel", | |
| div(class = "card-title", "Analysis Summary"), | |
| fluidRow( | |
| column(2, make_stat(nrow(cr), "Total Crosses")), | |
| column(2, make_stat(length(res$parents), "Parents")), | |
| column(2, make_stat(round(mean(cr$Predicted_Offspring_Value), 3), "Mean POV")), | |
| column(2, make_stat(round(max(cr$Predicted_Offspring_Value), 3), "Max POV")), | |
| column(2, make_stat(sum(cr$Kinship_Coefficient < 0.125), "K < 0.125")), | |
| column(2, make_stat(round(mean(cr$Kinship_Coefficient), 4), "Mean Kinship")) | |
| ) | |
| ) | |
| }) | |
| # ββ Plots ββ | |
| output$hist_pov <- renderPlot({ | |
| req(analysis_results()) | |
| cr <- analysis_results()$crosses | |
| ggplot(cr, aes(x = Predicted_Offspring_Value)) + | |
| geom_histogram(bins = 40, fill = "#58a6ff", alpha = 0.85, color = "#0d1117") + | |
| geom_vline(xintercept = mean(cr$Predicted_Offspring_Value), color = "#3fb950", linetype = "dashed", size = 0.8) + | |
| labs(x = "Predicted Offspring Value", y = "Count") + | |
| dark_theme() | |
| }, bg = "#161b22") | |
| output$hist_kinship <- renderPlot({ | |
| req(analysis_results()) | |
| cr <- analysis_results()$crosses | |
| ggplot(cr, aes(x = Kinship_Coefficient)) + | |
| geom_histogram(bins = 40, fill = "#f78166", alpha = 0.85, color = "#0d1117") + | |
| geom_vline(xintercept = 0.125, color = "#d29922", linetype = "dashed", size = 0.8) + | |
| geom_vline(xintercept = 0.25, color = "#da3633", linetype = "dashed", size = 0.8) + | |
| labs(x = "Kinship Coefficient (K)", y = "Count") + | |
| dark_theme() | |
| }, bg = "#161b22") | |
| output$bar_relatedness <- renderPlot({ | |
| req(analysis_results()) | |
| cr <- analysis_results()$crosses | |
| cnt <- as.data.frame(table(cr$Relatedness_Category)) | |
| colnames(cnt) <- c("Category", "Count") | |
| ggplot(cnt, aes(x = reorder(Category, -Count), y = Count, fill = Category)) + | |
| geom_col(alpha = 0.9, width = 0.6) + | |
| scale_fill_manual(values = COLS, guide = "none") + | |
| labs(x = "", y = "Number of Crosses") + | |
| dark_theme() + | |
| theme(axis.text.x = element_text(angle = 20, hjust = 1)) | |
| }, bg = "#161b22") | |
| output$scatter_vk <- renderPlot({ | |
| req(analysis_results()) | |
| cr <- analysis_results()$crosses | |
| ggplot(cr, aes(x = Kinship_Coefficient, y = Predicted_Offspring_Value, color = Relatedness_Category)) + | |
| geom_point(alpha = 0.35, size = 0.8) + | |
| geom_vline(xintercept = 0.125, color = "#d29922", linetype = "dashed", size = 0.6, alpha = 0.7) + | |
| scale_color_manual(values = COLS, name = "Relatedness") + | |
| labs(x = "Kinship (K)", y = "Predicted Offspring Value") + | |
| dark_theme() + | |
| theme(legend.position = "bottom") | |
| }, bg = "#161b22") | |
| output$parent_bar <- renderPlot({ | |
| req(analysis_results()) | |
| pu <- analysis_results()$parent_usage | |
| pu$Genotype <- factor(pu$Genotype, levels = pu$Genotype[order(pu$Criterion_Value)]) | |
| ggplot(pu, aes(x = Criterion_Value, y = Genotype)) + | |
| geom_col(fill = "#58a6ff", alpha = 0.8, width = 0.7) + | |
| labs(x = "Criterion Value", y = "") + | |
| dark_theme() + | |
| theme(axis.text.y = element_text(size = 7)) | |
| }, bg = "#161b22") | |
| # ββ Tables ββ | |
| dt_opts <- function(dom = "lfrtip") { | |
| list(pageLength = 15, dom = dom, | |
| initComplete = JS("function(settings, json) { | |
| $(this.api().table().header()).css({'background-color': '#161b22', 'color': '#8b949e'}); | |
| }")) | |
| } | |
| filtered_crosses <- reactive({ | |
| req(analysis_results()) | |
| cr <- analysis_results()$crosses | |
| if (input$filter_relatedness != "All") cr <- cr[cr$Relatedness_Category == input$filter_relatedness, ] | |
| if (input$filter_performance != "All") cr <- cr[cr$Performance_Category == input$filter_performance, ] | |
| cr <- cr[cr$Kinship_Coefficient <= input$filter_kinship_max, ] | |
| cr | |
| }) | |
| output$crosses_table <- renderDT({ | |
| req(filtered_crosses()) | |
| datatable(filtered_crosses(), options = dt_opts(), rownames = FALSE, | |
| selection = "none") %>% | |
| formatRound(c("P1_Criterion_Value", "P2_Criterion_Value", "Predicted_Offspring_Value"), 4) %>% | |
| formatRound("Kinship_Coefficient", 6) | |
| }) | |
| output$top_low_kinship <- renderDT({ | |
| req(analysis_results()) | |
| d <- analysis_results()$crosses %>% filter(Kinship_Coefficient < 0.125) %>% head(50) %>% | |
| select(Parent1, Parent2, Predicted_Offspring_Value, Kinship_Coefficient, Relatedness_Category) | |
| datatable(d, options = dt_opts("tp"), rownames = FALSE) %>% | |
| formatRound(c("Predicted_Offspring_Value", "Kinship_Coefficient"), 4) | |
| }) | |
| output$top_med_kinship <- renderDT({ | |
| req(analysis_results()) | |
| d <- analysis_results()$crosses %>% | |
| filter(Kinship_Coefficient >= 0.125, Kinship_Coefficient < 0.25) %>% head(50) %>% | |
| select(Parent1, Parent2, Predicted_Offspring_Value, Kinship_Coefficient, Relatedness_Category) | |
| datatable(d, options = dt_opts("tp"), rownames = FALSE) %>% | |
| formatRound(c("Predicted_Offspring_Value", "Kinship_Coefficient"), 4) | |
| }) | |
| output$parent_table <- renderDT({ | |
| req(analysis_results()) | |
| datatable(analysis_results()$parent_usage, options = dt_opts("tp"), rownames = FALSE) %>% | |
| formatRound("Criterion_Value", 4) | |
| }) | |
| # ββ Downloads ββ | |
| output$download_all <- downloadHandler( | |
| filename = function() paste0("Half_Diallel_Crosses_", Sys.Date(), ".csv"), | |
| content = function(file) write.csv(filtered_crosses(), file, row.names = FALSE) | |
| ) | |
| output$dl_low_k <- downloadHandler( | |
| filename = function() paste0("Top50_Low_Kinship_", Sys.Date(), ".csv"), | |
| content = function(file) write.csv( | |
| analysis_results()$crosses %>% filter(Kinship_Coefficient < 0.125) %>% head(50), | |
| file, row.names = FALSE) | |
| ) | |
| output$dl_med_k <- downloadHandler( | |
| filename = function() paste0("Top50_Med_Kinship_", Sys.Date(), ".csv"), | |
| content = function(file) write.csv( | |
| analysis_results()$crosses %>% filter(Kinship_Coefficient >= 0.125, Kinship_Coefficient < 0.25) %>% head(50), | |
| file, row.names = FALSE) | |
| ) | |
| output$dl_parents <- downloadHandler( | |
| filename = function() paste0("Parent_Usage_", Sys.Date(), ".csv"), | |
| content = function(file) write.csv(analysis_results()$parent_usage, file, row.names = FALSE) | |
| ) | |
| } | |
| shinyApp(ui, server) | |