├── .Rbuildignore ├── .github ├── .gitignore └── workflows │ └── deploy-app.yaml ├── .gitignore ├── DESCRIPTION ├── LICENSE ├── NAMESPACE ├── R ├── datamap-package.R ├── main_app.R ├── mod_code_generation.R ├── mod_file_upload.R ├── mod_heatmap.R ├── mod_pca.R ├── mod_transform.R ├── mod_tsne.R └── utilities.R ├── README.md ├── app.R ├── extdata ├── RNAseq.csv ├── RNAseq_design.csv ├── RNAseq_gene_info.csv ├── countries.csv ├── iris.csv ├── iris_column_annot.csv ├── scRNAseq_PCA.csv └── scRNAseq_clusters.csv ├── inst ├── extdata │ ├── RNAseq.csv │ ├── RNAseq_design.csv │ ├── RNAseq_gene_info.csv │ ├── countries.csv │ ├── iris.csv │ ├── iris_column_annot.csv │ ├── scRNAseq_PCA.csv │ └── scRNAseq_clusters.csv └── www │ ├── countries.png │ ├── countries_label.png │ ├── google_analytics.html │ ├── heatmap.png │ ├── help.html │ ├── pca.png │ └── tsne.png ├── man ├── create_dr_plot.Rd ├── datamap-package.Rd ├── datamap_resource.Rd ├── process_column_annotations.Rd ├── process_row_annotations.Rd ├── run_app.Rd ├── transform_server.Rd └── transform_ui.Rd └── www ├── countries.png ├── countries_label.png ├── google_analytics.html ├── heatmap.png ├── help.html ├── pca.png └── tsne.png /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^\.github$ 2 | ^\.git$ 3 | ^app\.R$ 4 | ^README\.md$ 5 | ^\.Rproj\.user$ 6 | ^extdata$ 7 | ^www$ 8 | -------------------------------------------------------------------------------- /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /.github/workflows/deploy-app.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/posit-dev/r-shinylive/tree/actions-v1/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | # 4 | # Basic example of a GitHub Actions workflow that builds a Shiny app and deploys 5 | # it to GitHub Pages. 6 | name: Deploy app to gh-pages 7 | 8 | on: 9 | # Manually trigger the workflow 10 | workflow_dispatch: 11 | # Trigger on push to `main` branch 12 | push: 13 | branches: ["main"] 14 | # Trigger on pull request to all branches (but do not deploy to gh-pages) 15 | pull_request: 16 | 17 | jobs: 18 | shinylive: 19 | uses: posit-dev/r-shinylive/.github/workflows/deploy-app.yaml@actions-v1 20 | # Grant GITHUB_TOKEN the permissions required to make a Pages deployment 21 | permissions: 22 | pages: write # to deploy to Pages 23 | id-token: write # to verify the deployment originates from an appropriate source 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .Ruserdata 5 | /test data -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: datamap 2 | Title: Interactive Visualization of Data Matrices 3 | Version: 0.1.0 4 | Authors@R: person("Steven", "Ge", email = "Xijin.Ge@sdstate.edu", role = c("aut", "cre")) 5 | Description: A Shiny application for visualizing data matrices with heatmaps, PCA, and t-SNE. 6 | Allows users to upload, transform, and visualize their data interactively. 7 | License: MIT + file LICENSE 8 | Encoding: UTF-8 9 | LazyData: true 10 | Roxygen: list(markdown = TRUE) 11 | RoxygenNote: 7.3.2 12 | Imports: 13 | shiny, 14 | pheatmap, 15 | readxl, 16 | Rtsne, 17 | stats 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [2025] [Xijin Ge] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(run_app) 4 | -------------------------------------------------------------------------------- /R/datamap-package.R: -------------------------------------------------------------------------------- 1 | #' @keywords internal 2 | "_PACKAGE" 3 | 4 | ## usethis namespace: start 5 | ## usethis namespace: end 6 | NULL -------------------------------------------------------------------------------- /R/main_app.R: -------------------------------------------------------------------------------- 1 | # DataMap: A Shiny app for visualizing data matrices with heatmaps, PCA, and t-SNE 2 | # by Steven Ge 4/5/2025 3 | 4 | #' Run the DataMap Shiny application 5 | #' 6 | #' @export 7 | #' 8 | #' @return A Shiny application object 9 | #' @examples 10 | #' if (interactive()) { 11 | #' run_app() 12 | #' } 13 | run_app <- function() { 14 | 15 | 16 | 17 | library(shiny) 18 | library(munsell) # otherwise it will not work; Pheatmap dependency. Sometimes shinylive does not install this. 19 | 20 | # these are needed showing static images for both the R package and directly from the app 21 | # Note: The images and html files are saved into two copies!!!!! 22 | # when running directly: using www and extdata 23 | # when running as a package: using inst/www and inst/extdata 24 | 25 | max_rows_to_show <- 1000 # Maximum number of rows to show row names in the heatmap 26 | default_width <- 600 27 | default_height <- 600 28 | 29 | ui <- fluidPage( 30 | 31 | sidebarLayout( 32 | sidebarPanel( 33 | width = 3, 34 | 35 | # Place Upload Files and Reset buttons on the same row 36 | fluidRow( 37 | column(6, 38 | actionButton("show_upload_modal", "Files", 39 | icon = icon("upload")) 40 | ), 41 | column(6, 42 | conditionalPanel( 43 | condition = "output.data_loaded", 44 | actionButton("reset_session", "Reset", 45 | icon = icon("refresh"), 46 | style = "background-color: #f8d7da; color: #721c24;") 47 | ) 48 | ) 49 | ), 50 | 51 | conditionalPanel( 52 | condition = "output.data_loaded", 53 | fluidRow( 54 | column(8, transform_ui("transform"), style = "margin-top: 5px;"), 55 | column(4, uiOutput("transform_status"), style = "margin-top: 5px;") 56 | ) 57 | ), 58 | 59 | # Dynamic UI for selecting row annotation columns 60 | conditionalPanel( 61 | condition = "output.row_annotation_available", 62 | uiOutput("row_annotation_select_ui") 63 | ), 64 | conditionalPanel( 65 | condition = "output.col_annotation_uploaded", 66 | uiOutput("col_annotation_select_ui") 67 | ), 68 | 69 | # Heatmap customization section - only shown after data is loaded and Heatmap tab is active 70 | conditionalPanel( 71 | condition = "output.data_loaded && input['main_tabs'] === 'Heatmap'", 72 | hr(), 73 | # Use the heatmap control UI function from the module 74 | heatmap_control_ui("heatmap") 75 | ), 76 | 77 | # PCA and t-SNE plot control section - only shown when those tabs are active 78 | conditionalPanel( 79 | condition = "output.data_loaded && (input['main_tabs'] === 'PCA' || input['main_tabs'] === 't-SNE')", 80 | hr(), 81 | h4("Plot Settings"), 82 | 83 | # Font size control 84 | fluidRow( 85 | column(3, p("Font:", style = "padding-top: 7px; text-align: right;")), 86 | column(9, sliderInput("global_fontsize", NULL, min = 5, max = 30, value = 12)) 87 | ), 88 | 89 | # Width & Height controls 90 | fluidRow( 91 | column(3, p("Width:", style = "padding-top: 7px; text-align: right;")), 92 | column(9, sliderInput("global_width", NULL, min = 200, max = 2000, value = 600, step = 20)) 93 | ), 94 | 95 | fluidRow( 96 | column(3, p("Height:", style = "padding-top: 7px; text-align: right;")), 97 | column(9, sliderInput("global_height", NULL, min = 200, max = 2000, value = 500, step = 20)) 98 | ) 99 | ) 100 | ), 101 | 102 | mainPanel( 103 | width = 9, 104 | tabsetPanel(id = "main_tabs", selected = "About", 105 | tabPanel("Heatmap", 106 | # Use the heatmap module UI 107 | heatmap_ui("heatmap") 108 | ), 109 | tabPanel("PCA", 110 | pca_plot_ui("pca") 111 | ), 112 | tabPanel("t-SNE", 113 | tsne_plot_ui("tsne") 114 | ), 115 | tabPanel("Code", 116 | code_generation_ui("code_gen") 117 | ), 118 | tabPanel("Data", 119 | fluidRow( 120 | column(12, 121 | div(style = "overflow-x: auto;", 122 | tableOutput("data_preview") 123 | ), 124 | downloadButton("download_data", "Transformed Data") 125 | ) 126 | ) 127 | ), 128 | tabPanel("About", 129 | titlePanel("DataMap: a portable app for visualizing data matrices v0.11"), 130 | img(src = "heatmap.png", width = "375px", height = "300px", alt = "Example heatmap does not show when using R pckage"), 131 | img(src = "pca.png", width = "384px", height = "329px", alt = "Exaple PCA plot does not show when using R pckage"), 132 | img(src = "countries_label.png", width = "401px", height = "300px", alt = "Exaple heatmap does not show when using R pckage"), 133 | img(src = "tsne.png", width = "335px", height = "300px", alt = "Exaple tSNE plot does not show when using R pckage"), 134 | includeHTML(datamap_resource("www/help.html")) 135 | ) 136 | ) 137 | ,tags$head(includeHTML(datamap_resource("www/google_analytics.html"))) 138 | ) 139 | ) 140 | ) 141 | 142 | server <- function(input, output, session) { 143 | 144 | # Track if main data is loaded for UI conditionals 145 | output$data_loaded <- reactive({ 146 | return(file_data$data_loaded()) 147 | }) 148 | outputOptions(output, "data_loaded", suspendWhenHidden = FALSE) 149 | 150 | # Switch to Heatmap tab when data is loaded 151 | observe({ 152 | if (file_data$data_loaded()) { 153 | updateTabsetPanel(session, "main_tabs", selected = "Heatmap") 154 | } 155 | }) 156 | 157 | # Show the modal when the button is clicked 158 | output$main_file_upload_ui <- renderUI({ 159 | if (!is.null(file_data$data())) { 160 | # If data is already uploaded 161 | tags$div( 162 | style = "padding: 15px; background-color: #f8f9fa; border-radius: 4px; text-align: center;", 163 | tags$p( 164 | icon("info-circle"), 165 | "Reset the app to upload a new data matrix.", 166 | style = "margin: 0; color: #495057;" 167 | ) 168 | ) 169 | } else { 170 | tags$div( 171 | tags$h4("Data file (Excel, CSV, ...)"), 172 | file_upload_ui("file_upload"), 173 | fluidRow( 174 | column(2, h5("Examples:", style = "margin-top: -8px; text-align: right;")), 175 | column(10, 176 | downloadButton("download_countries", "Countries", style = "margin-top: -15px;"), 177 | downloadButton("download_rnaseq", "RNAseq", style = "margin-top: -15px;"), 178 | downloadButton("download_iris", "Iris", style = "margin-top: -15px;"), 179 | downloadButton("download_scrnaseq", "scRNAseq", style = "margin-top: -15px;"), 180 | align = "left" 181 | ) 182 | ) 183 | ) 184 | } 185 | }) 186 | 187 | observeEvent(input$show_upload_modal, { 188 | showModal(modalDialog( 189 | title = "Upload Files", 190 | 191 | uiOutput("main_file_upload_ui"), 192 | 193 | hr(), 194 | tags$div( 195 | tags$h4("Optional: Column Annotation"), 196 | file_upload_ui("col_annotation_file_upload"), 197 | fluidRow( 198 | column(2, h5("Examples:", style = "margin-top: -8px; text-align: right;")), 199 | column(10, 200 | downloadButton("download_col_rnaseq", "RNA-seq factors", style = "margin-top: -15px;"), 201 | downloadButton("download_col_iris", "Iris column info", style = "margin-top: -15px;"), 202 | align = "left" 203 | ) 204 | ) 205 | ), 206 | hr(), 207 | tags$div( 208 | tags$h4("Optional: Row Annotations"), 209 | file_upload_ui("row_annotation_file_upload"), 210 | fluidRow( 211 | column(2, h5("Examples:", style = "margin-top: -8px; text-align: right;")), 212 | column(10, 213 | downloadButton("download_row_rnaseq", "RNAseq gene info", style = "margin-top: -15px;"), 214 | downloadButton("download_row_scrnaseq", "scRNAseq clusters", style = "margin-top: -15px;"), 215 | align = "left" 216 | ) 217 | ) 218 | ), 219 | 220 | footer = tagList( 221 | modalButton("Close") 222 | ), 223 | size = "m", 224 | easyClose = TRUE 225 | )) 226 | }) 227 | 228 | output$download_iris <- downloadHandler( 229 | filename = function() { 230 | "iris.csv" 231 | }, 232 | content = function(file) { 233 | file.copy(datamap_resource("extdata/iris.csv"), file) 234 | } 235 | ) 236 | output$download_rnaseq <- downloadHandler( 237 | filename = function() { 238 | "RNAseq.csv" 239 | }, 240 | content = function(file) { 241 | file.copy(datamap_resource("extdata/RNAseq.csv"), file) 242 | } 243 | ) 244 | 245 | output$download_scrnaseq <- downloadHandler( 246 | filename = function() { 247 | "scRNAseq_PCA.csv" 248 | }, 249 | content = function(file) { 250 | file.copy(datamap_resource("extdata/scRNAseq_PCA.csv"), file) 251 | } 252 | ) 253 | output$download_countries <- downloadHandler( 254 | filename = function() { 255 | "countries.csv" 256 | }, 257 | content = function(file) { 258 | file.copy(datamap_resource("extdata/countries.csv"), file) 259 | } 260 | ) 261 | output$download_col_rnaseq <- downloadHandler( 262 | filename = function() { 263 | "RNAseq_design.csv" 264 | }, 265 | content = function(file) { 266 | file.copy(datamap_resource("extdata/RNAseq_design.csv"), file) 267 | } 268 | ) 269 | output$download_col_iris <- downloadHandler( 270 | filename = function() { 271 | "iris_column_annot.csv" 272 | }, 273 | content = function(file) { 274 | file.copy(datamap_resource("extdata/iris_column_annot.csv"), file) 275 | } 276 | ) 277 | output$download_row_rnaseq <- downloadHandler( 278 | filename = function() { 279 | "RNAseq_gene_info.csv" 280 | }, 281 | content = function(file) { 282 | file.copy(datamap_resource("extdata/RNAseq_gene_info.csv"), file) 283 | } 284 | ) 285 | output$download_row_scrnaseq <- downloadHandler( 286 | filename = function() { 287 | "scRNAseq_clusters.csv" 288 | }, 289 | content = function(file) { 290 | file.copy(datamap_resource("extdata/scRNAseq_clusters.csv"), file) 291 | } 292 | ) 293 | # Use the file upload module for main data 294 | file_data <- file_upload_server("file_upload") 295 | 296 | # Use a separate file upload module for the column annotation file 297 | col_annotation_file_data <- file_upload_server("col_annotation_file_upload") 298 | 299 | # Use a separate file upload module for the row annotation file 300 | row_annotation_file_data <- file_upload_server("row_annotation_file_upload") 301 | 302 | # Reactive to indicate if column annotation file is uploaded 303 | output$col_annotation_uploaded <- reactive({ 304 | !is.null(col_annotation_file_data$data()) 305 | }) 306 | outputOptions(output, "col_annotation_uploaded", suspendWhenHidden = FALSE) 307 | 308 | output$row_annotation_available <- reactive({ 309 | has_uploaded <- !is.null(row_annotation_file_data$data()) 310 | has_factors <- !is.null(transform_data$factor_columns()) && 311 | ncol(transform_data$factor_columns()) > 0 312 | return(has_uploaded || has_factors) 313 | }) 314 | outputOptions(output, "row_annotation_available", suspendWhenHidden = FALSE) 315 | 316 | # Render UI for column annotation row selection 317 | output$col_annotation_select_ui <- renderUI({ 318 | req(col_annotation_file_data$data()) 319 | annot_df <- col_annotation_file_data$data() 320 | row_choices <- rownames(annot_df) 321 | if (is.null(row_choices) || length(row_choices) == 0) { 322 | row_choices <- as.character(seq_len(nrow(annot_df))) 323 | } 324 | selectInput("col_annotation_select", "Column annotation:", 325 | choices = row_choices, selected = row_choices[1], 326 | multiple = TRUE) 327 | }) 328 | 329 | # Render UI for row annotation column selection (merged from both sources) 330 | output$row_annotation_select_ui <- renderUI({ 331 | all_choices <- c() 332 | default_selected <- c() 333 | 334 | # Get choices from uploaded row annotation file 335 | if (!is.null(row_annotation_file_data$data())) { 336 | uploaded_choices <- colnames(row_annotation_file_data$data()) 337 | if (!is.null(uploaded_choices) && length(uploaded_choices) > 0) { 338 | all_choices <- c(all_choices, uploaded_choices) 339 | # Select first uploaded column by default 340 | default_selected <- c(default_selected, uploaded_choices[1]) 341 | } 342 | } 343 | 344 | # Get choices from auto-detected factor columns 345 | if (!is.null(transform_data$factor_columns()) && 346 | ncol(transform_data$factor_columns()) > 0) { 347 | factor_choices <- colnames(transform_data$factor_columns()) 348 | if (!is.null(factor_choices) && length(factor_choices) > 0) { 349 | all_choices <- c(all_choices, factor_choices) 350 | # Select all factor columns by default 351 | default_selected <- c(default_selected, factor_choices) 352 | } 353 | } 354 | 355 | # Remove any duplicates 356 | all_choices <- unique(all_choices) 357 | default_selected <- unique(default_selected) 358 | 359 | # Create the selectInput if we have any choices 360 | if (length(all_choices) > 0) { 361 | tags$div(style = "margin-top: 5px;", 362 | selectInput("row_annotation_select", "Row annotations:", 363 | choices = all_choices, selected = default_selected, multiple = TRUE)) 364 | } else { 365 | return(NULL) 366 | } 367 | }) 368 | 369 | # Use the transform module 370 | transform_data <- transform_server("transform", reactive({ 371 | file_data$data() 372 | })) 373 | 374 | initial_loading <- reactiveVal(TRUE) 375 | # Create a reactive that provides the main data for processing. 376 | current_data <- reactive({ 377 | # If there's no data uploaded yet, return NULL 378 | if (is.null(file_data$data())) { 379 | return(NULL) 380 | } 381 | 382 | # During initial loading, don't show heatmap if transform dialog is open 383 | if (initial_loading() && !transform_data$modal_closed()) { 384 | return(NULL) 385 | } 386 | 387 | # Once transformation is applied for the first time, mark initial loading as complete 388 | if (transform_data$has_transformed()) { 389 | initial_loading(FALSE) 390 | } 391 | 392 | # Return appropriate data based on transformation state 393 | if (!is.null(transform_data$processed_data()) && transform_data$has_transformed()) { 394 | return(transform_data$processed_data()) 395 | } else { 396 | return(file_data$data()) 397 | } 398 | }) 399 | 400 | # Show transform status 401 | output$transform_status <- renderUI({ 402 | if (transform_data$has_transformed()) { 403 | tags$div( 404 | icon("check-circle"), 405 | style = "color: green; margin-top: 7px;" 406 | ) 407 | } else { 408 | tags$div( 409 | icon("info-circle"), 410 | style = "color: grey; margin-top: 7px;" 411 | ) 412 | } 413 | }) 414 | 415 | # Modified reactive function that matches original behavior 416 | col_annotation_for_heatmap <- reactive({ 417 | req(current_data()) 418 | 419 | # Check if we have annotation data 420 | if (is.null(col_annotation_file_data$data())) { 421 | return(NULL) 422 | } 423 | 424 | # Get annotation data 425 | annot_df <- col_annotation_file_data$data() 426 | main_cols <- colnames(current_data()) 427 | selected <- input$col_annotation_select 428 | 429 | # Process annotations using the extracted function 430 | process_column_annotations( 431 | main_data_cols = main_cols, 432 | annotation_df = annot_df, 433 | selected_annotations = selected 434 | ) 435 | }) 436 | 437 | row_annotation_for_heatmap <- reactive({ 438 | req(current_data()) 439 | 440 | # Get selected annotations 441 | selected <- input$row_annotation_select 442 | 443 | # Get main data rows 444 | main_rows <- rownames(current_data()) 445 | 446 | # Get file annotation data 447 | file_annot_df <- NULL 448 | if (!is.null(row_annotation_file_data$data())) { 449 | file_annot_df <- row_annotation_file_data$data() 450 | } 451 | 452 | # Get factor annotation data 453 | factor_annot_df <- NULL 454 | if (!is.null(transform_data$factor_columns()) && 455 | ncol(transform_data$factor_columns()) > 0) { 456 | factor_annot_df <- transform_data$factor_columns() 457 | } 458 | 459 | # Process annotations using the utility function 460 | process_row_annotations( 461 | main_data_rows = main_rows, 462 | file_annotation_df = file_annot_df, 463 | factor_annotation_df = factor_annot_df, 464 | selected_annotations = selected 465 | ) 466 | }) 467 | 468 | 469 | # Create reactives for font size, width, and height to ensure availability for PCA and t-SNE 470 | fontsize_for_plots <- reactive({ 471 | if (!is.null(input$global_fontsize)) input$global_fontsize else 12 472 | }) 473 | width_for_plots <- reactive({ 474 | if (!is.null(input$global_width)) input$global_width else default_width 475 | }) 476 | height_for_plots <- reactive({ 477 | if (!is.null(input$global_height)) input$global_height else default_height 478 | }) 479 | 480 | # Use the heatmap module 481 | heatmap_results <- heatmap_server( 482 | "heatmap", 483 | current_data, 484 | file_data, 485 | transform_data$unprocessed_data, 486 | col_annotation_for_heatmap, 487 | row_annotation_for_heatmap, 488 | transform_data, 489 | max_rows_to_show, 490 | default_width, 491 | default_height 492 | ) 493 | 494 | observeEvent(input$reset_session, { 495 | # Create a modal dialog asking for confirmation 496 | showModal(modalDialog( 497 | title = "Confirm Reset", 498 | "This will clear all loaded data and reset the application to its initial state. Continue?", 499 | footer = tagList( 500 | modalButton("Cancel"), 501 | actionButton("confirm_reset", "Reset", class = "btn-danger") 502 | ), 503 | easyClose = TRUE 504 | )) 505 | }) 506 | 507 | # Handle confirmation 508 | observeEvent(input$confirm_reset, { 509 | # Close the confirmation dialog 510 | removeModal() 511 | 512 | # Reload the page to reset the session 513 | session$reload() 514 | }) 515 | 516 | pca_results <- pca_plot_server( 517 | "pca", 518 | current_data, 519 | col_annotation_for_heatmap, 520 | row_annotation_for_heatmap, 521 | fontsize_for_plots, # Use our reactive 522 | width_for_plots, # Use our reactive 523 | height_for_plots # Use our reactive 524 | ) 525 | 526 | tsne_results <- tsne_plot_server( 527 | "tsne", 528 | current_data, 529 | col_annotation_for_heatmap, 530 | row_annotation_for_heatmap, 531 | fontsize_for_plots, # Use our reactive 532 | width_for_plots, # Use our reactive 533 | height_for_plots # Use our reactive 534 | ) 535 | 536 | code_gen_results <- code_generation_server( 537 | "code_gen", 538 | file_data, 539 | transform_data, 540 | heatmap_results, 541 | pca_results, 542 | tsne_results, 543 | col_annotation_file_data, 544 | row_annotation_file_data 545 | ) 546 | 547 | output$data_preview <- renderTable({ 548 | req(current_data()) 549 | data <- current_data() 550 | 551 | # Ensure data is a data frame 552 | if (!is.data.frame(data)) { 553 | data <- as.data.frame(data) 554 | } 555 | 556 | # Limit to 30 rows and 30 columns 557 | max_rows <- min(30, nrow(data)) 558 | max_cols <- min(30, ncol(data)) 559 | 560 | # Get the row indices and column indices 561 | row_indices <- seq_len(max_rows) 562 | col_indices <- seq_len(max_cols) 563 | 564 | # Create a preview version of the data 565 | preview_data <- data[row_indices, col_indices, drop = FALSE] 566 | 567 | # Return the preview with row names preserved 568 | preview_data 569 | }, rownames = TRUE, colnames = TRUE, bordered = TRUE, width = "100%", 570 | digits = 3, align = 'c') 571 | 572 | 573 | # Download handler for the data 574 | output$download_data <- downloadHandler( 575 | filename = function() { 576 | paste0("transformed_data-", format(Sys.time(), "%Y%m%d-%H%M%S"), ".csv") 577 | }, 578 | content = function(file) { 579 | data <- current_data() 580 | write.csv(data, file, row.names = TRUE) 581 | } 582 | ) 583 | } 584 | 585 | # Run the application 586 | shinyApp(ui = ui, server = server) 587 | 588 | } -------------------------------------------------------------------------------- /R/mod_code_generation.R: -------------------------------------------------------------------------------- 1 | ## code_generation_module.R - Module for code generation and display 2 | ## This module handles the generation, display, and downloading of R code 3 | 4 | # UI function for the code generation module 5 | code_generation_ui <- function(id) { 6 | ns <- NS(id) 7 | 8 | # UI is just a placeholder that will be filled by the server 9 | uiOutput(ns("code_display")) 10 | } 11 | 12 | # Server function for the code generation module 13 | code_generation_server <- function(id, file_data, transform_data, heatmap_results, 14 | pca_results, tsne_results, col_annotation_data, row_annotation_data) { 15 | moduleServer(id, function(input, output, session) { 16 | ns <- session$ns 17 | 18 | delimiter <- "\n\n################################################################################" 19 | 20 | # Create a reactive expression for generating the full code 21 | full_code <- reactive({ 22 | # Initialize an empty vector to store code parts 23 | code_parts <- c() 24 | 25 | utilities_file <- datamap_resource("R/utilities.R") 26 | if (file.exists(utilities_file)) { 27 | utilities_code <- readLines(utilities_file) 28 | code_parts <- c(delimiter, "# Utilty functions", utilities_code, "") 29 | } else { 30 | code_parts <- c(paste("# Warning: utilities file not found at", utilities_file), "") 31 | } 32 | 33 | # Add file upload code if available 34 | if (!is.null(file_data$code())) { 35 | code_parts <- c(code_parts, delimiter, "# Data Import Code", file_data$code(), 36 | "raw_data <- data", "rm(data)", "") 37 | } 38 | 39 | # Add column annotation file upload code if available 40 | if (!is.null(col_annotation_data$code())) { 41 | code_parts <- c(code_parts, "# Column Annotation Import Code", 42 | col_annotation_data$code(), "col_annotation_raw <- data", "rm(data)", "") 43 | } 44 | 45 | # Add row annotation file upload code if available 46 | if (!is.null(row_annotation_data$code())) { 47 | code_parts <- c(code_parts, "# Row Annotation Import Code", 48 | row_annotation_data$code(), "row_annotation_raw <- data", "rm(data)","") 49 | } 50 | 51 | # Add transform code if available 52 | if (!is.null(transform_data$code())) { 53 | code_parts <- c(code_parts, delimiter, "# Data Transformation Code", transform_data$code(), "") 54 | } 55 | 56 | # Add heatmap code from the module if available 57 | if (!is.null(heatmap_results$heatmap_code())) { 58 | code_parts <- c(code_parts, delimiter, heatmap_results$heatmap_code()) 59 | } 60 | 61 | # Get PCA code from the module 62 | if (!is.null(pca_results$pca_code())) { 63 | code_parts <- c(code_parts, delimiter, "# PCA plot", pca_results$pca_code()) 64 | } 65 | 66 | # Get t-SNE code from the module 67 | if (!is.null(tsne_results$tsne_code())) { 68 | code_parts <- c(code_parts, delimiter, "# tSNE plot", tsne_results$tsne_code()) 69 | } 70 | 71 | # Combine all parts and return 72 | paste(code_parts, collapse = "\n") 73 | }) 74 | 75 | output$code_display <- renderUI({ 76 | req(full_code()) 77 | 78 | code_text <- full_code() 79 | 80 | # Split the code into sections for better display 81 | sections <- strsplit(code_text, delimiter)[[1]] 82 | 83 | # Initialize HTML output 84 | html_output <- tagList() 85 | 86 | # Process each section 87 | current_section <- NULL 88 | section_content <- NULL 89 | 90 | for (section in sections) { 91 | if (nchar(section) == 0) next 92 | 93 | # First line is likely a section title 94 | lines <- strsplit(section, "\n")[[1]] 95 | 96 | section_title <- gsub("#", "", lines[2]) 97 | if(length(lines) > 2) { 98 | lines <- lines[-(1:2)] 99 | } 100 | 101 | # If we were building a previous section, add it to output 102 | if (!is.null(current_section) && !is.null(section_content)) { 103 | html_output <- tagAppendChild(html_output, 104 | tags$div( 105 | tags$h3(current_section, class = "code-section-title"), 106 | tags$pre(tags$code(class = "r", section_content)) 107 | )) 108 | } 109 | 110 | # Start new section 111 | current_section <- section_title 112 | section_content <- paste(lines[-1], collapse = "\n") 113 | } 114 | 115 | # Add the last section 116 | if (!is.null(current_section) && !is.null(section_content)) { 117 | html_output <- tagAppendChild(html_output, 118 | tags$div( 119 | tags$h3(current_section, class = "code-section-title"), 120 | tags$pre(tags$code(class = "r", section_content)) 121 | )) 122 | } 123 | 124 | # Add some CSS for styling 125 | css <- tags$style(HTML(" 126 | .code-section-title { 127 | color: #2c3e50; 128 | border-bottom: 1px solid #eee; 129 | padding-bottom: 10px; 130 | margin-top: 20px; 131 | } 132 | pre { 133 | background-color: #f5f5f5; 134 | border: 1px solid #ccc; 135 | border-radius: 4px; 136 | padding: 10px; 137 | margin-bottom: 20px; 138 | overflow: auto; 139 | } 140 | code.r { 141 | font-family: 'Courier New', Courier, monospace; 142 | white-space: pre; 143 | } 144 | ")) 145 | 146 | # Return the complete UI 147 | tagList( 148 | css, 149 | tags$div( 150 | fluidRow( 151 | column(width = 8, style = "margin-top:5px;", tags$p("To reproduce the plots, download and execute the generated R script in RStudio, ensuring all required data files are in the same folder.")), 152 | column(width = 4, style = "margin-top:5px;", downloadButton(ns("download_combined_code"), "Generated R Script")) 153 | ), 154 | html_output 155 | ) 156 | ) 157 | }) 158 | 159 | output$download_combined_code <- downloadHandler( 160 | filename = function() { 161 | paste0("data_analysis_code-", format(Sys.time(), "%Y%m%d-%H%M%S"), ".R") 162 | }, 163 | content = function(file) { 164 | # Get the code as text 165 | code_text <- full_code() 166 | # Write it to the file 167 | writeLines(code_text, file) 168 | } 169 | ) 170 | 171 | # Return the code reactive to make it available to the main app if needed 172 | return(list( 173 | full_code = full_code 174 | )) 175 | }) 176 | } -------------------------------------------------------------------------------- /R/mod_file_upload.R: -------------------------------------------------------------------------------- 1 | # mod_file_upload.R 2 | # A Shiny module for smart file upload and parsing with reproducible code generation 3 | 4 | library(shiny) 5 | 6 | 7 | file_upload_ui <- function(id) { 8 | ns <- NS(id) 9 | 10 | tagList( 11 | fileInput(ns("file"), NULL, 12 | accept = c( 13 | "text/csv", 14 | "text/comma-separated-values,text/plain", 15 | ".csv", ".txt", ".tsv", ".xls", ".xlsx" 16 | )), 17 | uiOutput(ns("code_ui")) 18 | ) 19 | } 20 | 21 | file_upload_server <- function(id) { 22 | moduleServer(id, function(input, output, session) { 23 | ns <- session$ns 24 | 25 | # Reactive values for storing processed data and flags 26 | rv <- reactiveValues( 27 | data = NULL, 28 | file_extension = NULL, 29 | data_loaded = FALSE, 30 | has_rownames = FALSE, 31 | code = NULL 32 | ) 33 | 34 | # Helper function to count delimiters in a text sample 35 | count_delimiters <- function(text_sample) { 36 | delimiters <- c(",", "\t", ";", "|", " ") 37 | counts <- sapply(delimiters, function(d) { 38 | total_count <- 0 39 | lines <- strsplit(text_sample, "\n")[[1]] 40 | for (line in lines) { 41 | # Count actual occurrences of delimiter in each line 42 | positions <- gregexpr(d, line, fixed = TRUE)[[1]] 43 | # When delimiter isn't found, gregexpr returns -1 44 | occurrences <- sum(positions != -1) 45 | total_count <- total_count + occurrences 46 | } 47 | return(total_count) 48 | }) 49 | names(counts) <- delimiters 50 | return(counts) 51 | } 52 | 53 | # Helper function to check if a column looks like row names 54 | is_likely_rownames <- function(column) { 55 | # Check if values are unique 56 | if(length(unique(column)) != length(column)) { 57 | return(FALSE) 58 | } 59 | 60 | # Check if values are mostly character-like (not purely numeric) 61 | numeric_count <- sum(!is.na(suppressWarnings(as.numeric(column)))) 62 | if(numeric_count / length(column) > 0.9) { 63 | return(FALSE) 64 | } 65 | 66 | return(TRUE) 67 | } 68 | 69 | # Helper function to check if a row looks like column headers 70 | is_likely_header <- function(row) { 71 | # Check if row is different from the rest of the data in type 72 | numeric_count <- sum(!is.na(suppressWarnings(as.numeric(row)))) 73 | if(numeric_count / length(row) < 0.5) { 74 | return(TRUE) 75 | } 76 | 77 | return(FALSE) 78 | } 79 | 80 | # Helper function to generate reproducible code 81 | generate_code <- function(file_path, file_ext, delimiter = NULL, 82 | sheet = NULL, header = TRUE, 83 | rownames = FALSE) { 84 | # Start with library imports 85 | code <- "# Reproducible code for data import\n" 86 | 87 | # Add necessary libraries 88 | if (file_ext %in% c("xls", "xlsx")) { 89 | code <- paste0(code, "library(readxl)\n") 90 | } 91 | code <- paste0(code, "library(utils) # For read.csv/read.delim\n") # Add utils library 92 | 93 | # Add the data import code 94 | if (file_ext %in% c("xls", "xlsx")) { 95 | # For Excel files 96 | # Note: The original app reads Excel sheets directly to data.frame 97 | # with readxl::read_excel and then as.data.frame. This is replicated. 98 | code <- paste0( 99 | code, 100 | "data <- readxl::read_excel(\n", 101 | " path = \"", file_path, "\",\n", 102 | " sheet = ", if (is.numeric(sheet)) as.character(sheet) else paste0("\"", sheet, "\""), ",\n", # Handle numeric or named sheets 103 | " col_names = ", as.character(header), "\n", 104 | ")\n", 105 | "data <- as.data.frame(data, stringsAsFactors = FALSE)\n" 106 | ) 107 | } else { 108 | # For CSV and other delimited text files 109 | delimiter_name <- switch(delimiter, 110 | "," = "comma", 111 | "\t" = "tab", 112 | ";" = "semicolon", 113 | "|" = "pipe", 114 | " " = "space") 115 | 116 | if (delimiter == "\t") { 117 | code <- paste0( 118 | code, 119 | "data <- utils::read.delim(\n", # Explicitly use utils:: 120 | " file = \"", file_path, "\",\n", 121 | " header = ", as.character(header), ",\n", 122 | " sep = \"\\t\",\n", 123 | " stringsAsFactors = FALSE,\n", 124 | " check.names = TRUE\n", # Changed from FALSE to TRUE to match server logic 125 | ")\n" 126 | ) 127 | } else { 128 | code <- paste0( 129 | code, 130 | "data <- utils::read.csv(\n", # Explicitly use utils:: 131 | " file = \"", file_path, "\",\n", 132 | " header = ", as.character(header), ",\n", 133 | " sep = \"", gsub("\\", "\\\\", delimiter, fixed = TRUE), "\",\n", 134 | " stringsAsFactors = FALSE,\n", 135 | " check.names = TRUE\n", # Changed from FALSE to TRUE to match server logic 136 | ")\n" 137 | ) 138 | } 139 | } 140 | 141 | # Add column name sanitization step to match server logic 142 | code <- paste0( 143 | code, 144 | "\n# Sanitize column names (replicate app behavior)\n", 145 | "colnames(data) <- gsub(\"-\", \"_\", colnames(data))\n", 146 | "colnames(data) <- gsub(\" \", \"\", colnames(data))\n" 147 | ) 148 | 149 | # Handle row names if applicable 150 | if (rownames) { 151 | code <- paste0( 152 | code, 153 | "\n# Set row names from first column (replicate app behavior)\n", 154 | "# Ensure first column exists before setting row names\n", 155 | "if (ncol(data) > 1) {\n", # Add check for number of columns 156 | " row_names <- data[[1]]\n", 157 | " data <- data[, -1, drop = FALSE]\n", 158 | " rownames(data) <- row_names\n", 159 | "} else {\n", 160 | " warning(\"Cannot set row names: dataset has only one column after import.\")\n", # Add warning 161 | "}\n" 162 | ) 163 | } 164 | 165 | return(code) 166 | } 167 | 168 | # Reactive value to track if first column is suitable for row names 169 | can_use_rownames <- reactiveVal(FALSE) 170 | 171 | # Re-evaluate first column when delimiter or sheet changes 172 | observeEvent(list(input$import_delimiter, input$import_sheet), { 173 | req(input$file) 174 | 175 | # Skip if we're just initializing 176 | if (is.null(input$import_delimiter) && is.null(input$import_sheet)) { 177 | return() 178 | } 179 | 180 | file_ext <- rv$file_extension 181 | 182 | tryCatch({ 183 | # Get current data based on selected options 184 | if(file_ext %in% c("xls", "xlsx")) { 185 | if(!is.null(input$import_sheet)) { 186 | # Read entire dataset to check for duplicate row names 187 | sample_data <- readxl::read_excel( 188 | input$file$datapath, 189 | sheet = input$import_sheet, 190 | col_names = input$import_header 191 | ) 192 | } else { 193 | return() 194 | } 195 | } else { 196 | delimiter <- input$import_delimiter 197 | if(is.null(delimiter)) return() 198 | 199 | # Read entire dataset to check for duplicate row names 200 | if(delimiter == "\t") { 201 | sample_data <- read.delim( 202 | input$file$datapath, 203 | header = input$import_header, 204 | sep = delimiter, 205 | stringsAsFactors = FALSE, 206 | check.names = FALSE 207 | ) 208 | } else { 209 | sample_data <- read.csv( 210 | input$file$datapath, 211 | header = input$import_header, 212 | sep = delimiter, 213 | stringsAsFactors = FALSE, 214 | check.names = FALSE 215 | ) 216 | } 217 | } 218 | 219 | # Re-evaluate if first column can be used as row names 220 | if(ncol(sample_data) > 1) { 221 | can_use_rownames(is_likely_rownames(sample_data[[1]])) 222 | } else { 223 | can_use_rownames(FALSE) 224 | } 225 | 226 | }, error = function(e) { 227 | can_use_rownames(FALSE) 228 | }) 229 | }) 230 | 231 | # Watch for file uploads 232 | observeEvent(input$file, { 233 | req(input$file) 234 | 235 | # Get file extension 236 | file_ext <- tolower(gsub("^.*\\.(.*)$", "\\1", input$file$name)) 237 | rv$file_extension <- file_ext 238 | 239 | # Initialize settings based on file type 240 | has_header <- TRUE 241 | has_rownames <- FALSE 242 | delimiter <- "," 243 | sheet <- 1 244 | 245 | # Read a sample of the file to analyze 246 | if(file_ext %in% c("xls", "xlsx")) { 247 | # For Excel files, get sheet names 248 | sheets <- readxl::excel_sheets(input$file$datapath) 249 | 250 | # Read the entire Excel sheet to check for uniqueness in the first column 251 | sample_data <- readxl::read_excel(input$file$datapath, sheet = 1) 252 | 253 | # Check if first column might be row names 254 | if(ncol(sample_data) > 1) { 255 | has_rownames <- is_likely_rownames(sample_data[[1]]) 256 | # Store this value in the reactive for later use 257 | can_use_rownames(has_rownames) 258 | } 259 | 260 | # Default to having headers for Excel 261 | has_header <- TRUE 262 | 263 | } else { 264 | # For text files, read first few lines to analyze 265 | file_con <- file(input$file$datapath, "r") 266 | file_lines <- readLines(file_con, n = 10) 267 | close(file_con) 268 | 269 | # Determine the most likely delimiter by counting occurrences 270 | if(length(file_lines) > 0) { 271 | delim_counts <- count_delimiters(paste(file_lines, collapse = "\n")) 272 | delimiter <- names(which.max(delim_counts)) 273 | 274 | # If space was detected as the delimiter, double-check if tab is more appropriate 275 | if(delimiter == " " && grepl("\t", paste(file_lines, collapse = ""))) { 276 | delimiter <- "\t" 277 | } 278 | 279 | # Read the entire file to check for uniqueness in the first column 280 | if(delimiter == "\t") { 281 | sample_data <- read.delim(input$file$datapath, sep = delimiter, header = FALSE, stringsAsFactors = FALSE) 282 | } else { 283 | sample_data <- read.csv(input$file$datapath, sep = delimiter, header = FALSE, stringsAsFactors = FALSE) 284 | } 285 | 286 | # Check if first row looks like a header 287 | if(nrow(sample_data) > 1) { 288 | has_header <- is_likely_header(as.character(unlist(sample_data[1, ]))) 289 | } 290 | 291 | # Check if first column might be row names 292 | if(ncol(sample_data) > 1) { 293 | has_rownames <- is_likely_rownames(sample_data[[1]]) 294 | # Store this value in the reactive for later use 295 | can_use_rownames(has_rownames) 296 | } 297 | } 298 | } 299 | 300 | # Display a modal dialog with configurable import settings 301 | showModal(modalDialog( 302 | title = "Data Import", 303 | 304 | # File type-specific controls 305 | if(file_ext %in% c("xls", "xlsx")) { 306 | tagList( 307 | selectInput(ns("import_sheet"), "Sheet:", choices = sheets, selected = sheet), 308 | fluidRow( 309 | column(6, checkboxInput(ns("import_header"), "First Row as Header", value = has_header)), 310 | column(6, uiOutput(ns("rownames_ui"))) 311 | ) 312 | ) 313 | } else { 314 | fluidRow( 315 | column(4, 316 | div(style = "display: flex; align-items: center;", 317 | span("Delimiter:", style = "margin-right: 10px;"), 318 | selectInput(ns("import_delimiter"), NULL, 319 | choices = c(Comma = ",", Tab = "\t", Semicolon = ";", Pipe = "|", Space = " "), 320 | selected = delimiter, width = "120px") 321 | ) 322 | ), 323 | column(4, checkboxInput(ns("import_header"), "First Row as Header", value = has_header)), 324 | column(4, uiOutput(ns("rownames_ui"))) 325 | ) 326 | }, 327 | 328 | # Preview table 329 | div(style = "overflow-x: auto; max-width: 100%;", 330 | tableOutput(ns("import_preview"))), 331 | 332 | footer = tagList( 333 | actionButton(ns("import_cancel"), "Cancel"), 334 | actionButton(ns("import_confirm"), "Import Data", class = "btn-primary") 335 | ), 336 | 337 | size = "l", 338 | easyClose = FALSE 339 | )) 340 | }) 341 | 342 | # Dynamic UI for rownames checkbox - only show if first column can be used as row names 343 | output$rownames_ui <- renderUI({ 344 | if (can_use_rownames()) { 345 | checkboxInput(ns("import_rownames"), "First Column as Row Names", value = can_use_rownames()) 346 | } 347 | }) 348 | 349 | # Update import preview based on selected options 350 | output$import_preview <- renderTable({ 351 | req(input$file) 352 | 353 | file_ext <- rv$file_extension 354 | # Make sure using_rownames is a logical value, not NULL 355 | using_rownames <- isTRUE(!is.null(input$import_rownames) && input$import_rownames) 356 | 357 | tryCatch({ 358 | # Get preview data based on selected import options 359 | if(file_ext %in% c("xls", "xlsx")) { 360 | if(!is.null(input$import_sheet)) { 361 | preview_data <- readxl::read_excel( 362 | input$file$datapath, 363 | sheet = input$import_sheet, 364 | col_names = input$import_header, 365 | n_max = 10 366 | ) 367 | # Convert to data.frame to ensure compatibility with rownames 368 | preview_data <- as.data.frame(preview_data, stringsAsFactors = FALSE) 369 | } else { 370 | preview_data <- readxl::read_excel( 371 | input$file$datapath, 372 | col_names = TRUE, 373 | n_max = 10 374 | ) 375 | # Convert to data.frame to ensure compatibility with rownames 376 | preview_data <- as.data.frame(preview_data, stringsAsFactors = FALSE) 377 | } 378 | } else { 379 | delimiter <- input$import_delimiter 380 | if(is.null(delimiter)) delimiter <- "," 381 | 382 | if(delimiter == "\t") { 383 | preview_data <- read.delim( 384 | input$file$datapath, 385 | header = input$import_header, 386 | sep = delimiter, 387 | nrows = 10, 388 | stringsAsFactors = FALSE, 389 | check.names = FALSE 390 | ) 391 | } else { 392 | preview_data <- read.csv( 393 | input$file$datapath, 394 | header = input$import_header, 395 | sep = delimiter, 396 | nrows = 10, 397 | stringsAsFactors = FALSE, 398 | check.names = FALSE 399 | ) 400 | } 401 | } 402 | 403 | # Process row names if selected for preview - with safe logical checks 404 | if(isTRUE(using_rownames) && !is.null(preview_data) && ncol(preview_data) > 1) { 405 | row_names <- preview_data[[1]] 406 | preview_data <- preview_data[, -1, drop = FALSE] 407 | # Set custom row names for the preview 408 | rownames(preview_data) <- row_names 409 | } 410 | 411 | return(preview_data) 412 | 413 | }, error = function(e) { 414 | return(data.frame(Error = paste("Could not parse file with current settings:", e$message))) 415 | }) 416 | }, 417 | # Control whether to show row names in the table 418 | rownames = function() { 419 | # Only show row names when using first column as row names 420 | return(isTRUE(!is.null(input$import_rownames) && input$import_rownames)) 421 | }, 422 | striped = TRUE, 423 | bordered = TRUE) 424 | 425 | # Cancel import 426 | observeEvent(input$import_cancel, { 427 | removeModal() 428 | }) 429 | 430 | # Confirm import and load the full dataset 431 | observeEvent(input$import_confirm, { 432 | req(input$file) 433 | 434 | file_ext <- rv$file_extension 435 | # Make sure using_rownames is a logical value, not NULL 436 | using_rownames <- isTRUE(!is.null(input$import_rownames) && input$import_rownames) 437 | 438 | # Import full dataset based on selected options 439 | tryCatch({ 440 | if(file_ext %in% c("xls", "xlsx")) { 441 | df <- readxl::read_excel( 442 | input$file$datapath, 443 | sheet = input$import_sheet, 444 | col_names = input$import_header 445 | ) 446 | df <- as.data.frame(df) 447 | } else { 448 | delimiter <- input$import_delimiter 449 | 450 | if(delimiter == "\t") { 451 | df <- read.delim( 452 | input$file$datapath, 453 | header = input$import_header, 454 | sep = delimiter, 455 | stringsAsFactors = FALSE, 456 | check.names = TRUE # Ensure column names are valid R names 457 | ) 458 | } else { 459 | df <- read.csv( 460 | input$file$datapath, 461 | header = input$import_header, 462 | sep = delimiter, 463 | stringsAsFactors = FALSE, 464 | check.names = TRUE # Ensure column names are valid R names 465 | ) 466 | } 467 | } 468 | 469 | # Remove "-" or "." from sample names ---------- 470 | colnames(df) <- gsub("-", "_", colnames(df)) 471 | colnames(df) <- gsub(" ", "", colnames(df)) 472 | #browser() 473 | # Process row names if selected - with safe logical checks 474 | if(isTRUE(using_rownames) && !is.null(df) && ncol(df) > 1) { 475 | row_names <- df[[1]] 476 | df <- df[, -1, drop = FALSE] 477 | rownames(df) <- row_names 478 | rv$has_rownames <- TRUE 479 | } else { 480 | rownames(df) <- NULL # does nothing 481 | rv$has_rownames <- FALSE 482 | } 483 | 484 | # Store the data 485 | rv$data <- df 486 | rv$data_loaded <- TRUE 487 | 488 | # Generate reproducible code 489 | rv$code <- generate_code( 490 | file_path = input$file$name, 491 | file_ext = file_ext, 492 | delimiter = input$import_delimiter, 493 | sheet = input$import_sheet, 494 | header = input$import_header, 495 | rownames = using_rownames 496 | ) 497 | 498 | removeModal() 499 | 500 | }, error = function(e) { 501 | showNotification( 502 | paste("Error importing data:", e$message), 503 | type = "error", 504 | duration = 10 505 | ) 506 | }) 507 | }) 508 | 509 | # Return a list of reactive values to be used in the main app 510 | return(list( 511 | data = reactive({ rv$data }), 512 | data_loaded = reactive({ rv$data_loaded }), 513 | has_rownames = reactive({ rv$has_rownames }), 514 | code = reactive({ rv$code }) 515 | )) 516 | }) 517 | } 518 | 519 | 520 | guess_transform <- function(data_matrix) { 521 | # sample this meany rows or columns 522 | n_sample <- 500 523 | 524 | # Check if input is valid 525 | if (!is.matrix(data_matrix) && !is.data.frame(data_matrix)) { 526 | return(0) # Return 0 for invalid input 527 | } 528 | 529 | # Convert to matrix if it's a data frame 530 | if (is.data.frame(data_matrix)) { 531 | # Try to convert to numeric matrix, handling potential errors 532 | tryCatch({ 533 | data_matrix <- as.matrix(data_matrix) 534 | data_matrix <- matrix(as.numeric(data_matrix), nrow = nrow(data_matrix)) 535 | }, error = function(e) { 536 | return(0) # Return 0 if conversion fails 537 | }) 538 | } 539 | 540 | # Check if matrix is empty or contains only NAs 541 | if (nrow(data_matrix) == 0 || ncol(data_matrix) == 0 || all(is.na(data_matrix))) { 542 | return(0) 543 | } 544 | 545 | if (nrow(data_matrix) > n_sample) { 546 | sampled_rows <- sample(1:nrow(data_matrix), n_sample) 547 | data_matrix <- data_matrix[sampled_rows, , drop = FALSE] 548 | } 549 | 550 | # Sample columns if more than 500 551 | if (ncol(data_matrix) > n_sample) { 552 | sampled_cols <- sample(1:ncol(data_matrix), n_sample) 553 | data_matrix <- data_matrix[, sampled_cols, drop = FALSE] 554 | } 555 | 556 | # Calculate row and column medians, ignoring NA values 557 | row_medians <- apply(data_matrix, 1, median, na.rm = TRUE) 558 | col_medians <- apply(data_matrix, 2, median, na.rm = TRUE) 559 | 560 | # Check if all medians are NA 561 | if (all(is.na(row_medians)) || all(is.na(col_medians))) { 562 | return(0) 563 | } 564 | 565 | # Calculate MAD of medians 566 | row_mad <- mad(row_medians, na.rm = TRUE) 567 | col_mad <- mad(col_medians, na.rm = TRUE) 568 | 569 | # Handle edge cases where MAD is 0 or NA 570 | if (is.na(row_mad) || is.na(col_mad) || row_mad == 0 || col_mad == 0) { 571 | # If MAD is 0, calculate standard deviation instead 572 | row_mad <- sd(row_medians, na.rm = TRUE) 573 | col_mad <- sd(col_medians, na.rm = TRUE) 574 | 575 | # If still 0 or NA, return 0 576 | if (is.na(row_mad) || is.na(col_mad) || row_mad == 0 || col_mad == 0) { 577 | return(0) 578 | } 579 | } 580 | 581 | # Compare MADs without division 582 | if (col_mad <= row_mad) { # Rows are variables, columns are observations 583 | if (max(row_medians) < 10 * min(row_medians)) { 584 | return(2) # Largest row_mad < 10 times smallest row_mad 585 | } else { 586 | return(3) # Otherwise, recommend row scaling 587 | } 588 | } else { # Columns are variables, rows are observations 589 | if (max(col_medians) < 10 * min(col_medians)) { 590 | return(4) # Largest col_mad < 10 times smallest col_mad 591 | } else { 592 | return(5) # Otherwise, recommend column scaling 593 | } 594 | } 595 | } -------------------------------------------------------------------------------- /R/mod_heatmap.R: -------------------------------------------------------------------------------- 1 | # this solves the issue of the download button not working from Chromium when this app is deployed as Shinylive 2 | downloadButton <- function(...) { 3 | tag <- shiny::downloadButton(...) 4 | tag$attribs$download <- NULL 5 | tag 6 | } 7 | 8 | # Module UI function 9 | heatmap_ui <- function(id) { 10 | ns <- NS(id) 11 | tagList( 12 | fluidRow( 13 | column( 14 | 12, 15 | downloadButton(ns("download_pdf"), "PDF"), 16 | downloadButton(ns("download_png"), "PNG"), 17 | align = "right") 18 | ), 19 | plotOutput(ns("heatmap"), width = "100%", height = "600px"), 20 | ) 21 | } 22 | 23 | # Module server function 24 | heatmap_server <- function(id, current_data, 25 | file_data, 26 | unprocessed_data, 27 | col_annotation_for_heatmap, 28 | row_annotation_for_heatmap, 29 | transform_data, 30 | max_rows_to_show = 1000, 31 | default_width = 600, 32 | default_height = 600) { 33 | 34 | moduleServer(id, function(input, output, session) { 35 | # Initialize internal reactive values for width/height 36 | width <- reactiveVal(default_width) 37 | height <- reactiveVal(default_height) 38 | 39 | # Update width/height reactives when inputs change 40 | observe({ 41 | if(!is.null(input$width)) width(input$width) 42 | if(!is.null(input$height)) height(input$height) 43 | }) 44 | 45 | # Create a reactive to store and access the actual pheatmap parameters 46 | pheatmap_params_used <- reactiveVal(NULL) 47 | 48 | # Modify the heatmap_obj reactive function to correctly handle distance objects: 49 | heatmap_obj <- reactive({ 50 | req(transform_data$processed_data()) 51 | 52 | withProgress(message = 'Generating heatmap', value = 0, { 53 | # Convert the current data to a numeric matrix for the heatmap 54 | incProgress(0.1, detail = "Preparing data") 55 | heatmap_data <- transform_data$processed_data() 56 | 57 | show_row_names <- file_data$has_rownames() && !is.null(rownames(heatmap_data)) 58 | # Use the user's input if available; otherwise, use the default 59 | if(show_row_names && !is.null(input$show_row_names)) { 60 | show_row_names <- input$show_row_names 61 | } 62 | 63 | # Select the color palette 64 | if (input$color == "GreenBlackRed") { 65 | colors <- colorRampPalette(c("green", "black", "red"))(100) 66 | } else { 67 | colors <- colorRampPalette(rev(RColorBrewer::brewer.pal(11, input$color)))(100) 68 | } 69 | 70 | # Prepare clustering parameters 71 | distance_method <- if (!is.null(input$distance_method)) input$distance_method else "euclidean" 72 | correlation_method <- "pearson" 73 | 74 | # Flag to determine if we're using correlation-based distance 75 | using_correlation <- FALSE 76 | 77 | if(distance_method %in% c("pearson", "spearman", "kendall")) { 78 | correlation_method <- distance_method 79 | using_correlation <- TRUE 80 | } 81 | 82 | clustering_method <- if (!is.null(input$clustering_method)) input$clustering_method else "complete" 83 | 84 | # x is a matrix of data 85 | # Returns a distance object for columns 86 | # to get row distances, transpose the matrix before calling this function 87 | custom_cor <- function(x) { 88 | cors <- withCallingHandlers( 89 | tryCatch( 90 | cor(x, method = correlation_method, use = "pairwise.complete.obs"), 91 | error = function(e) { 92 | showNotification(paste("Error in cor:", conditionMessage(e)), type = "error") 93 | return(diag(nrow(x))) 94 | } 95 | ), 96 | warning = function(w) { 97 | showNotification(paste("Warning in cor:", conditionMessage(w)), type = "warning") 98 | invokeRestart("muffleWarning") 99 | } 100 | ) 101 | # Replace NAs with 0 correlations 102 | cors[is.na(cors)] <- 0 103 | as.dist(1 - cors) 104 | } 105 | 106 | # Try to generate the heatmap with error handling 107 | incProgress(0.3, detail = "Rendering heatmap") 108 | tryCatch({ 109 | if (using_correlation) { 110 | # Calculate the distance matrices for rows and columns 111 | dist_rows <- NULL 112 | if (input$cluster_rows) { 113 | dist_rows <- custom_cor(t(heatmap_data)) 114 | } 115 | dist_cols <- NULL 116 | if (input$cluster_cols) { 117 | dist_cols <- custom_cor(heatmap_data) 118 | } 119 | 120 | display_params <- list( 121 | mat = heatmap_data, 122 | color = colors, 123 | cluster_rows = input$cluster_rows, 124 | cluster_cols = input$cluster_cols, 125 | clustering_method = clustering_method, 126 | fontsize = input$fontsize, 127 | annotation_col = col_annotation_for_heatmap(), 128 | annotation_row = row_annotation_for_heatmap(), 129 | show_rownames = show_row_names, 130 | silent = TRUE, 131 | display_numbers = if (input$label_heatmap) round(as.matrix(unprocessed_data()), 2) else FALSE 132 | ) 133 | 134 | if (input$cluster_rows) { 135 | display_params$clustering_distance_rows = correlation_method 136 | } 137 | if (input$cluster_cols) { 138 | display_params$clustering_distance_cols = correlation_method 139 | } 140 | 141 | # Only add cutree parameters if clustering is enabled and value > 0 142 | if(!is.na(input$cutree_rows)) { # when user delete the number in the input box, it will be NA 143 | if (input$cluster_rows && input$cutree_rows > 1 && input$cutree_rows <= nrow(heatmap_data)) { 144 | display_params$cutree_rows <- input$cutree_rows 145 | } 146 | } 147 | if(!is.na(input$cutree_cols)) { 148 | if (input$cluster_cols && input$cutree_cols > 1 && input$cutree_cols <= ncol(heatmap_data)) { 149 | display_params$cutree_cols <- input$cutree_cols 150 | } 151 | } 152 | 153 | # Store the parameters for code generation 154 | pheatmap_params_used(display_params) 155 | 156 | # Create the actual parameters with the distance objects for rendering 157 | render_params <- display_params 158 | if (input$cluster_rows) { 159 | render_params$clustering_distance_rows <- dist_rows 160 | } 161 | if (input$cluster_cols) { 162 | render_params$clustering_distance_cols <- dist_cols 163 | } 164 | 165 | # Call pheatmap with the parameter list that includes the distance objects 166 | do.call(pheatmap::pheatmap, render_params) 167 | } else { 168 | # For non-correlation methods, use the standard distance_method 169 | pheatmap_params <- list( 170 | mat = heatmap_data, 171 | color = colors, 172 | cluster_rows = input$cluster_rows, 173 | cluster_cols = input$cluster_cols, 174 | clustering_method = clustering_method, 175 | clustering_distance_rows = distance_method, 176 | clustering_distance_cols = distance_method, 177 | fontsize = input$fontsize, 178 | annotation_col = col_annotation_for_heatmap(), 179 | annotation_row = row_annotation_for_heatmap(), 180 | show_rownames = show_row_names, 181 | silent = TRUE, 182 | display_numbers = if (input$label_heatmap) round(as.matrix(unprocessed_data()), 2) else FALSE 183 | ) 184 | 185 | # Only add cutree parameters if clustering is enabled and value > 0 186 | if(!is.null(input$cutree_rows)) { 187 | if (input$cluster_rows && input$cutree_rows > 1 && input$cutree_rows <= nrow(heatmap_data)) { 188 | pheatmap_params$cutree_rows <- input$cutree_rows 189 | } 190 | } 191 | 192 | if(!is.null(input$cutree_cols)) { 193 | if (input$cluster_cols && input$cutree_cols > 1 && input$cutree_cols <= ncol(heatmap_data)) { 194 | pheatmap_params$cutree_cols <- input$cutree_cols 195 | } 196 | } 197 | 198 | # Store the parameters for code generation 199 | pheatmap_params_used(pheatmap_params) 200 | 201 | do.call(pheatmap::pheatmap, pheatmap_params) 202 | } 203 | }, error = function(e) { 204 | # If any clustering fails, fall back to euclidean 205 | incProgress(0.1, detail = "Clustering error, falling back to euclidean distance") 206 | message("Clustering error: ", e$message, ". Falling back to euclidean distance.") 207 | fallback_params <- list( 208 | mat = heatmap_data, 209 | color = colors, 210 | cluster_rows = input$cluster_rows, 211 | cluster_cols = input$cluster_cols, 212 | clustering_method = clustering_method, 213 | clustering_distance_rows = "euclidean", 214 | clustering_distance_cols = "euclidean", 215 | fontsize = input$fontsize, 216 | annotation_col = col_annotation_for_heatmap(), 217 | annotation_row = row_annotation_for_heatmap(), 218 | show_rownames = show_row_names, 219 | silent = TRUE 220 | ) 221 | 222 | # Store the fallback parameters 223 | pheatmap_params_used(fallback_params) 224 | 225 | pheatmap::pheatmap( 226 | mat = heatmap_data, 227 | color = colors, 228 | cluster_rows = input$cluster_rows, 229 | cluster_cols = input$cluster_cols, 230 | clustering_method = clustering_method, 231 | clustering_distance_rows = "euclidean", 232 | clustering_distance_cols = "euclidean", 233 | fontsize = input$fontsize, 234 | annotation_col = col_annotation_for_heatmap(), 235 | annotation_row = row_annotation_for_heatmap(), 236 | show_rownames = show_row_names, 237 | silent = TRUE 238 | ) 239 | }) 240 | }) 241 | }) 242 | 243 | observe({ 244 | req(current_data()) 245 | heatmap_data <- current_data() 246 | # Compute default: TRUE if the data has row names and row count is less than max_rows_to_show 247 | default_show <- file_data$has_rownames() && !is.null(rownames(heatmap_data)) && nrow(heatmap_data) < max_rows_to_show 248 | updateCheckboxInput(session, "show_row_names", value = default_show) 249 | }) 250 | 251 | # Generate the heatmap using current data 252 | output$heatmap <- renderPlot({ 253 | req(heatmap_obj()) 254 | grid::grid.newpage() 255 | grid::grid.draw(heatmap_obj()) 256 | }, width = function() width(), height = function() height()) 257 | 258 | # Download handlers using the same heatmap object 259 | output$download_pdf <- downloadHandler( 260 | filename = function() { 261 | paste0("heatmap-", format(Sys.time(), "%Y%m%d-%H%M%S"), ".pdf") 262 | }, 263 | content = function(file) { 264 | pdf(file, width = width()/72, height = height()/72) 265 | grid::grid.newpage() 266 | grid::grid.draw(heatmap_obj()) 267 | dev.off() 268 | } 269 | ) 270 | 271 | output$download_png <- downloadHandler( 272 | filename = function() { 273 | paste0("heatmap-", format(Sys.time(), "%Y%m%d-%H%M%S"), ".png") 274 | }, 275 | content = function(file) { 276 | png(file, width = width(), height = height(), res = 72) 277 | grid::grid.newpage() 278 | grid::grid.draw(heatmap_obj()) 279 | dev.off() 280 | } 281 | ) 282 | 283 | # Generate heatmap code using the actual parameters that were used 284 | heatmap_code <- reactive({ 285 | req(current_data(), pheatmap_params_used()) 286 | # Extract the params that were used 287 | params <- pheatmap_params_used() 288 | 289 | code_parts <- c( 290 | "# Heatmap Generation Code", 291 | "library(pheatmap)", 292 | "library(RColorBrewer)", 293 | "library(grid)", 294 | "", 295 | "processed_data <- transformed_data$numeric_data", 296 | "" 297 | ) 298 | 299 | # Add color palette code 300 | if (!is.null(params$color)) { 301 | if (identical(params$color, colorRampPalette(c("green", "black", "red"))(100))) { 302 | code_parts <- c(code_parts, "# Define color palette", 303 | "colors <- colorRampPalette(c(\"green\", \"black\", \"red\"))(100)") 304 | } else { 305 | # Determine which palette was used 306 | for (palette_name in c("RdBu", "RdYlBu", "YlOrRd", "YlGnBu", "Blues", "Greens", "Purples", "Reds", "OrRd")) { 307 | if (identical(params$color, colorRampPalette(rev(RColorBrewer::brewer.pal(11, palette_name)))(100))) { 308 | code_parts <- c(code_parts, "# Define color palette", 309 | paste0("colors <- colorRampPalette(rev(RColorBrewer::brewer.pal(11, \"", palette_name, "\")))(100)")) 310 | break 311 | } 312 | } 313 | } 314 | } 315 | params$silent <- FALSE # otherwise pheatmap will not show 316 | 317 | # Check if we need to add custom correlation functions 318 | needs_custom_cor <- FALSE 319 | 320 | if (!is.null(params$clustering_distance_rows)) { 321 | if (params$clustering_distance_rows %in% c("pearson", "spearman", "kendall")) { 322 | needs_custom_cor <- TRUE 323 | } 324 | } 325 | 326 | if (!is.null(params$clustering_distance_cols)) { 327 | if (params$clustering_distance_cols %in% c("pearson", "spearman", "kendall")) { 328 | needs_custom_cor <- TRUE 329 | } 330 | } 331 | 332 | # If correlation methods are used, add the custom correlation function 333 | if (needs_custom_cor) { 334 | code_parts <- c(code_parts, "", 335 | "# Custom correlation function for distance calculation", 336 | "custom_cor <- function(x, method = \"pearson\") {", 337 | " cors <- cor(x, method = method, use = \"pairwise.complete.obs\")", 338 | " # Replace NAs with 0 correlations", 339 | " cors[is.na(cors)] <- 0", 340 | " as.dist(1 - cors)", 341 | "}") 342 | } 343 | 344 | # Add annotation handling code if annotations are used 345 | if (!is.null(params$annotation_col)) { 346 | # Extract the selected column annotation rows 347 | if (!is.null(col_annotation_for_heatmap())) { 348 | selected_col_anno_rows <- colnames(col_annotation_for_heatmap()) 349 | if (length(selected_col_anno_rows) > 0) { 350 | selected_rows_code <- paste0("c(", paste0("\"", selected_col_anno_rows, "\"", collapse = ", "), ")") 351 | code_parts <- c(code_parts, "", 352 | "# Column annotation code", 353 | paste0("selected_col_anno_rows <- ", selected_rows_code), 354 | "main_cols <- colnames(processed_data)", 355 | "col_annotation <- NULL", 356 | "if (exists(\"col_annotation_raw\") && !is.null(col_annotation_raw)) {", 357 | " col_annotation <- process_column_annotations(", 358 | " main_data_cols = main_cols,", 359 | " annotation_df = col_annotation_raw,", 360 | " selected_annotations = selected_col_anno_rows", 361 | " )", 362 | "}") 363 | } 364 | } 365 | } 366 | 367 | if (!is.null(params$annotation_row)) { 368 | # Extract the selected row annotation rows 369 | if (!is.null(row_annotation_for_heatmap())) { 370 | selected_row_anno_rows <- colnames(row_annotation_for_heatmap()) 371 | if (length(selected_row_anno_rows) > 0) { 372 | selected_rows_code <- paste0("c(", paste0("\"", selected_row_anno_rows, "\"", collapse = ", "), ")") 373 | code_parts <- c(code_parts, "", 374 | "# Row annotation code", 375 | paste0("selected_row_anno_rows <- ", selected_rows_code), 376 | "main_rows <- rownames(processed_data)", 377 | "row_annotation <- NULL", 378 | "if (exists(\"row_annotation_raw\") && !is.null(row_annotation_raw)) {", 379 | " factor_annotation_df <- NULL", 380 | " # Factor columns might come from transformed_data if available", 381 | " if (exists(\"transformed_data\") && !is.null(transformed_data$factor_columns)) {", 382 | " factor_annotation_df <- transformed_data$factor_columns", 383 | " }", 384 | " row_annotation <- process_row_annotations(", 385 | " main_data_rows = main_rows,", 386 | " file_annotation_df = row_annotation_raw,", 387 | " factor_annotation_df = factor_annotation_df,", 388 | " selected_annotations = selected_row_anno_rows", 389 | " )", 390 | "}") 391 | } 392 | } 393 | } 394 | 395 | # Start building pheatmap code 396 | pheatmap_call <- "pheatmap(\n processed_data" 397 | 398 | # Add all the non-complex parameters 399 | simple_params <- c( 400 | "cluster_rows", "cluster_cols", "clustering_method", 401 | "fontsize", "show_rownames", "silent", "cutree_rows", "cutree_cols" 402 | ) 403 | 404 | for (param in simple_params) { 405 | if (!is.null(params[[param]])) { 406 | # Format the value based on its type 407 | if (is.logical(params[[param]])) { 408 | value <- ifelse(params[[param]], "TRUE", "FALSE") 409 | } else if (is.character(params[[param]])) { 410 | value <- paste0("\"", params[[param]], "\"") 411 | } else { 412 | value <- as.character(params[[param]]) 413 | } 414 | 415 | pheatmap_call <- paste0(pheatmap_call, ",\n ", param, " = ", value) 416 | } 417 | } 418 | 419 | # Handle distance methods correctly 420 | if (!is.null(params$clustering_distance_rows)) { 421 | if (params$clustering_distance_rows %in% c("pearson", "spearman", "kendall")) { 422 | # Add code to create the distance object before the pheatmap call 423 | code_parts <- c(code_parts, "", 424 | "# Calculate distance matrices for clustering", 425 | paste0("row_dist <- custom_cor(t(processed_data), method = \"", params$clustering_distance_rows, "\")")) 426 | pheatmap_call <- paste0(pheatmap_call, ",\n clustering_distance_rows = row_dist") 427 | } else { 428 | # For standard distance methods, use the string 429 | pheatmap_call <- paste0(pheatmap_call, ",\n clustering_distance_rows = \"", params$clustering_distance_rows, "\"") 430 | } 431 | } 432 | 433 | if (!is.null(params$clustering_distance_cols)) { 434 | if (params$clustering_distance_cols %in% c("pearson", "spearman", "kendall")) { 435 | # Add code to create the distance object before the pheatmap call 436 | code_parts <- c(code_parts, "", 437 | paste0("col_dist <- custom_cor(processed_data, method = \"", params$clustering_distance_cols, "\")")) 438 | pheatmap_call <- paste0(pheatmap_call, ",\n clustering_distance_cols = col_dist") 439 | } else { 440 | # For standard distance methods, use the string 441 | pheatmap_call <- paste0(pheatmap_call, ",\n clustering_distance_cols = \"", params$clustering_distance_cols, "\"") 442 | } 443 | } 444 | 445 | # Handle annotation parameters 446 | if (!is.null(params$annotation_col)) { 447 | pheatmap_call <- paste0(pheatmap_call, ",\n annotation_col = col_annotation") 448 | } 449 | 450 | if (!is.null(params$annotation_row)) { 451 | pheatmap_call <- paste0(pheatmap_call, ",\n annotation_row = row_annotation") 452 | } 453 | 454 | # Handle display_numbers if it was used 455 | if (!is.null(params$display_numbers)) { 456 | if (is.logical(params$display_numbers)) { 457 | if (params$display_numbers) { 458 | pheatmap_call <- paste0(pheatmap_call, ",\n display_numbers = TRUE") 459 | } 460 | } else { 461 | # If display_numbers contains a matrix of values, use the raw data 462 | code_parts <- c(code_parts, "", 463 | "# Create display numbers matrix from raw data", 464 | "# In standalone RStudio, we'll just use the raw_data directly", 465 | "display_numbers <- round(as.matrix(raw_data), 2)") 466 | pheatmap_call <- paste0(pheatmap_call, ",\n display_numbers = display_numbers") 467 | } 468 | } 469 | 470 | # Add colors 471 | pheatmap_call <- paste0(pheatmap_call, ",\n color = colors") 472 | 473 | # Close the pheatmap call 474 | pheatmap_call <- paste0(pheatmap_call, "\n)") 475 | 476 | # Complete the heatmap code 477 | code_parts <- c(code_parts, "", "# Generate the heatmap", pheatmap_call) 478 | 479 | paste(code_parts, collapse = "\n") 480 | }) 481 | 482 | 483 | # Return reactives that will be needed by the parent module 484 | return(list( 485 | heatmap_code = heatmap_code, 486 | params_used = pheatmap_params_used 487 | )) 488 | }) 489 | } 490 | 491 | # Helper function to generate heatmap UI elements for the sidebar 492 | heatmap_control_ui <- function(id) { 493 | ns <- NS(id) 494 | tagList( 495 | # Clustering options - compact layout 496 | fluidRow( 497 | column(6, checkboxInput(ns("cluster_rows"), "Cluster Rows", TRUE)), 498 | column(6, checkboxInput(ns("cluster_cols"), "Cluster Columns", TRUE)) 499 | ), 500 | 501 | # Linkage method - label to the left 502 | fluidRow( 503 | column(3, p("Linkage:", style="padding-top: 7px; text-align: right;")), 504 | column(9, selectInput(ns("clustering_method"), NULL, 505 | choices = c("complete", "average", "single", "ward.D", "ward.D2", "mcquitty", "median", "centroid"), 506 | selected = "average")) 507 | ), 508 | 509 | # Distance method - label to the left 510 | fluidRow( 511 | column(3, p("Distance:", style="padding-top: 7px; text-align: right;")), 512 | column(9, selectInput(ns("distance_method"), NULL, 513 | choices = c("euclidean", "manhattan", "maximum", "canberra", "binary", "minkowski", "pearson", "spearman", "kendall"), 514 | selected = "euclidean")) 515 | ), 516 | 517 | # Color palette - compact 518 | hr(), 519 | fluidRow( 520 | column(3, p("Colors:", style="padding-top: 7px; text-align: right;")), 521 | column(9, selectInput(ns("color"), NULL, 522 | choices = c("Green Black Red" = "GreenBlackRed", "Red yellow blue" ="RdYlBu", "Red Blue" = "RdBu", "Yellow orange red" = "YlOrRd", 523 | "Yellow Green Blue" = "YlGnBu", "Blues", "Greens", "Purples", "Reds", "OrRd"), 524 | selected = "RdYlBu")) 525 | ), 526 | 527 | # Font size - more compact 528 | fluidRow( 529 | column(3, p("Font:", style="padding-top: 7px; text-align: right;")), 530 | column(9, sliderInput(ns("fontsize"), NULL, min = 5, max = 25, value = 12)) 531 | ), 532 | 533 | # Width & Height in more compact form 534 | fluidRow( 535 | column(3, p("Width:", style="padding-top: 7px; text-align: right;")), 536 | column(9, sliderInput(ns("width"), NULL, min = 200, max = 4000, value = 600, step = 20)) 537 | ), 538 | 539 | fluidRow( 540 | column(3, p("Height:", style="padding-top: 7px; text-align: right;")), 541 | column(9, sliderInput(ns("height"), NULL, min = 200, max = 6000, value = 600, step = 20)) 542 | ), 543 | fluidRow( 544 | column(6, checkboxInput(ns("label_heatmap"), "Label Data", value = FALSE)), 545 | column(6, checkboxInput(ns("show_row_names"), "Row Names", value = FALSE)) 546 | ), 547 | fluidRow( 548 | column(6, numericInput(ns("cutree_rows"), "Row clusters", value = 1, min = 1, max = 100, step = 1)), 549 | column(6, numericInput(ns("cutree_cols"), "Col. Clusters", value = 1, min = 1, max = 100, step = 1)) 550 | ) 551 | ) 552 | } -------------------------------------------------------------------------------- /R/mod_pca.R: -------------------------------------------------------------------------------- 1 | # PCA Plot Module 2 | # This module encapsulates the PCA plot functionality 3 | 4 | # this solves the issue of the download button not working from Chromium when this app is deployed as Shinylive 5 | downloadButton <- function(...) { 6 | tag <- shiny::downloadButton(...) 7 | tag$attribs$download <- NULL 8 | tag 9 | } 10 | 11 | # UI Function 12 | pca_plot_ui <- function(id) { 13 | ns <- NS(id) 14 | 15 | tagList( 16 | fluidRow( 17 | column(3, selectInput(ns("pca_transpose"), NULL, 18 | choices = c("Row vectors" = "row", 19 | "Column vectors" = "column"), 20 | selected = "row"), style = "margin-top:5px;"), 21 | column(9, checkboxInput(ns("show_point_labels"), "Show names", value = FALSE), 22 | style = "margin-top:5px;", align = "left") 23 | ), 24 | plotOutput(ns("pca_plot"), width = "100%", height = "auto"), 25 | downloadButton(ns("download_pca_pdf"), "PDF"), 26 | downloadButton(ns("download_pca_png"), "PNG") 27 | ) 28 | } 29 | 30 | # Server Function 31 | pca_plot_server <- function(id, current_data, col_annotation_for_heatmap, row_annotation_for_heatmap, 32 | input_fontsize, input_width, input_height) { 33 | moduleServer(id, function(input, output, session) { 34 | 35 | # Modify the pca_data reactive to handle transposition 36 | pca_data <- reactive({ 37 | req(current_data()) 38 | 39 | # Get the data and handle transposition based on user selection 40 | data_mat <- as.matrix(current_data()) 41 | 42 | # Transpose if column PCA is selected 43 | if (input$pca_transpose == "column") { 44 | data_mat <- t(data_mat) 45 | } 46 | 47 | # Use prcomp for PCA calculation, scaling the data 48 | tryCatch({ 49 | # Handle missing values 50 | if (any(is.na(data_mat))) { 51 | showNotification("Warning: Missing values found. Using pairwise complete observations.", type = "warning") 52 | data_mat <- na.omit(data_mat) 53 | } 54 | 55 | # Perform PCA 56 | pca_result <- prcomp(data_mat, center = TRUE, scale. = TRUE) 57 | 58 | # Store the transposition information with the result 59 | attr(pca_result, "transposed") <- (input$pca_transpose == "column") 60 | 61 | return(pca_result) 62 | }, error = function(e) { 63 | showNotification(paste("Error in PCA calculation:", e$message), type = "error") 64 | return(NULL) 65 | }) 66 | }) 67 | 68 | # Implementation for PCA plot using the refactored function 69 | create_pca_plot <- function() { 70 | req(pca_data()) 71 | req(current_data()) 72 | 73 | # Get PCA results and extract the first two principal components 74 | pca_result <- pca_data() 75 | pc_data <- as.data.frame(pca_result$x[, 1:2]) 76 | 77 | # Check if we're in transposed mode 78 | transposed <- attr(pca_result, "transposed") 79 | 80 | # Get appropriate annotations based on transposition mode 81 | if (transposed) { 82 | # For column PCA mode (columns as points), use column annotation data 83 | point_annot <- col_annotation_for_heatmap() 84 | } else { 85 | # For row PCA mode (rows as points), use row annotation data 86 | point_annot <- row_annotation_for_heatmap() 87 | } 88 | 89 | # PC variances for axis labels 90 | pc1_var <- round(summary(pca_result)$importance[2, 1] * 100, 1) 91 | pc2_var <- round(summary(pca_result)$importance[2, 2] * 100, 1) 92 | 93 | # Create x and y labels 94 | x_label <- paste0("PC1 (", pc1_var, "%)") 95 | y_label <- paste0("PC2 (", pc2_var, "%)") 96 | 97 | # Get point labels if checkbox is selected and enabled 98 | point_labels <- NULL 99 | show_labels <- FALSE 100 | 101 | # Check if the checkbox is available and checked 102 | if (!is.null(input$show_point_labels) && input$show_point_labels) { 103 | show_labels <- TRUE 104 | data_mat <- as.matrix(current_data()) 105 | 106 | if (transposed) { 107 | # Column names as labels in column mode 108 | point_labels <- colnames(data_mat) 109 | } else { 110 | # Row names as labels in row mode 111 | point_labels <- rownames(data_mat) 112 | } 113 | } 114 | 115 | # Use the generic function to create the plot 116 | create_dr_plot(pc_data, x_label, y_label, point_annot, input_fontsize(), 117 | show_labels = show_labels, point_labels = point_labels) 118 | } 119 | 120 | output$pca_plot <- renderPlot({ 121 | replayPlot(create_pca_plot()) 122 | }, width = function() input_width(), height = function() input_height()) 123 | 124 | # Download handlers for PCA plots 125 | output$download_pca_pdf <- downloadHandler( 126 | filename = function() { 127 | paste0("pca-plot-", format(Sys.time(), "%Y%m%d-%H%M%S"), ".pdf") 128 | }, 129 | content = function(file) { 130 | pdf(file, width = input_width()/72, height = input_height()/72) 131 | replayPlot(create_pca_plot()) 132 | dev.off() 133 | } 134 | ) 135 | 136 | output$download_pca_png <- downloadHandler( 137 | filename = function() { 138 | paste0("pca-plot-", format(Sys.time(), "%Y%m%d-%H%M%S"), ".png") 139 | }, 140 | content = function(file) { 141 | png(file, width = input_width(), height = input_height(), res = 72) 142 | replayPlot(create_pca_plot()) 143 | dev.off() 144 | } 145 | ) 146 | 147 | # Generate the PCA code 148 | pca_code <- function() { 149 | req(pca_data()) 150 | 151 | # Capture the current user settings as values 152 | transpose_selection <- input$pca_transpose 153 | show_labels <- !is.null(input$show_point_labels) && input$show_point_labels 154 | font_size <- input_fontsize() 155 | 156 | # Start with required inputs documentation 157 | code <- c( 158 | "library(stats)", 159 | "" 160 | ) 161 | 162 | # Convert to matrix and handle transposition (matching app's pca_data reactive) 163 | code <- c(code, 164 | "# Get the data and handle transposition based on user selection", 165 | "data_mat <- as.matrix(processed_data)", 166 | "", 167 | "# Transpose if column PCA is selected", 168 | if (transpose_selection == "column") { 169 | "data_mat <- t(data_mat)" 170 | }, 171 | "" 172 | ) 173 | 174 | # Add error handling for missing values (matching app exactly) 175 | code <- c(code, 176 | "# Handle missing values", 177 | "if (any(is.na(data_mat))) {", 178 | " print(\"Warning: Missing values found. Using pairwise complete observations.\")", 179 | " data_mat <- na.omit(data_mat)", 180 | "}", 181 | "" 182 | ) 183 | 184 | # Perform PCA (matching app exactly) 185 | code <- c(code, 186 | "# Perform PCA", 187 | "pca_result <- prcomp(data_mat, center = TRUE, scale. = TRUE)", 188 | "", 189 | "# Store the transposition information with the result", 190 | sprintf("attr(pca_result, \"transposed\") <- %s", if (transpose_selection == "column") "TRUE" else "FALSE"), 191 | "" 192 | ) 193 | 194 | # Now match the app's create_pca_plot function exactly 195 | code <- c(code, 196 | "# Get PCA results and extract the first two principal components", 197 | "pc_data <- as.data.frame(pca_result$x[, 1:2])", 198 | "", 199 | "# Check if we're in transposed mode", 200 | "transposed <- attr(pca_result, \"transposed\")", 201 | "", 202 | "# Get appropriate annotations based on transposition mode", 203 | "if (transposed) {", 204 | " # For column PCA mode (columns as points), use column annotation data", 205 | " point_annot <- col_annotation", 206 | "} else {", 207 | " # For row PCA mode (rows as points), use row annotation data", 208 | " point_annot <- row_annotation", 209 | "}", 210 | "", 211 | "# PC variances for axis labels", 212 | "pc1_var <- round(summary(pca_result)$importance[2, 1] * 100, 1)", 213 | "pc2_var <- round(summary(pca_result)$importance[2, 2] * 100, 1)", 214 | "", 215 | "# Create x and y labels", 216 | "x_label <- paste0(\"PC1 (\", pc1_var, \"%)\")", 217 | "y_label <- paste0(\"PC2 (\", pc2_var, \"%)\")", 218 | "", 219 | "# Get point labels if checkbox is selected", 220 | "point_labels <- NULL", 221 | sprintf("show_labels <- %s", if (show_labels) "TRUE" else "FALSE"), 222 | "", 223 | "# Check if the checkbox is available and checked", 224 | "if (show_labels) {", 225 | " data_mat <- as.matrix(processed_data)", # matching the app's logic exactly 226 | " ", 227 | " if (transposed) {", 228 | " # Column names as labels in column mode", 229 | " point_labels <- colnames(data_mat)", 230 | " } else {", 231 | " # Row names as labels in row mode", 232 | " point_labels <- rownames(data_mat)", 233 | " }", 234 | "}", 235 | "" 236 | ) 237 | 238 | # Use the generic function to create the plot (matching the app's call exactly) 239 | code <- c(code, 240 | "# Use the generic function to create the plot", 241 | sprintf("create_dr_plot(pc_data, x_label, y_label, point_annot, %d,", font_size), 242 | " show_labels = show_labels, point_labels = point_labels)" 243 | ) 244 | 245 | return(code) 246 | } 247 | 248 | # Return the pca_data and code generation function for use outside the module 249 | return(list( 250 | pca_data = pca_data, 251 | pca_code = pca_code 252 | )) 253 | }) 254 | } -------------------------------------------------------------------------------- /R/mod_tsne.R: -------------------------------------------------------------------------------- 1 | # t-SNE module 2 | 3 | # This solves the issue of the download button not working from Chromium when this app is deployed as Shinylive 4 | downloadButton <- function(...) { 5 | tag <- shiny::downloadButton(...) 6 | tag$attribs$download <- NULL 7 | tag 8 | } 9 | 10 | # UI function 11 | tsne_plot_ui <- function(id) { 12 | ns <- NS(id) 13 | 14 | tagList( 15 | # Single row with 4 elements 16 | fluidRow( 17 | column(3, selectInput(ns("tsne_transpose"), "Analysis Mode:", 18 | choices = c("Row vectors" = "row", "Column vectors" = "column"), 19 | selected = "row")), 20 | column(3, sliderInput(ns("tsne_perplexity"), "Perplexity:", 21 | min = 5, max = 50, value = 30, step = 5)), 22 | column(3, sliderInput(ns("tsne_early_exaggeration"), "Early Exagg.:", 23 | min = 4, max = 20, value = 12, step = 1)), 24 | column(3, sliderInput(ns("tsne_learning_rate"), "Learning Rate:", 25 | min = 50, max = 1000, value = 200, step = 50)) 26 | ), 27 | # Second row with 3 elements and a checkbox 28 | fluidRow( 29 | column(3, sliderInput(ns("tsne_iterations"), "Max Iterations:", 30 | min = 500, max = 2000, value = 1000, step = 100)), 31 | column(3, checkboxInput(ns("tsne_pca_preprocessing"), "Use PCA Preprocessing", value = FALSE)), 32 | column(6, 33 | tags$div( 34 | style = "background-color: #f8f9fa; padding: 10px; border-radius: 5px; margin-bottom: 15px;", 35 | tags$p( 36 | style = "margin-bottom: 0; font-size: 0.85em; color: #495057;", 37 | "Increase Early Exaggeration (12-20) for more distinct clusters. Perplexity balances local (5-10) vs global (30-50) structure. Higher Learning Rate can improve separation but may cause instability. More Iterations allow for better optimization and clearer boundaries. PCA preprocessing can reduce noise." 38 | ) 39 | ) 40 | ) 41 | ), 42 | checkboxInput(ns("show_point_labels"), "Show point labels", value = FALSE), 43 | plotOutput(ns("tsne_plot"), width = "100%", height = "auto"), 44 | downloadButton(ns("download_tsne_pdf"), "PDF"), 45 | downloadButton(ns("download_tsne_png"), "PNG") 46 | ) 47 | } 48 | 49 | # Server function 50 | tsne_plot_server <- function(id, current_data, col_annotation_for_heatmap, row_annotation_for_heatmap, 51 | fontsize, width, height) { 52 | moduleServer(id, function(input, output, session) { 53 | 54 | # t-SNE data reactive 55 | tsne_data <- reactive({ 56 | req(current_data()) 57 | 58 | # Get the data and handle transposition based on user selection 59 | data_mat <- as.matrix(current_data()) 60 | 61 | # Transpose if column t-SNE is selected 62 | if (input$tsne_transpose == "column") { 63 | data_mat <- t(data_mat) 64 | } 65 | 66 | # Use Rtsne for t-SNE calculation with progress indicator 67 | withProgress(message = 'Computing t-SNE', value = 0, { 68 | tryCatch({ 69 | # Handle missing values 70 | if (any(is.na(data_mat))) { 71 | showNotification("Warning: Missing values found in t-SNE calculation. Using complete cases only.", type = "warning") 72 | data_mat <- na.omit(data_mat) 73 | } 74 | 75 | # Check if we have enough data points for the perplexity 76 | # t-SNE requires at least perplexity*3 + 1 points 77 | perplexity <- min(input$tsne_perplexity, floor(nrow(data_mat)/3) - 1) 78 | if (perplexity < 5) { 79 | perplexity <- 5 80 | showNotification(paste("Perplexity adjusted to", perplexity, "due to small sample size"), type = "warning") 81 | } 82 | 83 | # Double-check we have enough data 84 | if (nrow(data_mat) < perplexity * 3 + 1) { 85 | showNotification("Not enough samples for t-SNE with current perplexity. Try reducing perplexity.", type = "error") 86 | return(NULL) 87 | } 88 | 89 | # Scale data to have mean=0 and sd=1 (important for t-SNE) 90 | incProgress(0.2, detail = "Scaling data") 91 | scaled_data <- scale(data_mat) 92 | 93 | # Set seed for reproducibility 94 | set.seed(42) 95 | 96 | # Run t-SNE with progress updates 97 | incProgress(0.2, detail = "Running t-SNE optimization (this may take a while)") 98 | 99 | # Apply Rtsne with all user-defined parameters 100 | tsne_result <- Rtsne::Rtsne( 101 | scaled_data, 102 | dims = 2, # Always use 2D for visualization 103 | perplexity = perplexity, # From UI slider with validation 104 | check_duplicates = FALSE, # Skip duplicate checking for performance 105 | pca = input$tsne_pca_preprocessing, # From UI checkbox 106 | normalize = FALSE, # Already normalized above 107 | max_iter = input$tsne_iterations, # From UI slider 108 | eta = input$tsne_learning_rate, # From UI slider 109 | exaggeration_factor = input$tsne_early_exaggeration, # From UI slider 110 | verbose = FALSE # Disable verbose output 111 | ) 112 | 113 | # Store the transposition information for the plotting function 114 | attr(tsne_result, "transposed") <- (input$tsne_transpose == "column") 115 | 116 | return(tsne_result) 117 | }, error = function(e) { 118 | # Handle any errors during t-SNE computation 119 | showNotification(paste("Error in t-SNE calculation:", e$message), type = "error") 120 | return(NULL) 121 | }) 122 | }) 123 | }) 124 | 125 | # Implementation for t-SNE plot using the refactored function 126 | create_tsne_plot <- function() { 127 | req(tsne_data()) 128 | req(current_data()) 129 | 130 | # Get t-SNE results and extract the two dimensions 131 | tsne_result <- tsne_data() 132 | tsne_coords <- as.data.frame(tsne_result$Y) 133 | colnames(tsne_coords) <- c("tSNE1", "tSNE2") 134 | 135 | # Check if we're in transposed mode 136 | transposed <- attr(tsne_result, "transposed") 137 | 138 | # Get appropriate annotations based on transposition mode 139 | if (transposed) { 140 | # For column t-SNE mode (columns as points), use column annotation data 141 | point_annot <- col_annotation_for_heatmap() 142 | } else { 143 | # For row t-SNE mode (rows as points), use row annotation data 144 | point_annot <- row_annotation_for_heatmap() 145 | } 146 | 147 | # Create x and y labels 148 | x_label <- "tSNE 1" 149 | y_label <- "tSNE 2" 150 | 151 | # Get point labels if checkbox is selected and enabled 152 | point_labels <- NULL 153 | show_labels <- FALSE 154 | 155 | # Check if the checkbox is available and checked 156 | if (!is.null(input$show_point_labels) && input$show_point_labels) { 157 | show_labels <- TRUE 158 | data_mat <- as.matrix(current_data()) 159 | 160 | if (transposed) { 161 | # Column names as labels in column mode 162 | point_labels <- colnames(data_mat) 163 | } else { 164 | # Row names as labels in row mode 165 | point_labels <- rownames(data_mat) 166 | } 167 | } 168 | 169 | # Use the generic function to create the plot 170 | create_dr_plot(tsne_coords, x_label, y_label, point_annot, fontsize(), 171 | show_labels = show_labels, point_labels = point_labels) 172 | } 173 | 174 | # Use the reactive plot in the renderPlot function 175 | output$tsne_plot <- renderPlot({ 176 | replayPlot(create_tsne_plot()) 177 | }, width = function() width(), height = function() height()) 178 | 179 | # Use the same reactive plot in the download handlers 180 | output$download_tsne_pdf <- downloadHandler( 181 | filename = function() { 182 | paste0("tsne-plot-", format(Sys.time(), "%Y%m%d-%H%M%S"), ".pdf") 183 | }, 184 | content = function(file) { 185 | pdf(file, width = width()/72, height = height()/72) 186 | replayPlot(create_tsne_plot()) 187 | dev.off() 188 | } 189 | ) 190 | 191 | output$download_tsne_png <- downloadHandler( 192 | filename = function() { 193 | paste0("tsne-plot-", format(Sys.time(), "%Y%m%d-%H%M%S"), ".png") 194 | }, 195 | content = function(file) { 196 | png(file, width = width(), height = height(), res = 72) 197 | replayPlot(create_tsne_plot()) 198 | dev.off() 199 | } 200 | ) 201 | 202 | # Generate the t-SNE code (matching the PCA module's approach) 203 | tsne_code <- function() { 204 | req(tsne_data()) 205 | 206 | # Capture the actual adjusted perplexity value from the tsne_data reactive 207 | # to ensure reproducibility 208 | tsne_result <- tsne_data() 209 | data_mat <- as.matrix(current_data()) 210 | if (input$tsne_transpose == "column") { 211 | data_mat <- t(data_mat) 212 | } 213 | actual_perplexity <- min(input$tsne_perplexity, floor(nrow(data_mat)/3) - 1) 214 | if (actual_perplexity < 5) actual_perplexity <- 5 215 | 216 | # Start with required libraries 217 | code <- c( 218 | "library(Rtsne)", 219 | "" 220 | ) 221 | 222 | # Convert to matrix and handle transposition 223 | code <- c(code, 224 | "# Get the data and handle transposition based on user selection", 225 | "data_mat <- as.matrix(processed_data)", 226 | "" 227 | ) 228 | 229 | # Add the transpose code only if needed 230 | if (input$tsne_transpose == "column") { 231 | code <- c(code, 232 | "# Transpose matrix for column-based analysis", 233 | "data_mat <- t(data_mat)", 234 | "" 235 | ) 236 | } 237 | 238 | # Add error handling for missing values 239 | code <- c(code, 240 | "# Handle missing values", 241 | "if (any(is.na(data_mat))) {", 242 | " print(\"Warning: Missing values found. Using complete cases only.\")", 243 | " data_mat <- na.omit(data_mat)", 244 | "}", 245 | "" 246 | ) 247 | 248 | # Check perplexity and data size - use the ACTUAL value being used in the app 249 | code <- c(code, 250 | "# Check perplexity against data size", 251 | paste0("perplexity <- ", actual_perplexity, " # Adjusted perplexity value"), 252 | "", 253 | "# Verify we have enough data", 254 | "if (nrow(data_mat) < perplexity * 3 + 1) {", 255 | " stop(\"Not enough samples for t-SNE with current perplexity. Try reducing perplexity.\")", 256 | "}", 257 | "" 258 | ) 259 | 260 | # Scale data 261 | code <- c(code, 262 | "# Scale data to have mean=0 and sd=1 (important for t-SNE)", 263 | "scaled_data <- scale(data_mat)", 264 | "", 265 | "# Set seed for reproducibility", 266 | "set.seed(42)", 267 | "" 268 | ) 269 | 270 | # Perform t-SNE (use the exact parameters from the UI) 271 | code <- c(code, 272 | "# Perform t-SNE", 273 | paste0("tsne_result <- Rtsne::Rtsne(", 274 | "\n scaled_data,", 275 | "\n dims = 2,", 276 | "\n perplexity = perplexity,", 277 | "\n check_duplicates = FALSE,", 278 | sprintf("\n pca = %s,", ifelse(input$tsne_pca_preprocessing, "TRUE", "FALSE")), 279 | "\n normalize = FALSE,", 280 | sprintf("\n max_iter = %d,", input$tsne_iterations), 281 | sprintf("\n eta = %d,", input$tsne_learning_rate), 282 | sprintf("\n exaggeration_factor = %d,", input$tsne_early_exaggeration), 283 | "\n verbose = TRUE", 284 | "\n)"), 285 | "", 286 | "# Extract t-SNE coordinates", 287 | "tsne_coords <- as.data.frame(tsne_result$Y)", 288 | "colnames(tsne_coords) <- c(\"tSNE1\", \"tSNE2\")", 289 | "" 290 | ) 291 | 292 | # Prepare for plotting (matching the logic in create_tsne_plot) 293 | code <- c(code, 294 | "# Define transposed setting based on analysis mode", 295 | sprintf("transposed <- %s", ifelse(input$tsne_transpose == "column", "TRUE", "FALSE")), 296 | "", 297 | "# Get appropriate annotations based on transposition mode", 298 | "if (transposed) {", 299 | " # For column t-SNE mode (columns as points), use column annotation data", 300 | " point_annot <- col_annotation", 301 | "} else {", 302 | " # For row t-SNE mode (rows as points), use row annotation data", 303 | " point_annot <- row_annotation", 304 | "}", 305 | "", 306 | "# Create axis labels", 307 | "x_label <- \"tSNE 1\"", 308 | "y_label <- \"tSNE 2\"", 309 | "" 310 | ) 311 | 312 | # Handle point labels 313 | code <- c(code, 314 | "# Point label settings", 315 | sprintf("show_labels <- %s", ifelse(input$show_point_labels, "TRUE", "FALSE")), 316 | "point_labels <- NULL", 317 | "", 318 | "# Set up point labels if needed", 319 | "if (show_labels) {", 320 | " if (transposed) {", 321 | " # Use column names as labels in column mode", 322 | " point_labels <- colnames(processed_data)", 323 | " } else {", 324 | " # Use row names as labels in row mode", 325 | " point_labels <- rownames(processed_data)", 326 | " }", 327 | "}", 328 | "" 329 | ) 330 | 331 | # Use the generic function to create the plot 332 | code <- c(code, 333 | "# Plot the t-SNE results", 334 | sprintf("create_dr_plot(tsne_coords, x_label, y_label, point_annot, %d,", fontsize()), 335 | " show_labels = show_labels, point_labels = point_labels)" 336 | ) 337 | 338 | return(code) 339 | } 340 | 341 | # Return the tsne_data and code generation function for use outside the module 342 | return(list( 343 | tsne_data = tsne_data, 344 | tsne_code = tsne_code 345 | )) 346 | }) 347 | } -------------------------------------------------------------------------------- /R/utilities.R: -------------------------------------------------------------------------------- 1 | # Utility functions for plotting and processing annotations. DataMap 2 | 3 | 4 | #' Create Dimensionality Reduction Plot 5 | #' 6 | #' Generates a scatter plot for dimensionality reduction with support for annotations that alter 7 | #' point colors and shapes, and optionally includes point labels. 8 | #' 9 | #' @param coords_data A numeric matrix or data frame containing at least two columns for the x and y coordinates. 10 | #' @param x_label A character string specifying the label for the x-axis. 11 | #' @param y_label A character string specifying the label for the y-axis. 12 | #' @param point_annot Optional data frame containing annotation data. The first column is used for assigning colors, 13 | #' and if a second column is provided, its values are used for assigning point shapes. 14 | #' @param fontsize A numeric value specifying the base font size used for labels, axes, and legends. Default is 12. 15 | #' @param show_labels Logical flag indicating whether to display text labels next to points. Default is FALSE. 16 | #' @param point_labels Optional character vector containing labels for each point. Must have at least as many elements as rows in coords_data when show_labels is TRUE. 17 | #' 18 | #' @return A recorded plot object that can be replayed using \code{replayPlot()}. 19 | #' 20 | #' @details The function sets up the plotting environment, adjusts plot limits based on whether labels are shown, 21 | #' and manages legends for both color and shape annotations. If annotation data is provided, the first column 22 | #' determines the color palette (using a rainbow palette) and the second column (if available) assigns point shapes. 23 | #' 24 | #' @examples 25 | #' # Example with simulated data: 26 | #' coords <- matrix(rnorm(200), ncol = 2) 27 | #' annot <- data.frame(Group = sample(c("A", "B", "C"), 100, replace = TRUE), 28 | #' Type = sample(c("X", "Y"), 100, replace = TRUE)) 29 | #' plot_obj <- create_dr_plot(coords, "X Axis", "Y Axis", point_annot = annot, show_labels = TRUE, 30 | #' point_labels = paste("P", 1:100, sep="")) 31 | #' replayPlot(plot_obj) 32 | #' 33 | create_dr_plot <- function(coords_data, x_label, y_label, point_annot = NULL, fontsize = 12, 34 | show_labels = FALSE, point_labels = NULL) { 35 | # Default plot settings 36 | point_colors <- "black" 37 | point_shapes <- 16 38 | point_sizes <- rep(fontsize/12, nrow(coords_data)) 39 | 40 | # If annotations are available, use them for colors and shapes 41 | legend_items <- list() 42 | 43 | if (!is.null(point_annot) && ncol(point_annot) > 0) { 44 | # Get all annotation columns 45 | selected_cols <- names(point_annot) 46 | 47 | # Use first column for colors 48 | if (length(selected_cols) >= 1) { 49 | color_col <- selected_cols[1] 50 | color_factor <- as.factor(point_annot[[color_col]]) 51 | color_levels <- levels(color_factor) 52 | color_palette <- rainbow(length(color_levels)) 53 | point_colors <- color_palette[as.numeric(color_factor)] 54 | 55 | # Store color legend info 56 | legend_items$colors <- list( 57 | title = color_col, 58 | labels = color_levels, 59 | palette = color_palette 60 | ) 61 | } 62 | 63 | # Use second column for shapes (ONLY if we have multiple annotations) 64 | if (length(selected_cols) >= 2) { 65 | shape_col <- selected_cols[2] 66 | shape_factor <- as.factor(point_annot[[shape_col]]) 67 | shape_levels <- levels(shape_factor) 68 | 69 | available_shapes <- c(16, 17, 15, 18, 19, 1, 2, 5, 6, 8) 70 | shape_numbers <- available_shapes[1:min(length(available_shapes), length(shape_levels))] 71 | point_shapes <- shape_numbers[as.numeric(shape_factor)] 72 | 73 | # Store shape legend info 74 | legend_items$shapes <- list( 75 | title = shape_col, 76 | labels = shape_levels, 77 | shapes = shape_numbers 78 | ) 79 | } else { 80 | # If only one annotation, use circle shape for all points 81 | point_shapes <- 16 82 | } 83 | } 84 | 85 | # Create a new plotting environment 86 | plot_env <- new.env() 87 | 88 | # Create and record the plot 89 | p <- with(plot_env, { 90 | # Set margins 91 | par(mar = c(5, 5, 2, 10) + 0.1) 92 | 93 | adjusted_xlim <- c(min(coords_data[, 1]), max(coords_data[, 1])) 94 | adjusted_ylim <- c(min(coords_data[, 2]), max(coords_data[, 2])) 95 | if (show_labels) { 96 | # extend xlimit in both directions 97 | xrange_adjust <- (max(coords_data[, 1]) - min(coords_data[, 1])) * 0.1 98 | adjusted_xlim <- c(min(coords_data[, 1]) - xrange_adjust, 99 | max(coords_data[, 1]) + xrange_adjust) 100 | # increase ylim on top 101 | yrange_adjust <- (max(coords_data[, 2]) - min(coords_data[, 2])) * 0.05 102 | adjusted_ylim <- c(min(coords_data[, 2]), max(coords_data[, 2]) + yrange_adjust) 103 | } 104 | 105 | # Create the points plot 106 | plot(coords_data[, 1], coords_data[, 2], 107 | xlab = x_label, 108 | ylab = y_label, 109 | main = "", 110 | pch = point_shapes, 111 | col = point_colors, 112 | cex = point_sizes, 113 | cex.lab = fontsize/12, 114 | cex.axis = fontsize/12, # increase limits so that the labels show up. 115 | xlim = adjusted_xlim, 116 | ylim = adjusted_ylim) 117 | 118 | # Add point labels if enabled 119 | if (show_labels && !is.null(point_labels) && length(point_labels) >= nrow(coords_data)) { 120 | text(coords_data[, 1], coords_data[, 2], 121 | labels = point_labels[1:nrow(coords_data)], 122 | pos = 3, offset = 0.5, cex = fontsize/15) 123 | } 124 | 125 | # Add legend if using annotations 126 | if (length(legend_items) > 0) { 127 | # Allow plotting outside the plot region 128 | par(xpd = TRUE) 129 | 130 | # Color legend 131 | if (!is.null(legend_items$colors)) { 132 | color_info <- legend_items$colors 133 | 134 | legend("topright", 135 | legend = color_info$labels, 136 | fill = color_info$palette, 137 | title = color_info$title, 138 | cex = fontsize/15, 139 | inset = c(-0.25, 0), 140 | bty = "n") 141 | } 142 | 143 | # Shape legend (if available) 144 | if (!is.null(legend_items$shapes)) { 145 | shape_info <- legend_items$shapes 146 | 147 | legend("topright", 148 | legend = shape_info$labels, 149 | pch = shape_info$shapes, 150 | title = shape_info$title, 151 | cex = fontsize/15, 152 | inset = c(-0.25, 0.3), 153 | bty = "n") 154 | } 155 | 156 | par(xpd = FALSE) 157 | } 158 | 159 | # Return the recorded plot 160 | recordPlot() 161 | }) 162 | 163 | return(p) 164 | } 165 | 166 | 167 | 168 | #' Process Column Annotations for Heatmap 169 | #' 170 | #' This function processes column annotations by aligning selected annotation rows with the main dataset's columns. 171 | #' It attempts to safely subset and merge annotation data based on common sample names and handles errors during processing. 172 | #' 173 | #' @param main_data_cols A character vector of column names corresponding to the main dataset. 174 | #' @param annotation_df A data frame containing annotation information where rows correspond to different annotation types 175 | #' and columns represent samples. 176 | #' @param selected_annotations A vector indicating the rows (by their names or indices) in annotation_df to be selected. 177 | #' 178 | #' @return A data frame with the main dataset's column names as its row names and the selected annotation types as its column names. 179 | #' If there are no annotations selected, no common samples between datasets, or if all annotations fail to process, the function returns NULL. 180 | #' 181 | #' 182 | process_column_annotations <- function(main_data_cols, annotation_df, selected_annotations) { 183 | # Check if annotation rows are selected 184 | if (is.null(selected_annotations) || length(selected_annotations) == 0) { 185 | return(NULL) 186 | } 187 | 188 | # Safely select annotation rows - note the difference in approach here 189 | annot_selected <- NULL 190 | tryCatch({ 191 | annot_selected <- annotation_df[selected_annotations, , drop = FALSE] 192 | }, error = function(e) { 193 | # Log the error 194 | warning("Error selecting annotations: ", e$message) 195 | }) 196 | 197 | # Check if annotation selection failed 198 | if (is.null(annot_selected)) { 199 | return(NULL) 200 | } 201 | 202 | # Check if there are any common samples between main data and annotation file 203 | common_samples <- intersect(main_data_cols, colnames(annot_selected)) 204 | if (length(common_samples) == 0) { 205 | return(NULL) 206 | } 207 | 208 | # Create an empty data frame with all main data samples 209 | output_df <- data.frame(matrix(NA, nrow = length(main_data_cols), ncol = nrow(annot_selected))) 210 | rownames(output_df) <- main_data_cols 211 | colnames(output_df) <- rownames(annot_selected) 212 | 213 | for (ann in rownames(annot_selected)) { 214 | success <- tryCatch({ 215 | values <- as.character(annot_selected[ann, common_samples]) 216 | names(values) <- common_samples 217 | output_df[common_samples, ann] <- values 218 | TRUE #indicate success 219 | }, error = function(e) { 220 | warning("Error processing annotation '", ann, "': ", e$message) 221 | FALSE #indicate failure 222 | }) 223 | if (!success) { 224 | output_df <- output_df[, colnames(output_df) != ann, drop = FALSE] 225 | } 226 | } 227 | 228 | # Check if we have any annotations left 229 | if (ncol(output_df) == 0) { 230 | warning("All annotations failed to process") 231 | return(NULL) 232 | } 233 | 234 | return(as.data.frame(output_df)) 235 | } 236 | 237 | 238 | 239 | #' Process Row Annotations 240 | #' 241 | #' This function processes and combines annotation data for rows from two potential sources: 242 | #' a file-uploaded annotation data frame and an auto-detected factor annotation data frame. It selects 243 | #' desired annotation columns based on the provided list, aligns these annotations with the main data rows, 244 | #' and merges them into a single data frame. 245 | #' 246 | #' @param main_data_rows A vector of row identifiers from the main data set. 247 | #' @param file_annotation_df An optional data frame containing row annotations from an uploaded file. 248 | #' Default is NULL. 249 | #' @param factor_annotation_df An optional data frame containing row annotations based on factors. 250 | #' Default is NULL. 251 | #' @param selected_annotations A character vector specifying the names of annotations to extract. 252 | #' 253 | #' @return A data frame with combined annotations that correspond to the main data rows. Returns 254 | #' NULL if no annotations are selected or if no matching annotations are found. 255 | #' 256 | process_row_annotations <- function(main_data_rows, 257 | file_annotation_df = NULL, 258 | factor_annotation_df = NULL, 259 | selected_annotations) { 260 | # Return NULL if no annotations are selected 261 | if (is.null(selected_annotations) || length(selected_annotations) == 0) { 262 | return(NULL) 263 | } 264 | 265 | # Initialize combined annotations as NULL 266 | combined_annot <- NULL 267 | added_columns <- c() 268 | 269 | # First try to add columns from uploaded row annotation file 270 | if (!is.null(file_annotation_df)) { 271 | # Find selected columns that exist in the file 272 | file_cols <- intersect(selected_annotations, colnames(file_annotation_df)) 273 | 274 | if (length(file_cols) > 0) { 275 | # Create a new data frame with the same structure as the annotation columns 276 | template_df <- file_annotation_df[1:0, file_cols, drop = FALSE] # Empty df with same column types 277 | 278 | # Add rows for all main data rows (with NAs) 279 | template_df[main_data_rows,] <- NA 280 | 281 | # Fill in values for rows that exist in the annotation file 282 | common_rows <- intersect(main_data_rows, rownames(file_annotation_df)) 283 | if (length(common_rows) > 0) { 284 | template_df[common_rows, ] <- file_annotation_df[common_rows, file_cols, drop = FALSE] 285 | } 286 | 287 | # Start the combined annotations 288 | combined_annot <- template_df 289 | added_columns <- c(added_columns, file_cols) 290 | } 291 | } 292 | 293 | # Next try to add columns from auto-detected factors 294 | if (!is.null(factor_annotation_df) && ncol(factor_annotation_df) > 0) { 295 | # Find selected columns that exist in factors (and aren't already added) 296 | factor_cols <- setdiff(intersect(selected_annotations, colnames(factor_annotation_df)), added_columns) 297 | 298 | if (length(factor_cols) > 0) { 299 | # Create a new data frame with the same structure as the factor columns 300 | template_df <- factor_annotation_df[1:0, factor_cols, drop = FALSE] # Empty df with same column types 301 | 302 | # Add rows for all main data rows (with NAs) 303 | template_df[main_data_rows,] <- NA 304 | 305 | # Fill in values for rows that exist in the factor data 306 | common_rows <- intersect(main_data_rows, rownames(factor_annotation_df)) 307 | if (length(common_rows) > 0) { 308 | template_df[common_rows, ] <- factor_annotation_df[common_rows, factor_cols, drop = FALSE] 309 | } 310 | 311 | # Start or add to the combined annotations 312 | if (is.null(combined_annot)) { 313 | combined_annot <- template_df 314 | } else { 315 | combined_annot <- cbind(combined_annot, template_df) 316 | } 317 | } 318 | } 319 | 320 | # Return NULL if no annotations were added 321 | if (is.null(combined_annot) || ncol(combined_annot) == 0) { 322 | return(NULL) 323 | } 324 | 325 | return(combined_annot) 326 | } 327 | 328 | 329 | #' Find resource files for datamap 330 | #' 331 | #' @param path Path to the resource within the package 332 | #' @return The full path to the resource file 333 | #' @keywords internal 334 | datamap_resource <- function(path) { 335 | # First check if we're running in app directory 336 | app_path <- file.path(".", path) 337 | if (file.exists(app_path)) { 338 | return(app_path) 339 | } 340 | 341 | # Then check inst/ directory during development 342 | dev_path <- file.path("inst", path) 343 | if (file.exists(dev_path)) { 344 | return(dev_path) 345 | } 346 | 347 | # Finally check installed package location 348 | system_path <- system.file(path, package = "datamap") 349 | if (system_path != "") { 350 | return(system_path) 351 | } 352 | 353 | # Fallback for packages not yet installed (development) 354 | file.path("inst", path) 355 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## DataMap: Visualizing Data Matrices in Your Browser 2 | 3 | To use this app, just visit this static [GitHub page](https://gexijin.github.io/datamap/) deployed from this repo. For large datasets, install it as an R package: 4 | ```{R} 5 | install.packages("remotes") 6 | remotes::install_github("gexijin/datamap", upgrade = "never") 7 | datamap::run_app() 8 | ``` 9 | 10 | DataMap is a browser-based application for visualizing high-dimensional 'omics and other data matrices with heatmaps, PCA, and t-SNE. Built with R/Shiny using [shinylive](https://posit-dev.github.io/r-shinylive/), DataMap is a serverless app that is secure and scalable. 11 | 12 | ![heatmap](https://github.com/user-attachments/assets/b649808a-d8d3-4a84-94ed-bec42a9b8f81) 13 | ![image](https://github.com/user-attachments/assets/cbdaaa45-e681-4cbd-b8ef-500b0c4b0b8a) 14 | ![tsne](https://github.com/user-attachments/assets/e732c12b-d042-475a-baaf-3424232f63ce) 15 | 16 | ## Video tutorials 17 | 18 | Quick start: watch a 2-min video on [**YouTube.**](https://youtu.be/9G508BxzjBk) Another [ **video.**](https://www.youtube.com/watch?v=a4ioAVTcCoo) 19 | 20 | ## Features 21 | 22 | - **Browser-based**: No installation required, runs completely in your browser 23 | - **Visualizations**: Heatmaps with hierarchical clustering, principal component analysis (PCA) plot, and t-SNE plot 24 | - **Exportable code**: Generate R code to reproduce the plots 25 | - **Publication-ready plots**: download PNG or PDF formats 26 | - **Multiple input File Formats**: Supports CSV, TSV, TXT, and Excel files 27 | 28 | ## Data Format 29 | 30 | Your data should be organized in a matrix format where: 31 | - The first row must contain column headers 32 | - The first column may contain row identifiers (optional) 33 | - Some columns can be categorical, which will be used to color rows 34 | - Column annotation can be uploaded separately 35 | 36 | ## FAQ 37 | 38 | **How do I know my data is secure?** 39 | Once DataMap is loaded, you can disconnect your computer from the internet and then upload your data (to your browser!) for analysis. It is hosted here as a static web page. 40 | 41 | **What browsers are supported?** 42 | Chrome, Firefox (slower to load), Edge, and Safari. 43 | 44 | **Can I use DataMap offline?** 45 | Once loaded, DataMap actually already runs offline inside your browser. You can intsall DataMap as an R package and use it from RStudio. Alternatively, you can download this repo to a folder, and turn this into a [shinylive](https://posit-dev.github.io/r-shinylive/) application, which could be used from the browser: 46 | ```{R} 47 | # first download this repo to a folder called datamap 48 | install.packages("shinylive") 49 | shinylive::export("datamap", "site") 50 | httpuv::runStaticServer("site") # app opens in browser 51 | ``` 52 | 53 | **How can I reproduce my analysis?** 54 | After getting a plot, you can go to the Code tab to export the R code, which records all your settings and can be run in RStudio to reproduce the plot. Alternatively, you can note the version of DataMap used. Later, you or other scientists can find the corresponding version of the app from our previous [releases](https://github.com/gexijin/datamap/releases), which can be downloaded and run (see above). 55 | 56 | **Limitations?** 57 | Slower in the browser when clustering 5000 rows or columns. Can take up to 2 minutes. For large datasets, install and use it as an R package. Also, we only use pheatmap package to render heatmaps. 58 | 59 | **Do you track usage?** 60 | Yes. I added this Google Analytics [script](https://github.com/gexijin/datamap/blob/main/www/google_analytics.html) to track how many times it is used. If there are more users, I will spend more time to improve it. Google Analytics does report city-level location. If you are opposed to this, please let me know. I can disable tracking. 61 | 62 | **Why did you write DataMap?** 63 | a) I love heatmaps! b) I wanted to do a vibe coding experiment. Claude.ai wrote 95% of the code. I mostly served as a product manager. See my [blog](https://www.ge-lab.org/2025/04/21/extreme-vibe-coding-the-making-of-datamap/) on how DataMap was developed. 64 | 65 | ## Preprint and citation 66 | DataMap is described in details in this preprint. Please cite it if you use it. 67 | > Ge, X. (2025). DataMap: A Portable Application for Visualizing High-Dimensional Data, [arXiv:2504.08875](https://arxiv.org/abs/2504.08875), 2025. 68 | 69 | **Dr. Xijin Ge** is a Professor at South Dakota State University. [LinkedIn](https://www.linkedin.com/in/steven-ge-ab016947/), [BlueSky.](https://bsky.app/profile/stevenge.bsky.social) 70 | 71 | -------------------------------------------------------------------------------- /app.R: -------------------------------------------------------------------------------- 1 | #' Run the DataMap Shiny application 2 | #' 3 | #' @export 4 | #' 5 | #' @return A Shiny application object 6 | #' @examples 7 | #' if (interactive()) { 8 | #' run_datamap() 9 | #' } 10 | #' 11 | 12 | source(datamap_resource("R/utilities.R")) 13 | source(datamap_resource("R/mod_file_upload.R")) 14 | source(datamap_resource("R/mod_transform.R")) 15 | source(datamap_resource("R/mod_pca.R")) 16 | source(datamap_resource("R/mod_tsne.R")) 17 | source(datamap_resource("R/mod_heatmap.R")) 18 | source(datamap_resource("R/mod_code_generation.R")) 19 | source(datamap_resource("R/main_app.R")) 20 | 21 | run_app() 22 | 23 | -------------------------------------------------------------------------------- /extdata/RNAseq_design.csv: -------------------------------------------------------------------------------- 1 | Study_Design,p53_mock_1,p53_mock_2,p53_mock_3,p53_mock_4,p53_IR_1,p53_IR_2,p53_IR_3,p53_IR_4,null_mock_1,null_mock_2,null_IR_1,null_IR_2 2 | p53,wt,wt,wt,wt,wt,wt,wt,wt,null,null,null,null 3 | Radiation,mock,mock,mock,mock,IR,IR,IR,IR,mock,mock,IR,IR 4 | -------------------------------------------------------------------------------- /extdata/RNAseq_gene_info.csv: -------------------------------------------------------------------------------- 1 | symbol,IR-mock,Apoptosis 2 | Phlda3,None,Yes 3 | Cdkn1a,None,Yes 4 | Tnfsf4,Up,Yes 5 | Gm10721,None,No 6 | Gm10719,None,No 7 | Tex15,Up,No 8 | Plb1,None,No 9 | 1700007K13Rik,None,No 10 | Grhl3,None,No 11 | Plk2,None,Yes 12 | Zfp365,None,No 13 | Psrc1,None,No 14 | Bbc3,None,Yes 15 | Gtse1,None,No 16 | Dact3,None,No 17 | Matn1,None,No 18 | Ckmt1,None,Yes 19 | Prrg4,None,No 20 | Cox6b2,None,No 21 | Inka2,None,No 22 | Ddias,None,Yes 23 | Slc19a2,None,No 24 | Robo3,None,No 25 | Scn4b,None,No 26 | Cep170b,None,No 27 | Gm9949,Down,No 28 | Hmcn2,None,No 29 | Kank3,Up,No 30 | Cd80,None,No 31 | Dcxr,None,No 32 | Sesn2,None,No 33 | Hic1,None,Yes 34 | Rhod,None,No 35 | Epha2,None,Yes 36 | Fam131a,None,No 37 | Enc1,None,No 38 | Ier5l,None,No 39 | Snai3,None,No 40 | Celf5,None,No 41 | Akap1,Up,Yes 42 | Dglucy,Down,No 43 | Plekha7,Down,No 44 | Plod2,None,No 45 | Thyn1,Up,No 46 | Rac3,None,No 47 | Dennd2c,None,No 48 | Ankrd13b,Up,No 49 | Cdc42bpg,None,No 50 | Ikbke,None,Yes 51 | Pkd1l2,None,No 52 | Abcb1b,Up,No 53 | Cfap126,None,No 54 | Ogdhl,None,No 55 | Asap3,None,No 56 | Ccng1,None,Yes 57 | Pidd1,Up,Yes 58 | Mef2b,Up,No 59 | Lpin1,Down,No 60 | Macroh2a3,Up,No 61 | Dzip1,None,No 62 | Ak1,None,No 63 | Polk,None,No 64 | Ppp1r36,None,No 65 | Gm8482,None,No 66 | Gm7451,Up,No 67 | Fas,Up,Yes 68 | Mybl1,None,No 69 | Macroh2a2,Up,No 70 | Zmat3,None,No 71 | Ckap2,Up,Yes 72 | Ddit4l,None,No 73 | Gcdh,None,No 74 | Aldh4a1,None,No 75 | Mgmt,None,Yes 76 | Bmp1,None,No 77 | Ei24,None,Yes 78 | Cpt1c,None,No 79 | Jag2,None,Yes 80 | Pmaip1,None,Yes 81 | Cad,Up,No 82 | Acad8,None,No 83 | Aen,Up,Yes 84 | ENSMUSG00000072812,None,No 85 | Ccnf,None,No 86 | Aaas,None,No 87 | Gm13854,None,No 88 | Cercam,None,No 89 | Scarf2,None,No 90 | Pcdhgc3,None,Yes 91 | Dyrk3,None,Yes 92 | Exoc4,None,No 93 | Gnb1l,Up,No 94 | Vcan,Up,No 95 | Lacc1,None,No 96 | Plxna1,None,No 97 | Atg9b,None,No 98 | Mcam,None,No 99 | Ass1,None,No 100 | Rtl10,Up,No 101 | Kifc1,None,No 102 | Ptpdc1,None,No 103 | Tnfsf8,None,No 104 | Syt12,None,No 105 | Rxylt1,None,No 106 | Spc25,None,No 107 | Bax,Up,Yes 108 | Tmem63b,Up,No 109 | Efcab8,None,No 110 | Apaf1,None,Yes 111 | Notch3,None,No 112 | Qpctl,None,No 113 | Trp53inp1,Down,Yes 114 | Ddit4,None,Yes 115 | Cdkn2b,None,No 116 | Rasd1,None,No 117 | BC034090,None,No 118 | Gm21969,None,No 119 | Dcaf4,None,No 120 | Sulf2,None,No 121 | Gria3,None,No 122 | Ctc1,None,No 123 | Hdac4,None,Yes 124 | Gm28041,Up,No 125 | Gm6786,None,No 126 | Wnt6,None,No 127 | Sp6,None,No 128 | Klhl26,None,No 129 | Zfp385a,Down,Yes 130 | Sh3yl1,Down,No 131 | Ccdc122,None,No 132 | Hmgcll1,Down,No 133 | Slc35e4,None,No 134 | Klrb1,None,No 135 | Peli3,None,Yes 136 | Rap2b,None,No 137 | Jun,None,Yes 138 | Slc2a9,Down,No 139 | Mdm2,None,Yes 140 | Kif18b,None,No 141 | Slc22a12,None,No 142 | Gpr179,None,No 143 | Sgsm2,None,No 144 | Cpne2,None,No 145 | Fosl1,Up,Yes 146 | Slc39a14,Up,No 147 | Lif,None,No 148 | Gm12895,Down,No 149 | Gm12896,Down,No 150 | Ldhb,None,No 151 | Rps27l,Up,Yes 152 | Csf1,None,No 153 | Heyl,None,No 154 | Fam216a,None,No 155 | Cby1,None,No 156 | Pomt2,Up,No 157 | Nexmif,None,No 158 | Stac2,Down,No 159 | Fam83h,None,No 160 | Gna15,Down,No 161 | Rrm2,None,No 162 | Creb3l1,None,Yes 163 | Dnm1,None,No 164 | Muc5ac,None,No 165 | Tsen54,Up,No 166 | Bhlhe41,Down,No 167 | Akr1b10,Down,No 168 | Carhsp1,None,No 169 | Bcl2l11,None,Yes 170 | Ercc5,None,No 171 | Zbtb42,Down,No 172 | Nme4,Up,No 173 | Nudcd2,Up,No 174 | Notch1,None,Yes 175 | Arhgap35,None,No 176 | Dagla,None,No 177 | Gm18959,None,No 178 | Klhl22,None,No 179 | Thumpd2,None,No 180 | Pigf,Up,No 181 | Spsb1,None,No 182 | Oxld1,None,No 183 | Ahi1,None,No 184 | Rgs12,None,No 185 | Micall2,None,No 186 | Ppm1d,None,No 187 | Palm,None,No 188 | Serpine2,None,No 189 | Nfil3,None,No 190 | Gm15185,None,No 191 | Rnf169,None,No 192 | Sac3d1,Up,No 193 | Nfyb,None,No 194 | Gm44216,None,No 195 | Il2ra,None,Yes 196 | Gne,None,No 197 | Bloc1s2,None,Yes 198 | Rap2a,None,No 199 | Clec2g,None,No 200 | Glipr1,None,No 201 | Mapkapk3,None,No 202 | Cpped1,None,No 203 | Bcl2l15,None,Yes 204 | Zfp958,None,No 205 | Efna5,None,No 206 | Gpr68,None,No 207 | Adam8,None,Yes 208 | Commd3,None,No 209 | Ston1,None,No 210 | Ppp2r5d,Up,No 211 | Itm2a,None,No 212 | Tnfrsf10b,None,Yes 213 | Dnah12,None,No 214 | Gm17981,None,No 215 | Eng,None,No 216 | Phlda1,None,Yes 217 | Scd4,None,No 218 | Trim7,Down,No 219 | Zfp874a,None,No 220 | Slc25a42,None,No 221 | Tnfrsf18,None,Yes 222 | Acot1,None,Yes 223 | St14,None,No 224 | Xrra1,None,No 225 | Pcdhgc5,None,Yes 226 | Abhd4,None,No 227 | Rarg,None,Yes 228 | Fbxw9,None,No 229 | Rbl2,None,No 230 | Mettl6,Up,No 231 | Crip2,Down,No 232 | Gpr55,None,No 233 | Homer3,None,No 234 | Arhgef9,None,No 235 | Nectin4,None,No 236 | Ttc9,None,No 237 | H2aj,None,No 238 | Snx20,None,No 239 | Clec2f,None,No 240 | Tef,None,No 241 | Irak1bp1,None,No 242 | Cuedc1,None,No 243 | Kif1c,None,No 244 | Hspa2,None,No 245 | Msh6,Up,Yes 246 | Sdc1,Down,No 247 | Zbtb10,Up,No 248 | Ephx1,Down,No 249 | Kifc5b,None,No 250 | Sspn,Down,No 251 | Kcnc3,Up,No 252 | Vopp1,None,No 253 | Dffb,None,Yes 254 | Zfp456,None,No 255 | Hpcal1,Down,No 256 | Cacna1b,None,No 257 | Gm9961,None,No 258 | Zfp688,None,No 259 | Samsn1,Up,No 260 | Anks1b,None,No 261 | Gm16071,None,No 262 | Depdc5,None,No 263 | Rnf144a,Down,No 264 | Bfsp2,None,No 265 | Espn,None,No 266 | Dkkl1,Down,Yes 267 | Csnk1g1,None,No 268 | Trim32,None,Yes 269 | Gpr132,Up,No 270 | Gss,None,No 271 | Wdr91,None,No 272 | Txlnb,None,No 273 | Slc2a4,None,No 274 | Eid3,None,No 275 | Fam110a,None,No 276 | Paqr5,None,No 277 | Foxo3,None,Yes 278 | Ptp4a3,None,No 279 | Ttll9,None,No 280 | Kifc5c-ps,None,No 281 | Chek2,None,Yes 282 | Frrs1,None,No 283 | Trib2,None,No 284 | Tbxa2r,Down,No 285 | Myo3b,Down,No 286 | Txnrd1,Up,No 287 | Igsf11,None,No 288 | Ehd4,Down,No 289 | Tbc1d2,None,No 290 | Ptpn14,None,No 291 | Erich1,None,No 292 | Sarm1,None,Yes 293 | Gm15988,None,No 294 | Clcn2,None,No 295 | Nanos1,None,No 296 | Gm6092,None,No 297 | Tnip3,None,No 298 | Myo10,None,No 299 | Fosl2,None,No 300 | Pinlyp,None,No 301 | Rgs18,None,No 302 | Celsr2,None,No 303 | Mx1,None,No 304 | Tmtc3,None,No 305 | Rapgef3,Down,Yes 306 | Tcp11l2,Down,No 307 | Avpr1b,None,No 308 | Pdrg1,None,No 309 | D630023F18Rik,None,No 310 | Tnfrsf26,Down,Yes 311 | Cecr2,None,Yes 312 | Zfyve21,None,No 313 | Cers4,None,No 314 | Scd2,None,No 315 | Slc35d1,None,No 316 | ENSMUSG00000107877,None,No 317 | Gm45209,None,No 318 | Fhip1b,None,No 319 | Trio,None,No 320 | Gask1b,None,No 321 | Upf3b,None,No 322 | Gm10819,None,No 323 | Mmp15,None,No 324 | Fbxo34,None,No 325 | Syne3,Down,No 326 | Bscl2,None,No 327 | Sytl2,None,No 328 | 1700073E17Rik,None,No 329 | Zfp692,None,No 330 | Gm13285,None,No 331 | Tmem108,Down,No 332 | Rab40c,None,No 333 | P2rx7,None,Yes 334 | Utrn,Down,No 335 | Wrap73,None,No 336 | Glipr2,None,No 337 | B3gnt8,Down,No 338 | Gm10309,Down,No 339 | Mfge8,None,No 340 | Fzd5,None,Yes 341 | Stra8,None,No 342 | Gabpb1,None,No 343 | Casp7,None,Yes 344 | Dstn,None,No 345 | Fuca1,None,No 346 | Fam53b,None,No 347 | Map3k9,Down,Yes 348 | Arhgap21,None,No 349 | Ttc28,Down,No 350 | Tm7sf3,None,No 351 | Ulk1,None,No 352 | Tert,None,Yes 353 | Mturn,Down,No 354 | Mxd4,Down,No 355 | Gm8818,None,No 356 | Ypel3,Down,Yes 357 | Plekho2,None,Yes 358 | Rhbdf2,Down,No 359 | Cep290,None,No 360 | Vim,Down,No 361 | Eola1,None,No 362 | Ldlrap1,None,No 363 | Gm16106,None,No 364 | Arhgef18,Down,No 365 | Zfp219,None,No 366 | Nrxn2,Down,No 367 | Fam13a,Down,No 368 | Bach2,Down,No 369 | Zfp703,None,No 370 | Fam168a,Down,No 371 | Scml4,Down,No 372 | Gm15602,None,No 373 | Pdk2,Down,Yes 374 | Zcchc24,Down,No 375 | Tmsb15b2,Down,No 376 | Lamc1,Down,No 377 | Papln,Down,No 378 | Capg,Down,No 379 | Kif21b,Down,No 380 | Dgka,Down,No 381 | ENSMUSG00000074497,Down,No 382 | Mafa,None,No 383 | Dab2ip,None,Yes 384 | Cd55,Down,No 385 | Vmac,None,No 386 | Ddx25,Down,No 387 | Trpm2,None,No 388 | Wipi1,None,No 389 | Gm3200,None,No 390 | Nfic,None,No 391 | Rell1,Down,No 392 | Ust,None,No 393 | Aanat,Down,No 394 | Zbtb7b,None,No 395 | Ngfr,Down,Yes 396 | App,None,Yes 397 | Pear1,Down,No 398 | Vasn,Down,No 399 | Fam214a,Down,No 400 | Tmem71,Down,No 401 | Orai2,Down,No 402 | Akap12,Down,Yes 403 | Tsku,Down,No 404 | Btg2,Down,Yes 405 | Prkce,Down,Yes 406 | Sptbn5,Down,No 407 | Lca5,Down,No 408 | Slc2a8,Down,No 409 | S100a11,None,No 410 | Pdzd2,Down,No 411 | Nfatc2,None,No 412 | Psd3,Down,No 413 | Sfxn3,Down,No 414 | Ypel2,Down,No 415 | Rb1,None,Yes 416 | Ctse,Down,No 417 | Pitpnc1,Down,No 418 | Cd93,Down,No 419 | Trp53i11,Down,No 420 | Zc3h6,None,No 421 | Pde2a,Down,No 422 | Cast,Down,Yes 423 | Cd55b,Down,No 424 | Tcn2,None,No 425 | S100a10,Down,No 426 | Etv5,None,No 427 | Coro7,Down,No 428 | Gm26528,Down,No 429 | Rps6ka5,Down,No 430 | Brdt,Down,No 431 | Esr1,Down,Yes 432 | Mgst2,Down,No 433 | Eps8,Down,No 434 | Cep250,Down,No 435 | Nlrx1,None,No 436 | Runx1,Down,No 437 | Plec,Down,No 438 | Itga6,Down,Yes 439 | Rgl1,None,No 440 | Stard10,Down,No 441 | Calhm2,Down,Yes 442 | Rnasel,Down,No 443 | Hmgb1-ps8,Down,No 444 | Scand1,None,No 445 | Fmo5,Down,No 446 | Chst14,Down,No 447 | Prxl2c,Down,No 448 | Dck,Down,No 449 | Gtf2ird1,None,No 450 | Sh2d3c,Down,No 451 | Calcoco1,Down,No 452 | Trafd1,Down,No 453 | Tex9,Down,No 454 | Svil,Down,No 455 | Gab3,Down,No 456 | Fam149b,Down,No 457 | Fam234a,Down,No 458 | Pstpip1,Down,No 459 | Baz2b,Down,No 460 | Sgms1,Down,Yes 461 | Tom1l2,Down,No 462 | Ptpre,Down,No 463 | Abtb1,Down,No 464 | Atp13a2,None,No 465 | Maml3,Down,No 466 | Thra,Down,Yes 467 | Tsc22d1,None,No 468 | -------------------------------------------------------------------------------- /extdata/countries.csv: -------------------------------------------------------------------------------- 1 | country,Area,Birth Rate,Death Rate,Infant Death,Internet Users,Life Span,Maternal Death,Net Migration,Population,Pop Growth,Continent 2 | Russia,7.23295146,11.87,13.83,7.08,7.611223954,70.16,34,1.69,8.153724253,-0.03,Europe 3 | Japan,5.57739413,8.07,9.38,2.13,7.996432862,84.46,5,0,8.104157127,-0.13,Asia 4 | Germany,5.552694979,8.42,11.29,3.46,7.813747736,80.44,7,1.06,7.908467245,-0.18,Europe 5 | South Africa,6.086035769,18.94,17.49,41.61,6.645422269,49.56,300,-6.27,7.684626769,-0.48,Africa 6 | Ukraine,5.780713254,9.41,15.72,8.1,6.890421019,69.14,32,-0.06,7.646319536,-0.64,Europe 7 | Poland,5.495107048,9.77,10.37,6.19,7.351255034,76.65,5,-0.47,7.583723228,-0.11,Europe 8 | Romania,5.377289855,9.27,11.88,10.16,6.891370175,74.69,27,-0.24,7.337057148,-0.29,Europe 9 | Syria,5.26759408,22.76,6.51,15.79,6.650210355,68.41,70,-113.51,7.254104106,-9.73,Asia 10 | Cuba,5.044774874,9.9,7.64,4.7,6.205745541,78.22,73,-3.64,7.043254222,-0.14,N. America 11 | Hungary,4.968613684,9.26,12.72,5.09,6.790707287,75.46,21,1.34,6.996473495,-0.21,Europe 12 | Belarus,5.317227349,10.86,13.51,3.64,6.422097163,72.15,4,0.78,6.982635616,-0.19,Europe 13 | Serbia,4.889155979,9.13,13.71,6.16,6.613524703,75.02,12,0,6.857921049,-0.46,Europe 14 | Bulgaria,5.0448493,8.92,14.3,15.08,6.530839779,74.33,11,-2.89,6.840401967,-0.83,Europe 15 | Georgia,4.843232778,12.93,10.77,16.68,6.113943352,75.72,67,-3.25,6.693364593,-0.11,Europe 16 | Croatia,4.75277039,9.49,12.13,5.87,6.349083169,76.41,17,1.43,6.650359402,-0.12,Europe 17 | Bosnia,4.709244513,8.89,9.64,5.84,6.152899596,76.33,8,-0.38,6.587895305,-0.11,Europe 18 | Puerto Rico,4.139564266,10.9,8.51,7.73,6,79.09,20,-8.93,6.558816171,-0.65,N. America 19 | Moldova,4.529571503,12.21,12.6,12.93,6.124830149,70.12,41,-9.8,6.554281715,-1.02,Europe 20 | Lithuania,4.814913181,9.36,11.55,6,6.293141483,75.98,8,-0.73,6.544779456,-0.29,Europe 21 | Armenia,4.473384771,13.92,9.3,13.97,5.318480725,74.12,30,-5.88,6.485810973,-0.13,Europe 22 | -------------------------------------------------------------------------------- /extdata/iris.csv: -------------------------------------------------------------------------------- 1 | Sepal.Length,Sepal.Width,Petal.Length,Petal.Width,Species 2 | 5.1,3.5,1.4,0.2,setosa 3 | 4.9,3,1.4,0.2,setosa 4 | 4.7,3.2,1.3,0.2,setosa 5 | 4.6,3.1,1.5,0.2,setosa 6 | 5,3.6,1.4,0.2,setosa 7 | 5.4,3.9,1.7,0.4,setosa 8 | 4.6,3.4,1.4,0.3,setosa 9 | 5,3.4,1.5,0.2,setosa 10 | 4.4,2.9,1.4,0.2,setosa 11 | 4.9,3.1,1.5,0.1,setosa 12 | 5.4,3.7,1.5,0.2,setosa 13 | 4.8,3.4,1.6,0.2,setosa 14 | 4.8,3,1.4,0.1,setosa 15 | 4.3,3,1.1,0.1,setosa 16 | 5.8,4,1.2,0.2,setosa 17 | 5.7,4.4,1.5,0.4,setosa 18 | 5.4,3.9,1.3,0.4,setosa 19 | 5.1,3.5,1.4,0.3,setosa 20 | 5.7,3.8,1.7,0.3,setosa 21 | 5.1,3.8,1.5,0.3,setosa 22 | 5.4,3.4,1.7,0.2,setosa 23 | 5.1,3.7,1.5,0.4,setosa 24 | 4.6,3.6,1,0.2,setosa 25 | 5.1,3.3,1.7,0.5,setosa 26 | 4.8,3.4,1.9,0.2,setosa 27 | 5,3,1.6,0.2,setosa 28 | 5,3.4,1.6,0.4,setosa 29 | 5.2,3.5,1.5,0.2,setosa 30 | 5.2,3.4,1.4,0.2,setosa 31 | 4.7,3.2,1.6,0.2,setosa 32 | 4.8,3.1,1.6,0.2,setosa 33 | 5.4,3.4,1.5,0.4,setosa 34 | 5.2,4.1,1.5,0.1,setosa 35 | 5.5,4.2,1.4,0.2,setosa 36 | 4.9,3.1,1.5,0.2,setosa 37 | 5,3.2,1.2,0.2,setosa 38 | 5.5,3.5,1.3,0.2,setosa 39 | 4.9,3.6,1.4,0.1,setosa 40 | 4.4,3,1.3,0.2,setosa 41 | 5.1,3.4,1.5,0.2,setosa 42 | 5,3.5,1.3,0.3,setosa 43 | 4.5,2.3,1.3,0.3,setosa 44 | 4.4,3.2,1.3,0.2,setosa 45 | 5,3.5,1.6,0.6,setosa 46 | 5.1,3.8,1.9,0.4,setosa 47 | 4.8,3,1.4,0.3,setosa 48 | 5.1,3.8,1.6,0.2,setosa 49 | 4.6,3.2,1.4,0.2,setosa 50 | 5.3,3.7,1.5,0.2,setosa 51 | 5,3.3,1.4,0.2,setosa 52 | 7,3.2,4.7,1.4,versicolor 53 | 6.4,3.2,4.5,1.5,versicolor 54 | 6.9,3.1,4.9,1.5,versicolor 55 | 5.5,2.3,4,1.3,versicolor 56 | 6.5,2.8,4.6,1.5,versicolor 57 | 5.7,2.8,4.5,1.3,versicolor 58 | 6.3,3.3,4.7,1.6,versicolor 59 | 4.9,2.4,3.3,1,versicolor 60 | 6.6,2.9,4.6,1.3,versicolor 61 | 5.2,2.7,3.9,1.4,versicolor 62 | 5,2,3.5,1,versicolor 63 | 5.9,3,4.2,1.5,versicolor 64 | 6,2.2,4,1,versicolor 65 | 6.1,2.9,4.7,1.4,versicolor 66 | 5.6,2.9,3.6,1.3,versicolor 67 | 6.7,3.1,4.4,1.4,versicolor 68 | 5.6,3,4.5,1.5,versicolor 69 | 5.8,2.7,4.1,1,versicolor 70 | 6.2,2.2,4.5,1.5,versicolor 71 | 5.6,2.5,3.9,1.1,versicolor 72 | 5.9,3.2,4.8,1.8,versicolor 73 | 6.1,2.8,4,1.3,versicolor 74 | 6.3,2.5,4.9,1.5,versicolor 75 | 6.1,2.8,4.7,1.2,versicolor 76 | 6.4,2.9,4.3,1.3,versicolor 77 | 6.6,3,4.4,1.4,versicolor 78 | 6.8,2.8,4.8,1.4,versicolor 79 | 6.7,3,5,1.7,versicolor 80 | 6,2.9,4.5,1.5,versicolor 81 | 5.7,2.6,3.5,1,versicolor 82 | 5.5,2.4,3.8,1.1,versicolor 83 | 5.5,2.4,3.7,1,versicolor 84 | 5.8,2.7,3.9,1.2,versicolor 85 | 6,2.7,5.1,1.6,versicolor 86 | 5.4,3,4.5,1.5,versicolor 87 | 6,3.4,4.5,1.6,versicolor 88 | 6.7,3.1,4.7,1.5,versicolor 89 | 6.3,2.3,4.4,1.3,versicolor 90 | 5.6,3,4.1,1.3,versicolor 91 | 5.5,2.5,4,1.3,versicolor 92 | 5.5,2.6,4.4,1.2,versicolor 93 | 6.1,3,4.6,1.4,versicolor 94 | 5.8,2.6,4,1.2,versicolor 95 | 5,2.3,3.3,1,versicolor 96 | 5.6,2.7,4.2,1.3,versicolor 97 | 5.7,3,4.2,1.2,versicolor 98 | 5.7,2.9,4.2,1.3,versicolor 99 | 6.2,2.9,4.3,1.3,versicolor 100 | 5.1,2.5,3,1.1,versicolor 101 | 5.7,2.8,4.1,1.3,versicolor 102 | 6.3,3.3,6,2.5,virginica 103 | 5.8,2.7,5.1,1.9,virginica 104 | 7.1,3,5.9,2.1,virginica 105 | 6.3,2.9,5.6,1.8,virginica 106 | 6.5,3,5.8,2.2,virginica 107 | 7.6,3,6.6,2.1,virginica 108 | 4.9,2.5,4.5,1.7,virginica 109 | 7.3,2.9,6.3,1.8,virginica 110 | 6.7,2.5,5.8,1.8,virginica 111 | 7.2,3.6,6.1,2.5,virginica 112 | 6.5,3.2,5.1,2,virginica 113 | 6.4,2.7,5.3,1.9,virginica 114 | 6.8,3,5.5,2.1,virginica 115 | 5.7,2.5,5,2,virginica 116 | 5.8,2.8,5.1,2.4,virginica 117 | 6.4,3.2,5.3,2.3,virginica 118 | 6.5,3,5.5,1.8,virginica 119 | 7.7,3.8,6.7,2.2,virginica 120 | 7.7,2.6,6.9,2.3,virginica 121 | 6,2.2,5,1.5,virginica 122 | 6.9,3.2,5.7,2.3,virginica 123 | 5.6,2.8,4.9,2,virginica 124 | 7.7,2.8,6.7,2,virginica 125 | 6.3,2.7,4.9,1.8,virginica 126 | 6.7,3.3,5.7,2.1,virginica 127 | 7.2,3.2,6,1.8,virginica 128 | 6.2,2.8,4.8,1.8,virginica 129 | 6.1,3,4.9,1.8,virginica 130 | 6.4,2.8,5.6,2.1,virginica 131 | 7.2,3,5.8,1.6,virginica 132 | 7.4,2.8,6.1,1.9,virginica 133 | 7.9,3.8,6.4,2,virginica 134 | 6.4,2.8,5.6,2.2,virginica 135 | 6.3,2.8,5.1,1.5,virginica 136 | 6.1,2.6,5.6,1.4,virginica 137 | 7.7,3,6.1,2.3,virginica 138 | 6.3,3.4,5.6,2.4,virginica 139 | 6.4,3.1,5.5,1.8,virginica 140 | 6,3,4.8,1.8,virginica 141 | 6.9,3.1,5.4,2.1,virginica 142 | 6.7,3.1,5.6,2.4,virginica 143 | 6.9,3.1,5.1,2.3,virginica 144 | 5.8,2.7,5.1,1.9,virginica 145 | 6.8,3.2,5.9,2.3,virginica 146 | 6.7,3.3,5.7,2.5,virginica 147 | 6.7,3,5.2,2.3,virginica 148 | 6.3,2.5,5,1.9,virginica 149 | 6.5,3,5.2,2,virginica 150 | 6.2,3.4,5.4,2.3,virginica 151 | 5.9,3,5.1,1.8,virginica 152 | -------------------------------------------------------------------------------- /extdata/iris_column_annot.csv: -------------------------------------------------------------------------------- 1 | ,Sepal.Length,Sepal.Width,Petal.Length,Petal.Width 2 | Object,Sepal,Sepal,Petal,Petal 3 | Type,Length,Width,Length,Width 4 | -------------------------------------------------------------------------------- /inst/extdata/RNAseq_design.csv: -------------------------------------------------------------------------------- 1 | Study_Design,p53_mock_1,p53_mock_2,p53_mock_3,p53_mock_4,p53_IR_1,p53_IR_2,p53_IR_3,p53_IR_4,null_mock_1,null_mock_2,null_IR_1,null_IR_2 2 | p53,wt,wt,wt,wt,wt,wt,wt,wt,null,null,null,null 3 | Radiation,mock,mock,mock,mock,IR,IR,IR,IR,mock,mock,IR,IR 4 | -------------------------------------------------------------------------------- /inst/extdata/RNAseq_gene_info.csv: -------------------------------------------------------------------------------- 1 | symbol,IR-mock,Apoptosis 2 | Phlda3,None,Yes 3 | Cdkn1a,None,Yes 4 | Tnfsf4,Up,Yes 5 | Gm10721,None,No 6 | Gm10719,None,No 7 | Tex15,Up,No 8 | Plb1,None,No 9 | 1700007K13Rik,None,No 10 | Grhl3,None,No 11 | Plk2,None,Yes 12 | Zfp365,None,No 13 | Psrc1,None,No 14 | Bbc3,None,Yes 15 | Gtse1,None,No 16 | Dact3,None,No 17 | Matn1,None,No 18 | Ckmt1,None,Yes 19 | Prrg4,None,No 20 | Cox6b2,None,No 21 | Inka2,None,No 22 | Ddias,None,Yes 23 | Slc19a2,None,No 24 | Robo3,None,No 25 | Scn4b,None,No 26 | Cep170b,None,No 27 | Gm9949,Down,No 28 | Hmcn2,None,No 29 | Kank3,Up,No 30 | Cd80,None,No 31 | Dcxr,None,No 32 | Sesn2,None,No 33 | Hic1,None,Yes 34 | Rhod,None,No 35 | Epha2,None,Yes 36 | Fam131a,None,No 37 | Enc1,None,No 38 | Ier5l,None,No 39 | Snai3,None,No 40 | Celf5,None,No 41 | Akap1,Up,Yes 42 | Dglucy,Down,No 43 | Plekha7,Down,No 44 | Plod2,None,No 45 | Thyn1,Up,No 46 | Rac3,None,No 47 | Dennd2c,None,No 48 | Ankrd13b,Up,No 49 | Cdc42bpg,None,No 50 | Ikbke,None,Yes 51 | Pkd1l2,None,No 52 | Abcb1b,Up,No 53 | Cfap126,None,No 54 | Ogdhl,None,No 55 | Asap3,None,No 56 | Ccng1,None,Yes 57 | Pidd1,Up,Yes 58 | Mef2b,Up,No 59 | Lpin1,Down,No 60 | Macroh2a3,Up,No 61 | Dzip1,None,No 62 | Ak1,None,No 63 | Polk,None,No 64 | Ppp1r36,None,No 65 | Gm8482,None,No 66 | Gm7451,Up,No 67 | Fas,Up,Yes 68 | Mybl1,None,No 69 | Macroh2a2,Up,No 70 | Zmat3,None,No 71 | Ckap2,Up,Yes 72 | Ddit4l,None,No 73 | Gcdh,None,No 74 | Aldh4a1,None,No 75 | Mgmt,None,Yes 76 | Bmp1,None,No 77 | Ei24,None,Yes 78 | Cpt1c,None,No 79 | Jag2,None,Yes 80 | Pmaip1,None,Yes 81 | Cad,Up,No 82 | Acad8,None,No 83 | Aen,Up,Yes 84 | ENSMUSG00000072812,None,No 85 | Ccnf,None,No 86 | Aaas,None,No 87 | Gm13854,None,No 88 | Cercam,None,No 89 | Scarf2,None,No 90 | Pcdhgc3,None,Yes 91 | Dyrk3,None,Yes 92 | Exoc4,None,No 93 | Gnb1l,Up,No 94 | Vcan,Up,No 95 | Lacc1,None,No 96 | Plxna1,None,No 97 | Atg9b,None,No 98 | Mcam,None,No 99 | Ass1,None,No 100 | Rtl10,Up,No 101 | Kifc1,None,No 102 | Ptpdc1,None,No 103 | Tnfsf8,None,No 104 | Syt12,None,No 105 | Rxylt1,None,No 106 | Spc25,None,No 107 | Bax,Up,Yes 108 | Tmem63b,Up,No 109 | Efcab8,None,No 110 | Apaf1,None,Yes 111 | Notch3,None,No 112 | Qpctl,None,No 113 | Trp53inp1,Down,Yes 114 | Ddit4,None,Yes 115 | Cdkn2b,None,No 116 | Rasd1,None,No 117 | BC034090,None,No 118 | Gm21969,None,No 119 | Dcaf4,None,No 120 | Sulf2,None,No 121 | Gria3,None,No 122 | Ctc1,None,No 123 | Hdac4,None,Yes 124 | Gm28041,Up,No 125 | Gm6786,None,No 126 | Wnt6,None,No 127 | Sp6,None,No 128 | Klhl26,None,No 129 | Zfp385a,Down,Yes 130 | Sh3yl1,Down,No 131 | Ccdc122,None,No 132 | Hmgcll1,Down,No 133 | Slc35e4,None,No 134 | Klrb1,None,No 135 | Peli3,None,Yes 136 | Rap2b,None,No 137 | Jun,None,Yes 138 | Slc2a9,Down,No 139 | Mdm2,None,Yes 140 | Kif18b,None,No 141 | Slc22a12,None,No 142 | Gpr179,None,No 143 | Sgsm2,None,No 144 | Cpne2,None,No 145 | Fosl1,Up,Yes 146 | Slc39a14,Up,No 147 | Lif,None,No 148 | Gm12895,Down,No 149 | Gm12896,Down,No 150 | Ldhb,None,No 151 | Rps27l,Up,Yes 152 | Csf1,None,No 153 | Heyl,None,No 154 | Fam216a,None,No 155 | Cby1,None,No 156 | Pomt2,Up,No 157 | Nexmif,None,No 158 | Stac2,Down,No 159 | Fam83h,None,No 160 | Gna15,Down,No 161 | Rrm2,None,No 162 | Creb3l1,None,Yes 163 | Dnm1,None,No 164 | Muc5ac,None,No 165 | Tsen54,Up,No 166 | Bhlhe41,Down,No 167 | Akr1b10,Down,No 168 | Carhsp1,None,No 169 | Bcl2l11,None,Yes 170 | Ercc5,None,No 171 | Zbtb42,Down,No 172 | Nme4,Up,No 173 | Nudcd2,Up,No 174 | Notch1,None,Yes 175 | Arhgap35,None,No 176 | Dagla,None,No 177 | Gm18959,None,No 178 | Klhl22,None,No 179 | Thumpd2,None,No 180 | Pigf,Up,No 181 | Spsb1,None,No 182 | Oxld1,None,No 183 | Ahi1,None,No 184 | Rgs12,None,No 185 | Micall2,None,No 186 | Ppm1d,None,No 187 | Palm,None,No 188 | Serpine2,None,No 189 | Nfil3,None,No 190 | Gm15185,None,No 191 | Rnf169,None,No 192 | Sac3d1,Up,No 193 | Nfyb,None,No 194 | Gm44216,None,No 195 | Il2ra,None,Yes 196 | Gne,None,No 197 | Bloc1s2,None,Yes 198 | Rap2a,None,No 199 | Clec2g,None,No 200 | Glipr1,None,No 201 | Mapkapk3,None,No 202 | Cpped1,None,No 203 | Bcl2l15,None,Yes 204 | Zfp958,None,No 205 | Efna5,None,No 206 | Gpr68,None,No 207 | Adam8,None,Yes 208 | Commd3,None,No 209 | Ston1,None,No 210 | Ppp2r5d,Up,No 211 | Itm2a,None,No 212 | Tnfrsf10b,None,Yes 213 | Dnah12,None,No 214 | Gm17981,None,No 215 | Eng,None,No 216 | Phlda1,None,Yes 217 | Scd4,None,No 218 | Trim7,Down,No 219 | Zfp874a,None,No 220 | Slc25a42,None,No 221 | Tnfrsf18,None,Yes 222 | Acot1,None,Yes 223 | St14,None,No 224 | Xrra1,None,No 225 | Pcdhgc5,None,Yes 226 | Abhd4,None,No 227 | Rarg,None,Yes 228 | Fbxw9,None,No 229 | Rbl2,None,No 230 | Mettl6,Up,No 231 | Crip2,Down,No 232 | Gpr55,None,No 233 | Homer3,None,No 234 | Arhgef9,None,No 235 | Nectin4,None,No 236 | Ttc9,None,No 237 | H2aj,None,No 238 | Snx20,None,No 239 | Clec2f,None,No 240 | Tef,None,No 241 | Irak1bp1,None,No 242 | Cuedc1,None,No 243 | Kif1c,None,No 244 | Hspa2,None,No 245 | Msh6,Up,Yes 246 | Sdc1,Down,No 247 | Zbtb10,Up,No 248 | Ephx1,Down,No 249 | Kifc5b,None,No 250 | Sspn,Down,No 251 | Kcnc3,Up,No 252 | Vopp1,None,No 253 | Dffb,None,Yes 254 | Zfp456,None,No 255 | Hpcal1,Down,No 256 | Cacna1b,None,No 257 | Gm9961,None,No 258 | Zfp688,None,No 259 | Samsn1,Up,No 260 | Anks1b,None,No 261 | Gm16071,None,No 262 | Depdc5,None,No 263 | Rnf144a,Down,No 264 | Bfsp2,None,No 265 | Espn,None,No 266 | Dkkl1,Down,Yes 267 | Csnk1g1,None,No 268 | Trim32,None,Yes 269 | Gpr132,Up,No 270 | Gss,None,No 271 | Wdr91,None,No 272 | Txlnb,None,No 273 | Slc2a4,None,No 274 | Eid3,None,No 275 | Fam110a,None,No 276 | Paqr5,None,No 277 | Foxo3,None,Yes 278 | Ptp4a3,None,No 279 | Ttll9,None,No 280 | Kifc5c-ps,None,No 281 | Chek2,None,Yes 282 | Frrs1,None,No 283 | Trib2,None,No 284 | Tbxa2r,Down,No 285 | Myo3b,Down,No 286 | Txnrd1,Up,No 287 | Igsf11,None,No 288 | Ehd4,Down,No 289 | Tbc1d2,None,No 290 | Ptpn14,None,No 291 | Erich1,None,No 292 | Sarm1,None,Yes 293 | Gm15988,None,No 294 | Clcn2,None,No 295 | Nanos1,None,No 296 | Gm6092,None,No 297 | Tnip3,None,No 298 | Myo10,None,No 299 | Fosl2,None,No 300 | Pinlyp,None,No 301 | Rgs18,None,No 302 | Celsr2,None,No 303 | Mx1,None,No 304 | Tmtc3,None,No 305 | Rapgef3,Down,Yes 306 | Tcp11l2,Down,No 307 | Avpr1b,None,No 308 | Pdrg1,None,No 309 | D630023F18Rik,None,No 310 | Tnfrsf26,Down,Yes 311 | Cecr2,None,Yes 312 | Zfyve21,None,No 313 | Cers4,None,No 314 | Scd2,None,No 315 | Slc35d1,None,No 316 | ENSMUSG00000107877,None,No 317 | Gm45209,None,No 318 | Fhip1b,None,No 319 | Trio,None,No 320 | Gask1b,None,No 321 | Upf3b,None,No 322 | Gm10819,None,No 323 | Mmp15,None,No 324 | Fbxo34,None,No 325 | Syne3,Down,No 326 | Bscl2,None,No 327 | Sytl2,None,No 328 | 1700073E17Rik,None,No 329 | Zfp692,None,No 330 | Gm13285,None,No 331 | Tmem108,Down,No 332 | Rab40c,None,No 333 | P2rx7,None,Yes 334 | Utrn,Down,No 335 | Wrap73,None,No 336 | Glipr2,None,No 337 | B3gnt8,Down,No 338 | Gm10309,Down,No 339 | Mfge8,None,No 340 | Fzd5,None,Yes 341 | Stra8,None,No 342 | Gabpb1,None,No 343 | Casp7,None,Yes 344 | Dstn,None,No 345 | Fuca1,None,No 346 | Fam53b,None,No 347 | Map3k9,Down,Yes 348 | Arhgap21,None,No 349 | Ttc28,Down,No 350 | Tm7sf3,None,No 351 | Ulk1,None,No 352 | Tert,None,Yes 353 | Mturn,Down,No 354 | Mxd4,Down,No 355 | Gm8818,None,No 356 | Ypel3,Down,Yes 357 | Plekho2,None,Yes 358 | Rhbdf2,Down,No 359 | Cep290,None,No 360 | Vim,Down,No 361 | Eola1,None,No 362 | Ldlrap1,None,No 363 | Gm16106,None,No 364 | Arhgef18,Down,No 365 | Zfp219,None,No 366 | Nrxn2,Down,No 367 | Fam13a,Down,No 368 | Bach2,Down,No 369 | Zfp703,None,No 370 | Fam168a,Down,No 371 | Scml4,Down,No 372 | Gm15602,None,No 373 | Pdk2,Down,Yes 374 | Zcchc24,Down,No 375 | Tmsb15b2,Down,No 376 | Lamc1,Down,No 377 | Papln,Down,No 378 | Capg,Down,No 379 | Kif21b,Down,No 380 | Dgka,Down,No 381 | ENSMUSG00000074497,Down,No 382 | Mafa,None,No 383 | Dab2ip,None,Yes 384 | Cd55,Down,No 385 | Vmac,None,No 386 | Ddx25,Down,No 387 | Trpm2,None,No 388 | Wipi1,None,No 389 | Gm3200,None,No 390 | Nfic,None,No 391 | Rell1,Down,No 392 | Ust,None,No 393 | Aanat,Down,No 394 | Zbtb7b,None,No 395 | Ngfr,Down,Yes 396 | App,None,Yes 397 | Pear1,Down,No 398 | Vasn,Down,No 399 | Fam214a,Down,No 400 | Tmem71,Down,No 401 | Orai2,Down,No 402 | Akap12,Down,Yes 403 | Tsku,Down,No 404 | Btg2,Down,Yes 405 | Prkce,Down,Yes 406 | Sptbn5,Down,No 407 | Lca5,Down,No 408 | Slc2a8,Down,No 409 | S100a11,None,No 410 | Pdzd2,Down,No 411 | Nfatc2,None,No 412 | Psd3,Down,No 413 | Sfxn3,Down,No 414 | Ypel2,Down,No 415 | Rb1,None,Yes 416 | Ctse,Down,No 417 | Pitpnc1,Down,No 418 | Cd93,Down,No 419 | Trp53i11,Down,No 420 | Zc3h6,None,No 421 | Pde2a,Down,No 422 | Cast,Down,Yes 423 | Cd55b,Down,No 424 | Tcn2,None,No 425 | S100a10,Down,No 426 | Etv5,None,No 427 | Coro7,Down,No 428 | Gm26528,Down,No 429 | Rps6ka5,Down,No 430 | Brdt,Down,No 431 | Esr1,Down,Yes 432 | Mgst2,Down,No 433 | Eps8,Down,No 434 | Cep250,Down,No 435 | Nlrx1,None,No 436 | Runx1,Down,No 437 | Plec,Down,No 438 | Itga6,Down,Yes 439 | Rgl1,None,No 440 | Stard10,Down,No 441 | Calhm2,Down,Yes 442 | Rnasel,Down,No 443 | Hmgb1-ps8,Down,No 444 | Scand1,None,No 445 | Fmo5,Down,No 446 | Chst14,Down,No 447 | Prxl2c,Down,No 448 | Dck,Down,No 449 | Gtf2ird1,None,No 450 | Sh2d3c,Down,No 451 | Calcoco1,Down,No 452 | Trafd1,Down,No 453 | Tex9,Down,No 454 | Svil,Down,No 455 | Gab3,Down,No 456 | Fam149b,Down,No 457 | Fam234a,Down,No 458 | Pstpip1,Down,No 459 | Baz2b,Down,No 460 | Sgms1,Down,Yes 461 | Tom1l2,Down,No 462 | Ptpre,Down,No 463 | Abtb1,Down,No 464 | Atp13a2,None,No 465 | Maml3,Down,No 466 | Thra,Down,Yes 467 | Tsc22d1,None,No 468 | -------------------------------------------------------------------------------- /inst/extdata/countries.csv: -------------------------------------------------------------------------------- 1 | country,Area,Birth Rate,Death Rate,Infant Death,Internet Users,Life Span,Maternal Death,Net Migration,Population,Pop Growth,Continent 2 | Russia,7.23295146,11.87,13.83,7.08,7.611223954,70.16,34,1.69,8.153724253,-0.03,Europe 3 | Japan,5.57739413,8.07,9.38,2.13,7.996432862,84.46,5,0,8.104157127,-0.13,Asia 4 | Germany,5.552694979,8.42,11.29,3.46,7.813747736,80.44,7,1.06,7.908467245,-0.18,Europe 5 | South Africa,6.086035769,18.94,17.49,41.61,6.645422269,49.56,300,-6.27,7.684626769,-0.48,Africa 6 | Ukraine,5.780713254,9.41,15.72,8.1,6.890421019,69.14,32,-0.06,7.646319536,-0.64,Europe 7 | Poland,5.495107048,9.77,10.37,6.19,7.351255034,76.65,5,-0.47,7.583723228,-0.11,Europe 8 | Romania,5.377289855,9.27,11.88,10.16,6.891370175,74.69,27,-0.24,7.337057148,-0.29,Europe 9 | Syria,5.26759408,22.76,6.51,15.79,6.650210355,68.41,70,-113.51,7.254104106,-9.73,Asia 10 | Cuba,5.044774874,9.9,7.64,4.7,6.205745541,78.22,73,-3.64,7.043254222,-0.14,N. America 11 | Hungary,4.968613684,9.26,12.72,5.09,6.790707287,75.46,21,1.34,6.996473495,-0.21,Europe 12 | Belarus,5.317227349,10.86,13.51,3.64,6.422097163,72.15,4,0.78,6.982635616,-0.19,Europe 13 | Serbia,4.889155979,9.13,13.71,6.16,6.613524703,75.02,12,0,6.857921049,-0.46,Europe 14 | Bulgaria,5.0448493,8.92,14.3,15.08,6.530839779,74.33,11,-2.89,6.840401967,-0.83,Europe 15 | Georgia,4.843232778,12.93,10.77,16.68,6.113943352,75.72,67,-3.25,6.693364593,-0.11,Europe 16 | Croatia,4.75277039,9.49,12.13,5.87,6.349083169,76.41,17,1.43,6.650359402,-0.12,Europe 17 | Bosnia,4.709244513,8.89,9.64,5.84,6.152899596,76.33,8,-0.38,6.587895305,-0.11,Europe 18 | Puerto Rico,4.139564266,10.9,8.51,7.73,6,79.09,20,-8.93,6.558816171,-0.65,N. America 19 | Moldova,4.529571503,12.21,12.6,12.93,6.124830149,70.12,41,-9.8,6.554281715,-1.02,Europe 20 | Lithuania,4.814913181,9.36,11.55,6,6.293141483,75.98,8,-0.73,6.544779456,-0.29,Europe 21 | Armenia,4.473384771,13.92,9.3,13.97,5.318480725,74.12,30,-5.88,6.485810973,-0.13,Europe 22 | -------------------------------------------------------------------------------- /inst/extdata/iris.csv: -------------------------------------------------------------------------------- 1 | Sepal.Length,Sepal.Width,Petal.Length,Petal.Width,Species 2 | 5.1,3.5,1.4,0.2,setosa 3 | 4.9,3,1.4,0.2,setosa 4 | 4.7,3.2,1.3,0.2,setosa 5 | 4.6,3.1,1.5,0.2,setosa 6 | 5,3.6,1.4,0.2,setosa 7 | 5.4,3.9,1.7,0.4,setosa 8 | 4.6,3.4,1.4,0.3,setosa 9 | 5,3.4,1.5,0.2,setosa 10 | 4.4,2.9,1.4,0.2,setosa 11 | 4.9,3.1,1.5,0.1,setosa 12 | 5.4,3.7,1.5,0.2,setosa 13 | 4.8,3.4,1.6,0.2,setosa 14 | 4.8,3,1.4,0.1,setosa 15 | 4.3,3,1.1,0.1,setosa 16 | 5.8,4,1.2,0.2,setosa 17 | 5.7,4.4,1.5,0.4,setosa 18 | 5.4,3.9,1.3,0.4,setosa 19 | 5.1,3.5,1.4,0.3,setosa 20 | 5.7,3.8,1.7,0.3,setosa 21 | 5.1,3.8,1.5,0.3,setosa 22 | 5.4,3.4,1.7,0.2,setosa 23 | 5.1,3.7,1.5,0.4,setosa 24 | 4.6,3.6,1,0.2,setosa 25 | 5.1,3.3,1.7,0.5,setosa 26 | 4.8,3.4,1.9,0.2,setosa 27 | 5,3,1.6,0.2,setosa 28 | 5,3.4,1.6,0.4,setosa 29 | 5.2,3.5,1.5,0.2,setosa 30 | 5.2,3.4,1.4,0.2,setosa 31 | 4.7,3.2,1.6,0.2,setosa 32 | 4.8,3.1,1.6,0.2,setosa 33 | 5.4,3.4,1.5,0.4,setosa 34 | 5.2,4.1,1.5,0.1,setosa 35 | 5.5,4.2,1.4,0.2,setosa 36 | 4.9,3.1,1.5,0.2,setosa 37 | 5,3.2,1.2,0.2,setosa 38 | 5.5,3.5,1.3,0.2,setosa 39 | 4.9,3.6,1.4,0.1,setosa 40 | 4.4,3,1.3,0.2,setosa 41 | 5.1,3.4,1.5,0.2,setosa 42 | 5,3.5,1.3,0.3,setosa 43 | 4.5,2.3,1.3,0.3,setosa 44 | 4.4,3.2,1.3,0.2,setosa 45 | 5,3.5,1.6,0.6,setosa 46 | 5.1,3.8,1.9,0.4,setosa 47 | 4.8,3,1.4,0.3,setosa 48 | 5.1,3.8,1.6,0.2,setosa 49 | 4.6,3.2,1.4,0.2,setosa 50 | 5.3,3.7,1.5,0.2,setosa 51 | 5,3.3,1.4,0.2,setosa 52 | 7,3.2,4.7,1.4,versicolor 53 | 6.4,3.2,4.5,1.5,versicolor 54 | 6.9,3.1,4.9,1.5,versicolor 55 | 5.5,2.3,4,1.3,versicolor 56 | 6.5,2.8,4.6,1.5,versicolor 57 | 5.7,2.8,4.5,1.3,versicolor 58 | 6.3,3.3,4.7,1.6,versicolor 59 | 4.9,2.4,3.3,1,versicolor 60 | 6.6,2.9,4.6,1.3,versicolor 61 | 5.2,2.7,3.9,1.4,versicolor 62 | 5,2,3.5,1,versicolor 63 | 5.9,3,4.2,1.5,versicolor 64 | 6,2.2,4,1,versicolor 65 | 6.1,2.9,4.7,1.4,versicolor 66 | 5.6,2.9,3.6,1.3,versicolor 67 | 6.7,3.1,4.4,1.4,versicolor 68 | 5.6,3,4.5,1.5,versicolor 69 | 5.8,2.7,4.1,1,versicolor 70 | 6.2,2.2,4.5,1.5,versicolor 71 | 5.6,2.5,3.9,1.1,versicolor 72 | 5.9,3.2,4.8,1.8,versicolor 73 | 6.1,2.8,4,1.3,versicolor 74 | 6.3,2.5,4.9,1.5,versicolor 75 | 6.1,2.8,4.7,1.2,versicolor 76 | 6.4,2.9,4.3,1.3,versicolor 77 | 6.6,3,4.4,1.4,versicolor 78 | 6.8,2.8,4.8,1.4,versicolor 79 | 6.7,3,5,1.7,versicolor 80 | 6,2.9,4.5,1.5,versicolor 81 | 5.7,2.6,3.5,1,versicolor 82 | 5.5,2.4,3.8,1.1,versicolor 83 | 5.5,2.4,3.7,1,versicolor 84 | 5.8,2.7,3.9,1.2,versicolor 85 | 6,2.7,5.1,1.6,versicolor 86 | 5.4,3,4.5,1.5,versicolor 87 | 6,3.4,4.5,1.6,versicolor 88 | 6.7,3.1,4.7,1.5,versicolor 89 | 6.3,2.3,4.4,1.3,versicolor 90 | 5.6,3,4.1,1.3,versicolor 91 | 5.5,2.5,4,1.3,versicolor 92 | 5.5,2.6,4.4,1.2,versicolor 93 | 6.1,3,4.6,1.4,versicolor 94 | 5.8,2.6,4,1.2,versicolor 95 | 5,2.3,3.3,1,versicolor 96 | 5.6,2.7,4.2,1.3,versicolor 97 | 5.7,3,4.2,1.2,versicolor 98 | 5.7,2.9,4.2,1.3,versicolor 99 | 6.2,2.9,4.3,1.3,versicolor 100 | 5.1,2.5,3,1.1,versicolor 101 | 5.7,2.8,4.1,1.3,versicolor 102 | 6.3,3.3,6,2.5,virginica 103 | 5.8,2.7,5.1,1.9,virginica 104 | 7.1,3,5.9,2.1,virginica 105 | 6.3,2.9,5.6,1.8,virginica 106 | 6.5,3,5.8,2.2,virginica 107 | 7.6,3,6.6,2.1,virginica 108 | 4.9,2.5,4.5,1.7,virginica 109 | 7.3,2.9,6.3,1.8,virginica 110 | 6.7,2.5,5.8,1.8,virginica 111 | 7.2,3.6,6.1,2.5,virginica 112 | 6.5,3.2,5.1,2,virginica 113 | 6.4,2.7,5.3,1.9,virginica 114 | 6.8,3,5.5,2.1,virginica 115 | 5.7,2.5,5,2,virginica 116 | 5.8,2.8,5.1,2.4,virginica 117 | 6.4,3.2,5.3,2.3,virginica 118 | 6.5,3,5.5,1.8,virginica 119 | 7.7,3.8,6.7,2.2,virginica 120 | 7.7,2.6,6.9,2.3,virginica 121 | 6,2.2,5,1.5,virginica 122 | 6.9,3.2,5.7,2.3,virginica 123 | 5.6,2.8,4.9,2,virginica 124 | 7.7,2.8,6.7,2,virginica 125 | 6.3,2.7,4.9,1.8,virginica 126 | 6.7,3.3,5.7,2.1,virginica 127 | 7.2,3.2,6,1.8,virginica 128 | 6.2,2.8,4.8,1.8,virginica 129 | 6.1,3,4.9,1.8,virginica 130 | 6.4,2.8,5.6,2.1,virginica 131 | 7.2,3,5.8,1.6,virginica 132 | 7.4,2.8,6.1,1.9,virginica 133 | 7.9,3.8,6.4,2,virginica 134 | 6.4,2.8,5.6,2.2,virginica 135 | 6.3,2.8,5.1,1.5,virginica 136 | 6.1,2.6,5.6,1.4,virginica 137 | 7.7,3,6.1,2.3,virginica 138 | 6.3,3.4,5.6,2.4,virginica 139 | 6.4,3.1,5.5,1.8,virginica 140 | 6,3,4.8,1.8,virginica 141 | 6.9,3.1,5.4,2.1,virginica 142 | 6.7,3.1,5.6,2.4,virginica 143 | 6.9,3.1,5.1,2.3,virginica 144 | 5.8,2.7,5.1,1.9,virginica 145 | 6.8,3.2,5.9,2.3,virginica 146 | 6.7,3.3,5.7,2.5,virginica 147 | 6.7,3,5.2,2.3,virginica 148 | 6.3,2.5,5,1.9,virginica 149 | 6.5,3,5.2,2,virginica 150 | 6.2,3.4,5.4,2.3,virginica 151 | 5.9,3,5.1,1.8,virginica 152 | -------------------------------------------------------------------------------- /inst/extdata/iris_column_annot.csv: -------------------------------------------------------------------------------- 1 | ,Sepal.Length,Sepal.Width,Petal.Length,Petal.Width 2 | Object,Sepal,Sepal,Petal,Petal 3 | Type,Length,Width,Length,Width 4 | -------------------------------------------------------------------------------- /inst/www/countries.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gexijin/datamap/98a21eb9e3ad661b88991770d57fd1401beac4ff/inst/www/countries.png -------------------------------------------------------------------------------- /inst/www/countries_label.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gexijin/datamap/98a21eb9e3ad661b88991770d57fd1401beac4ff/inst/www/countries_label.png -------------------------------------------------------------------------------- /inst/www/google_analytics.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /inst/www/heatmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gexijin/datamap/98a21eb9e3ad661b88991770d57fd1401beac4ff/inst/www/heatmap.png -------------------------------------------------------------------------------- /inst/www/pca.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gexijin/datamap/98a21eb9e3ad661b88991770d57fd1401beac4ff/inst/www/pca.png -------------------------------------------------------------------------------- /inst/www/tsne.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gexijin/datamap/98a21eb9e3ad661b88991770d57fd1401beac4ff/inst/www/tsne.png -------------------------------------------------------------------------------- /man/create_dr_plot.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utilities.R 3 | \name{create_dr_plot} 4 | \alias{create_dr_plot} 5 | \title{Create Dimensionality Reduction Plot} 6 | \usage{ 7 | create_dr_plot( 8 | coords_data, 9 | x_label, 10 | y_label, 11 | point_annot = NULL, 12 | fontsize = 12, 13 | show_labels = FALSE, 14 | point_labels = NULL 15 | ) 16 | } 17 | \arguments{ 18 | \item{coords_data}{A numeric matrix or data frame containing at least two columns for the x and y coordinates.} 19 | 20 | \item{x_label}{A character string specifying the label for the x-axis.} 21 | 22 | \item{y_label}{A character string specifying the label for the y-axis.} 23 | 24 | \item{point_annot}{Optional data frame containing annotation data. The first column is used for assigning colors, 25 | and if a second column is provided, its values are used for assigning point shapes.} 26 | 27 | \item{fontsize}{A numeric value specifying the base font size used for labels, axes, and legends. Default is 12.} 28 | 29 | \item{show_labels}{Logical flag indicating whether to display text labels next to points. Default is FALSE.} 30 | 31 | \item{point_labels}{Optional character vector containing labels for each point. Must have at least as many elements as rows in coords_data when show_labels is TRUE.} 32 | } 33 | \value{ 34 | A recorded plot object that can be replayed using \code{replayPlot()}. 35 | } 36 | \description{ 37 | Generates a scatter plot for dimensionality reduction with support for annotations that alter 38 | point colors and shapes, and optionally includes point labels. 39 | } 40 | \details{ 41 | The function sets up the plotting environment, adjusts plot limits based on whether labels are shown, 42 | and manages legends for both color and shape annotations. If annotation data is provided, the first column 43 | determines the color palette (using a rainbow palette) and the second column (if available) assigns point shapes. 44 | } 45 | \examples{ 46 | # Example with simulated data: 47 | coords <- matrix(rnorm(200), ncol = 2) 48 | annot <- data.frame(Group = sample(c("A", "B", "C"), 100, replace = TRUE), 49 | Type = sample(c("X", "Y"), 100, replace = TRUE)) 50 | plot_obj <- create_dr_plot(coords, "X Axis", "Y Axis", point_annot = annot, show_labels = TRUE, 51 | point_labels = paste("P", 1:100, sep="")) 52 | replayPlot(plot_obj) 53 | 54 | } 55 | -------------------------------------------------------------------------------- /man/datamap-package.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/datamap-package.R 3 | \docType{package} 4 | \name{datamap-package} 5 | \alias{datamap} 6 | \alias{datamap-package} 7 | \title{datamap: Interactive Visualization of Data Matrices} 8 | \description{ 9 | A Shiny application for visualizing data matrices with heatmaps, PCA, and t-SNE. Allows users to upload, transform, and visualize their data interactively. 10 | } 11 | \author{ 12 | \strong{Maintainer}: Steven Ge \email{Xijin.Ge@sdstate.edu} 13 | 14 | } 15 | \keyword{internal} 16 | -------------------------------------------------------------------------------- /man/datamap_resource.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utilities.R 3 | \name{datamap_resource} 4 | \alias{datamap_resource} 5 | \title{Find resource files for datamap} 6 | \usage{ 7 | datamap_resource(path) 8 | } 9 | \arguments{ 10 | \item{path}{Path to the resource within the package} 11 | } 12 | \value{ 13 | The full path to the resource file 14 | } 15 | \description{ 16 | Find resource files for datamap 17 | } 18 | \keyword{internal} 19 | -------------------------------------------------------------------------------- /man/process_column_annotations.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utilities.R 3 | \name{process_column_annotations} 4 | \alias{process_column_annotations} 5 | \title{Process Column Annotations for Heatmap} 6 | \usage{ 7 | process_column_annotations(main_data_cols, annotation_df, selected_annotations) 8 | } 9 | \arguments{ 10 | \item{main_data_cols}{A character vector of column names corresponding to the main dataset.} 11 | 12 | \item{annotation_df}{A data frame containing annotation information where rows correspond to different annotation types 13 | and columns represent samples.} 14 | 15 | \item{selected_annotations}{A vector indicating the rows (by their names or indices) in annotation_df to be selected.} 16 | } 17 | \value{ 18 | A data frame with the main dataset's column names as its row names and the selected annotation types as its column names. 19 | If there are no annotations selected, no common samples between datasets, or if all annotations fail to process, the function returns NULL. 20 | } 21 | \description{ 22 | This function processes column annotations by aligning selected annotation rows with the main dataset's columns. 23 | It attempts to safely subset and merge annotation data based on common sample names and handles errors during processing. 24 | } 25 | -------------------------------------------------------------------------------- /man/process_row_annotations.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utilities.R 3 | \name{process_row_annotations} 4 | \alias{process_row_annotations} 5 | \title{Process Row Annotations} 6 | \usage{ 7 | process_row_annotations( 8 | main_data_rows, 9 | file_annotation_df = NULL, 10 | factor_annotation_df = NULL, 11 | selected_annotations 12 | ) 13 | } 14 | \arguments{ 15 | \item{main_data_rows}{A vector of row identifiers from the main data set.} 16 | 17 | \item{file_annotation_df}{An optional data frame containing row annotations from an uploaded file. 18 | Default is NULL.} 19 | 20 | \item{factor_annotation_df}{An optional data frame containing row annotations based on factors. 21 | Default is NULL.} 22 | 23 | \item{selected_annotations}{A character vector specifying the names of annotations to extract.} 24 | } 25 | \value{ 26 | A data frame with combined annotations that correspond to the main data rows. Returns 27 | NULL if no annotations are selected or if no matching annotations are found. 28 | } 29 | \description{ 30 | This function processes and combines annotation data for rows from two potential sources: 31 | a file-uploaded annotation data frame and an auto-detected factor annotation data frame. It selects 32 | desired annotation columns based on the provided list, aligns these annotations with the main data rows, 33 | and merges them into a single data frame. 34 | } 35 | -------------------------------------------------------------------------------- /man/run_app.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/main_app.R 3 | \name{run_app} 4 | \alias{run_app} 5 | \title{Run the DataMap Shiny application} 6 | \usage{ 7 | run_app() 8 | } 9 | \value{ 10 | A Shiny application object 11 | } 12 | \description{ 13 | Run the DataMap Shiny application 14 | } 15 | \examples{ 16 | if (interactive()) { 17 | run_app() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /man/transform_server.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/mod_transform.R 3 | \name{transform_server} 4 | \alias{transform_server} 5 | \title{Server function for preprocessing module} 6 | \usage{ 7 | transform_server(id, data) 8 | } 9 | \arguments{ 10 | \item{id}{The module namespace id} 11 | 12 | \item{data}{Reactive data frame to process} 13 | } 14 | \value{ 15 | A list with the processed data reactive and reproducible code 16 | } 17 | \description{ 18 | Server function for preprocessing module 19 | } 20 | -------------------------------------------------------------------------------- /man/transform_ui.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/mod_transform.R 3 | \name{transform_ui} 4 | \alias{transform_ui} 5 | \title{UI function for preprocessing module button} 6 | \usage{ 7 | transform_ui(id) 8 | } 9 | \arguments{ 10 | \item{id}{The module namespace id} 11 | } 12 | \value{ 13 | A button that triggers the preprocessing modal 14 | } 15 | \description{ 16 | UI function for preprocessing module button 17 | } 18 | -------------------------------------------------------------------------------- /www/countries.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gexijin/datamap/98a21eb9e3ad661b88991770d57fd1401beac4ff/www/countries.png -------------------------------------------------------------------------------- /www/countries_label.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gexijin/datamap/98a21eb9e3ad661b88991770d57fd1401beac4ff/www/countries_label.png -------------------------------------------------------------------------------- /www/google_analytics.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /www/heatmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gexijin/datamap/98a21eb9e3ad661b88991770d57fd1401beac4ff/www/heatmap.png -------------------------------------------------------------------------------- /www/help.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | DataMap Documentation 7 | 8 | 9 |
10 | 227 | 230 | 231 |

