OCS / app.R
softwareDevelopment's picture
Update app.R
f5493a0 verified
Raw
History Blame Contribute Delete
27.5 kB
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)