| |
| |
| |
|
|
| tf_activity_server <- function(input, output, session, deg_results) { |
|
|
| |
| collectri_net <- reactive({ |
| req(input$data_source) |
|
|
| if (input$data_source == "counts") { |
| species_code <- input$species_select |
| } else { |
| species_code <- input$deg_species |
| } |
|
|
| organism_code <- if(species_code == "Hs") 'human' else 'mouse' |
| current_file <- paste0("collectri_", organism_code, ".rds") |
|
|
| if (file.exists(current_file)) { |
| showNotification(paste0("正在从本地加载 CollecTRI (", organism_code, ")..."), type = "message", duration = 3) |
| return(readRDS(current_file)) |
| } |
|
|
| showNotification(paste0("本地文件不存在,正在从网上下载 CollecTRI (", organism_code, ")..."), type = "warning", duration = 5) |
|
|
| tryCatch({ |
| net <- decoupleR::get_collectri(organism = organism_code, split_complexes = FALSE) |
| saveRDS(net, current_file) |
| showNotification(paste0("CollecTRI (", organism_code, ") 下载并保存成功!"), type = "message", duration = 3) |
| return(net) |
| }, error = function(e) { |
| showNotification(paste("下载 CollecTRI 网络失败:", e$message), type = "error") |
| return(NULL) |
| }) |
| }) |
|
|
| |
| tf_activity_results <- eventReactive(input$run_tf_activity, { |
| req(deg_results(), collectri_net()) |
|
|
| |
| method <- input$tf_method |
|
|
| showNotification(paste("正在推断转录因子活性 (算法:", toupper(method), ")..."), type = "message") |
|
|
| |
| deg_data <- deg_results() |
| deg_res <- deg_data$deg_df |
| net <- collectri_net() |
|
|
| |
| stats_df <- deg_res %>% |
| filter(!is.na(SYMBOL), !is.na(t_stat)) %>% |
|
|
| group_by(SYMBOL) %>% |
| filter(abs(log2FoldChange) == max(abs(log2FoldChange))) %>% |
| ungroup() %>% |
| distinct(SYMBOL, .keep_all = TRUE) |
|
|
| if (nrow(stats_df) < 5) { |
| showNotification( |
| paste0("TF 分析失败: 用于 TF 推断的基因数量 (", nrow(stats_df), ") 不足,请检查数据和阈值。"), |
| type = "error", |
| duration = 15 |
| ) |
| return(NULL) |
| } |
|
|
| |
| input_genes <- stats_df$SYMBOL |
| net_targets <- unique(net$target) |
| shared_genes <- intersect(input_genes, net_targets) |
|
|
| if(length(shared_genes) < input$tf_min_size) { |
| showNotification( |
| paste0("TF 分析失败: 共享靶基因数量 (", length(shared_genes), ") 小于最小要求 (", input$tf_min_size, ")。"), |
| type = "error", |
| duration = 15 |
| ) |
| return(NULL) |
| } |
|
|
| stats_df_filtered <- stats_df %>% filter(SYMBOL %in% shared_genes) |
|
|
| |
| stats_df_clean <- stats_df_filtered %>% |
| filter(!is.na(t_stat)) %>% |
| filter(is.finite(t_stat)) %>% |
| filter(t_stat != 0) |
|
|
| cat(sprintf("📊 TF分析: 原始 %d 基因 -> 清洗后 %d 基因\n", |
| nrow(stats_df_filtered), nrow(stats_df_clean))) |
|
|
| if (nrow(stats_df_clean) < 5) { |
| showNotification( |
| paste0("TF 分析失败: 清洗后的有效基因数量 (", nrow(stats_df_clean), ") 不足,请检查数据质量。"), |
| type = "error", |
| duration = 15 |
| ) |
| return(NULL) |
| } |
|
|
| mat_input <- stats_df_clean %>% |
| select(SYMBOL, t_stat) %>% |
| column_to_rownames(var = "SYMBOL") %>% |
| as.matrix() |
|
|
| |
| if (any(is.na(mat_input)) || any(!is.finite(mat_input))) { |
| cat("⚠️ 警告: 矩阵中仍有NA或Inf值\n") |
| mat_input <- mat_input[is.finite(rowSums(mat_input)), ] |
| mat_input <- mat_input[, is.finite(colSums(mat_input))] |
| } |
|
|
| |
| tryCatch({ |
| contrast_acts <- switch(method, |
| "ulm" = { |
| decoupleR::run_ulm( |
| mat = mat_input, |
| net = net, |
| .source = 'source', |
| .target = 'target', |
| .mor = 'mor', |
| minsize = input$tf_min_size |
| ) |
| }, |
| "mlm" = { |
| decoupleR::run_mlm( |
| mat = mat_input, |
| net = net, |
| .source = 'source', |
| .target = 'target', |
| .mor = 'mor', |
| minsize = input$tf_min_size |
| ) |
| }, |
| "wmean" = { |
| decoupleR::run_wmean( |
| mat = mat_input, |
| net = net, |
| .source = 'source', |
| .target = 'target', |
| .mor = 'mor', |
| minsize = input$tf_min_size |
| ) |
| }, |
| "wsum" = { |
| decoupleR::run_wsum( |
| mat = mat_input, |
| net = net, |
| .source = 'source', |
| .target = 'target', |
| .mor = 'mor', |
| minsize = input$tf_min_size |
| ) |
| }, |
| { |
| |
| decoupleR::run_ulm( |
| mat = mat_input, |
| net = net, |
| .source = 'source', |
| .target = 'target', |
| .mor = 'mor', |
| minsize = input$tf_min_size |
| ) |
| } |
| ) |
|
|
| |
| if (is.data.frame(contrast_acts)) { |
| result_df <- contrast_acts |
| } else if (is.list(contrast_acts)) { |
| |
| if ("statistic" %in% names(contrast_acts)) { |
| result_df <- contrast_acts$statistic |
| } else { |
| result_df <- as.data.frame(contrast_acts) |
| } |
| } else { |
| result_df <- as.data.frame(contrast_acts) |
| } |
|
|
| |
| result_df <- result_df %>% |
| mutate(rnk = NA) |
|
|
| msk_pos <- result_df$score > 0 |
| result_df[msk_pos, 'rnk'] <- rank(-result_df[msk_pos, 'score']) |
|
|
| msk_neg <- result_df$score < 0 |
| result_df[msk_neg, 'rnk'] <- rank(-abs(result_df[msk_neg, 'score'])) |
|
|
| |
| result_df$method <- toupper(method) |
|
|
| showNotification(paste("转录因子活性推断完成! (算法:", toupper(method), ")"), type = "message") |
|
|
| return(result_df) |
|
|
| }, error = function(e) { |
| error_msg <- e$message |
|
|
| |
| if (grepl("colinear", error_msg, ignore.case = TRUE)) { |
| showNotification( |
| paste("TF 活性分析失败 (", toupper(method), "): 检测到共线性问题。", |
| "\n建议: 请尝试使用ULM、WMEAN或WSUM算法代替MLM算法。", |
| "\nMLM算法对数据质量要求较高,容易出现共线性问题。"), |
| type = "error", |
| duration = 10 |
| ) |
| } else { |
| showNotification(paste("TF 活性分析失败 (", toupper(method), "):", error_msg), type = "error") |
| } |
|
|
| return(NULL) |
| }) |
| }) |
|
|
| |
| output$tf_activity_bar_plot <- renderPlot({ |
| req(tf_activity_results()) |
|
|
| df_acts <- tf_activity_results() |
|
|
| n_tfs <- input$tf_top_n |
|
|
| tfs_to_plot <- df_acts %>% |
| arrange(rnk) %>% |
| head(n_tfs) %>% |
| pull(source) |
|
|
| f_contrast_acts <- df_acts %>% |
| filter(source %in% tfs_to_plot) |
|
|
| txt_col <- if(input$theme_toggle) "white" else "black" |
| grid_col <- if(input$theme_toggle) "#444444" else "#cccccc" |
|
|
| ggplot(f_contrast_acts, aes(x = reorder(source, score), y = score)) + |
| geom_bar(aes(fill = score), stat = "identity") + |
| scale_fill_gradient2( |
| low = input$tf_inactive_col, |
| high = input$tf_active_col, |
| mid = "whitesmoke", |
| midpoint = 0, |
| name = "TF 活性分数" |
| ) + |
| geom_hline(yintercept = 0, linetype = 'dashed', color = txt_col) + |
| theme_minimal() + |
| labs(x = "转录因子 (TFs)", y = "活性分数 (ULM Score)", |
| title = paste("Top", n_tfs, "转录因子活性变化 (T vs C)")) + |
| theme( |
| panel.background = element_rect(fill = "transparent", colour = NA), |
| plot.background = element_rect(fill = "transparent", colour = NA), |
| plot.title = element_text(color = txt_col, face = "bold", hjust = 0.5), |
| axis.title = element_text(color = txt_col, face = "bold", size = 12), |
| axis.text.x = element_text(angle = 45, hjust = 1, size = 10, face = "bold", color = txt_col), |
| axis.text.y = element_text(size = 10, face = "bold", color = txt_col), |
| legend.text = element_text(color = txt_col), |
| legend.title = element_text(color = txt_col), |
| axis.line = element_line(color = txt_col), |
| panel.grid.major = element_line(color = grid_col), |
| panel.grid.minor = element_line(color = grid_col) |
| ) |
| }) |
|
|
| output$download_tf_results <- downloadHandler( |
| filename = function() { |
| paste0("TF_Activity_Results_", Sys.Date(), ".csv") |
| }, |
| content = function(file) { |
| req(tf_activity_results()) |
| df <- tf_activity_results() %>% |
| select(source, score, p_value, rnk) %>% |
| rename(TF = source, Score = score, P.Value = p_value, Rank = rnk) |
| write.csv(df, file, row.names = FALSE) |
| } |
| ) |
|
|
| output$tf_activity_table <- DT::renderDataTable({ |
| req(tf_activity_results()) |
|
|
| df <- tf_activity_results() %>% |
| select(source, score, p_value, rnk) %>% |
| rename(TF = source, Score = score, P.Value = p_value, Rank = rnk) %>% |
| arrange(Rank) |
|
|
| DT::datatable(df, selection = 'single', options = list(scrollX=T, pageLength=10), rownames=F) %>% |
| formatRound(c("Score", "P.Value"), 4) |
| }) |
|
|
| |
| |
| |
| selected_tf_targets <- reactive({ |
| req(tf_activity_results(), collectri_net(), deg_results()) |
|
|
| selected_row <- input$tf_activity_table_rows_selected |
| if (length(selected_row) == 0) { |
| return(NULL) |
| } |
|
|
| |
| tf_res <- tf_activity_results() |
| if (!is.data.frame(tf_res)) { |
| showNotification("TF结果格式错误: 不是数据框", type = "error") |
| return(NULL) |
| } |
|
|
| net <- collectri_net() |
| if (!is.data.frame(net)) { |
| showNotification("CollecTRI网络格式错误: 不是数据框", type = "error") |
| return(NULL) |
| } |
|
|
| deg_res <- deg_results() |
| if (!is.list(deg_res) || is.null(deg_res$deg_df)) { |
| showNotification("差异分析结果格式错误", type = "error") |
| return(NULL) |
| } |
|
|
| tf_name <- tf_res %>% |
| arrange(rnk) %>% |
| slice(selected_row) %>% |
| pull(source) |
|
|
| tf_net <- net %>% |
| filter(source == tf_name) %>% |
| select(target, mor) %>% |
| rename(SYMBOL = target, Mode_of_Regulation = mor) |
|
|
| |
| deg_data <- deg_res$deg_df %>% |
| select(SYMBOL, log2FoldChange, pvalue, padj, t_stat, Status) |
|
|
| final_table <- tf_net %>% |
| left_join(deg_data, by = "SYMBOL") %>% |
| mutate( |
| t_stat_clean = tidyr::replace_na(t_stat, 0), |
| mor_clean = tidyr::replace_na(Mode_of_Regulation, 0) |
| ) %>% |
| mutate(Is_DE = ifelse(Status != "Not DE", "Yes", "No") ) %>% |
| |
| mutate( |
| Predicted_Change = case_when( |
| Mode_of_Regulation > 0 ~ "Activator (Up)", |
| Mode_of_Regulation < 0 ~ "Repressor (Down)", |
| TRUE ~ "Unknown" |
| ), |
| Actual_Change = case_when( |
| log2FoldChange > 0 ~ "Up", |
| log2FoldChange < 0 ~ "Down", |
| TRUE ~ "Not DE" |
| ), |
| Match_Status = case_when( |
| Predicted_Change == "Activator (Up)" & Actual_Change == "Up" ~ "Consistent", |
| Predicted_Change == "Repressor (Down)" & Actual_Change == "Down" ~ "Consistent", |
| Predicted_Change == "Activator (Up)" & Actual_Change == "Down" ~ "Inconsistent", |
| Predicted_Change == "Repressor (Down)" & Actual_Change == "Up" ~ "Inconsistent", |
| TRUE ~ "Neutral/Unknown" |
| ) |
| ) %>% |
| select(SYMBOL, Mode_of_Regulation, Is_DE, Status, log2FoldChange, t_stat, pvalue, padj, |
| Predicted_Change, Actual_Change, Match_Status) %>% |
| arrange(desc(abs(log2FoldChange))) %>% |
| |
| select(-Predicted_Change, -Actual_Change) |
|
|
| return(list(tf_name = tf_name, data = final_table)) |
| }) |
|
|
| output$tf_target_table <- DT::renderDataTable({ |
| req(selected_tf_targets()) |
|
|
| data_list <- selected_tf_targets() |
|
|
| DT::datatable( |
| data_list$data, |
| caption = paste0("TF: ", data_list$tf_name, " 的靶基因"), |
| options = list(scrollX=T, pageLength=10), rownames=F |
| ) %>% |
| formatRound(c("log2FoldChange", "t_stat", "pvalue", "padj"), 4) |
| }) |
|
|
| |
| output$tf_consistency_summary <- renderText({ |
| req(selected_tf_targets()) |
|
|
| df <- selected_tf_targets()$data |
|
|
| |
| n_consistent <- sum(df$Match_Status == "Consistent", na.rm = TRUE) |
| n_inconsistent <- sum(df$Match_Status == "Inconsistent", na.rm = TRUE) |
| n_neutral <- sum(df$Match_Status == "Neutral/Unknown", na.rm = TRUE) |
| n_total <- n_consistent + n_inconsistent + n_neutral |
|
|
| if (n_total > 0) { |
| pct_consistent <- round(100 * n_consistent / n_total, 1) |
| pct_inconsistent <- round(100 * n_inconsistent / n_total, 1) |
| pct_neutral <- round(100 * n_neutral / n_total, 1) |
|
|
| paste0( |
| "📊 调控一致性统计 | ", |
| "✅ 一致: ", n_consistent, " (", pct_consistent, "%) | ", |
| "❌ 不一致: ", n_inconsistent, " (", pct_inconsistent, "%) | ", |
| "⚪ 未知: ", n_neutral, " (", pct_neutral, "%)" |
| ) |
| } else { |
| "📊 无数据" |
| } |
| }) |
|
|
| output$tf_target_plot <- renderPlot({ |
| req(selected_tf_targets()) |
|
|
| |
| point_size <- input$tf_scatter_point_size |
| if (is.null(point_size)) point_size <- 3 |
|
|
| point_alpha <- input$tf_scatter_alpha |
| if (is.null(point_alpha)) point_alpha <- 0.7 |
|
|
| label_size <- input$tf_scatter_label_size |
| if (is.null(label_size)) label_size <- 3 |
|
|
| n_labels <- input$tf_scatter_n_labels |
| if (is.null(n_labels)) n_labels <- 15 |
|
|
| data_list <- selected_tf_targets() |
| df <- data_list$data |
| tf_name <- data_list$tf_name |
|
|
| |
| |
|
|
| txt_col <- if(input$theme_toggle) "white" else "black" |
| grid_col <- if(input$theme_toggle) "#444444" else "#cccccc" |
|
|
| p <- ggplot(df, aes(x = log2FoldChange, y = -log10(pvalue))) + |
| geom_point(aes(color = Match_Status), size = point_size, alpha = point_alpha) + |
| scale_color_manual( |
| values = c("Consistent" = "#2ecc71", "Inconsistent" = "#e74c3c", "Neutral/Unknown" = "#95a5a6"), |
| name = "调控一致性" |
| ) + |
| geom_vline(xintercept = 0, linetype = "dashed", color = txt_col, alpha = 0.7) + |
| geom_hline(yintercept = -log10(input$pval_cutoff), linetype = "dotted", color = txt_col, alpha = 0.7) + |
| labs( |
| title = paste("TF:", tf_name, "的靶基因差异表达 (Target Genes DE Plot)"), |
| x = "log2(Fold Change)", |
| y = "-log10(P Value)" |
| ) + |
| theme_minimal() + |
| theme( |
| panel.background = element_rect(fill = "transparent", colour = NA), |
| plot.background = element_rect(fill = "transparent", colour = NA), |
| plot.title = element_text(color = txt_col, face = "bold", hjust = 0.5), |
| axis.title = element_text(color = txt_col, face = "bold"), |
| axis.text = element_text(color = txt_col), |
| legend.text = element_text(color = txt_col), |
| legend.title = element_text(color = txt_col), |
| axis.line = element_line(color = txt_col), |
| panel.grid.major = element_line(color = grid_col), |
| panel.grid.minor = element_line(color = grid_col) |
| ) |
|
|
| |
| if (n_labels > 0) { |
| top_genes <- df %>% |
| filter(!is.na(pvalue)) %>% |
| arrange(pvalue) %>% |
| head(min(n_labels, nrow(df))) |
|
|
| if (nrow(top_genes) > 0) { |
| |
| top_genes <- top_genes %>% |
| mutate( |
| label_x = log2FoldChange + ifelse(log2FoldChange > 0, 0.2, -0.2), |
| label_y = -log10(pvalue) + 0.5 |
| ) |
|
|
| p <- p + |
| geom_text( |
| data = top_genes, |
| aes(x = label_x, y = label_y, label = SYMBOL), |
| size = label_size, |
| color = txt_col, |
| fontface = "bold", |
| check_overlap = TRUE, |
| vjust = 0.5, |
| hjust = ifelse(top_genes$log2FoldChange > 0, 0, 1) |
| ) |
| } |
| } |
|
|
| print(p) |
| }) |
|
|
| |
| output$tf_network_plot_interactive <- renderPlotly({ |
| req(selected_tf_targets()) |
|
|
| |
| input$tf_network_node_size |
| input$tf_network_label_size |
| input$tf_tf_node_col |
| input$tf_consistent_act_col |
| input$tf_consistent_rep_col |
| input$tf_inconsistent_act_col |
| input$tf_inconsistent_rep_col |
| input$tf_neutral_col |
| input$theme_toggle |
|
|
| |
| node_size_mult <- input$tf_network_node_size |
| if (is.null(node_size_mult)) node_size_mult <- 1 |
|
|
| |
| label_size <- input$tf_network_label_size |
| if (is.null(label_size)) label_size <- 3.5 |
|
|
| data_list <- selected_tf_targets() |
| df <- data_list$data |
| tf_name <- data_list$tf_name |
|
|
| |
| top_targets <- df %>% |
| filter(!is.na(pvalue)) %>% |
| arrange(pvalue, abs(log2FoldChange)) %>% |
| head(min(30, nrow(df))) %>% |
| mutate( |
| node_type = "target", |
| |
| edge_color = ifelse(Mode_of_Regulation > 0, "#e74c3c", "#3498db"), |
| |
| node_size = ifelse(Is_DE == "Yes", 8, 5) |
| ) |
|
|
| if (nrow(top_targets) < 2) { |
| return(NULL) |
| } |
|
|
| |
| n_targets <- nrow(top_targets) |
| angles <- seq(0, 2*pi, length.out = n_targets + 1)[1:n_targets] |
| top_targets$x <- 2 * cos(angles) |
| top_targets$y <- 2 * sin(angles) |
|
|
| |
| top_targets$color <- ifelse(top_targets$Match_Status == "Consistent", |
| ifelse(top_targets$Mode_of_Regulation > 0, |
| input$tf_consistent_act_col, |
| input$tf_consistent_rep_col), |
| ifelse(top_targets$Match_Status == "Inconsistent", |
| ifelse(top_targets$Mode_of_Regulation > 0, |
| input$tf_inconsistent_act_col, |
| input$tf_inconsistent_rep_col), |
| input$tf_neutral_col)) |
|
|
| |
| edges_list <- list() |
| for (i in 1:n_targets) { |
| edges_list[[i]] <- list( |
| x = c(0, top_targets$x[i], NA), |
| y = c(0, top_targets$y[i], NA), |
| color = top_targets$edge_color[i], |
| linetype = ifelse(top_targets$Match_Status[i] == "Consistent", "solid", "dashed") |
| ) |
| } |
|
|
| |
|
|
| |
| tf_node_data <- data.frame(x = 0, y = 0) |
|
|
| |
| p <- plot_ly() %>% |
| |
| add_trace( |
| data = tf_node_data, |
| x = ~x, y = ~y, |
| type = 'scatter', |
| mode = 'markers+text', |
| name = tf_name, |
| text = ~tf_name, |
| textfont = list( |
| size = label_size, |
| color = if(input$theme_toggle) "white" else "black" |
| ), |
| textposition = 'top center', |
| marker = list( |
| size = 12 * node_size_mult, |
| color = input$tf_tf_node_col, |
| line = list(color = 'white', width = 2) |
| ), |
| hoverinfo = 'text', |
| showlegend = FALSE |
| ) %>% |
| |
| add_trace( |
| data = top_targets, |
| x = ~x, y = ~y, |
| type = 'scatter', |
| mode = 'markers+text', |
| name = 'Target Genes', |
| text = ~SYMBOL, |
| textfont = list( |
| size = label_size * 0.8, |
| color = if(input$theme_toggle) "white" else "black" |
| ), |
| textposition = 'top center', |
| hovertext = ~paste( |
| "Gene:", SYMBOL, "<br>", |
| "log2FC:", round(log2FoldChange, 3), "<br>", |
| "p-value:", format(pvalue, scientific = TRUE, digits = 3), "<br>", |
| "Status:", Match_Status |
| ), |
| marker = list( |
| size = ~node_size * node_size_mult, |
| color = ~color, |
| line = list(color = 'white', width = 1) |
| ), |
| hoverinfo = 'text', |
| showlegend = FALSE |
| ) %>% |
| |
| layout( |
| title = paste("TF:", tf_name, "的靶基因调控网络(可拖动节点)"), |
| showlegend = FALSE, |
| xaxis = list( |
| title = "", |
| showgrid = FALSE, |
| showticklabels = FALSE, |
| zeroline = FALSE, |
| range = c(-3.5, 3.5) |
| ), |
| yaxis = list( |
| title = "", |
| showgrid = FALSE, |
| showticklabels = FALSE, |
| zeroline = FALSE, |
| scaleanchor = "x", |
| range = c(-3.5, 3.5) |
| ), |
| plot_bgcolor = if(input$theme_toggle) "#2b2b2b" else "white", |
| paper_bgcolor = if(input$theme_toggle) "#1a1a1a" else "white", |
| font = list(color = if(input$theme_toggle) "white" else "black"), |
| hovermode = 'closest', |
| dragmode = 'move' |
| ) |
|
|
| |
| for (i in 1:length(edges_list)) { |
| edge_data <- data.frame( |
| x = edges_list[[i]]$x, |
| y = edges_list[[i]]$y |
| ) |
| p <- p %>% add_trace( |
| data = edge_data, |
| x = ~x, y = ~y, |
| type = 'scatter', |
| mode = 'lines', |
| line = list(color = edges_list[[i]]$color, width = 2), |
| showlegend = FALSE, |
| hoverinfo = 'skip', |
| inherit = FALSE |
| ) |
| } |
|
|
| p |
| }) |
|
|
| |
| output$tf_network_plot <- renderPlot({ |
| req(selected_tf_targets()) |
|
|
| |
| node_size_mult <- input$tf_network_node_size |
| if (is.null(node_size_mult)) node_size_mult <- 1 |
|
|
| label_size <- input$tf_network_label_size |
| if (is.null(label_size)) label_size <- 3.5 |
|
|
| data_list <- selected_tf_targets() |
| df <- data_list$data |
| tf_name <- data_list$tf_name |
|
|
| |
| top_targets <- df %>% |
| filter(!is.na(pvalue)) %>% |
| arrange(pvalue, abs(log2FoldChange)) %>% |
| head(min(30, nrow(df))) %>% |
| mutate( |
| node_type = "target", |
| |
| edge_color = ifelse(Mode_of_Regulation > 0, "#e74c3c", "#3498db"), |
| |
| node_size = ifelse(Is_DE == "Yes", 8, 5) |
| ) |
|
|
| if (nrow(top_targets) < 2) { |
| |
| plot.new() |
| title(main = paste("TF:", tf_name, "- 数据不足以绘制网络图")) |
| return() |
| } |
|
|
| |
| |
| tf_node <- data.frame( |
| name = tf_name, |
| node_type = "tf", |
| x = 0, |
| y = 0, |
| node_size = 12, |
| color = input$tf_tf_node_col |
| ) |
|
|
| |
| n_targets <- nrow(top_targets) |
| angles <- seq(0, 2*pi, length.out = n_targets + 1)[1:n_targets] |
|
|
| top_targets$x <- 2 * cos(angles) |
| top_targets$y <- 2 * sin(angles) |
|
|
| |
| top_targets$color <- ifelse(top_targets$Match_Status == "Consistent", |
| ifelse(top_targets$Mode_of_Regulation > 0, |
| input$tf_consistent_act_col, |
| input$tf_consistent_rep_col), |
| ifelse(top_targets$Match_Status == "Inconsistent", |
| ifelse(top_targets$Mode_of_Regulation > 0, |
| input$tf_inconsistent_act_col, |
| input$tf_inconsistent_rep_col), |
| input$tf_neutral_col)) |
|
|
| |
| all_nodes <- rbind( |
| tf_node[, c("name", "x", "y", "node_size", "color")], |
| top_targets[, c("SYMBOL", "x", "y", "node_size", "color")] %>% |
| rename(name = SYMBOL) |
| ) |
|
|
| |
| edges <- data.frame( |
| x = rep(0, n_targets), |
| y = rep(0, n_targets), |
| xend = top_targets$x, |
| yend = top_targets$y, |
| color = top_targets$edge_color, |
| linetype = ifelse(top_targets$Match_Status == "Consistent", "solid", "dashed") |
| ) |
|
|
| txt_col <- if(input$theme_toggle) "white" else "black" |
|
|
| |
| |
| |
| |
|
|
| |
| |
| cat(sprintf("🔍 调试: tf_network_node_size = %s (使用值: %s)\n", |
| input$tf_network_node_size, node_size_mult)) |
| cat(sprintf("🔍 调试: tf_network_label_size = %s (使用值: %s)\n", |
| input$tf_network_label_size, label_size)) |
|
|
| all_nodes$node_size_scaled <- all_nodes$node_size * node_size_mult |
|
|
| cat(sprintf("🔍 调试: 节点大小缩放: %s -> %s\n", |
| all_nodes$node_size[1], all_nodes$node_size_scaled[1])) |
|
|
| |
| p <- ggplot() + |
| |
| geom_segment( |
| data = edges, |
| aes(x = x, xend = xend, y = y, yend = yend, color = color, linetype = linetype), |
| linewidth = 0.8, |
| alpha = 0.6 |
| ) + |
| |
| geom_point( |
| data = all_nodes, |
| aes(x = x, y = y, size = node_size_scaled, color = color), |
| alpha = 0.9 |
| ) + |
| |
| geom_text( |
| data = all_nodes, |
| aes(x = x, y = y, label = name), |
| size = label_size, |
| fontface = "bold", |
| color = txt_col, |
| vjust = ifelse(all_nodes$y > 0, -0.5, 1.5), |
| check_overlap = TRUE |
| ) + |
| scale_color_identity() + |
| scale_linetype_identity() + |
| scale_size_identity() + |
| labs( |
| title = paste("TF:", tf_name, "的靶基因调控网络"), |
| subtitle = paste("Top", n_targets, "靶基因 | 红色=激活, 蓝色=抑制, 实线=一致, 虚线=不一致") |
| ) + |
| theme_minimal() + |
| theme( |
| panel.background = element_rect(fill = "transparent", colour = NA), |
| plot.background = element_rect(fill = "transparent", colour = NA), |
| plot.title = element_text(color = txt_col, face = "bold", hjust = 0.5), |
| plot.subtitle = element_text(color = txt_col, hjust = 0.5), |
| axis.title = element_blank(), |
| axis.text = element_blank(), |
| axis.ticks = element_blank(), |
| panel.grid = element_blank(), |
| legend.position = "none" |
| ) + |
| coord_equal() + |
| xlim(-3, 3) + |
| ylim(-3, 3) |
|
|
| print(p) |
| }) |
|
|
| |
| output$download_tf_network_svg <- downloadHandler( |
| filename = function() { |
| req(selected_tf_targets()) |
| tf_name <- selected_tf_targets()$tf_name |
| paste0("TF_Network_", tf_name, "_", Sys.Date(), ".svg") |
| }, |
| content = function(file) { |
| req(selected_tf_targets()) |
|
|
| |
| data_list <- selected_tf_targets() |
| df <- data_list$data |
| tf_name <- data_list$tf_name |
|
|
| top_targets <- df %>% |
| filter(!is.na(pvalue)) %>% |
| arrange(pvalue, abs(log2FoldChange)) %>% |
| head(min(30, nrow(df))) %>% |
| mutate( |
| node_type = "target", |
| edge_color = ifelse(Mode_of_Regulation > 0, "#e74c3c", "#3498db"), |
| node_size = ifelse(Is_DE == "Yes", 8, 5) |
| ) |
|
|
| if (nrow(top_targets) < 2) return() |
|
|
| tf_node <- data.frame( |
| name = tf_name, node_type = "tf", x = 0, y = 0, |
| node_size = 12, color = input$tf_tf_node_col |
| ) |
|
|
| n_targets <- nrow(top_targets) |
| angles <- seq(0, 2*pi, length.out = n_targets + 1)[1:n_targets] |
| top_targets$x <- 2 * cos(angles) |
| top_targets$y <- 2 * sin(angles) |
| top_targets$color <- ifelse(top_targets$Match_Status == "Consistent", |
| ifelse(top_targets$Mode_of_Regulation > 0, |
| input$tf_consistent_act_col, |
| input$tf_consistent_rep_col), |
| ifelse(top_targets$Match_Status == "Inconsistent", |
| ifelse(top_targets$Mode_of_Regulation > 0, |
| input$tf_inconsistent_act_col, |
| input$tf_inconsistent_rep_col), |
| input$tf_neutral_col)) |
|
|
| all_nodes <- rbind( |
| tf_node[, c("name", "x", "y", "node_size", "color")], |
| top_targets[, c("SYMBOL", "x", "y", "node_size", "color")] %>% rename(name = SYMBOL) |
| ) |
|
|
| edges <- data.frame( |
| x = rep(0, n_targets), y = rep(0, n_targets), |
| xend = top_targets$x, yend = top_targets$y, |
| color = top_targets$edge_color, |
| linetype = ifelse(top_targets$Match_Status == "Consistent", "solid", "dashed") |
| ) |
|
|
| all_nodes$node_size_scaled <- all_nodes$node_size * input$tf_network_node_size |
|
|
| txt_col <- if(input$theme_toggle) "white" else "black" |
|
|
| p <- ggplot() + |
| geom_segment(data = edges, aes(x = x, xend = xend, y = y, yend = yend, |
| color = color, linetype = linetype), |
| linewidth = 0.8, alpha = 0.6) + |
| geom_point(data = all_nodes, aes(x = x, y = y, size = node_size_scaled, color = color), alpha = 0.9) + |
| geom_text(data = all_nodes, aes(x = x, y = y, label = name), |
| size = input$tf_network_label_size, fontface = "bold", |
| color = txt_col, vjust = ifelse(all_nodes$y > 0, -0.5, 1.5), check_overlap = TRUE) + |
| scale_color_identity() + scale_linetype_identity() + scale_size_identity() + |
| labs(title = paste("TF:", tf_name, "的靶基因调控网络"), |
| subtitle = paste("Top", n_targets, "靶基因")) + |
| theme_minimal() + |
| theme(panel.background = element_rect(fill = "white", colour = NA), |
| plot.title = element_text(face = "bold", hjust = 0.5), |
| axis.title = element_blank(), axis.text = element_blank(), |
| axis.ticks = element_blank(), panel.grid = element_blank()) + |
| coord_equal() + xlim(-3, 3) + ylim(-3, 3) |
|
|
| |
| svg(file, width = 10, height = 8) |
| print(p) |
| dev.off() |
| }, |
| contentType = "image/svg+xml" |
| ) |
|
|
| output$download_tf_scatter_svg <- downloadHandler( |
| filename = function() { |
| req(selected_tf_targets()) |
| tf_name <- selected_tf_targets()$tf_name |
| paste0("TF_Scatter_", tf_name, "_", Sys.Date(), ".svg") |
| }, |
| content = function(file) { |
| req(selected_tf_targets()) |
|
|
| |
| data_list <- selected_tf_targets() |
| df <- data_list$data |
| tf_name <- data_list$tf_name |
|
|
| |
| point_size <- input$tf_scatter_point_size |
| if (is.null(point_size)) point_size <- 3 |
|
|
| point_alpha <- input$tf_scatter_alpha |
| if (is.null(point_alpha)) point_alpha <- 0.7 |
|
|
| label_size <- input$tf_scatter_label_size |
| if (is.null(label_size)) label_size <- 3 |
|
|
| n_labels <- input$tf_scatter_n_labels |
| if (is.null(n_labels)) n_labels <- 15 |
|
|
| txt_col <- "black" |
| grid_col <- "#cccccc" |
|
|
| p <- ggplot(df, aes(x = log2FoldChange, y = -log10(pvalue))) + |
| geom_point(aes(color = Match_Status), size = point_size, alpha = point_alpha) + |
| scale_color_manual( |
| values = c("Consistent" = "#2ecc71", "Inconsistent" = "#e74c3c", "Neutral/Unknown" = "#95a5a6"), |
| name = "调控一致性" |
| ) + |
| geom_vline(xintercept = 0, linetype = "dashed", color = txt_col, alpha = 0.7) + |
| geom_hline(yintercept = -log10(input$pval_cutoff), linetype = "dotted", color = txt_col, alpha = 0.7) + |
| labs(title = paste("TF:", tf_name, "的靶基因差异表达"), |
| x = "log2(Fold Change)", y = "-log10(P Value)") + |
| theme_minimal() + |
| theme( |
| panel.background = element_rect(fill = "white", colour = NA), |
| plot.title = element_text(face = "bold", hjust = 0.5), |
| axis.title = element_text(face = "bold") |
| ) |
|
|
| |
| if (n_labels > 0) { |
| top_genes <- df %>% |
| filter(!is.na(pvalue)) %>% |
| arrange(pvalue) %>% |
| head(min(n_labels, nrow(df))) |
|
|
| if (nrow(top_genes) > 0) { |
| top_genes <- top_genes %>% |
| mutate( |
| label_x = log2FoldChange + ifelse(log2FoldChange > 0, 0.2, -0.2), |
| label_y = -log10(pvalue) + 0.5 |
| ) |
|
|
| p <- p + |
| geom_text( |
| data = top_genes, |
| aes(x = label_x, y = label_y, label = SYMBOL), |
| size = label_size, color = txt_col, fontface = "bold", |
| check_overlap = TRUE, vjust = 0.5, |
| hjust = ifelse(top_genes$log2FoldChange > 0, 0, 1) |
| ) |
| } |
| } |
|
|
| |
| svg(file, width = 10, height = 8) |
| print(p) |
| dev.off() |
| }, |
| contentType = "image/svg+xml" |
| ) |
|
|
| |
| output$download_tf_scatter_data <- downloadHandler( |
| filename = function() { |
| req(selected_tf_targets()) |
| tf_name <- selected_tf_targets()$tf_name |
| paste0("TF_Target_Genes_", tf_name, "_", Sys.Date(), ".csv") |
| }, |
| content = function(file) { |
| req(selected_tf_targets()) |
|
|
| |
| data_list <- selected_tf_targets() |
| df <- data_list$data |
| tf_name <- data_list$tf_name |
|
|
| |
| export_df <- df %>% |
| select( |
| GeneSymbol = SYMBOL, |
| Log2FoldChange = log2FoldChange, |
| PValue = pvalue, |
| Padj = padj, |
| Regulation_Mode = MOR, |
| Match_Status = Match_Status, |
| Source = source |
| ) %>% |
| arrange(PValue) |
|
|
| |
| metadata <- data.frame( |
| Parameter = c( |
| "TF_Name", |
| "N_Targets", |
| "N_Consistent", |
| "N_Inconsistent", |
| "Export_Date" |
| ), |
| Value = c( |
| tf_name, |
| nrow(export_df), |
| sum(export_df$Match_Status == "Consistent", na.rm = TRUE), |
| sum(export_df$Match_Status == "Inconsistent", na.rm = TRUE), |
| Sys.Date() |
| ) |
| ) |
|
|
| |
| write.csv(metadata, file, row.names = FALSE) |
| write.table("\n\n=== Target Gene Expression Data ===\n", file, append = TRUE, row.names = FALSE, col.names = FALSE) |
| write.csv(export_df, file, append = TRUE, row.names = FALSE) |
| }, |
| contentType = "text/csv" |
| ) |
| } |