DataMap is a secure, browser-based application for visualizing high-dimensional omics and other data matrices with heatmaps, PCA, and t-SNE.

232 |
233 |

Video tutorials

234 |

235 | Watch a 2-min video on 236 | YouTube 237 | to get a quick start. Here is another 238 | 239 | video 240 | on countries with negative population growth. 241 |

242 |
243 |
244 |

Overview

245 |

DataMap is a Shiny application designed to provide researchers and data analysts with an easy-to-use tool for visualizing high-dimensional data matrices. What makes DataMap unique is that it runs entirely in your browser through Shinylive technology, ensuring your data never leaves your device, providing both security and scalability.

246 | 247 |
248 | Why DataMap? 249 |
    250 |
  • Runs completely in your browser - no server required
  • 251 |
  • Your data stays on your device, providing maximum security
  • 252 |
  • Interactive visualizations including heatmaps, PCA, and t-SNE
  • 253 |
  • Support for data transformations (scaling, normalization, etc.)
  • 254 |
  • Exportable code to reproduce your analysis in R
  • 255 |
  • Supports multiple data formats (CSV, TSV, TXT, Excel)
  • 256 |
  • Customizable visualization parameters
  • 257 |
258 |
259 |
260 | 261 |
262 |

Getting Started

263 | 264 |

Launching the Application

265 |

Simply navigate to this GitHub page to load the application in your browser. No installation is required. You can choose to install it as an R package or download the source code and turn it into a shinylive app yourself. See instructions below.

266 | 267 |

Interface Overview

268 |

The DataMap interface consists of:

269 | 273 |
274 | 275 |
276 |

Data Upload

277 | 278 |

Supported File Formats

279 |

DataMap supports the following file formats:

280 | 285 | 286 |

Data Format Requirements

287 |

Your data should be organized in a matrix format where:

288 | 294 | 295 |
296 | 297 | 298 |
299 | 300 |
301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 |
Name1Name2Name3Name4Name5Name6
5.62.94.39.13.13.4
3.22.1NA2.81.52.9
8.98.79.08.53.52.4
1.98.01.01.52.51.8
1.02.11.53.16.67.8
351 |

The column names should not have duplicates. Missing values in the data matrix are tolerated.

352 |
353 | 354 |
355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 |
GeneIDNameSample1Sample2Sample3Sample4Protein_coding
Gene1DNA Repair 25.67.84.39.1Yes
Gene2Energy 3B3.22.13.52.8Yes
Gene3Stress related 18.98.79.08.5No
Gene4Transporter 21.21.01.41.3Yes
Gene56.76.96.27.1Yes
411 |

The first column contains row names (here is gene ids). 412 | The row must not contain duplicates. Otherwise, it will be ignored, just like the 2nd column. 413 | You can have one or more columns with categorical data, such as the last one here. 414 | Such information will be used to label rows. You can also save such columns in a separate 415 | file to be uploaded as row annotations (See below).

416 | 417 |
418 | 419 |

Optional Annotation Files

420 |

DataMap allows you to upload additional annotation files to provide context for your data:

421 | 422 |

Column Annotation File

423 |

This file provides metadata about your samples (columns). The file should have:

424 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 |
SampleIDSample1Sample2Sample3Sample4
TreatmentControlTreatmentControlTreatment
TimePoint0h0h24h24h
451 | 452 |

Row Annotation File

453 |

This file provides metadata about your features (rows). The file should have:

454 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 |
GeneIDPathwayFunction
Gene1GlycolysisEnzyme
Gene2TCA CycleTransporter
Gene3GlycolysisTranscription Factor
Gene4TCA CycleEnzyme
Gene5Lipid MetabolismSignaling
490 | 491 |

Uploading Your Data

492 |
    493 |
  1. Click the Files button in the sidebar
  2. 494 |
  3. Use the file upload controls to select and upload your files
  4. 495 |
  5. Configure import settings (delimiter, headers, row names) in the dialog
  6. 496 |
  7. Preview your data to ensure it's imported correctly
  8. 497 |
  9. Click Import Data to load your data into the application
  10. 498 |
499 | 500 |
501 | Tip: The application will attempt to auto-detect the optimal import settings, but you can adjust these manually if needed. 502 |
503 |
504 | 505 |
506 |

Data Transformation

507 | 508 |

DataMap provides various options for transforming your data to improve visualization and analysis:

509 | 510 |

Available Transformations

511 | 525 | 526 |

Applying Transformations

527 |
    528 |
  1. Click the Data Prep. button in the sidebar
  2. 529 |
  3. Select the desired transformations in the dialog
  4. 530 |
  5. Preview the effect of your transformations in the histogram
  6. 531 |
  7. Click Apply Changes to implement the transformations
  8. 532 |
533 | 534 |
535 | Note: The application will recommend appropriate transformations based on your data characteristics, such as skewness and the presence of missing values. 536 |
537 |
538 | 539 |
540 |

Visualization Options

541 | 542 |

Heatmap

543 |

The heatmap visualization allows you to see patterns in your data matrix through color intensity.

544 | 545 |

Customization Options

546 | 557 | 558 |

Principal Component Analysis (PCA)

559 |

PCA reduces the dimensionality of your data to display the main sources of variation.

560 | 561 |

PCA Options

562 | 566 | 567 |

t-SNE (t-Distributed Stochastic Neighbor Embedding)

568 |

t-SNE is a nonlinear dimensionality reduction technique that is particularly good at visualizing high-dimensional data clusters.

569 | 570 |

t-SNE Options

571 | 576 |
577 | 578 |
579 |

Generating Reproducible Code

580 |

One of the most powerful features of DataMap is its ability to generate reproducible R code for your analysis.

581 | 582 |

Code Generation

583 |

The application automatically generates R code that replicates all steps of your analysis:

584 | 590 | 591 |

Accessing the Code

592 |
    593 |
  1. Navigate to the Code tab in the main panel
  2. 594 |
  3. Review the generated R code
  4. 595 |
  5. Click Download Code as R Script to save the code for later use
  6. 596 |
597 | 598 |
599 | Tip: This feature is especially useful for documentation, publications, and ensuring reproducibility of your analysis. 600 |
601 |
602 | 603 |
604 |

Exporting Results

605 | 606 |

Saving Visualizations

607 |

You can save your visualizations in various formats:

608 | 612 | 613 |
614 | 615 |
616 |

Tips and Best Practices

617 | 618 |

Data Preparation

619 | 624 | 625 |

Visualization

626 | 631 | 632 |

Troubleshooting

633 | 638 |
639 | 640 |
641 |

FAQ

642 | 643 |

Is my data secure?

644 |

Yes. DataMap runs entirely in your browser using WebAssembly technology. Your data never leaves your device and is not sent to any server, providing maximum security and privacy. Once DataMap is loaded, you can disconnect your computer from the internet and then upload your data (to your browser!) for analysis.

645 | 646 |

What is the maximum file size I can upload?

647 |

Since the application runs in your browser, the maximum file size depends on your device's memory. However, this browser app gets slow when there are 5000 rows or columns. Users can install DataMap as an R package to run it natively.

648 | 649 |

What browsers are supported?

650 |

Chrome, Edge, Safari, and Firefox (slower).

651 | 652 |

Can I use DataMap offline?

653 |

Once loaded, DataMap actually already runs offline inside your browser. You can intsall DataMap as an R package and use it from RStudio.

654 |
655 | install.packages("remotes")
656 | remotes::install_github("gexijin/datamap", upgrade = "never")
657 | datamap::run_app()
658 | 
659 |

Alternatively, you can download the source code to a folder, and turn it into a shinylive application that could be used from the browser:

660 |
661 | # first download this repo to a folder called datamap
662 | install.packages("shinylive")
663 | shinylive::export("datamap", "site")
664 | httpuv::runStaticServer("site") # app opens in browser
665 | 
666 | 667 |

How can I reproduce my analysis?

668 |

After getting a plot, you can go to the Code tab to export the R code, which records all your settings and can be run in RStudio to reproduce the plot. Alternatively, you can note the version of DataMap used. Later, you or other scientists can find the corresponding version of the app from our previous releases on GitHub, which can be downloaded and run (see above).

669 | 670 |

My visualization is taking a long time to generate. What can I do?

671 |

For large datasets, consider these strategies to improve performance:

672 | 678 | 679 |

Do you track usage?

680 |

Yes. I added a Google Analytics script to track how many times it is used. If there are more users, I will spend more time to improve it. Google Analytics does report city-level location. If you are opposed to this, please let me know. I can disable tracking.

681 | 682 |

Why did you write DataMap?

683 |

a) I love heatmaps! b) I wanted to do a vibe coding experiment. Claude.ai wrote 95% of the code. I mostly served as a product manager. See my blog on how DataMap was developed.

684 | 685 |

Who owns the copyright of the produced plots?

686 |

You!

687 |
688 | 689 |
690 |

Install as an R package

691 |
692 | install.packages("remotes")
693 | remotes::install_github("gexijin/datamap", upgrade = "never")
694 | datamap::run_app()
695 | 
696 |
697 | 698 |
699 |

Source code, Bug Reports, and Feature Requests

700 |

If you encounter any issues or have suggestions, feel free to email me. Or report them on Issues tab on the GitHub repository, where you can also find the source code: https://github.com/gexijin/datamap

701 |
702 |
703 |

Preprint & Citation

704 | 705 |

For more information, please read this preprint. If you use DataMap in your research, please cite it:

706 | 707 |
708 | Ge, X. (2025). DataMap: A Portable Application for Visualizing High-Dimensional Data, 709 | arXiv:2504.08875, 2025. 710 |
711 |
712 |
713 |
714 |

About the Author

715 |

Dr. Xijin Ge is a Professor at South Dakota State University specializing in bioinformatics and data visualization.

716 | 717 | 724 |
725 |
726 | 727 | 731 | 732 | 787 |
788 | 789 | -------------------------------------------------------------------------------- /www/pca.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gexijin/datamap/98a21eb9e3ad661b88991770d57fd1401beac4ff/www/pca.png -------------------------------------------------------------------------------- /www/tsne.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gexijin/datamap/98a21eb9e3ad661b88991770d57fd1401beac4ff/www/tsne.png --------------------------------------------------------------------------------