From b2549f6c07232421d52daf0453d508bc4a135430 Mon Sep 17 00:00:00 2001 From: Kelly Sovacool Date: Thu, 16 Jul 2026 16:54:40 -0400 Subject: [PATCH] fix: switch to MOSuite FigOutSync --- .gitattributes | 2 +- .syncweaver-lock.json | 4 +- code/MOSuite/.Rbuildignore | 1 + .../workflows/syncweaver-source-dispatch.yml | 92 +++++++ code/MOSuite/.gitignore | 29 +- code/MOSuite/.lintr | 1 + code/MOSuite/.pre-commit-config.yaml | 4 +- code/MOSuite/.vscode/extensions.json | 5 - code/MOSuite/.vscode/settings.json | 13 - code/MOSuite/CITATION.cff | 60 ++++- code/MOSuite/DESCRIPTION | 2 +- code/MOSuite/NEWS.md | 7 + code/MOSuite/R/batch-correction.R | 126 +++++++-- code/MOSuite/R/clean.R | 27 +- code/MOSuite/R/colors.R | 92 ++++++- code/MOSuite/R/filter.R | 58 ++-- code/MOSuite/R/normalize.R | 27 +- code/MOSuite/R/plot_heatmap.R | 63 +++-- code/MOSuite/R/plot_histogram.R | 54 ++-- code/MOSuite/R/plot_pca.R | 69 ++++- code/MOSuite/R/plot_read_depth.R | 101 ++++++- code/MOSuite/R/plots.R | 129 +++++++++ code/MOSuite/SECURITY.md | 18 ++ code/MOSuite/codemeta.json | 2 +- .../1_mosuite-templates/filter_counts.json | 4 +- .../1_mosuite-templates/normalize_counts.json | 4 +- .../2_blueprints/batch_correct_counts.json | 3 +- .../galaxy/2_blueprints/filter_counts.json | 12 +- .../galaxy/2_blueprints/normalize_counts.json | 10 +- .../galaxy/2_blueprints/plot_pca_2d.json | 12 +- .../galaxy/2_blueprints/plot_pca_3d.json | 2 +- code/MOSuite/inst/extdata/galaxy/galaxy.R | 4 +- .../json_args/common/filter_counts.json | 6 +- .../json_args/common/normalize_counts.json | 19 +- .../extdata/json_args/common/plot_pca_2d.json | 2 +- .../defaults/batch_correct_counts.json | 20 +- .../json_args/defaults/clean_raw_counts.json | 2 + .../json_args/defaults/filter_counts.json | 8 +- .../json_args/defaults/normalize_counts.json | 6 +- .../json_args/defaults/plot_pca_2d.json | 3 +- code/MOSuite/man/batch_correct_counts.Rd | 75 +++++- code/MOSuite/man/clean_raw_counts.Rd | 9 + code/MOSuite/man/filter_counts.Rd | 20 +- code/MOSuite/man/get_colors_lst.Rd | 7 +- code/MOSuite/man/get_colors_vctr.Rd | 10 +- code/MOSuite/man/normalize_counts.Rd | 18 +- .../man/plot_corr_heatmap-data.frame.Rd | 4 +- code/MOSuite/man/plot_expr_heatmap.Rd | 4 +- code/MOSuite/man/plot_histogram.data.frame.Rd | 7 +- code/MOSuite/man/plot_pca_2d.Rd | 35 +-- code/MOSuite/man/plot_pca_3d.Rd | 4 +- .../MOSuite/man/plot_read_depth.data.frame.Rd | 9 + .../man/plot_read_depth.multiOmicDataSet.Rd | 7 + code/MOSuite/man/set_color_pal.Rd | 7 +- code/MOSuite/tests/testthat/_snaps/E2E.md | 4 +- .../tests/testthat/test-batch-correction.R | 147 +++++++++++ code/MOSuite/tests/testthat/test-colors.R | 247 ++++++++++++------ code/MOSuite/tests/testthat/test-deseq2.R | 1 + code/MOSuite/tests/testthat/test-filter.R | 130 +++++++++ code/MOSuite/tests/testthat/test-normalize.R | 127 +++++++++ .../tests/testthat/test-plot_heatmap.R | 71 +++++ .../tests/testthat/test-plot_histogram.R | 158 +++++++++++ code/MOSuite/tests/testthat/test-plot_pca.R | 210 +++++++++++++++ .../tests/testthat/test-plot_read_depth.R | 60 +++++ 64 files changed, 2138 insertions(+), 336 deletions(-) create mode 100644 code/MOSuite/.github/workflows/syncweaver-source-dispatch.yml delete mode 100644 code/MOSuite/.vscode/extensions.json delete mode 100644 code/MOSuite/.vscode/settings.json create mode 100644 code/MOSuite/SECURITY.md diff --git a/.gitattributes b/.gitattributes index abfaa29..d689bcd 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1 @@ -/code/MOSuite linguist-vendored +code/MOSuite linguist-vendored diff --git a/.syncweaver-lock.json b/.syncweaver-lock.json index 6827f65..e59a587 100644 --- a/.syncweaver-lock.json +++ b/.syncweaver-lock.json @@ -5,8 +5,8 @@ "sources": { "code/MOSuite": { "repo_url": "https://github.com/CCBR/MOSuite", - "ref": "v0.3.2", - "git_sha": "f4465c58a7d04f5feb41d4455a019ca4946a4ca8" + "ref": "FigOutSync", + "git_sha": "f2177f7c4b1efc6960e96361442e7be099c532c9" } } } diff --git a/code/MOSuite/.Rbuildignore b/code/MOSuite/.Rbuildignore index 7ee19e0..ab50d70 100644 --- a/code/MOSuite/.Rbuildignore +++ b/code/MOSuite/.Rbuildignore @@ -35,3 +35,4 @@ VennDiagram.*\.log ^[.]?air[.]toml$ ^\.vscode$ +^SECURITY\.md$ diff --git a/code/MOSuite/.github/workflows/syncweaver-source-dispatch.yml b/code/MOSuite/.github/workflows/syncweaver-source-dispatch.yml new file mode 100644 index 0000000..447e39c --- /dev/null +++ b/code/MOSuite/.github/workflows/syncweaver-source-dispatch.yml @@ -0,0 +1,92 @@ +name: syncweaver-source-dispatch + +on: + release: + types: [published] + workflow_dispatch: + inputs: + source_ref: + description: Optional source ref to dispatch (defaults to release tag for release events, or current ref for manual runs). + required: false + type: string + source_repo: + description: Optional source repository override in OWNER/REPO format (defaults to current repository). + required: false + type: string + orchestrator-repo: + description: Optional orchestrator repository override in OWNER/REPO format. + required: false + default: CCBR/syncweaver + type: string + +permissions: + contents: read + +jobs: + notify-syncweaver: + runs-on: ubuntu-latest + env: + SOURCE_REPOSITORY: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.source_repo || github.repository }} + SOURCE_REF: ${{ github.event_name == 'release' && github.event.release.tag_name || github.event.inputs.source_ref || github.ref_name }} + ORCHESTRATOR_REPO: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs['orchestrator-repo'] || vars['orchestrator-repo'] || 'CCBR/syncweaver' }} + steps: + - name: Validate dispatch payload + shell: bash + run: | + set -euo pipefail + if [[ -z "${SOURCE_REPOSITORY//[[:space:]]/}" ]]; then + echo "Error: source repository cannot be empty." >&2 + exit 1 + fi + if [[ -z "${SOURCE_REF//[[:space:]]/}" ]]; then + echo "Error: source ref cannot be empty." >&2 + exit 1 + fi + if [[ -z "${ORCHESTRATOR_REPO//[[:space:]]/}" ]]; then + echo "Error: orchestrator repository cannot be empty." >&2 + exit 1 + fi + if [[ "${ORCHESTRATOR_REPO}" != */* ]]; then + echo "Error: orchestrator repository must be in OWNER/REPO format." >&2 + exit 1 + fi + if [[ -z "${{ vars.CCBR_BOT_APP_ID }}" ]]; then + echo "Error: vars.CCBR_BOT_APP_ID is required to generate a dispatch token." >&2 + exit 1 + fi + if [[ -z "${{ secrets.CCBR_BOT_PRIVATE_KEY }}" ]]; then + echo "Error: secrets.CCBR_BOT_PRIVATE_KEY is required to generate a dispatch token." >&2 + exit 1 + fi + + - name: Resolve orchestrator repository owner and name + id: orchestrator_repo + shell: bash + run: | + set -euo pipefail + owner="${ORCHESTRATOR_REPO%%/*}" + repo="${ORCHESTRATOR_REPO#*/}" + if [[ -z "${owner}" || -z "${repo}" ]]; then + echo "Error: failed to parse orchestrator repository '${ORCHESTRATOR_REPO}'." >&2 + exit 1 + fi + echo "owner=${owner}" >> "$GITHUB_OUTPUT" + echo "repository=${repo}" >> "$GITHUB_OUTPUT" + + - name: Generate CCBR-bot token + id: ccbr_bot + uses: actions/create-github-app-token@v3 + with: + client-id: ${{ vars.CCBR_BOT_APP_ID }} + private-key: ${{ secrets.CCBR_BOT_PRIVATE_KEY }} + owner: ${{ steps.orchestrator_repo.outputs.owner }} + repositories: ${{ steps.orchestrator_repo.outputs.repository }} + + - name: Dispatch syncweaver-update-hosts workflow in orchestrator repo + uses: peter-evans/repository-dispatch@v4 + with: + token: ${{ steps.ccbr_bot.outputs.token }} + repository: ${{ env.ORCHESTRATOR_REPO }} + event-type: syncweaver-update-hosts + client-payload: >- + {"source_repository": ${{ toJson(env.SOURCE_REPOSITORY) }}, "ref": ${{ toJson(env.SOURCE_REF) }}} diff --git a/code/MOSuite/.gitignore b/code/MOSuite/.gitignore index a7f1be5..4d2ccac 100644 --- a/code/MOSuite/.gitignore +++ b/code/MOSuite/.gitignore @@ -1,21 +1,24 @@ -.Rproj.user -.Rhistory -.Rdata -.httr-oauth .DS_Store -.quarto -docs -inst/doc +*_files/ +**/figures/ +/.github/plans/ +/.httr-oauth +/.posit/ +/.quarto +/.Rdata +/.Rhistory +/.Rproj.user +/.vscode/ /doc/ +/docs +/inst/doc /Meta/ /README.html -**/figures/ -VennDiagram.*.log -*_files/ -tests/testthat/Rplots.pdf +/tests/testthat/Rplots.pdf /tmp -vignettes/args*.json -Rplots.pdf +/vignettes/args*.json moo_input.rds plot-volc-enh.json +Rplots.pdf +VennDiagram.*.log volc-sum-params.json diff --git a/code/MOSuite/.lintr b/code/MOSuite/.lintr index e636784..4250828 100644 --- a/code/MOSuite/.lintr +++ b/code/MOSuite/.lintr @@ -3,6 +3,7 @@ linters: linters_with_defaults( commented_code_linter = NULL, object_name_linter = NULL, object_length_linter = object_length_linter(60L), + object_usage_linter = NULL, return_linter = return_linter( return_style = "explicit" ), diff --git a/code/MOSuite/.pre-commit-config.yaml b/code/MOSuite/.pre-commit-config.yaml index a09d9f3..5bb8760 100644 --- a/code/MOSuite/.pre-commit-config.yaml +++ b/code/MOSuite/.pre-commit-config.yaml @@ -26,11 +26,11 @@ repos: args: ["-I=.github/WORDLIST.txt"] # R formatting - repo: https://github.com/posit-dev/air-pre-commit - rev: 0.9.0 + rev: 0.10.0 hooks: - id: air-format - repo: https://github.com/lorenzwalthert/precommit - rev: v0.4.3.9025 + rev: v0.4.3.9028 hooks: - id: parsable-R - id: readme-rmd-rendered diff --git a/code/MOSuite/.vscode/extensions.json b/code/MOSuite/.vscode/extensions.json deleted file mode 100644 index 344f76e..0000000 --- a/code/MOSuite/.vscode/extensions.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "recommendations": [ - "Posit.air-vscode" - ] -} diff --git a/code/MOSuite/.vscode/settings.json b/code/MOSuite/.vscode/settings.json deleted file mode 100644 index 61ac215..0000000 --- a/code/MOSuite/.vscode/settings.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "[r]": { - "editor.formatOnSave": true, - "editor.defaultFormatter": "Posit.air-vscode" - }, - "[quarto]": { - "editor.formatOnSave": true, - "editor.defaultFormatter": "quarto.quarto" - }, - "chat.tools.terminal.autoApprove": { - "timeout": true - } -} diff --git a/code/MOSuite/CITATION.cff b/code/MOSuite/CITATION.cff index 9768535..168a28c 100644 --- a/code/MOSuite/CITATION.cff +++ b/code/MOSuite/CITATION.cff @@ -8,7 +8,7 @@ message: 'To cite package "MOSuite" in publications use:' type: software license: MIT title: 'MOSuite: R package for differential multi-omics analysis' -version: 0.3.2 +version: 0.3.2.9000 doi: 10.5281/zenodo.16371580 identifiers: - description: Archived snapshots of all versions @@ -71,13 +71,10 @@ references: url: https://www.R-project.org/ authors: - name: R Core Team - website: https://ror.org/02zz1nj61 institution: name: R Foundation for Statistical Computing - website: https://ror.org/05qewa988 address: Vienna, Austria year: '2026' - doi: 10.32614/R.manuals version: '>= 4.0.0' - type: software title: assertthat @@ -280,8 +277,51 @@ references: authors: - family-names: Smyth given-names: Gordon - email: smyth@wehi.edu.au - orcid: https://orcid.org/0000-0001-9221-2892 + - family-names: Hu + given-names: Yifang + - family-names: Ritchie + given-names: Matthew + - family-names: Silver + given-names: Jeremy + - family-names: Wettenhall + given-names: James + - family-names: McCarthy + given-names: Davis + - family-names: Wu + given-names: Di + - family-names: Shi + given-names: Wei + - family-names: Phipson + given-names: Belinda + - family-names: Lun + given-names: Aaron + - family-names: Thorne + given-names: Natalie + - family-names: Oshlack + given-names: Alicia + - family-names: Graaf + given-names: Carolyn + name-particle: de + - family-names: Chen + given-names: Yunshun + - family-names: Giner + given-names: Goknur + - family-names: Langaas + given-names: Mette + - family-names: Ferkingstad + given-names: Egil + - family-names: Davy + given-names: Marcus + - family-names: Pepin + given-names: Francois + - family-names: Choi + given-names: Dongseok + - family-names: Law + given-names: Charity + - family-names: Li + given-names: Mengbo + - family-names: Chen + given-names: Lizhong year: '2026' doi: 10.18129/B9.bioc.limma - type: software @@ -303,13 +343,10 @@ references: notes: Imports authors: - name: R Core Team - website: https://ror.org/02zz1nj61 institution: name: R Foundation for Statistical Computing - website: https://ror.org/05qewa988 address: Vienna, Austria year: '2026' - doi: 10.32614/R.manuals - type: software title: options abstract: 'options: Simple, Consistent Package Options' @@ -432,13 +469,10 @@ references: notes: Imports authors: - name: R Core Team - website: https://ror.org/02zz1nj61 institution: name: R Foundation for Statistical Computing - website: https://ror.org/05qewa988 address: Vienna, Austria year: '2026' - doi: 10.32614/R.manuals - type: software title: stringr abstract: 'stringr: Simple, Consistent Wrappers for Common String Operations' @@ -679,7 +713,7 @@ references: authors: - family-names: Auguie given-names: Baptiste - email: baptiste.auguie@gmail.com + email: baptiste.auguie@vuw.ac.nz year: '2026' doi: 10.32614/CRAN.package.gridExtra - type: software diff --git a/code/MOSuite/DESCRIPTION b/code/MOSuite/DESCRIPTION index 8f728ed..a274476 100644 --- a/code/MOSuite/DESCRIPTION +++ b/code/MOSuite/DESCRIPTION @@ -1,6 +1,6 @@ Package: MOSuite Title: R package for differential multi-omics analysis -Version: 0.3.2 +Version: 0.3.2.9000 Authors@R: c( person("Kelly", "Sovacool", , "kelly.sovacool@nih.gov", role = c("aut", "cre"), comment = c(ORCID = "0000-0003-3283-829X")), diff --git a/code/MOSuite/NEWS.md b/code/MOSuite/NEWS.md index a1c1f01..d73be30 100644 --- a/code/MOSuite/NEWS.md +++ b/code/MOSuite/NEWS.md @@ -1,3 +1,10 @@ +## MOSuite development version + +- Align plot color defaults with the MOSuite Code Ocean capsule defaults while preserving fallback random colors when a requested palette is too short. +- Improve top and bottom legend wrapping and automatic legend text sizing for PCA and histogram plots with many or long labels. +- Align PCA and histogram plotting defaults used by `filter_counts()`, `normalize_counts()`, and `batch_correct_counts()` with the affected Code Ocean capsules: MOSuite-filter-counts, MOSuite-normalize-counts, MOSuite-plot-pca-2D, and MOSuite-plot-pca-3D. +- Add optional group coloring to `plot_read_depth()` and the `clean_raw_counts()` read-depth output while preserving the single-color default when no group is selected. + ## 0.3.2 - Support ggplot2 v4.0.0 for correlation heatmap plots in `filter_counts()`, `normalize_counts()`, and `batch_correct_counts()`. (#205, @copilot, @kelly-sovacool) diff --git a/code/MOSuite/R/batch-correction.R b/code/MOSuite/R/batch-correction.R index bcbf63e..0cd8165 100644 --- a/code/MOSuite/R/batch-correction.R +++ b/code/MOSuite/R/batch-correction.R @@ -55,9 +55,40 @@ batch_correct_counts <- function( covariates_colnames = "Group", batch_colname = "Batch", label_colname = NULL, - colors_for_plots = NULL, + samples_to_rename = c(""), + add_label_to_pca = TRUE, + principal_component_on_x_axis = 1, + principal_component_on_y_axis = 2, + legend_position_for_pca = "top", + label_offset_x_ = 2, + label_offset_y_ = 2, + label_font_size = 3, + point_size_for_pca = 3, + color_histogram_by_group = TRUE, + set_min_max_for_x_axis_for_histogram = FALSE, + minimum_for_x_axis_for_histogram = -1, + maximum_for_x_axis_for_histogram = 1, + legend_font_size_for_histogram = NULL, + legend_position_for_histogram = "top", + number_of_histogram_legend_columns = 6, + plot_corr_matrix_heatmap = TRUE, + colors_for_plots = c( + "#5954d6", + "#e1562c", + "#b80058", + "#00c6f8", + "#d163e6", + "#00a76c", + "#ff9287", + "#008cf9", + "#006e00", + "#796880", + "#FFA500", + "#878500" + ), print_plots = options::opt("print_plots"), save_plots = options::opt("save_plots"), + interactive_plots = FALSE, plots_subdir = "batch" ) { abort_packages_not_installed("sva") @@ -150,6 +181,11 @@ batch_correct_counts <- function( if (is.null(colors_for_plots)) { colors_for_plots <- moo@analyses[["colors"]][[batch_colname]] } + if (isTRUE(color_histogram_by_group)) { + colors_for_histogram <- colors_for_plots + } else { + colors_for_histogram <- moo@analyses[["colors"]][[label_colname]] + } pca_plot <- plot_pca( combat_edata, sample_metadata = sample_metadata, @@ -157,7 +193,18 @@ batch_correct_counts <- function( feature_id_colname = feature_id_colname, group_colname = batch_colname, label_colname = label_colname, + samples_to_rename = samples_to_rename, color_values = colors_for_plots, + principal_components = c( + principal_component_on_x_axis, + principal_component_on_y_axis + ), + legend_position = legend_position_for_pca, + point_size = point_size_for_pca, + add_label = add_label_to_pca, + label_font_size = label_font_size, + label_offset_y_ = label_offset_y_, + label_offset_x_ = label_offset_x_, print_plots = FALSE, save_plots = FALSE ) + @@ -170,39 +217,66 @@ batch_correct_counts <- function( feature_id_colname = feature_id_colname, group_colname = batch_colname, label_colname = label_colname, - color_values = colors_for_plots, - color_by_group = TRUE + color_values = colors_for_histogram, + color_by_group = color_histogram_by_group, + set_min_max_for_x_axis = set_min_max_for_x_axis_for_histogram, + minimum_for_x_axis = minimum_for_x_axis_for_histogram, + maximum_for_x_axis = maximum_for_x_axis_for_histogram, + legend_position = legend_position_for_histogram, + legend_font_size = legend_font_size_for_histogram, + number_of_legend_columns = number_of_histogram_legend_columns ) + ggplot2::labs(caption = "batch-corrected counts") - corHM_plot <- plot_corr_heatmap( - combat_edata, - sample_metadata = sample_metadata, - sample_id_colname = sample_id_colname, - feature_id_colname = feature_id_colname, - group_colname = batch_colname, - label_colname = label_colname, - color_values = colors_for_plots - ) + if (isTRUE(plot_corr_matrix_heatmap)) { + corHM_plot <- plot_corr_heatmap( + combat_edata, + sample_metadata = sample_metadata, + sample_id_colname = sample_id_colname, + feature_id_colname = feature_id_colname, + group_colname = batch_colname, + label_colname = label_colname, + color_values = colors_for_plots + ) + print_or_save_plot( + corHM_plot, + filename = file.path(plots_subdir, "corr_heatmap.png"), + print_plots = print_plots, + save_plots = save_plots, + caption = "batch-corrected counts" + ) + } - print_or_save_plot( - pca_plot, - filename = file.path(plots_subdir, "pca.png"), - print_plots = print_plots, - save_plots = save_plots - ) + plot_ext <- "png" + if (isTRUE(interactive_plots)) { + pca_plot <- pca_plot |> plotly::ggplotly(tooltip = c("sample", "group")) + hist_plot <- (hist_plot + ggplot2::theme(legend.position = "none")) |> + plotly::ggplotly(tooltip = c("sample")) + plot_ext <- "html" + } + if (identical(plot_ext, "png")) { + print_or_save_plot( + pca_plot, + filename = file.path(plots_subdir, glue::glue("pca.{plot_ext}")), + print_plots = print_plots, + save_plots = save_plots, + width = 7, + height = 7, + units = "in" + ) + } else { + print_or_save_plot( + pca_plot, + filename = file.path(plots_subdir, glue::glue("pca.{plot_ext}")), + print_plots = print_plots, + save_plots = save_plots + ) + } print_or_save_plot( hist_plot, - filename = file.path(plots_subdir, "histogram.png"), + filename = file.path(plots_subdir, glue::glue("histogram.{plot_ext}")), print_plots = print_plots, save_plots = save_plots ) - print_or_save_plot( - corHM_plot, - filename = file.path(plots_subdir, "corr_heatmap.png"), - print_plots = print_plots, - save_plots = save_plots, - caption = "batch-corrected counts" - ) } message(glue::glue( diff --git a/code/MOSuite/R/clean.R b/code/MOSuite/R/clean.R index d14b2ca..7c5c730 100644 --- a/code/MOSuite/R/clean.R +++ b/code/MOSuite/R/clean.R @@ -9,6 +9,10 @@ #' @inheritParams filter_counts #' @inheritParams option_params #' +#' @param group_colname sample metadata column used to color the read-depth plot. Leave blank to use the current +#' single-color bar fill. +#' @param colors_for_plots colors used when `group_colname` is supplied. Named vectors are matched to group values; +#' unnamed vectors follow group order and are extended with MOSuite colors when too few colors are supplied. #' @param cleanup_column_names Invalid raw counts column names can cause errors #' in the downstream analysis. If this is `TRUE`, any invalid column names #' will be automatically altered to a correct format. These format changes @@ -55,6 +59,21 @@ clean_raw_counts <- function( sample_id_colname = NULL, feature_id_colname = NULL, samples_to_rename = "", + group_colname = "", + colors_for_plots = c( + "#5954d6", + "#e1562c", + "#b80058", + "#00c6f8", + "#d163e6", + "#00a76c", + "#ff9287", + "#008cf9", + "#006e00", + "#796880", + "#FFA500", + "#878500" + ), cleanup_column_names = TRUE, split_gene_name = TRUE, aggregate_rows_with_duplicate_gene_names = TRUE, @@ -74,7 +93,13 @@ clean_raw_counts <- function( } # Sample Read Counts Plot if (isTRUE(print_plots) || isTRUE(save_plots)) { - read_plot <- plot_read_depth(counts_dat) + read_plot <- plot_read_depth( + counts_dat, + sample_metadata = sample_metadata, + sample_id_colname = sample_id_colname, + group_colname = group_colname, + color_values = colors_for_plots + ) print_or_save_plot( read_plot, filename = file.path(plots_subdir, "read_depth.png"), diff --git a/code/MOSuite/R/colors.R b/code/MOSuite/R/colors.R index 291901f..969776b 100644 --- a/code/MOSuite/R/colors.R +++ b/code/MOSuite/R/colors.R @@ -32,13 +32,43 @@ get_random_colors <- function(num_colors, n = 2e3) { return(unname(colorspace::hex(colorspace::LAB(km$centers)))) } +get_mosuite_colors <- function(n, ...) { + colors <- c( + "#5954d6", + "#e1562c", + "#b80058", + "#00c6f8", + "#d163e6", + "#00a76c", + "#ff9287", + "#008cf9", + "#006e00", + "#796880", + "#FFA500", + "#878500" + ) + return(colors[seq_len(min(n, length(colors)))]) +} + +get_observed_values <- function(dat, colname) { + values <- dplyr::pull(dat, colname) + observed_values <- stats::na.omit(as.character(values)) + + if (is.factor(values)) { + return(levels(values)[levels(values) %in% observed_values]) + } + + return(unique(observed_values)) +} + #' Create named list of default colors for plotting #' #' @inheritParams create_multiOmicDataSet_from_dataframes #' -#' @param palette_fun Function for selecting colors. Assumed to contain `n` for the number of colors. Default: -#' `grDevices::palette.colors()` +#' @param palette_fun Function for selecting colors. Assumed to contain `n` for the number of colors. Defaults to +#' MOSuite's default plot palette. To use the previous R default palette behavior, pass +#' `grDevices::palette.colors`. #' @param ... additional arguments forwarded to `palette_fun` #' #' @returns named list, with each column in `sample_metadata` containing entry with a named vector of colors @@ -51,7 +81,7 @@ get_random_colors <- function(num_colors, n = 2e3) { #' } get_colors_lst <- function( sample_metadata, - palette_fun = grDevices::palette.colors, + palette_fun = get_mosuite_colors, ... ) { dat_colnames <- colnames(sample_metadata) @@ -71,18 +101,17 @@ get_colors_lst <- function( #' @inheritParams get_colors_lst #' @param dat data frame #' @param colname column name in `dat` -#' @returns named vector of colors for each unique observation in `dat$colname` +#' @returns named vector of colors for each unique observation in `dat$colname`. Factor columns use factor level order; +#' other columns use first-observed order. #' @export #' get_colors_vctr <- function( dat, colname, - palette_fun = grDevices::palette.colors, + palette_fun = get_mosuite_colors, ... ) { - obs <- dat |> - dplyr::pull(colname) |> - unique() + obs <- get_observed_values(dat, colname) n_obs <- length(obs) warned_cnd <- NULL @@ -118,6 +147,49 @@ get_colors_vctr <- function( return(colors_vctr) } +resolve_plot_colors <- function( + dat, + colname, + color_values = NULL, + palette_fun = get_mosuite_colors, + ... +) { + obs <- get_observed_values(dat, colname) + + if (length(obs) == 0) { + return(color_values) + } + + if (is.null(color_values)) { + return(get_colors_vctr(dat, colname, palette_fun = palette_fun, ...)) + } + + if (!is.null(names(color_values))) { + if (all(obs %in% names(color_values))) { + return(color_values) + } + } + + if (length(color_values) < length(obs)) { + n_missing <- length(obs) - length(color_values) + message(glue::glue( + "color_values contains {length(color_values)} colors for {length(obs)} values in column {colname}. Generating {n_missing} additional colors." + )) + generated_colors <- get_colors_vctr( + dat, + colname, + palette_fun = palette_fun, + ... + ) + color_values <- c( + unname(color_values), + unname(generated_colors)[seq.int(length(color_values) + 1, length(obs))] + ) + } + + return(stats::setNames(unname(color_values)[seq_along(obs)], obs)) +} + #' Set color palette for a single group/column #' #' This allows you to set custom palettes individually for groups in the dataset @@ -143,7 +215,7 @@ get_colors_vctr <- function( set_color_pal <- S7::new_generic( "set_color_pal", "moo", - function(moo, colname, palette_fun = grDevices::palette.colors, ...) { + function(moo, colname, palette_fun = get_mosuite_colors, ...) { return(S7::S7_dispatch()) } ) @@ -151,7 +223,7 @@ set_color_pal <- S7::new_generic( S7::method(set_color_pal, multiOmicDataSet) <- function( moo, colname, - palette_fun = grDevices::palette.colors, + palette_fun = get_mosuite_colors, ... ) { moo@analyses[["colors"]][[colname]] <- get_colors_vctr( diff --git a/code/MOSuite/R/filter.R b/code/MOSuite/R/filter.R index b520ef1..b63da69 100644 --- a/code/MOSuite/R/filter.R +++ b/code/MOSuite/R/filter.R @@ -66,16 +66,21 @@ #' renaming. Use the following format to describe which old name (in your sample metadata table) you want to rename to #' which new name: old_name: new_name #' @param color_histogram_by_group Set to FALSE to label histogram by Sample Names, or set to TRUE to label histogram by -#' the column you select in the "Group Column Used to Color Histogram" parameter (below). Default is FALSE. +#' the column you select in the "Group Column Used to Color Histogram" parameter (below). Default is TRUE. #' @param set_min_max_for_x_axis_for_histogram whether to set min/max value for histogram x-axis #' @param minimum_for_x_axis_for_histogram x-axis minimum for histogram plot #' @param maximum_for_x_axis_for_histogram x-axis maximum for histogram plot #' @param legend_position_for_histogram legend position for the histogram plot. consider setting to 'none' for a large #' number of samples. -#' @param legend_font_size_for_histogram legend font size for the histogram plot +#' @param legend_font_size_for_histogram legend font size for the histogram plot. If `NULL`, the size is scaled automatically. #' @param number_of_histogram_legend_columns number of columns for the histogram legend #' @param colors_for_plots Colors for the PCA and histogram will be picked, in order, from this list. -#' Colors must either be names in `grDevices::colors()` or valid hex codes. +#' Colors must either be names in `grDevices::colors()` or valid hex codes. Defaults to the MOSuite palette returned +#' by `get_mosuite_colors()`. Unnamed colors are assigned by factor level order when the grouping column is a factor; +#' otherwise, they follow the order in which groups first appear in the metadata column. If more groups are present +#' than colors provided, +#' supplied colors are used first and additional colors are generated from the selected palette for the remaining +#' groups; random colors are used only if that palette returns fewer colors than the number of groups. #' @param plot_corr_matrix_heatmap Datasets with a large number of samples may be too large to create a correlation #' matrix heatmap. If this function takes longer than 5 minutes to run, Set to `FALSE` and the correlation matrix will #' not be be created. Default is `TRUE`. @@ -116,20 +121,33 @@ filter_counts <- function( principal_component_on_x_axis = 1, principal_component_on_y_axis = 2, legend_position_for_pca = "top", - point_size_for_pca = 1, + point_size_for_pca = 3, add_label_to_pca = TRUE, label_font_size = 3, label_offset_y_ = 2, label_offset_x_ = 2, samples_to_rename = c(""), - color_histogram_by_group = FALSE, + color_histogram_by_group = TRUE, set_min_max_for_x_axis_for_histogram = FALSE, minimum_for_x_axis_for_histogram = -1, maximum_for_x_axis_for_histogram = 1, legend_position_for_histogram = "top", - legend_font_size_for_histogram = 10, + legend_font_size_for_histogram = NULL, number_of_histogram_legend_columns = 6, - colors_for_plots = NULL, + colors_for_plots = c( + "#5954d6", + "#e1562c", + "#b80058", + "#00c6f8", + "#d163e6", + "#00a76c", + "#ff9287", + "#008cf9", + "#006e00", + "#796880", + "#FFA500", + "#878500" + ), plot_corr_matrix_heatmap = TRUE, print_plots = options::opt("print_plots"), save_plots = options::opt("save_plots"), @@ -181,8 +199,6 @@ filter_counts <- function( # use consistent colors if (is.null(colors_for_plots)) { colors_for_plots <- moo@analyses[["colors"]][[group_colname]] - } else { - colors_for_plots <- as.vector(colors_for_plots) } if (isTRUE(color_histogram_by_group)) { colors_for_histogram <- colors_for_plots @@ -264,12 +280,24 @@ filter_counts <- function( plotly::ggplotly(tooltip = c("sample")) plot_ext <- "html" } - print_or_save_plot( - pca_plot, - filename = file.path(plots_subdir, glue::glue("pca.{plot_ext}")), - print_plots = print_plots, - save_plots = save_plots - ) + if (identical(plot_ext, "png")) { + print_or_save_plot( + pca_plot, + filename = file.path(plots_subdir, glue::glue("pca.{plot_ext}")), + print_plots = print_plots, + save_plots = save_plots, + width = 7, + height = 7, + units = "in" + ) + } else { + print_or_save_plot( + pca_plot, + filename = file.path(plots_subdir, glue::glue("pca.{plot_ext}")), + print_plots = print_plots, + save_plots = save_plots + ) + } print_or_save_plot( hist_plot, filename = file.path(plots_subdir, glue::glue("histogram.{plot_ext}")), diff --git a/code/MOSuite/R/normalize.R b/code/MOSuite/R/normalize.R index 4823217..697bb12 100644 --- a/code/MOSuite/R/normalize.R +++ b/code/MOSuite/R/normalize.R @@ -45,16 +45,29 @@ normalize_counts <- function( label_offset_x_ = 2, label_offset_y_ = 2, label_font_size = 3, - point_size_for_pca = 8, + point_size_for_pca = 3, color_histogram_by_group = TRUE, set_min_max_for_x_axis_for_histogram = FALSE, minimum_for_x_axis_for_histogram = -1, maximum_for_x_axis_for_histogram = 1, - legend_font_size_for_histogram = 10, + legend_font_size_for_histogram = NULL, legend_position_for_histogram = "top", number_of_histogram_legend_columns = 6, plot_corr_matrix_heatmap = TRUE, - colors_for_plots = NULL, + colors_for_plots = c( + "#5954d6", + "#e1562c", + "#b80058", + "#00c6f8", + "#d163e6", + "#00a76c", + "#ff9287", + "#008cf9", + "#006e00", + "#796880", + "#FFA500", + "#878500" + ), print_plots = options::opt("print_plots"), save_plots = options::opt("save_plots"), interactive_plots = FALSE, @@ -143,6 +156,9 @@ normalize_counts <- function( color_values = colors_for_histogram, color_by_group = color_histogram_by_group, x_axis_label = "Normalized Counts", + set_min_max_for_x_axis = set_min_max_for_x_axis_for_histogram, + minimum_for_x_axis = minimum_for_x_axis_for_histogram, + maximum_for_x_axis = maximum_for_x_axis_for_histogram, legend_position = legend_position_for_histogram, legend_font_size = legend_font_size_for_histogram, number_of_legend_columns = number_of_histogram_legend_columns @@ -171,7 +187,10 @@ normalize_counts <- function( pca_plot, filename = file.path(plots_subdir, "pca.png"), print_plots = print_plots, - save_plots = save_plots + save_plots = save_plots, + width = 7, + height = 7, + units = "in" ) print_or_save_plot( hist_plot, diff --git a/code/MOSuite/R/plot_heatmap.R b/code/MOSuite/R/plot_heatmap.R index 879f0ee..552822f 100644 --- a/code/MOSuite/R/plot_heatmap.R +++ b/code/MOSuite/R/plot_heatmap.R @@ -105,7 +105,9 @@ S7::method(plot_corr_heatmap, multiOmicDataSet) <- function( #' labels to display on your figure (e.g. shorter labels are sometimes preferred on plots). In that case, select the #' column with your preferred Labels here. The selected column should contain unique names for each sample. (Default: #' `NULL` -- `sample_id_colname` will be used.) -#' @param color_values vector of colors as hex values or names recognized by R +#' @param color_values vector of colors as hex values or names recognized by R. Unnamed colors are assigned by factor +#' level order when the grouping column is a factor; otherwise, they are assigned in the order groups first appear in +#' the metadata column. #' #' @rdname plot_corr_heatmap-data.frame #' @aliases plot_corr_heatmap.data.frame @@ -154,20 +156,8 @@ S7::method(plot_corr_heatmap, S7::class_data.frame) <- function( # cannot set rownames on a tibble sample_metadata <- sample_metadata |> as.data.frame() rownames(sample_metadata) <- sample_metadata[[label_colname]] - annoVal <- lapply(group_colname, function(x) { - # TODO this only works on dataframes, not tibbles - out <- as.factor(sample_metadata |> dplyr::pull(x)) |> levels() - # names(out)=x - return(out) - }) |> - unlist() - col <- color_values[seq_along(annoVal)] - names(col) <- annoVal - cols <- lapply(group_colname, function(x) { - ax <- as.factor(sample_metadata |> dplyr::pull(x)) |> levels() - out <- col[ax] - return(out) + return(resolve_plot_colors(sample_metadata, x, color_values)) }) names(cols) <- (group_colname) @@ -255,7 +245,9 @@ S7::method(plot_corr_heatmap, S7::class_data.frame) <- function( #' labels to display on your figure (e.g. shorter labels are sometimes preferred on plots). In that case, select the #' column with your preferred Labels here. The selected column should contain unique names for each sample. (Default: #' `NULL` -- `sample_id_colname` will be used.) -#' @param color_values vector of colors as hex values or names recognized by R +#' @param color_values vector of colors as hex values or names recognized by R. Unnamed colors are assigned by factor +#' level order when the grouping column is a factor; otherwise, they are assigned in the order groups first appear in +#' the metadata column. #' @param samples_to_include Which samples would you like to include? Usually, you will choose all sample columns, or #' you could choose to remove certain samples. Samples excluded here will be removed in this step and from further #' analysis downstream of this step. (Default: `NULL` - all sample IDs in `moo@sample_meta` will be used.) @@ -1026,24 +1018,37 @@ S7::method(plot_expr_heatmap, S7::class_data.frame) <- function( annotation_col[] <- lapply(annotation_col, factor) x <- length(unlist(lapply(annotation_col, levels))) if (x > length(group_colors)) { - k <- x - length(group_colors) - more_cols <- get_random_colors(k) + generated_group_colors <- get_colors_vctr( + data.frame(group_color_index = seq_len(x)), + "group_color_index" + ) + more_cols <- unname(generated_group_colors)[seq.int( + length(group_colors) + 1, + x + )] group_colors <- c(group_colors, more_cols) } rownames(annotation_col) <- annot[[label_colname]] annot_col <- list() - b <- 1 - i <- 1 - while (i <= length(group_columns)) { - cnam <- group_columns[i] - grp <- as.factor(annotation_col[, i]) - c <- b + length(levels(grp)) - 1 - col <- group_colors[b:c] - names(col) <- levels(grp) - assign(cnam, col) - annot_col <- append(annot_col, mget(cnam)) - b <- c + 1 - i <- i + 1 + next_color <- 1 + for (cnam in group_columns) { + group_levels <- stats::na.omit(unique(annot[[cnam]])) + + if ( + !is.null(names(group_colors)) && + all(as.character(group_levels) %in% names(group_colors)) + ) { + col <- resolve_plot_colors(annot, cnam, group_colors) + } else { + color_slice <- group_colors[ + next_color:(next_color + length(group_levels) - 1) + ] + col <- resolve_plot_colors(annot, cnam, color_slice) + } + + next_color <- next_color + length(group_levels) + + annot_col[[cnam]] <- col } if (assign_group_colors == TRUE) { diff --git a/code/MOSuite/R/plot_histogram.R b/code/MOSuite/R/plot_histogram.R index 8dd3087..9f93a6d 100644 --- a/code/MOSuite/R/plot_histogram.R +++ b/code/MOSuite/R/plot_histogram.R @@ -107,7 +107,9 @@ S7::method(plot_histogram, multiOmicDataSet) <- function( #' labels to display on your figure (e.g. shorter labels are sometimes preferred on plots). In that case, select the #' column with your preferred Labels here. The selected column should contain unique names for each sample. (Default: #' `NULL` -- `sample_id_colname` will be used.) -#' @param color_values vector of colors as hex values or names recognized by R +#' @param color_values vector of colors as hex values or names recognized by R. Unnamed colors are assigned by factor +#' level order when the grouping column is a factor; otherwise, they follow the order in which groups first appear in +#' the metadata column. #' @param color_by_group Set to FALSE to label histogram by Sample Names, or set to TRUE to label histogram by the #' column you select in the "Group Column Used to Color Histogram" parameter (below). Default is FALSE. #' @param set_min_max_for_x_axis whether to override the default for `ggplot2::xlim()` (default: `FALSE`) @@ -116,7 +118,8 @@ S7::method(plot_histogram, multiOmicDataSet) <- function( #' @param x_axis_label text label for the x axis `ggplot2::xlab()` #' @param y_axis_label text label for the y axis `ggplot2::ylab()` #' @param legend_position passed to in `legend.position` `ggplot2::theme()` -#' @param legend_font_size passed to `ggplot2::element_text()` via `ggplot2::theme()` +#' @param legend_font_size passed to `ggplot2::element_text()` via `ggplot2::theme()`. If `NULL`, the size is scaled +#' automatically based on the number and length of legend labels. #' @param number_of_legend_columns passed to `ncol` in `ggplot2::guide_legend()` #' @param interactive_plots set to TRUE to make the plot interactive with `plotly`, allowing you to hover your mouse #' over a point or line to view sample information. The similarity heat map will not display if this toggle is set to @@ -174,7 +177,7 @@ S7::method(plot_histogram, S7::class_data.frame) <- function( x_axis_label = "Counts", y_axis_label = "Density", legend_position = "top", - legend_font_size = 10, + legend_font_size = NULL, number_of_legend_columns = 6, interactive_plots = FALSE, ... @@ -205,15 +208,13 @@ S7::method(plot_histogram, S7::class_data.frame) <- function( } if (color_by_group == TRUE) { + df_long <- df_long |> + dplyr::filter(!is.na(!!rlang::sym(group_colname))) + color_values <- resolve_plot_colors(df_long, group_colname, color_values) df_long <- df_long |> dplyr::mutate( - !!rlang::sym(group_colname) := as.factor(!!rlang::sym(group_colname)) - ) |> - dplyr::filter(!is.na(group_colname)) - n <- df_long |> - dplyr::pull(group_colname) |> - levels() |> - length() + !!rlang::sym(group_colname) := as.character(!!rlang::sym(group_colname)) + ) # plot Density hist_plot <- df_long |> @@ -226,10 +227,17 @@ S7::method(plot_histogram, S7::class_data.frame) <- function( linewidth = 1 ) } else { - n <- df_long |> - dplyr::pull(sample_id_colname) |> - unique() |> - length() + color_values <- resolve_plot_colors( + df_long, + sample_id_colname, + color_values + ) + df_long <- df_long |> + dplyr::mutate( + !!rlang::sym(sample_id_colname) := as.character( + !!rlang::sym(sample_id_colname) + ) + ) hist_plot <- df_long |> ggplot2::ggplot(ggplot2::aes( @@ -242,6 +250,11 @@ S7::method(plot_histogram, S7::class_data.frame) <- function( ) } + legend_font_size <- get_legend_text_size( + names(color_values), + legend_font_size + ) + hist_plot <- hist_plot + ggplot2::xlab(x_axis_label) + ggplot2::ylab(y_axis_label) + @@ -264,10 +277,15 @@ S7::method(plot_histogram, S7::class_data.frame) <- function( ggplot2::ggtitle("Frequency Histogram") + ggplot2::xlim(xmin, xmax) + # scale_linetype_manual(values=rep(c('solid', 'dashed','dotted','twodash'),n)) + - ggplot2::scale_colour_manual(values = color_values[1:n]) + - ggplot2::guides( - linetype = ggplot2::guide_legend(ncol = number_of_legend_columns) - ) + ggplot2::scale_colour_manual(values = color_values) + + hist_plot <- add_colour_legend_layout( + hist_plot, + labels = names(color_values), + legend_position = legend_position, + ncol = number_of_legend_columns, + legend_text_size = legend_font_size + ) if (isTRUE(interactive_plots)) { hist_plot <- (hist_plot + ggplot2::theme(legend.position = "none")) |> diff --git a/code/MOSuite/R/plot_pca.R b/code/MOSuite/R/plot_pca.R index d419b80..0e1562b 100644 --- a/code/MOSuite/R/plot_pca.R +++ b/code/MOSuite/R/plot_pca.R @@ -156,8 +156,9 @@ plot_pca_2d <- S7::new_generic( ), principal_components = c(1, 2), legend_position = "top", - point_size = 1, + point_size = 3, add_label = TRUE, + legend_font_size = NULL, label_font_size = 3, label_offset_x_ = 2, label_offset_y_ = 2, @@ -198,8 +199,9 @@ S7::method(plot_pca_2d, multiOmicDataSet) <- function( ), principal_components = c(1, 2), legend_position = "top", - point_size = 1, + point_size = 3, add_label = TRUE, + legend_font_size = NULL, label_font_size = 3, label_offset_x_ = 2, label_offset_y_ = 2, @@ -223,6 +225,7 @@ S7::method(plot_pca_2d, multiOmicDataSet) <- function( legend_position = legend_position, point_size = point_size, add_label = add_label, + legend_font_size = legend_font_size, label_font_size = label_font_size, label_offset_x_ = label_offset_x_, label_offset_y_ = label_offset_y_, @@ -261,11 +264,23 @@ S7::method(plot_pca_2d, multiOmicDataSet) <- function( #' parameter to rename samples manually for display on the PCA plot. Use "Add item" to add each additional sample for #' renaming. Use the following format to describe which old name (in your sample metadata table) you want to rename to #' which new name: old_name: new_name -#' @param color_values vector of colors as hex values or names recognized by R +#' @param color_values vector of colors as hex values or names recognized by R. Unnamed colors are assigned by factor +#' level order when the grouping column is a factor; otherwise, they follow the order in which groups first appear in +#' the metadata column. #' @param principal_components vector with numbered principal components to plot #' @param legend_position passed to in `legend.position` `ggplot2::theme()` #' @param point_size size for `ggplot2::geom_point()` #' @param add_label whether to add text labels for the points +#' @param legend_font_size font size for the PCA legend text. If `NULL`, the size is scaled automatically based on the +#' number and length of legend labels. +#' @param count_type the type of counts to use when `moo_counts` is a `multiOmicDataSet`; ignored for data frame input. +#' @param sub_count_type used when `count_type` refers to a list of count matrices; ignored for data frame input. +#' @param label_font_size font size for text labels on the PCA plot. +#' @param label_offset_x_ horizontal offset for text labels on the PCA plot. +#' @param label_offset_y_ vertical offset for text labels on the PCA plot. +#' @param interactive_plots set to TRUE to make the PCA plot interactive with `plotly`. +#' @param plots_subdir subdirectory in `figures/` where PCA plots are saved. +#' @param plot_filename output filename for the PCA plot image. #' #' @return ggplot object #' @@ -299,8 +314,9 @@ S7::method(plot_pca_2d, S7::class_data.frame) <- function( ), principal_components = c(1, 2), legend_position = "top", - point_size = 1, + point_size = 3, add_label = TRUE, + legend_font_size = NULL, label_font_size = 3, label_offset_x_ = 2, label_offset_y_ = 2, @@ -346,6 +362,11 @@ S7::method(plot_pca_2d, S7::class_data.frame) <- function( ) prin_comp_x <- principal_components[1] prin_comp_y <- principal_components[2] + color_values <- resolve_plot_colors(pca_wide, group_colname, color_values) + legend_font_size <- get_legend_text_size( + names(color_values), + legend_font_size + ) # plot PCA pca_plot <- pca_wide |> dplyr::mutate( @@ -375,13 +396,20 @@ S7::method(plot_pca_2d, S7::class_data.frame) <- function( linewidth = 1 ), axis.ticks = ggplot2::element_line(linewidth = 1), - legend.text = ggplot2::element_text(size = 18) + legend.text = ggplot2::element_text(size = legend_font_size), + aspect.ratio = 1 ) + - ggplot2::coord_fixed(ratio = 1.5) + ggplot2::scale_colour_manual(values = color_values) + ggplot2::xlab(get_pc_percent_lab(pca_df, prin_comp_x)) + ggplot2::ylab(get_pc_percent_lab(pca_df, prin_comp_y)) + pca_plot <- add_colour_legend_layout( + pca_plot, + labels = names(color_values), + legend_position = legend_position, + legend_text_size = legend_font_size + ) + if (add_label == TRUE) { abort_packages_not_installed("ggrepel") pca_plot <- pca_plot + @@ -401,12 +429,24 @@ S7::method(plot_pca_2d, S7::class_data.frame) <- function( plotly::ggplotly(tooltip = c(sample_id_colname, group_colname)) } - print_or_save_plot( - pca_plot, - filename = file.path(plots_subdir, plot_filename), - print_plots = print_plots, - save_plots = save_plots - ) + if (inherits(pca_plot, "ggplot")) { + print_or_save_plot( + pca_plot, + filename = file.path(plots_subdir, plot_filename), + print_plots = print_plots, + save_plots = save_plots, + width = 7, + height = 7, + units = "in" + ) + } else { + print_or_save_plot( + pca_plot, + filename = file.path(plots_subdir, plot_filename), + print_plots = print_plots, + save_plots = save_plots + ) + } return(pca_plot) } @@ -527,7 +567,9 @@ S7::method(plot_pca_3d, multiOmicDataSet) <- function( #' @param group_colname The column from sample metadata containing sample group information. #' @param label_colname The column from sample metadata containing sample labels. #' @param label_font_size font size used for labels in the interactive figure. -#' @param color_values vector of colors as hex values or names recognized by R. +#' @param color_values vector of colors as hex values or names recognized by R. Unnamed colors are assigned by factor +#' level order when the grouping column is a factor; otherwise, they follow the order in which groups first appear in +#' the metadata column. #' @param plot_filename output filename when saving plots. #' @param print_plots whether to print plot to the active graphics device. #' @param save_plots whether to save plot to disk. @@ -613,6 +655,7 @@ S7::method(plot_pca_3d, S7::class_data.frame) <- function( prin_comp_x <- principal_components[1] prin_comp_y <- principal_components[2] prin_comp_z <- principal_components[3] + color_values <- resolve_plot_colors(pca_wide, group_colname, color_values) fig <- plotly::plot_ly( pca_wide, diff --git a/code/MOSuite/R/plot_read_depth.R b/code/MOSuite/R/plot_read_depth.R index f78d073..8313cfc 100644 --- a/code/MOSuite/R/plot_read_depth.R +++ b/code/MOSuite/R/plot_read_depth.R @@ -56,6 +56,10 @@ plot_read_depth <- S7::new_generic( #' @param count_type the type of counts to use. Must be a name in the counts slot (`names(moo@counts)`). #' @param sub_count_type used if `count_type` is a list in the counts slot: specify the sub count type within the list. #' Must be a name in `names(moo@counts[[count_type]])`. +#' @param sample_id_colname column in sample metadata containing sample IDs. +#' @param group_colname sample metadata column used to color bars. Leave blank to use the current single-color bar fill. +#' @param color_values colors used when `group_colname` is supplied. Named vectors are matched to group values; +#' unnamed vectors follow group order and are extended with MOSuite colors when too few colors are supplied. #' #' @return ggplot barplot #' @@ -78,10 +82,38 @@ S7::method(plot_read_depth, multiOmicDataSet) <- function( moo_counts, count_type, sub_count_type = NULL, + sample_id_colname = NULL, + group_colname = "", + color_values = c( + "#5954d6", + "#e1562c", + "#b80058", + "#00c6f8", + "#d163e6", + "#00a76c", + "#ff9287", + "#008cf9", + "#006e00", + "#796880", + "#FFA500", + "#878500" + ), ... ) { counts_dat <- extract_counts(moo_counts, count_type, sub_count_type) - return(plot_read_depth(counts_dat, ...)) + color_by_group <- !is.null(group_colname) && trimws(group_colname) != "" + if (!isTRUE(color_by_group)) { + return(plot_read_depth(counts_dat, ...)) + } + + return(plot_read_depth( + counts_dat, + sample_metadata = moo_counts@sample_meta, + sample_id_colname = sample_id_colname, + group_colname = group_colname, + color_values = color_values, + ... + )) } #' Plot read depth for `data.frame` @@ -90,6 +122,11 @@ S7::method(plot_read_depth, multiOmicDataSet) <- function( #' @aliases plot_read_depth.data.frame #' @usage NULL #' +#' @param sample_metadata sample metadata dataframe, required when `group_colname` is supplied. +#' @param sample_id_colname column in sample metadata containing sample IDs. +#' @param group_colname sample metadata column used to color bars. Leave blank to use the current single-color bar fill. +#' @param color_values colors used when `group_colname` is supplied. Named vectors are matched to group values; +#' unnamed vectors follow group order and are extended with MOSuite colors when too few colors are supplied. #' @param ... additional arguments (ignored; accepted for compatibility with the moo dispatch) #' #' @return ggplot barplot @@ -100,7 +137,27 @@ S7::method(plot_read_depth, multiOmicDataSet) <- function( #' #' @seealso [plot_read_depth()] generic #' @family plotters for counts dataframes -S7::method(plot_read_depth, S7::class_data.frame) <- function(moo_counts, ...) { +S7::method(plot_read_depth, S7::class_data.frame) <- function( + moo_counts, + sample_metadata = NULL, + sample_id_colname = NULL, + group_colname = "", + color_values = c( + "#5954d6", + "#e1562c", + "#b80058", + "#00c6f8", + "#d163e6", + "#00a76c", + "#ff9287", + "#008cf9", + "#006e00", + "#796880", + "#FFA500", + "#878500" + ), + ... +) { sample_names <- column_sums <- NULL counts_dat <- moo_counts sum_df <- counts_dat |> @@ -111,12 +168,48 @@ S7::method(plot_read_depth, S7::class_data.frame) <- function(moo_counts, ...) { values_to = "column_sums" ) + color_by_group <- !is.null(group_colname) && trimws(group_colname) != "" + if (color_by_group) { + if (is.null(sample_metadata)) { + stop("sample_metadata is required when group_colname is supplied") + } + if (is.null(sample_id_colname)) { + sample_id_colname <- colnames(sample_metadata)[1] + } + sum_df <- sum_df |> + dplyr::left_join( + sample_metadata, + by = stats::setNames(sample_id_colname, "sample_names") + ) |> + dplyr::filter(!is.na(!!rlang::sym(group_colname))) + color_values <- resolve_plot_colors(sum_df, group_colname, color_values) + sum_df <- sum_df |> + dplyr::mutate( + !!rlang::sym(group_colname) := as.character( + !!rlang::sym(group_colname) + ) + ) + } + # Plotting read_plot <- ggplot2::ggplot( sum_df, ggplot2::aes(x = sample_names, y = column_sums) - ) + - ggplot2::geom_bar(stat = "identity", fill = "blue") + + ) + + if (color_by_group) { + read_plot <- read_plot + + ggplot2::geom_bar( + ggplot2::aes(fill = !!rlang::sym(group_colname)), + stat = "identity" + ) + + ggplot2::scale_fill_manual(values = color_values) + } else { + read_plot <- read_plot + + ggplot2::geom_bar(stat = "identity", fill = "blue") + } + + read_plot <- read_plot + ggplot2::labs( title = "Total Reads per Sample", x = "Samples", diff --git a/code/MOSuite/R/plots.R b/code/MOSuite/R/plots.R index 1d5770e..03cd871 100644 --- a/code/MOSuite/R/plots.R +++ b/code/MOSuite/R/plots.R @@ -77,3 +77,132 @@ print_or_save_plot <- function( } return(invisible(filename)) } + +#' Compute a wrapped colour legend column count +#' +#' Computes a conservative number of legend columns for horizontal ggplot colour +#' legends. Top and bottom legends are wrapped based on the number of labels and +#' the longest label length. Other legend positions return `NULL` so their +#' existing ggplot layout is preserved. +#' +#' @param labels Character vector of legend labels. +#' @param legend_position Legend position passed to `ggplot2::theme()`. +#' @param ncol Optional maximum number of legend columns. +#' @param legend_text_size Legend text size used to scale the horizontal space +#' estimate. Larger legend text uses fewer columns. +#' @param max_label_characters_per_row Approximate total label characters to fit +#' on one horizontal legend row. +#' +#' @return Integer column count for top/bottom legends, or `NULL` when no +#' wrapping should be applied. +#' @keywords internal +#' @noRd +get_legend_column_count <- function( + labels, + legend_position = "top", + ncol = NULL, + legend_text_size = 10, + max_label_characters_per_row = 45 +) { + if (!legend_position %in% c("top", "bottom")) { + return(NULL) + } + + labels <- stats::na.omit(as.character(labels)) + if (length(labels) == 0) { + return(NULL) + } + + max_label_length <- max(nchar(labels), 1) + text_size_multiplier <- legend_text_size / 10 + columns_by_label_length <- max( + 1, + floor( + max_label_characters_per_row / (max_label_length * text_size_multiplier) + ) + ) + legend_columns <- min(length(labels), columns_by_label_length) + if (!is.null(ncol)) { + legend_columns <- min(ncol, legend_columns) + } + legend_columns +} + +#' Compute colour legend text size +#' +#' Computes legend text size from legend labels. Short legends keep the larger +#' default text used by simple group legends, while longer or denser legends are +#' scaled down. +#' +#' @param labels Character vector of legend labels. +#' @param legend_text_size Optional explicit legend text size. When supplied, +#' this value is returned unchanged. +#' @param min_legend_text_size Smallest automatically selected legend text size. +#' @param max_legend_text_size Largest automatically selected legend text size. +#' +#' @return Numeric legend text size. +#' @keywords internal +#' @noRd +get_legend_text_size <- function( + labels, + legend_text_size = NULL, + min_legend_text_size = 8, + max_legend_text_size = 18 +) { + if (!is.null(legend_text_size)) { + return(legend_text_size) + } + + labels <- stats::na.omit(as.character(labels)) + if (length(labels) == 0) { + return(max_legend_text_size) + } + + label_pressure <- max( + max(nchar(labels), 1) / 5, + length(labels) / 4, + 1 + ) + + max(min_legend_text_size, floor(max_legend_text_size / sqrt(label_pressure))) +} + +#' Add wrapped colour legend layout to a ggplot +#' +#' Applies a colour guide with a wrapped column count for top and bottom legends. +#' Left, right, and hidden legends are returned unchanged. +#' +#' @param plot A `ggplot2` plot object. +#' @inheritParams get_legend_column_count +#' +#' @return A `ggplot2` plot object with colour legend layout applied when needed. +#' @keywords internal +#' @noRd +add_colour_legend_layout <- function( + plot, + labels, + legend_position = "top", + ncol = NULL, + legend_text_size = 10, + max_label_characters_per_row = 45 +) { + legend_columns <- get_legend_column_count( + labels = labels, + legend_position = legend_position, + ncol = ncol, + legend_text_size = legend_text_size, + max_label_characters_per_row = max_label_characters_per_row + ) + + if (is.null(legend_columns)) { + return(plot) + } + + plot + + ggplot2::guides( + colour = ggplot2::guide_legend(ncol = legend_columns, byrow = TRUE) + ) + + ggplot2::theme( + legend.box = "vertical" + ) +} diff --git a/code/MOSuite/SECURITY.md b/code/MOSuite/SECURITY.md new file mode 100644 index 0000000..99da43f --- /dev/null +++ b/code/MOSuite/SECURITY.md @@ -0,0 +1,18 @@ +# NCI Standard Public Repository Security Policy + +## Maintained Versions + +Actively maintained versions of contained software will vary from repository to repository, or may not be relevant at all. +The developers of this repository will update this section if any actively maintained versions of the software need to be publicly disclosed. Otherwise, contact the developers directly for any version information. + +## Vulnerability Disclosure: + +### Option 1: HHS Vulnerability Disclosure + +Follow the instructions listed in the [HHS vulnerability disclosure policy](https://www.hhs.gov/vulnerability-disclosure-policy/index.html). + +### Option 2: Private Vulnerability Report + +1. Click on the **Security and quality** tab of this repository. +2. Locate the **Report a vulnerability** button. If the button is not on the **Security and quality** landing page, look under the **Advisories** section in the side bar. +3. Click the **Report a vulnerability** button and submit the form. The developers will receive a notification of your submission. diff --git a/code/MOSuite/codemeta.json b/code/MOSuite/codemeta.json index 5cf8660..165fcf8 100644 --- a/code/MOSuite/codemeta.json +++ b/code/MOSuite/codemeta.json @@ -32,5 +32,5 @@ "license": "https://spdx.org/licenses/MIT", "name": "MOSuite: R package for differential multi-omics analysis", "url": "https://ccbr.github.io/MOSuite/", - "version": "0.3.1.9001" + "version": "0.3.2.9000" } diff --git a/code/MOSuite/inst/extdata/galaxy/1_mosuite-templates/filter_counts.json b/code/MOSuite/inst/extdata/galaxy/1_mosuite-templates/filter_counts.json index f2eca99..fbfc9e7 100644 --- a/code/MOSuite/inst/extdata/galaxy/1_mosuite-templates/filter_counts.json +++ b/code/MOSuite/inst/extdata/galaxy/1_mosuite-templates/filter_counts.json @@ -270,11 +270,11 @@ { "key": "color_histogram_by_group", "displayName": "Color Histogram by Group", - "description": "Toggle to FALSE to label histogram by Sample Names. Toggle to TRUE to label histogram by the column you select in the \"Group Column Used to Color Histogram\" parameter (below). Default is FALSE.", + "description": "Toggle to FALSE to label histogram by Sample Names. Toggle to TRUE to label histogram by the column you select in the \"Group Column Used to Color Histogram\" parameter (below). Default is TRUE.", "paramType": "BOOLEAN", "paramGroup": "Histogram", "paramValues": null, - "defaultValue": "FALSE", + "defaultValue": "TRUE", "condition": null, "content": null, "objectPropertyReference": null diff --git a/code/MOSuite/inst/extdata/galaxy/1_mosuite-templates/normalize_counts.json b/code/MOSuite/inst/extdata/galaxy/1_mosuite-templates/normalize_counts.json index d6073e8..a705030 100644 --- a/code/MOSuite/inst/extdata/galaxy/1_mosuite-templates/normalize_counts.json +++ b/code/MOSuite/inst/extdata/galaxy/1_mosuite-templates/normalize_counts.json @@ -251,11 +251,11 @@ { "key": "color_histogram_by_group", "displayName": "Color Histogram by Group", - "description": "Toggle to FALSE to label histogram by Sample Names. Toggle to TRUE to label histogram by the column you select in the \"Group Column Used to Color Histogram\" parameter (below). Default is FALSE.", + "description": "Toggle to FALSE to label histogram by Sample Names. Toggle to TRUE to label histogram by the column you select in the \"Group Column Used to Color Histogram\" parameter (below). Default is TRUE.", "paramType": "BOOLEAN", "paramGroup": "Histogram", "paramValues": null, - "defaultValue": "FALSE", + "defaultValue": "TRUE", "condition": null, "content": null, "objectPropertyReference": null diff --git a/code/MOSuite/inst/extdata/galaxy/2_blueprints/batch_correct_counts.json b/code/MOSuite/inst/extdata/galaxy/2_blueprints/batch_correct_counts.json index 006f336..f260403 100644 --- a/code/MOSuite/inst/extdata/galaxy/2_blueprints/batch_correct_counts.json +++ b/code/MOSuite/inst/extdata/galaxy/2_blueprints/batch_correct_counts.json @@ -114,7 +114,7 @@ { "key": "colors_for_plots", "displayName": "Colors for plots", - "description": "Colors for the PCA and histogram will be picked, in order, from this list.\nColors must either be names in `grDevices::colors()` or valid hex codes.\n", + "description": "Colors for the PCA and histogram will be picked, in order, from this list.\nColors must either be names in `grDevices::colors()` or valid hex codes. Defaults to the MOSuite palette returned\nby `get_mosuite_colors()`. Unnamed colors are assigned by factor level order when the grouping column is a factor;\notherwise, they follow the order in which groups first appear in the metadata column. If more groups are present\nthan colors provided,\nsupplied colors are used first and additional colors are generated from the selected palette for the remaining\ngroups; random colors are used only if that palette returns fewer colors than the number of groups.\n", "paramType": "MULTISELECT", "paramGroup": "Visualization", "paramValues": [ @@ -131,6 +131,7 @@ "#FFA500", "#878500" ], + "defaultValue": ["#5954d6", "#e1562c", "#b80058", "#00c6f8", "#d163e6", "#00a76c", "#ff9287", "#008cf9", "#006e00", "#796880", "#FFA500", "#878500"], "condition": null, "content": null, "objectPropertyReference": null diff --git a/code/MOSuite/inst/extdata/galaxy/2_blueprints/filter_counts.json b/code/MOSuite/inst/extdata/galaxy/2_blueprints/filter_counts.json index 58d5f49..fde2e80 100644 --- a/code/MOSuite/inst/extdata/galaxy/2_blueprints/filter_counts.json +++ b/code/MOSuite/inst/extdata/galaxy/2_blueprints/filter_counts.json @@ -198,7 +198,7 @@ "paramType": "NUMBER", "paramGroup": "PCA", "paramValues": null, - "defaultValue": 1, + "defaultValue": 3, "condition": null, "content": null, "objectPropertyReference": null @@ -266,11 +266,11 @@ { "key": "color_histogram_by_group", "displayName": "Color Histogram by Group", - "description": "Set to FALSE to label histogram by Sample Names, or set to TRUE to label histogram by\nthe column you select in the \"Group Column Used to Color Histogram\" parameter (below). Default is FALSE.\n", + "description": "Set to FALSE to label histogram by Sample Names, or set to TRUE to label histogram by\nthe column you select in the \"Group Column Used to Color Histogram\" parameter (below). Default is TRUE.\n", "paramType": "BOOLEAN", "paramGroup": "Histogram", "paramValues": null, - "defaultValue": false, + "defaultValue": true, "condition": null, "content": null, "objectPropertyReference": null @@ -314,11 +314,10 @@ { "key": "legend_font_size_for_histogram", "displayName": "Legend Font Size for Histogram", - "description": "legend font size for the histogram plot\n", + "description": "legend font size for the histogram plot. If `NULL`, the size is scaled automatically.\n", "paramType": "NUMBER", "paramGroup": "Histogram", "paramValues": null, - "defaultValue": 10, "condition": null, "content": null, "objectPropertyReference": null @@ -356,7 +355,7 @@ { "key": "colors_for_plots", "displayName": "Colors for Plots", - "description": "Colors for the PCA and histogram will be picked, in order, from this list.\nColors must either be names in `grDevices::colors()` or valid hex codes.\n", + "description": "Colors for the PCA and histogram will be picked, in order, from this list.\nColors must either be names in `grDevices::colors()` or valid hex codes. Defaults to the MOSuite palette returned\nby `get_mosuite_colors()`. Unnamed colors are assigned by factor level order when the grouping column is a factor;\notherwise, they follow the order in which groups first appear in the metadata column. If more groups are present\nthan colors provided,\nsupplied colors are used first and additional colors are generated from the selected palette for the remaining\ngroups; random colors are used only if that palette returns fewer colors than the number of groups.\n", "paramType": "MULTISELECT", "paramGroup": "Visualization", "paramValues": [ @@ -373,6 +372,7 @@ "#FFA500", "#878500" ], + "defaultValue": ["#5954d6", "#e1562c", "#b80058", "#00c6f8", "#d163e6", "#00a76c", "#ff9287", "#008cf9", "#006e00", "#796880", "#FFA500", "#878500"], "condition": null, "content": null, "objectPropertyReference": null diff --git a/code/MOSuite/inst/extdata/galaxy/2_blueprints/normalize_counts.json b/code/MOSuite/inst/extdata/galaxy/2_blueprints/normalize_counts.json index 118f7fe..0738a43 100644 --- a/code/MOSuite/inst/extdata/galaxy/2_blueprints/normalize_counts.json +++ b/code/MOSuite/inst/extdata/galaxy/2_blueprints/normalize_counts.json @@ -239,7 +239,7 @@ "paramType": "NUMBER", "paramGroup": "PCA", "paramValues": null, - "defaultValue": 8, + "defaultValue": 3, "condition": null, "content": null, "objectPropertyReference": null @@ -247,7 +247,7 @@ { "key": "color_histogram_by_group", "displayName": "Color Histogram by Group", - "description": "Set to FALSE to label histogram by Sample Names, or set to TRUE to label histogram by\nthe column you select in the \"Group Column Used to Color Histogram\" parameter (below). Default is FALSE.\n", + "description": "Set to FALSE to label histogram by Sample Names, or set to TRUE to label histogram by\nthe column you select in the \"Group Column Used to Color Histogram\" parameter (below). Default is TRUE.\n", "paramType": "BOOLEAN", "paramGroup": "Histogram", "paramValues": null, @@ -295,11 +295,10 @@ { "key": "legend_font_size_for_histogram", "displayName": "Legend Font Size for Histogram", - "description": "legend font size for the histogram plot\n", + "description": "legend font size for the histogram plot. If `NULL`, the size is scaled automatically.\n", "paramType": "NUMBER", "paramGroup": "Histogram", "paramValues": null, - "defaultValue": 10, "condition": null, "content": null, "objectPropertyReference": null @@ -337,7 +336,7 @@ { "key": "colors_for_plots", "displayName": "Colors for Plots", - "description": "Colors for the PCA and histogram will be picked, in order, from this list.\nColors must either be names in `grDevices::colors()` or valid hex codes.\n", + "description": "Colors for the PCA and histogram will be picked, in order, from this list.\nColors must either be names in `grDevices::colors()` or valid hex codes. Defaults to the MOSuite palette returned\nby `get_mosuite_colors()`. Unnamed colors are assigned by factor level order when the grouping column is a factor;\notherwise, they follow the order in which groups first appear in the metadata column. If more groups are present\nthan colors provided,\nsupplied colors are used first and additional colors are generated from the selected palette for the remaining\ngroups; random colors are used only if that palette returns fewer colors than the number of groups.\n", "paramType": "MULTISELECT", "paramGroup": "Visualization", "paramValues": [ @@ -354,6 +353,7 @@ "#FFA500", "#878500" ], + "defaultValue": ["#5954d6", "#e1562c", "#b80058", "#00c6f8", "#d163e6", "#00a76c", "#ff9287", "#008cf9", "#006e00", "#796880", "#FFA500", "#878500"], "condition": null, "content": null, "objectPropertyReference": null diff --git a/code/MOSuite/inst/extdata/galaxy/2_blueprints/plot_pca_2d.json b/code/MOSuite/inst/extdata/galaxy/2_blueprints/plot_pca_2d.json index 1655813..8f61ec4 100644 --- a/code/MOSuite/inst/extdata/galaxy/2_blueprints/plot_pca_2d.json +++ b/code/MOSuite/inst/extdata/galaxy/2_blueprints/plot_pca_2d.json @@ -60,7 +60,7 @@ { "key": "count_type", "displayName": "Count Type", - "description": "type to assign the values of `counts_dat` to in the `counts` slot\n", + "description": "the type of counts to use when `moo_counts` is a `multiOmicDataSet`; ignored for data frame input.\n", "paramType": "STRING", "paramGroup": "Basic", "paramValues": null, @@ -71,7 +71,7 @@ { "key": "sub_count_type", "displayName": "Sub Count Type", - "description": "used if `count_type` is a list in the counts slot: specify the sub count type within the list.\nMust be a name in `names(moo@counts[[count_type]])`.\n", + "description": "used when `count_type` refers to a list of count matrices; ignored for data frame input.\n", "paramType": "STRING", "paramGroup": "Basic", "paramValues": null, @@ -104,7 +104,7 @@ "paramType": "NUMBER", "paramGroup": "Visualization", "paramValues": null, - "defaultValue": 1, + "defaultValue": 3, "condition": null, "content": null, "objectPropertyReference": null @@ -124,7 +124,7 @@ { "key": "label_font_size", "displayName": "Label Font Size", - "description": "label font size for the PCA plot\n", + "description": "font size for text labels on the PCA plot.\n", "paramType": "NUMBER", "paramGroup": "Visualization", "paramValues": null, @@ -154,7 +154,7 @@ { "key": "color_values", "displayName": "Color Values", - "description": "vector of colors as hex values or names recognized by R\n", + "description": "vector of colors as hex values or names recognized by R. Unnamed colors are assigned by factor\nlevel order when the grouping column is a factor; otherwise, they follow the order in which groups first appear in\nthe metadata column.\n", "paramType": "MULTISELECT", "paramGroup": "Visualization", "paramValues": [ @@ -241,7 +241,7 @@ { "key": "interactive_plots", "displayName": "Interactive Plots", - "description": "set to TRUE to make PCA and Histogram plots interactive with `plotly`, allowing you to hover\nyour mouse over a point or line to view sample information. The similarity heat map will not display if this toggle\nis set to `TRUE`. Default is `FALSE`.\n", + "description": "set to TRUE to make the PCA plot interactive with `plotly`.\n", "paramType": "BOOLEAN", "paramGroup": "Visualization", "paramValues": null, diff --git a/code/MOSuite/inst/extdata/galaxy/2_blueprints/plot_pca_3d.json b/code/MOSuite/inst/extdata/galaxy/2_blueprints/plot_pca_3d.json index 54b06e8..9072ad8 100644 --- a/code/MOSuite/inst/extdata/galaxy/2_blueprints/plot_pca_3d.json +++ b/code/MOSuite/inst/extdata/galaxy/2_blueprints/plot_pca_3d.json @@ -94,7 +94,7 @@ { "key": "color_values", "displayName": "Color Values", - "description": "vector of colors as hex values or names recognized by R.\n", + "description": "vector of colors as hex values or names recognized by R. Unnamed colors are assigned by factor\nlevel order when the grouping column is a factor; otherwise, they follow the order in which groups first appear in\nthe metadata column.\n", "paramType": "MULTISELECT", "paramGroup": "Visualization", "paramValues": [ diff --git a/code/MOSuite/inst/extdata/galaxy/galaxy.R b/code/MOSuite/inst/extdata/galaxy/galaxy.R index 3d92b9f..33df8ce 100644 --- a/code/MOSuite/inst/extdata/galaxy/galaxy.R +++ b/code/MOSuite/inst/extdata/galaxy/galaxy.R @@ -135,9 +135,7 @@ update_function_template <- function( arg_name <- template[[arg_type]][[i]]$key if (arg_name %in% names(func_meta$args)) { arg_meta <- template[[arg_type]][[i]] - arg_meta$description <- safe_rd_to_md( - func_meta$args[[arg_name]]$description - ) + arg_meta$description <- safe_rd_to_md(func_meta$args[[arg_name]]$description) arg_meta$defaultValue <- func_meta$args[[arg_name]]$defaultValue args_in_template <- c(args_in_template, arg_name) new_template[[arg_type]][[ diff --git a/code/MOSuite/inst/extdata/json_args/common/filter_counts.json b/code/MOSuite/inst/extdata/json_args/common/filter_counts.json index 0e71be4..e25351d 100644 --- a/code/MOSuite/inst/extdata/json_args/common/filter_counts.json +++ b/code/MOSuite/inst/extdata/json_args/common/filter_counts.json @@ -14,17 +14,17 @@ "principal_component_on_x_axis": 1.0, "principal_component_on_y_axis": 2.0, "legend_position_for_pca": "top", - "point_size_for_pca": 1.0, + "point_size_for_pca": 3, "add_label_to_pca": true, "label_font_size": 3.0, "label_offset_x_": 2.0, "label_offset_y_": 2.0, "samples_to_rename": null, - "color_histogram_by_group": false, + "color_histogram_by_group": true, "set_min_max_for_x_axis_for_histogram": false, "minimum_for_x_axis_for_histogram": -1.0, "maximum_for_x_axis_for_histogram": 1.0, - "legend_font_size_for_histogram": 10.0, + "legend_font_size_for_histogram": null, "legend_position_for_histogram": "top", "number_of_histogram_legend_columns": 6.0, "colors_for_plots": [ diff --git a/code/MOSuite/inst/extdata/json_args/common/normalize_counts.json b/code/MOSuite/inst/extdata/json_args/common/normalize_counts.json index a5acdb5..484c4a5 100644 --- a/code/MOSuite/inst/extdata/json_args/common/normalize_counts.json +++ b/code/MOSuite/inst/extdata/json_args/common/normalize_counts.json @@ -18,14 +18,27 @@ "label_offset_x_": 2, "label_offset_y_": 2, "label_font_size": 3, - "point_size_for_pca": 8, + "point_size_for_pca": 3, "color_histogram_by_group": true, "set_min_max_for_x_axis_for_histogram": false, "minimum_for_x_axis_for_histogram": -1, "maximum_for_x_axis_for_histogram": 1, - "legend_font_size_for_histogram": 10, + "legend_font_size_for_histogram": null, "legend_position_for_histogram": "top", - "colors_for_plots": null, + "colors_for_plots": [ + "#5954d6", + "#e1562c", + "#b80058", + "#00c6f8", + "#d163e6", + "#00a76c", + "#ff9287", + "#008cf9", + "#006e00", + "#796880", + "#FFA500", + "#878500" + ], "print_plots": true, "save_plots": true, "interactive_plots": false, diff --git a/code/MOSuite/inst/extdata/json_args/common/plot_pca_2d.json b/code/MOSuite/inst/extdata/json_args/common/plot_pca_2d.json index ab25827..ed7459d 100644 --- a/code/MOSuite/inst/extdata/json_args/common/plot_pca_2d.json +++ b/code/MOSuite/inst/extdata/json_args/common/plot_pca_2d.json @@ -10,7 +10,7 @@ 1, 2 ], - "point_size": 1, + "point_size": 3, "add_label": true, "label_font_size": 3, "legend_position": "top", diff --git a/code/MOSuite/inst/extdata/json_args/defaults/batch_correct_counts.json b/code/MOSuite/inst/extdata/json_args/defaults/batch_correct_counts.json index fe8d42e..48cae82 100644 --- a/code/MOSuite/inst/extdata/json_args/defaults/batch_correct_counts.json +++ b/code/MOSuite/inst/extdata/json_args/defaults/batch_correct_counts.json @@ -9,8 +9,26 @@ "covariates_colnames": "Group", "batch_colname": "Batch", "label_colname": null, - "colors_for_plots": null, + "samples_to_rename": "", + "add_label_to_pca": true, + "principal_component_on_x_axis": 1, + "principal_component_on_y_axis": 2, + "legend_position_for_pca": "top", + "label_offset_x_": 2, + "label_offset_y_": 2, + "label_font_size": 3, + "point_size_for_pca": 3, + "color_histogram_by_group": true, + "set_min_max_for_x_axis_for_histogram": false, + "minimum_for_x_axis_for_histogram": -1, + "maximum_for_x_axis_for_histogram": 1, + "legend_font_size_for_histogram": null, + "legend_position_for_histogram": "top", + "number_of_histogram_legend_columns": 6, + "plot_corr_matrix_heatmap": true, + "colors_for_plots": ["#5954d6", "#e1562c", "#b80058", "#00c6f8", "#d163e6", "#00a76c", "#ff9287", "#008cf9", "#006e00", "#796880", "#FFA500", "#878500"], "print_plots": true, "save_plots": true, + "interactive_plots": false, "plots_subdir": "batch" } diff --git a/code/MOSuite/inst/extdata/json_args/defaults/clean_raw_counts.json b/code/MOSuite/inst/extdata/json_args/defaults/clean_raw_counts.json index 43c5568..f350b27 100644 --- a/code/MOSuite/inst/extdata/json_args/defaults/clean_raw_counts.json +++ b/code/MOSuite/inst/extdata/json_args/defaults/clean_raw_counts.json @@ -5,6 +5,8 @@ "sample_id_colname": null, "feature_id_colname": null, "samples_to_rename": "", + "group_colname": "", + "colors_for_plots": ["#5954d6", "#e1562c", "#b80058", "#00c6f8", "#d163e6", "#00a76c", "#ff9287", "#008cf9", "#006e00", "#796880", "#FFA500", "#878500"], "cleanup_column_names": true, "split_gene_name": true, "aggregate_rows_with_duplicate_gene_names": true, diff --git a/code/MOSuite/inst/extdata/json_args/defaults/filter_counts.json b/code/MOSuite/inst/extdata/json_args/defaults/filter_counts.json index 1e6feff..380654a 100644 --- a/code/MOSuite/inst/extdata/json_args/defaults/filter_counts.json +++ b/code/MOSuite/inst/extdata/json_args/defaults/filter_counts.json @@ -15,20 +15,20 @@ "principal_component_on_x_axis": 1, "principal_component_on_y_axis": 2, "legend_position_for_pca": "top", - "point_size_for_pca": 1, + "point_size_for_pca": 3, "add_label_to_pca": true, "label_font_size": 3, "label_offset_y_": 2, "label_offset_x_": 2, "samples_to_rename": "", - "color_histogram_by_group": false, + "color_histogram_by_group": true, "set_min_max_for_x_axis_for_histogram": false, "minimum_for_x_axis_for_histogram": -1, "maximum_for_x_axis_for_histogram": 1, "legend_position_for_histogram": "top", - "legend_font_size_for_histogram": 10, + "legend_font_size_for_histogram": null, "number_of_histogram_legend_columns": 6, - "colors_for_plots": null, + "colors_for_plots": ["#5954d6", "#e1562c", "#b80058", "#00c6f8", "#d163e6", "#00a76c", "#ff9287", "#008cf9", "#006e00", "#796880", "#FFA500", "#878500"], "plot_corr_matrix_heatmap": true, "print_plots": true, "save_plots": true, diff --git a/code/MOSuite/inst/extdata/json_args/defaults/normalize_counts.json b/code/MOSuite/inst/extdata/json_args/defaults/normalize_counts.json index 50c0e32..66b2638 100644 --- a/code/MOSuite/inst/extdata/json_args/defaults/normalize_counts.json +++ b/code/MOSuite/inst/extdata/json_args/defaults/normalize_counts.json @@ -18,16 +18,16 @@ "label_offset_x_": 2, "label_offset_y_": 2, "label_font_size": 3, - "point_size_for_pca": 8, + "point_size_for_pca": 3, "color_histogram_by_group": true, "set_min_max_for_x_axis_for_histogram": false, "minimum_for_x_axis_for_histogram": -1, "maximum_for_x_axis_for_histogram": 1, - "legend_font_size_for_histogram": 10, + "legend_font_size_for_histogram": null, "legend_position_for_histogram": "top", "number_of_histogram_legend_columns": 6, "plot_corr_matrix_heatmap": true, - "colors_for_plots": null, + "colors_for_plots": ["#5954d6", "#e1562c", "#b80058", "#00c6f8", "#d163e6", "#00a76c", "#ff9287", "#008cf9", "#006e00", "#796880", "#FFA500", "#878500"], "print_plots": true, "save_plots": true, "interactive_plots": false, diff --git a/code/MOSuite/inst/extdata/json_args/defaults/plot_pca_2d.json b/code/MOSuite/inst/extdata/json_args/defaults/plot_pca_2d.json index edada7f..c3c592c 100644 --- a/code/MOSuite/inst/extdata/json_args/defaults/plot_pca_2d.json +++ b/code/MOSuite/inst/extdata/json_args/defaults/plot_pca_2d.json @@ -12,8 +12,9 @@ "color_values": ["#5954d6", "#e1562c", "#b80058", "#00c6f8", "#d163e6", "#00a76c", "#ff9287", "#008cf9", "#006e00", "#796880", "#FFA500", "#878500"], "principal_components": [1, 2], "legend_position": "top", - "point_size": 1, + "point_size": 3, "add_label": true, + "legend_font_size": null, "label_font_size": 3, "label_offset_x_": 2, "label_offset_y_": 2, diff --git a/code/MOSuite/man/batch_correct_counts.Rd b/code/MOSuite/man/batch_correct_counts.Rd index 9be8aa8..9a89e89 100644 --- a/code/MOSuite/man/batch_correct_counts.Rd +++ b/code/MOSuite/man/batch_correct_counts.Rd @@ -14,9 +14,28 @@ batch_correct_counts( covariates_colnames = "Group", batch_colname = "Batch", label_colname = NULL, - colors_for_plots = NULL, + samples_to_rename = c(""), + add_label_to_pca = TRUE, + principal_component_on_x_axis = 1, + principal_component_on_y_axis = 2, + legend_position_for_pca = "top", + label_offset_x_ = 2, + label_offset_y_ = 2, + label_font_size = 3, + point_size_for_pca = 3, + color_histogram_by_group = TRUE, + set_min_max_for_x_axis_for_histogram = FALSE, + minimum_for_x_axis_for_histogram = -1, + maximum_for_x_axis_for_histogram = 1, + legend_font_size_for_histogram = NULL, + legend_position_for_histogram = "top", + number_of_histogram_legend_columns = 6, + plot_corr_matrix_heatmap = TRUE, + colors_for_plots = c("#5954d6", "#e1562c", "#b80058", "#00c6f8", "#d163e6", "#00a76c", + "#ff9287", "#008cf9", "#006e00", "#796880", "#FFA500", "#878500"), print_plots = options::opt("print_plots"), save_plots = options::opt("save_plots"), + interactive_plots = FALSE, plots_subdir = "batch" ) } @@ -59,13 +78,65 @@ labels to display on your figure (e.g. shorter labels are sometimes preferred on column with your preferred Labels here. The selected column should contain unique names for each sample. (Default: \code{NULL} -- \code{sample_id_colname} will be used.)} +\item{samples_to_rename}{If you do not have a Plot Labels Column in your sample metadata table, you can use this +parameter to rename samples manually for display on the PCA plot. Use "Add item" to add each additional sample for +renaming. Use the following format to describe which old name (in your sample metadata table) you want to rename to +which new name: old_name: new_name} + +\item{add_label_to_pca}{label points on the PCA plot} + +\item{principal_component_on_x_axis}{The principal component to plot on the x-axis for the PCA plot. Choices include +1, 2, 3, ... (default: 1)} + +\item{principal_component_on_y_axis}{The principal component to plot on the y-axis for the PCA plot. Choices include +1, 2, 3, ... (default: 2)} + +\item{legend_position_for_pca}{legend position for the PCA plot} + +\item{label_offset_x_}{label offset x for the PCA plot} + +\item{label_offset_y_}{label offset y for the PCA plot} + +\item{label_font_size}{label font size for the PCA plot} + +\item{point_size_for_pca}{geom point size for the PCA plot} + +\item{color_histogram_by_group}{Set to FALSE to label histogram by Sample Names, or set to TRUE to label histogram by +the column you select in the "Group Column Used to Color Histogram" parameter (below). Default is TRUE.} + +\item{set_min_max_for_x_axis_for_histogram}{whether to set min/max value for histogram x-axis} + +\item{minimum_for_x_axis_for_histogram}{x-axis minimum for histogram plot} + +\item{maximum_for_x_axis_for_histogram}{x-axis maximum for histogram plot} + +\item{legend_font_size_for_histogram}{legend font size for the histogram plot. If \code{NULL}, the size is scaled automatically.} + +\item{legend_position_for_histogram}{legend position for the histogram plot. consider setting to 'none' for a large +number of samples.} + +\item{number_of_histogram_legend_columns}{number of columns for the histogram legend} + +\item{plot_corr_matrix_heatmap}{Datasets with a large number of samples may be too large to create a correlation +matrix heatmap. If this function takes longer than 5 minutes to run, Set to \code{FALSE} and the correlation matrix will +not be be created. Default is \code{TRUE}.} + \item{colors_for_plots}{Colors for the PCA and histogram will be picked, in order, from this list. -Colors must either be names in \code{grDevices::colors()} or valid hex codes.} +Colors must either be names in \code{grDevices::colors()} or valid hex codes. Defaults to the MOSuite palette returned +by \code{get_mosuite_colors()}. Unnamed colors are assigned by factor level order when the grouping column is a factor; +otherwise, they follow the order in which groups first appear in the metadata column. If more groups are present +than colors provided, +supplied colors are used first and additional colors are generated from the selected palette for the remaining +groups; random colors are used only if that palette returns fewer colors than the number of groups.} \item{print_plots}{Whether to print plots during analysis (Defaults to \code{FALSE}, overwritable using option 'moo_print_plots' or environment variable 'MOO_PRINT_PLOTS')} \item{save_plots}{Whether to save plots to files during analysis (Defaults to \code{TRUE}, overwritable using option 'moo_save_plots' or environment variable 'MOO_SAVE_PLOTS')} +\item{interactive_plots}{set to TRUE to make PCA and Histogram plots interactive with \code{plotly}, allowing you to hover +your mouse over a point or line to view sample information. The similarity heat map will not display if this toggle +is set to \code{TRUE}. Default is \code{FALSE}.} + \item{plots_subdir}{subdirectory in \verb{figures/} where plots will be saved if \code{save_plots} is \code{TRUE}} } \value{ diff --git a/code/MOSuite/man/clean_raw_counts.Rd b/code/MOSuite/man/clean_raw_counts.Rd index f4f580d..4c54062 100644 --- a/code/MOSuite/man/clean_raw_counts.Rd +++ b/code/MOSuite/man/clean_raw_counts.Rd @@ -10,6 +10,9 @@ clean_raw_counts( sample_id_colname = NULL, feature_id_colname = NULL, samples_to_rename = "", + group_colname = "", + colors_for_plots = c("#5954d6", "#e1562c", "#b80058", "#00c6f8", "#d163e6", "#00a76c", + "#ff9287", "#008cf9", "#006e00", "#796880", "#FFA500", "#878500"), cleanup_column_names = TRUE, split_gene_name = TRUE, aggregate_rows_with_duplicate_gene_names = TRUE, @@ -38,6 +41,12 @@ parameter to rename samples manually for display on the PCA plot. Use "Add item" renaming. Use the following format to describe which old name (in your sample metadata table) you want to rename to which new name: old_name: new_name} +\item{group_colname}{sample metadata column used to color the read-depth plot. Leave blank to use the current +single-color bar fill.} + +\item{colors_for_plots}{colors used when \code{group_colname} is supplied. Named vectors are matched to group values; +unnamed vectors follow group order and are extended with MOSuite colors when too few colors are supplied.} + \item{cleanup_column_names}{Invalid raw counts column names can cause errors in the downstream analysis. If this is \code{TRUE}, any invalid column names will be automatically altered to a correct format. These format changes diff --git a/code/MOSuite/man/filter_counts.Rd b/code/MOSuite/man/filter_counts.Rd index 984545b..cdfbc56 100644 --- a/code/MOSuite/man/filter_counts.Rd +++ b/code/MOSuite/man/filter_counts.Rd @@ -20,20 +20,21 @@ filter_counts( principal_component_on_x_axis = 1, principal_component_on_y_axis = 2, legend_position_for_pca = "top", - point_size_for_pca = 1, + point_size_for_pca = 3, add_label_to_pca = TRUE, label_font_size = 3, label_offset_y_ = 2, label_offset_x_ = 2, samples_to_rename = c(""), - color_histogram_by_group = FALSE, + color_histogram_by_group = TRUE, set_min_max_for_x_axis_for_histogram = FALSE, minimum_for_x_axis_for_histogram = -1, maximum_for_x_axis_for_histogram = 1, legend_position_for_histogram = "top", - legend_font_size_for_histogram = 10, + legend_font_size_for_histogram = NULL, number_of_histogram_legend_columns = 6, - colors_for_plots = NULL, + colors_for_plots = c("#5954d6", "#e1562c", "#b80058", "#00c6f8", "#d163e6", "#00a76c", + "#ff9287", "#008cf9", "#006e00", "#796880", "#FFA500", "#878500"), plot_corr_matrix_heatmap = TRUE, print_plots = options::opt("print_plots"), save_plots = options::opt("save_plots"), @@ -109,7 +110,7 @@ renaming. Use the following format to describe which old name (in your sample me which new name: old_name: new_name} \item{color_histogram_by_group}{Set to FALSE to label histogram by Sample Names, or set to TRUE to label histogram by -the column you select in the "Group Column Used to Color Histogram" parameter (below). Default is FALSE.} +the column you select in the "Group Column Used to Color Histogram" parameter (below). Default is TRUE.} \item{set_min_max_for_x_axis_for_histogram}{whether to set min/max value for histogram x-axis} @@ -120,12 +121,17 @@ the column you select in the "Group Column Used to Color Histogram" parameter (b \item{legend_position_for_histogram}{legend position for the histogram plot. consider setting to 'none' for a large number of samples.} -\item{legend_font_size_for_histogram}{legend font size for the histogram plot} +\item{legend_font_size_for_histogram}{legend font size for the histogram plot. If \code{NULL}, the size is scaled automatically.} \item{number_of_histogram_legend_columns}{number of columns for the histogram legend} \item{colors_for_plots}{Colors for the PCA and histogram will be picked, in order, from this list. -Colors must either be names in \code{grDevices::colors()} or valid hex codes.} +Colors must either be names in \code{grDevices::colors()} or valid hex codes. Defaults to the MOSuite palette returned +by \code{get_mosuite_colors()}. Unnamed colors are assigned by factor level order when the grouping column is a factor; +otherwise, they follow the order in which groups first appear in the metadata column. If more groups are present +than colors provided, +supplied colors are used first and additional colors are generated from the selected palette for the remaining +groups; random colors are used only if that palette returns fewer colors than the number of groups.} \item{plot_corr_matrix_heatmap}{Datasets with a large number of samples may be too large to create a correlation matrix heatmap. If this function takes longer than 5 minutes to run, Set to \code{FALSE} and the correlation matrix will diff --git a/code/MOSuite/man/get_colors_lst.Rd b/code/MOSuite/man/get_colors_lst.Rd index fbe22fa..68a3a2f 100644 --- a/code/MOSuite/man/get_colors_lst.Rd +++ b/code/MOSuite/man/get_colors_lst.Rd @@ -4,14 +4,15 @@ \alias{get_colors_lst} \title{Create named list of default colors for plotting} \usage{ -get_colors_lst(sample_metadata, palette_fun = grDevices::palette.colors, ...) +get_colors_lst(sample_metadata, palette_fun = get_mosuite_colors, ...) } \arguments{ \item{sample_metadata}{sample metadata as a data frame or tibble. The first column is assumed to contain the sample IDs which must correspond to column names in the raw counts.} -\item{palette_fun}{Function for selecting colors. Assumed to contain \code{n} for the number of colors. Default: -\code{grDevices::palette.colors()}} +\item{palette_fun}{Function for selecting colors. Assumed to contain \code{n} for the number of colors. Defaults to +MOSuite's default plot palette. To use the previous R default palette behavior, pass +\code{grDevices::palette.colors}.} \item{...}{additional arguments forwarded to \code{palette_fun}} } diff --git a/code/MOSuite/man/get_colors_vctr.Rd b/code/MOSuite/man/get_colors_vctr.Rd index adef32e..6b94ed6 100644 --- a/code/MOSuite/man/get_colors_vctr.Rd +++ b/code/MOSuite/man/get_colors_vctr.Rd @@ -4,20 +4,22 @@ \alias{get_colors_vctr} \title{Get vector of colors for observations in one column of a data frame} \usage{ -get_colors_vctr(dat, colname, palette_fun = grDevices::palette.colors, ...) +get_colors_vctr(dat, colname, palette_fun = get_mosuite_colors, ...) } \arguments{ \item{dat}{data frame} \item{colname}{column name in \code{dat}} -\item{palette_fun}{Function for selecting colors. Assumed to contain \code{n} for the number of colors. Default: -\code{grDevices::palette.colors()}} +\item{palette_fun}{Function for selecting colors. Assumed to contain \code{n} for the number of colors. Defaults to +MOSuite's default plot palette. To use the previous R default palette behavior, pass +\code{grDevices::palette.colors}.} \item{...}{additional arguments forwarded to \code{palette_fun}} } \value{ -named vector of colors for each unique observation in \code{dat$colname} +named vector of colors for each unique observation in \code{dat$colname}. Factor columns use factor level order; +other columns use first-observed order. } \description{ Get vector of colors for observations in one column of a data frame diff --git a/code/MOSuite/man/normalize_counts.Rd b/code/MOSuite/man/normalize_counts.Rd index 0bfd0ea..cdfb481 100644 --- a/code/MOSuite/man/normalize_counts.Rd +++ b/code/MOSuite/man/normalize_counts.Rd @@ -23,16 +23,17 @@ normalize_counts( label_offset_x_ = 2, label_offset_y_ = 2, label_font_size = 3, - point_size_for_pca = 8, + point_size_for_pca = 3, color_histogram_by_group = TRUE, set_min_max_for_x_axis_for_histogram = FALSE, minimum_for_x_axis_for_histogram = -1, maximum_for_x_axis_for_histogram = 1, - legend_font_size_for_histogram = 10, + legend_font_size_for_histogram = NULL, legend_position_for_histogram = "top", number_of_histogram_legend_columns = 6, plot_corr_matrix_heatmap = TRUE, - colors_for_plots = NULL, + colors_for_plots = c("#5954d6", "#e1562c", "#b80058", "#00c6f8", "#d163e6", "#00a76c", + "#ff9287", "#008cf9", "#006e00", "#796880", "#FFA500", "#878500"), print_plots = options::opt("print_plots"), save_plots = options::opt("save_plots"), interactive_plots = FALSE, @@ -97,7 +98,7 @@ which new name: old_name: new_name} \item{point_size_for_pca}{geom point size for the PCA plot} \item{color_histogram_by_group}{Set to FALSE to label histogram by Sample Names, or set to TRUE to label histogram by -the column you select in the "Group Column Used to Color Histogram" parameter (below). Default is FALSE.} +the column you select in the "Group Column Used to Color Histogram" parameter (below). Default is TRUE.} \item{set_min_max_for_x_axis_for_histogram}{whether to set min/max value for histogram x-axis} @@ -105,7 +106,7 @@ the column you select in the "Group Column Used to Color Histogram" parameter (b \item{maximum_for_x_axis_for_histogram}{x-axis maximum for histogram plot} -\item{legend_font_size_for_histogram}{legend font size for the histogram plot} +\item{legend_font_size_for_histogram}{legend font size for the histogram plot. If \code{NULL}, the size is scaled automatically.} \item{legend_position_for_histogram}{legend position for the histogram plot. consider setting to 'none' for a large number of samples.} @@ -117,7 +118,12 @@ matrix heatmap. If this function takes longer than 5 minutes to run, Set to \cod not be be created. Default is \code{TRUE}.} \item{colors_for_plots}{Colors for the PCA and histogram will be picked, in order, from this list. -Colors must either be names in \code{grDevices::colors()} or valid hex codes.} +Colors must either be names in \code{grDevices::colors()} or valid hex codes. Defaults to the MOSuite palette returned +by \code{get_mosuite_colors()}. Unnamed colors are assigned by factor level order when the grouping column is a factor; +otherwise, they follow the order in which groups first appear in the metadata column. If more groups are present +than colors provided, +supplied colors are used first and additional colors are generated from the selected palette for the remaining +groups; random colors are used only if that palette returns fewer colors than the number of groups.} \item{print_plots}{Whether to print plots during analysis (Defaults to \code{FALSE}, overwritable using option 'moo_print_plots' or environment variable 'MOO_PRINT_PLOTS')} diff --git a/code/MOSuite/man/plot_corr_heatmap-data.frame.Rd b/code/MOSuite/man/plot_corr_heatmap-data.frame.Rd index 0eec133..573ea52 100644 --- a/code/MOSuite/man/plot_corr_heatmap-data.frame.Rd +++ b/code/MOSuite/man/plot_corr_heatmap-data.frame.Rd @@ -28,7 +28,9 @@ labels to display on your figure (e.g. shorter labels are sometimes preferred on column with your preferred Labels here. The selected column should contain unique names for each sample. (Default: \code{NULL} -- \code{sample_id_colname} will be used.)} -\item{color_values}{vector of colors as hex values or names recognized by R} +\item{color_values}{vector of colors as hex values or names recognized by R. Unnamed colors are assigned by factor +level order when the grouping column is a factor; otherwise, they are assigned in the order groups first appear in +the metadata column.} } \description{ Plot correlation heatmap for counts dataframe diff --git a/code/MOSuite/man/plot_expr_heatmap.Rd b/code/MOSuite/man/plot_expr_heatmap.Rd index 8f8b890..08c09ad 100644 --- a/code/MOSuite/man/plot_expr_heatmap.Rd +++ b/code/MOSuite/man/plot_expr_heatmap.Rd @@ -201,7 +201,9 @@ column with your preferred Labels here. The selected column should contain uniqu you could choose to remove certain samples. Samples excluded here will be removed in this step and from further analysis downstream of this step. (Default: \code{NULL} - all sample IDs in \code{moo@sample_meta} will be used.)} -\item{color_values}{vector of colors as hex values or names recognized by R} +\item{color_values}{vector of colors as hex values or names recognized by R. Unnamed colors are assigned by factor +level order when the grouping column is a factor; otherwise, they are assigned in the order groups first appear in +the metadata column.} \item{include_all_genes}{Set to TRUE if all genes are to be included. Set to FALSE if you want to filter genes by variance and/or provide a list of specific genes that will appear in the heatmap.} diff --git a/code/MOSuite/man/plot_histogram.data.frame.Rd b/code/MOSuite/man/plot_histogram.data.frame.Rd index ba46275..28c4956 100644 --- a/code/MOSuite/man/plot_histogram.data.frame.Rd +++ b/code/MOSuite/man/plot_histogram.data.frame.Rd @@ -26,7 +26,9 @@ labels to display on your figure (e.g. shorter labels are sometimes preferred on column with your preferred Labels here. The selected column should contain unique names for each sample. (Default: \code{NULL} -- \code{sample_id_colname} will be used.)} -\item{color_values}{vector of colors as hex values or names recognized by R} +\item{color_values}{vector of colors as hex values or names recognized by R. Unnamed colors are assigned by factor +level order when the grouping column is a factor; otherwise, they follow the order in which groups first appear in +the metadata column.} \item{color_by_group}{Set to FALSE to label histogram by Sample Names, or set to TRUE to label histogram by the column you select in the "Group Column Used to Color Histogram" parameter (below). Default is FALSE.} @@ -43,7 +45,8 @@ column you select in the "Group Column Used to Color Histogram" parameter (below \item{legend_position}{passed to in \code{legend.position} \code{ggplot2::theme()}} -\item{legend_font_size}{passed to \code{ggplot2::element_text()} via \code{ggplot2::theme()}} +\item{legend_font_size}{passed to \code{ggplot2::element_text()} via \code{ggplot2::theme()}. If \code{NULL}, the size is scaled +automatically based on the number and length of legend labels.} \item{number_of_legend_columns}{passed to \code{ncol} in \code{ggplot2::guide_legend()}} diff --git a/code/MOSuite/man/plot_pca_2d.Rd b/code/MOSuite/man/plot_pca_2d.Rd index 244d8b2..edf51e4 100644 --- a/code/MOSuite/man/plot_pca_2d.Rd +++ b/code/MOSuite/man/plot_pca_2d.Rd @@ -20,8 +20,9 @@ plot_pca_2d( "#ff9287", "#008cf9", "#006e00", "#796880", "#FFA500", "#878500"), principal_components = c(1, 2), legend_position = "top", - point_size = 1, + point_size = 3, add_label = TRUE, + legend_font_size = NULL, label_font_size = 3, label_offset_x_ = 2, label_offset_y_ = 2, @@ -47,8 +48,9 @@ plot_pca_2d( "#ff9287", "#008cf9", "#006e00", "#796880", "#FFA500", "#878500"), principal_components = c(1, 2), legend_position = "top", - point_size = 1, + point_size = 3, add_label = TRUE, + legend_font_size = NULL, label_font_size = 3, label_offset_x_ = 2, label_offset_y_ = 2, @@ -74,8 +76,9 @@ plot_pca_2d( "#ff9287", "#008cf9", "#006e00", "#796880", "#FFA500", "#878500"), principal_components = c(1, 2), legend_position = "top", - point_size = 1, + point_size = 3, add_label = TRUE, + legend_font_size = NULL, label_font_size = 3, label_offset_x_ = 2, label_offset_y_ = 2, @@ -90,10 +93,9 @@ plot_pca_2d( \item{moo_counts}{counts dataframe or \code{multiOmicDataSet} containing \code{count_type} & \code{sub_count_type} in the counts slot} -\item{count_type}{type to assign the values of \code{counts_dat} to in the \code{counts} slot} +\item{count_type}{the type of counts to use when \code{moo_counts} is a \code{multiOmicDataSet}; ignored for data frame input.} -\item{sub_count_type}{used if \code{count_type} is a list in the counts slot: specify the sub count type within the list. -Must be a name in \code{names(moo@counts[[count_type]])}.} +\item{sub_count_type}{used when \code{count_type} refers to a list of count matrices; ignored for data frame input.} \item{sample_metadata}{sample metadata as a data frame or tibble.} @@ -121,7 +123,9 @@ parameter to rename samples manually for display on the PCA plot. Use "Add item" renaming. Use the following format to describe which old name (in your sample metadata table) you want to rename to which new name: old_name: new_name} -\item{color_values}{vector of colors as hex values or names recognized by R} +\item{color_values}{vector of colors as hex values or names recognized by R. Unnamed colors are assigned by factor +level order when the grouping column is a factor; otherwise, they follow the order in which groups first appear in +the metadata column.} \item{principal_components}{vector with numbered principal components to plot} @@ -131,19 +135,20 @@ which new name: old_name: new_name} \item{add_label}{whether to add text labels for the points} -\item{label_font_size}{label font size for the PCA plot} +\item{legend_font_size}{font size for the PCA legend text. If \code{NULL}, the size is scaled automatically based on the +number and length of legend labels.} -\item{label_offset_x_}{label offset x for the PCA plot} +\item{label_font_size}{font size for text labels on the PCA plot.} -\item{label_offset_y_}{label offset y for the PCA plot} +\item{label_offset_x_}{horizontal offset for text labels on the PCA plot.} -\item{interactive_plots}{set to TRUE to make PCA and Histogram plots interactive with \code{plotly}, allowing you to hover -your mouse over a point or line to view sample information. The similarity heat map will not display if this toggle -is set to \code{TRUE}. Default is \code{FALSE}.} +\item{label_offset_y_}{vertical offset for text labels on the PCA plot.} -\item{plots_subdir}{subdirectory in \verb{figures/} where plots will be saved if \code{save_plots} is \code{TRUE}} +\item{interactive_plots}{set to TRUE to make the PCA plot interactive with \code{plotly}.} -\item{plot_filename}{plot output filename - only used if save_plots is TRUE} +\item{plots_subdir}{subdirectory in \verb{figures/} where PCA plots are saved.} + +\item{plot_filename}{output filename for the PCA plot image.} \item{print_plots}{Whether to print plots during analysis (Defaults to \code{FALSE}, overwritable using option 'moo_print_plots' or environment variable 'MOO_PRINT_PLOTS')} diff --git a/code/MOSuite/man/plot_pca_3d.Rd b/code/MOSuite/man/plot_pca_3d.Rd index acf569d..51031db 100644 --- a/code/MOSuite/man/plot_pca_3d.Rd +++ b/code/MOSuite/man/plot_pca_3d.Rd @@ -99,7 +99,9 @@ plot_pca_3d( \item{label_font_size}{font size used for labels in the interactive figure.} -\item{color_values}{vector of colors as hex values or names recognized by R.} +\item{color_values}{vector of colors as hex values or names recognized by R. Unnamed colors are assigned by factor +level order when the grouping column is a factor; otherwise, they follow the order in which groups first appear in +the metadata column.} \item{plot_title}{title for the plot} diff --git a/code/MOSuite/man/plot_read_depth.data.frame.Rd b/code/MOSuite/man/plot_read_depth.data.frame.Rd index 35c2689..cdae906 100644 --- a/code/MOSuite/man/plot_read_depth.data.frame.Rd +++ b/code/MOSuite/man/plot_read_depth.data.frame.Rd @@ -5,6 +5,15 @@ \alias{plot_read_depth.data.frame} \title{Plot read depth for \code{data.frame}} \arguments{ +\item{sample_metadata}{sample metadata dataframe, required when \code{group_colname} is supplied.} + +\item{sample_id_colname}{column in sample metadata containing sample IDs.} + +\item{group_colname}{sample metadata column used to color bars. Leave blank to use the current single-color bar fill.} + +\item{color_values}{colors used when \code{group_colname} is supplied. Named vectors are matched to group values; +unnamed vectors follow group order and are extended with MOSuite colors when too few colors are supplied.} + \item{...}{additional arguments (ignored; accepted for compatibility with the moo dispatch)} } \value{ diff --git a/code/MOSuite/man/plot_read_depth.multiOmicDataSet.Rd b/code/MOSuite/man/plot_read_depth.multiOmicDataSet.Rd index 50647ec..734bf33 100644 --- a/code/MOSuite/man/plot_read_depth.multiOmicDataSet.Rd +++ b/code/MOSuite/man/plot_read_depth.multiOmicDataSet.Rd @@ -9,6 +9,13 @@ \item{sub_count_type}{used if \code{count_type} is a list in the counts slot: specify the sub count type within the list. Must be a name in \code{names(moo@counts[[count_type]])}.} + +\item{sample_id_colname}{column in sample metadata containing sample IDs.} + +\item{group_colname}{sample metadata column used to color bars. Leave blank to use the current single-color bar fill.} + +\item{color_values}{colors used when \code{group_colname} is supplied. Named vectors are matched to group values; +unnamed vectors follow group order and are extended with MOSuite colors when too few colors are supplied.} } \value{ ggplot barplot diff --git a/code/MOSuite/man/set_color_pal.Rd b/code/MOSuite/man/set_color_pal.Rd index c603f0e..4e74d3d 100644 --- a/code/MOSuite/man/set_color_pal.Rd +++ b/code/MOSuite/man/set_color_pal.Rd @@ -4,15 +4,16 @@ \alias{set_color_pal} \title{Set color palette for a single group/column} \usage{ -set_color_pal(moo, colname, palette_fun = grDevices::palette.colors, ...) +set_color_pal(moo, colname, palette_fun = get_mosuite_colors, ...) } \arguments{ \item{moo}{\code{multiOmicDataSet} object (see \code{create_multiOmicDataSet_from_dataframes()})} \item{colname}{group column name to set the palette for} -\item{palette_fun}{Function for selecting colors. Assumed to contain \code{n} for the number of colors. Default: -\code{grDevices::palette.colors()}} +\item{palette_fun}{Function for selecting colors. Assumed to contain \code{n} for the number of colors. Defaults to +MOSuite's default plot palette. To use the previous R default palette behavior, pass +\code{grDevices::palette.colors}.} \item{...}{additional arguments forwarded to \code{palette_fun}} } diff --git a/code/MOSuite/tests/testthat/_snaps/E2E.md b/code/MOSuite/tests/testthat/_snaps/E2E.md index b420659..e2825c8 100644 --- a/code/MOSuite/tests/testthat/_snaps/E2E.md +++ b/code/MOSuite/tests/testthat/_snaps/E2E.md @@ -42,7 +42,7 @@ no duplicated IDs in gene_id * filtering clean counts Number of features after filtering: 291 - colors_for_plots NULL + colors_for_plots character * normalizing filt counts Total number of features included: 291 Sample columns: KO_S3, Sample columns: KO_S4, Sample columns: WT_S1, Sample columns: WT_S2 @@ -82,7 +82,7 @@ no duplicated IDs in GeneName * filtering clean counts Number of features after filtering: 7943 - colors_for_plots NULL + colors_for_plots character * normalizing filt counts Total number of features included: 7943 Sample columns: A1, Sample columns: A2, Sample columns: A3, Sample columns: B1, Sample columns: B2, Sample columns: B3, Sample columns: C1, Sample columns: C2, Sample columns: C3 diff --git a/code/MOSuite/tests/testthat/test-batch-correction.R b/code/MOSuite/tests/testthat/test-batch-correction.R index 850b004..e82c657 100644 --- a/code/MOSuite/tests/testthat/test-batch-correction.R +++ b/code/MOSuite/tests/testthat/test-batch-correction.R @@ -62,3 +62,150 @@ test_that("batch_correction warnings & errors", { "Batch column 'batch' cannot be included in covariates." ) }) + +test_that("batch_correct_counts forwards plot settings to PCA and histogram", { + pca_args <- NULL + histogram_args <- NULL + + local_mocked_bindings( + ComBat = function(dat, ...) dat, + .package = "sva" + ) + local_mocked_bindings( + plot_pca = function(...) { + pca_args <<- list(...) + ggplot2::ggplot() + }, + plot_histogram = function(...) { + histogram_args <<- list(...) + ggplot2::ggplot() + }, + print_or_save_plot = function(...) invisible(NULL), + .package = "MOSuite" + ) + + moo <- multiOmicDataSet( + sample_metadata = as.data.frame(nidap_sample_metadata), + anno_dat = data.frame(), + counts_lst = list( + "raw" = as.data.frame(nidap_raw_counts), + "clean" = as.data.frame(nidap_clean_raw_counts), + "filt" = as.data.frame(nidap_filtered_counts), + "norm" = list("voom" = as.data.frame(nidap_norm_counts)) + ) + ) + + batch_correct_counts( + moo, + count_type = "norm", + sub_count_type = "voom", + sample_id_colname = "Sample", + feature_id_colname = "Gene", + covariates_colnames = "Group", + batch_colname = "Batch", + label_colname = "Label", + samples_to_rename = c("A1:Alpha 1"), + add_label_to_pca = FALSE, + principal_component_on_x_axis = 2, + principal_component_on_y_axis = 3, + legend_position_for_pca = "bottom", + label_offset_x_ = 4, + label_offset_y_ = 5, + label_font_size = 6, + point_size_for_pca = 7, + color_histogram_by_group = FALSE, + set_min_max_for_x_axis_for_histogram = TRUE, + minimum_for_x_axis_for_histogram = -2, + maximum_for_x_axis_for_histogram = 2, + legend_font_size_for_histogram = 11, + legend_position_for_histogram = "right", + number_of_histogram_legend_columns = 2, + colors_for_plots = c(A = "red", B = "blue", C = "green"), + plot_corr_matrix_heatmap = FALSE, + print_plots = TRUE, + save_plots = FALSE + ) + + expect_equal(pca_args$samples_to_rename, c("A1:Alpha 1")) + expect_equal(pca_args$principal_components, c(2, 3)) + expect_equal(pca_args$legend_position, "bottom") + expect_equal(pca_args$point_size, 7) + expect_false(pca_args$add_label) + expect_equal(pca_args$label_font_size, 6) + expect_equal(pca_args$label_offset_x_, 4) + expect_equal(pca_args$label_offset_y_, 5) + expect_equal(pca_args$color_values, c(A = "red", B = "blue", C = "green")) + + expect_false(histogram_args$color_by_group) + expect_true(histogram_args$set_min_max_for_x_axis) + expect_equal(histogram_args$minimum_for_x_axis, -2) + expect_equal(histogram_args$maximum_for_x_axis, 2) + expect_equal(histogram_args$legend_font_size, 11) + expect_equal(histogram_args$legend_position, "right") + expect_equal(histogram_args$number_of_legend_columns, 2) + expect_equal(histogram_args$color_values, moo@analyses[["colors"]][["Label"]]) +}) + +test_that("batch_correct_counts forwards the default MOSuite plot colors", { + pca_args <- NULL + histogram_args <- NULL + default_colors <- c( + "#5954d6", + "#e1562c", + "#b80058", + "#00c6f8", + "#d163e6", + "#00a76c", + "#ff9287", + "#008cf9", + "#006e00", + "#796880", + "#FFA500", + "#878500" + ) + + local_mocked_bindings( + ComBat = function(dat, ...) dat, + .package = "sva" + ) + local_mocked_bindings( + plot_pca = function(...) { + pca_args <<- list(...) + ggplot2::ggplot() + }, + plot_histogram = function(...) { + histogram_args <<- list(...) + ggplot2::ggplot() + }, + print_or_save_plot = function(...) invisible(NULL), + .package = "MOSuite" + ) + + moo <- multiOmicDataSet( + sample_metadata = as.data.frame(nidap_sample_metadata), + anno_dat = data.frame(), + counts_lst = list( + "raw" = as.data.frame(nidap_raw_counts), + "clean" = as.data.frame(nidap_clean_raw_counts), + "filt" = as.data.frame(nidap_filtered_counts), + "norm" = list("voom" = as.data.frame(nidap_norm_counts)) + ) + ) + + batch_correct_counts( + moo, + count_type = "norm", + sub_count_type = "voom", + sample_id_colname = "Sample", + feature_id_colname = "Gene", + covariates_colnames = "Group", + batch_colname = "Batch", + label_colname = "Label", + plot_corr_matrix_heatmap = FALSE, + print_plots = TRUE, + save_plots = FALSE + ) + + expect_equal(pca_args$color_values, default_colors) + expect_equal(histogram_args$color_values, default_colors) +}) diff --git a/code/MOSuite/tests/testthat/test-colors.R b/code/MOSuite/tests/testthat/test-colors.R index 3aea8b4..2afd7d6 100644 --- a/code/MOSuite/tests/testthat/test-colors.R +++ b/code/MOSuite/tests/testthat/test-colors.R @@ -13,37 +13,37 @@ test_that("get_colors_lst works on nidap_sample_metadata", { get_colors_lst(nidap_sample_metadata), list( Sample = c( - A1 = "#000000", - A2 = "#E69F00", - A3 = "#56B4E9", - B1 = "#009E73", - B2 = "#F0E442", - B3 = "#0072B2", - C1 = "#D55E00", - C2 = "#CC79A7", - C3 = "#999999" + A1 = "#5954d6", + A2 = "#e1562c", + A3 = "#b80058", + B1 = "#00c6f8", + B2 = "#d163e6", + B3 = "#00a76c", + C1 = "#ff9287", + C2 = "#008cf9", + C3 = "#006e00" ), Group = c( - A = "#000000", - B = "#E69F00", - C = "#56B4E9" + A = "#5954d6", + B = "#e1562c", + C = "#b80058" ), Replicate = c( - `1` = "#000000", - `2` = "#E69F00", - `3` = "#56B4E9" + `1` = "#5954d6", + `2` = "#e1562c", + `3` = "#b80058" ), - Batch = c(`1` = "#000000", `2` = "#E69F00"), + Batch = c(`1` = "#5954d6", `2` = "#e1562c"), Label = c( - A1 = "#000000", - A2 = "#E69F00", - A3 = "#56B4E9", - B1 = "#009E73", - B2 = "#F0E442", - B3 = "#0072B2", - C1 = "#D55E00", - C2 = "#CC79A7", - C3 = "#999999" + A1 = "#5954d6", + A2 = "#e1562c", + A3 = "#b80058", + B1 = "#00c6f8", + B2 = "#d163e6", + B3 = "#00a76c", + C1 = "#ff9287", + C2 = "#008cf9", + C3 = "#006e00" ) ) ) @@ -68,10 +68,10 @@ test_that("get_colors_lst handles alternative palette functions", { ) }) test_that("get_colors_vctr falls back to random colors when n exceeds palette max", { - # Okabe-Ito palette has a maximum of 9 colors. When n > 9, the function + # MOSuite's default palette has 12 colors. When n > 12, the function # should fall back to get_random_colors() and emit a message. dat_many_cats <- data.frame( - group = paste0("cat", seq_len(12)) + group = paste0("cat", seq_len(13)) ) expect_no_warning( expect_message( @@ -79,8 +79,103 @@ test_that("get_colors_vctr falls back to random colors when n exceeds palette ma "exceeds the palette maximum" ) ) - expect_length(result, 12) - expect_named(result, paste0("cat", seq_len(12))) + expect_length(result, 13) + expect_named(result, paste0("cat", seq_len(13))) +}) + +test_that("resolve_plot_colors preserves named color mappings", { + dat <- data.frame(group = c("B", "A", "C", "A")) + colors <- c(A = "red", B = "blue", C = "green") + + expect_equal(resolve_plot_colors(dat, "group", colors), colors) +}) + +test_that("resolve_plot_colors names palettes by first observed category order", { + dat <- data.frame(group = c("B", "A", "C", "A")) + colors <- c("red", "blue", "green") + + expect_equal( + resolve_plot_colors(dat, "group", colors), + c(B = "red", A = "blue", C = "green") + ) +}) + +test_that("color vectors use factor level order when grouping column is a factor", { + dat <- data.frame( + group = factor(c("B", "A", "C", "A"), levels = c("C", "A", "B", "D")) + ) + + expect_equal( + get_colors_vctr(dat, "group"), + c(C = "#5954d6", A = "#e1562c", B = "#b80058") + ) + expect_equal( + resolve_plot_colors(dat, "group", c("red", "blue", "green")), + c(C = "red", A = "blue", B = "green") + ) +}) + +test_that("resolve_plot_colors generates colors when none are supplied", { + dat <- data.frame(group = c("B", "A", "C", "A")) + + expect_equal( + resolve_plot_colors(dat, "group"), + c(B = "#5954d6", A = "#e1562c", C = "#b80058") + ) +}) + +test_that("resolve_plot_colors generates additional colors for too few explicit colors", { + dat <- data.frame(group = c("B", "A", "C", "A")) + + expect_message( + result <- resolve_plot_colors(dat, "group", c("red", "blue")), + "Generating 1 additional colors" + ) + expect_named(result, c("B", "A", "C")) + expect_equal(unname(result[1:2]), c("red", "blue")) + expect_equal(unname(result[3]), "#b80058") +}) + +test_that("resolve_plot_colors uses random fallback only through get_colors_vctr", { + dat <- data.frame(group = paste0("cat", seq_len(13))) + colors <- c( + "#5954d6", + "#e1562c", + "#b80058", + "#00c6f8", + "#d163e6", + "#00a76c", + "#ff9287", + "#008cf9", + "#006e00", + "#796880", + "#FFA500", + "#878500" + ) + + expect_message( + expect_message( + result <- resolve_plot_colors(dat, "group", colors), + "Generating 1 additional colors" + ), + "exceeds the palette maximum" + ) + expect_named(result, paste0("cat", seq_len(13))) + expect_equal(unname(result[seq_along(colors)]), colors) + expect_match(unname(result[13]), "^#[0-9A-F]{6}$") +}) + +test_that("resolve_plot_colors treats non-matching names as palette labels", { + dat <- data.frame(group = c("B", "A", "C", "A")) + + expect_equal( + resolve_plot_colors( + dat, + "group", + c(indigo = "red", carrot = "blue", jade = "green") + ), + c(B = "red", A = "blue", C = "green") + ) }) test_that("set_color_pal overrides the color palette", { @@ -92,37 +187,37 @@ test_that("set_color_pal overrides the color palette", { moo@analyses$colors, list( Sample = c( - A1 = "#000000", - A2 = "#E69F00", - A3 = "#56B4E9", - B1 = "#009E73", - B2 = "#F0E442", - B3 = "#0072B2", - C1 = "#D55E00", - C2 = "#CC79A7", - C3 = "#999999" + A1 = "#5954d6", + A2 = "#e1562c", + A3 = "#b80058", + B1 = "#00c6f8", + B2 = "#d163e6", + B3 = "#00a76c", + C1 = "#ff9287", + C2 = "#008cf9", + C3 = "#006e00" ), Group = c( - A = "#000000", - B = "#E69F00", - C = "#56B4E9" + A = "#5954d6", + B = "#e1562c", + C = "#b80058" ), Replicate = c( - `1` = "#000000", - `2` = "#E69F00", - `3` = "#56B4E9" + `1` = "#5954d6", + `2` = "#e1562c", + `3` = "#b80058" ), - Batch = c(`1` = "#000000", `2` = "#E69F00"), + Batch = c(`1` = "#5954d6", `2` = "#e1562c"), Label = c( - A1 = "#000000", - A2 = "#E69F00", - A3 = "#56B4E9", - B1 = "#009E73", - B2 = "#F0E442", - B3 = "#0072B2", - C1 = "#D55E00", - C2 = "#CC79A7", - C3 = "#999999" + A1 = "#5954d6", + A2 = "#e1562c", + A3 = "#b80058", + B1 = "#00c6f8", + B2 = "#d163e6", + B3 = "#00a76c", + C1 = "#ff9287", + C2 = "#008cf9", + C3 = "#006e00" ) ) ) @@ -136,15 +231,15 @@ test_that("set_color_pal overrides the color palette", { moo2@analyses$colors, list( Sample = c( - A1 = "#000000", - A2 = "#E69F00", - A3 = "#56B4E9", - B1 = "#009E73", - B2 = "#F0E442", - B3 = "#0072B2", - C1 = "#D55E00", - C2 = "#CC79A7", - C3 = "#999999" + A1 = "#5954d6", + A2 = "#e1562c", + A3 = "#b80058", + B1 = "#00c6f8", + B2 = "#d163e6", + B3 = "#00a76c", + C1 = "#ff9287", + C2 = "#008cf9", + C3 = "#006e00" ), Group = c( A = "#66C2A5", @@ -152,21 +247,21 @@ test_that("set_color_pal overrides the color palette", { C = "#8DA0CB" ), Replicate = c( - `1` = "#000000", - `2` = "#E69F00", - `3` = "#56B4E9" + `1` = "#5954d6", + `2` = "#e1562c", + `3` = "#b80058" ), - Batch = c(`1` = "#000000", `2` = "#E69F00"), + Batch = c(`1` = "#5954d6", `2` = "#e1562c"), Label = c( - A1 = "#000000", - A2 = "#E69F00", - A3 = "#56B4E9", - B1 = "#009E73", - B2 = "#F0E442", - B3 = "#0072B2", - C1 = "#D55E00", - C2 = "#CC79A7", - C3 = "#999999" + A1 = "#5954d6", + A2 = "#e1562c", + A3 = "#b80058", + B1 = "#00c6f8", + B2 = "#d163e6", + B3 = "#00a76c", + C1 = "#ff9287", + C2 = "#008cf9", + C3 = "#006e00" ) ) ) diff --git a/code/MOSuite/tests/testthat/test-deseq2.R b/code/MOSuite/tests/testthat/test-deseq2.R index 5d62d7f..e33cb07 100644 --- a/code/MOSuite/tests/testthat/test-deseq2.R +++ b/code/MOSuite/tests/testthat/test-deseq2.R @@ -18,6 +18,7 @@ moo@sample_meta <- moo@sample_meta |> ) test_that("run_deseq2 works", { + expect_error( run_deseq2(moo, design = ~condition), "moo must contain filtered counts" diff --git a/code/MOSuite/tests/testthat/test-filter.R b/code/MOSuite/tests/testthat/test-filter.R index aabfc78..2b4199f 100644 --- a/code/MOSuite/tests/testthat/test-filter.R +++ b/code/MOSuite/tests/testthat/test-filter.R @@ -251,3 +251,133 @@ test_that("remove_low_count_genes works with group-based filtering (no grouped t expect_true("Gene" %in% colnames(result)) expect_true(nrow(result) > 0) }) + +test_that("filter_counts forwards plotting parameters", { + pca_args <- NULL + histogram_args <- NULL + + local_mocked_bindings( + plot_pca = function(...) { + pca_args <<- list(...) + ggplot2::ggplot() + }, + plot_histogram = function(...) { + histogram_args <<- list(...) + ggplot2::ggplot() + }, + print_or_save_plot = function(...) invisible(NULL), + .package = "MOSuite" + ) + + moo <- create_multiOmicDataSet_from_dataframes( + as.data.frame(nidap_sample_metadata), + as.data.frame(nidap_clean_raw_counts), + sample_id_colname = "Sample", + feature_id_colname = "Gene" + ) |> + calc_cpm(feature_id_colname = "Gene") + + filter_counts( + moo, + sample_id_colname = "Sample", + feature_id_colname = "Gene", + label_colname = "Label", + count_type = "raw", + samples_to_rename = c("A1:Alpha 1"), + add_label_to_pca = FALSE, + principal_component_on_x_axis = 2, + principal_component_on_y_axis = 3, + legend_position_for_pca = "bottom", + label_offset_x_ = 4, + label_offset_y_ = 5, + label_font_size = 6, + point_size_for_pca = 7, + color_histogram_by_group = TRUE, + set_min_max_for_x_axis_for_histogram = TRUE, + minimum_for_x_axis_for_histogram = -2, + maximum_for_x_axis_for_histogram = 2, + legend_font_size_for_histogram = 11, + legend_position_for_histogram = "right", + number_of_histogram_legend_columns = 2, + colors_for_plots = c(A = "red", B = "blue", C = "green"), + plot_corr_matrix_heatmap = FALSE, + print_plots = TRUE, + save_plots = FALSE + ) + + expect_equal(pca_args$samples_to_rename, c("A1:Alpha 1")) + expect_equal(pca_args$principal_components, c(2, 3)) + expect_equal(pca_args$legend_position, "bottom") + expect_equal(pca_args$point_size, 7) + expect_false(pca_args$add_label) + expect_equal(pca_args$label_font_size, 6) + expect_equal(pca_args$label_offset_x_, 4) + expect_equal(pca_args$label_offset_y_, 5) + expect_equal(pca_args$color_values, c(A = "red", B = "blue", C = "green")) + + expect_true(histogram_args$color_by_group) + expect_true(histogram_args$set_min_max_for_x_axis) + expect_equal(histogram_args$minimum_for_x_axis, -2) + expect_equal(histogram_args$maximum_for_x_axis, 2) + expect_equal(histogram_args$legend_font_size, 11) + expect_equal(histogram_args$legend_position, "right") + expect_equal(histogram_args$number_of_legend_columns, 2) + expect_equal( + histogram_args$color_values, + c(A = "red", B = "blue", C = "green") + ) +}) + +test_that("filter_counts forwards the default MOSuite plot colors", { + pca_args <- NULL + histogram_args <- NULL + default_colors <- c( + "#5954d6", + "#e1562c", + "#b80058", + "#00c6f8", + "#d163e6", + "#00a76c", + "#ff9287", + "#008cf9", + "#006e00", + "#796880", + "#FFA500", + "#878500" + ) + + local_mocked_bindings( + plot_pca = function(...) { + pca_args <<- list(...) + ggplot2::ggplot() + }, + plot_histogram = function(...) { + histogram_args <<- list(...) + ggplot2::ggplot() + }, + print_or_save_plot = function(...) invisible(NULL), + .package = "MOSuite" + ) + + moo <- create_multiOmicDataSet_from_dataframes( + as.data.frame(nidap_sample_metadata), + as.data.frame(nidap_clean_raw_counts), + sample_id_colname = "Sample", + feature_id_colname = "Gene" + ) |> + calc_cpm(feature_id_colname = "Gene") + + filter_counts( + moo, + sample_id_colname = "Sample", + feature_id_colname = "Gene", + label_colname = "Label", + count_type = "raw", + plot_corr_matrix_heatmap = FALSE, + print_plots = TRUE, + save_plots = FALSE + ) + + expect_equal(pca_args$color_values, default_colors) + expect_equal(histogram_args$color_values, default_colors) +}) diff --git a/code/MOSuite/tests/testthat/test-normalize.R b/code/MOSuite/tests/testthat/test-normalize.R index 87318d4..d245a16 100644 --- a/code/MOSuite/tests/testthat/test-normalize.R +++ b/code/MOSuite/tests/testthat/test-normalize.R @@ -137,3 +137,130 @@ test_that("normalize works for RENEE", { ) ) }) + +test_that("normalize_counts forwards plotting parameters", { + pca_args <- NULL + histogram_args <- NULL + + local_mocked_bindings( + plot_pca = function(...) { + pca_args <<- list(...) + ggplot2::ggplot() + }, + plot_histogram = function(...) { + histogram_args <<- list(...) + ggplot2::ggplot() + }, + print_or_save_plot = function(...) invisible(NULL), + .package = "MOSuite" + ) + + moo <- multiOmicDataSet( + sample_metadata = as.data.frame(nidap_sample_metadata), + anno_dat = data.frame(), + counts_lst = list( + "raw" = as.data.frame(nidap_raw_counts), + "clean" = as.data.frame(nidap_clean_raw_counts), + "filt" = as.data.frame(nidap_filtered_counts) + ) + ) + + normalize_counts( + moo, + group_colname = "Group", + label_colname = "Label", + samples_to_rename = c("A1:Alpha 1"), + add_label_to_pca = FALSE, + principal_component_on_x_axis = 2, + principal_component_on_y_axis = 3, + legend_position_for_pca = "bottom", + label_offset_x_ = 4, + label_offset_y_ = 5, + label_font_size = 6, + point_size_for_pca = 7, + color_histogram_by_group = FALSE, + set_min_max_for_x_axis_for_histogram = TRUE, + minimum_for_x_axis_for_histogram = -2, + maximum_for_x_axis_for_histogram = 2, + legend_font_size_for_histogram = 11, + legend_position_for_histogram = "right", + number_of_histogram_legend_columns = 2, + colors_for_plots = c(A = "red", B = "blue", C = "green"), + plot_corr_matrix_heatmap = FALSE, + print_plots = TRUE, + save_plots = FALSE + ) + + expect_equal(pca_args$samples_to_rename, c("A1:Alpha 1")) + expect_equal(pca_args$principal_components, c(2, 3)) + expect_equal(pca_args$legend_position, "bottom") + expect_equal(pca_args$point_size, 7) + expect_false(pca_args$add_label) + expect_equal(pca_args$label_font_size, 6) + expect_equal(pca_args$label_offset_x_, 4) + expect_equal(pca_args$label_offset_y_, 5) + expect_equal(pca_args$color_values, c(A = "red", B = "blue", C = "green")) + + expect_false(histogram_args$color_by_group) + expect_true(histogram_args$set_min_max_for_x_axis) + expect_equal(histogram_args$minimum_for_x_axis, -2) + expect_equal(histogram_args$maximum_for_x_axis, 2) + expect_equal(histogram_args$legend_font_size, 11) + expect_equal(histogram_args$legend_position, "right") + expect_equal(histogram_args$number_of_legend_columns, 2) + expect_equal(histogram_args$color_values, moo@analyses[["colors"]][["Label"]]) +}) + +test_that("normalize_counts forwards the default MOSuite plot colors", { + pca_args <- NULL + histogram_args <- NULL + default_colors <- c( + "#5954d6", + "#e1562c", + "#b80058", + "#00c6f8", + "#d163e6", + "#00a76c", + "#ff9287", + "#008cf9", + "#006e00", + "#796880", + "#FFA500", + "#878500" + ) + + local_mocked_bindings( + plot_pca = function(...) { + pca_args <<- list(...) + ggplot2::ggplot() + }, + plot_histogram = function(...) { + histogram_args <<- list(...) + ggplot2::ggplot() + }, + print_or_save_plot = function(...) invisible(NULL), + .package = "MOSuite" + ) + + moo <- multiOmicDataSet( + sample_metadata = as.data.frame(nidap_sample_metadata), + anno_dat = data.frame(), + counts_lst = list( + "raw" = as.data.frame(nidap_raw_counts), + "clean" = as.data.frame(nidap_clean_raw_counts), + "filt" = as.data.frame(nidap_filtered_counts) + ) + ) + + normalize_counts( + moo, + group_colname = "Group", + label_colname = "Label", + plot_corr_matrix_heatmap = FALSE, + print_plots = TRUE, + save_plots = FALSE + ) + + expect_equal(pca_args$color_values, default_colors) + expect_equal(histogram_args$color_values, default_colors) +}) diff --git a/code/MOSuite/tests/testthat/test-plot_heatmap.R b/code/MOSuite/tests/testthat/test-plot_heatmap.R index ba9fb1a..d12d5ae 100644 --- a/code/MOSuite/tests/testthat/test-plot_heatmap.R +++ b/code/MOSuite/tests/testthat/test-plot_heatmap.R @@ -121,6 +121,40 @@ test_that("correlation heatmap works", { ) }) +test_that("correlation heatmap resolves annotation colors by first observed group order", { + counts_dat <- nidap_filtered_counts[, c( + "Gene", + "B1", + "B2", + "B3", + "A1", + "A2", + "A3", + "C1", + "C2", + "C3" + )] + sample_metadata <- as.data.frame(nidap_sample_metadata) + sample_metadata <- sample_metadata[ + match(colnames(counts_dat)[-1], sample_metadata$Sample), + ] + + p <- plot_corr_heatmap( + counts_dat, + sample_metadata = sample_metadata, + sample_id_colname = "Sample", + feature_id_colname = "Gene", + label_colname = "Label", + group_colname = "Group", + color_values = c("#5954d6", "#e1562c", "#b80058") + ) + + expect_equal( + p@top_annotation@anno_list$Group@color_mapping@colors[c("B", "A", "C")], + c(B = "#5954D6FF", A = "#E1562CFF", C = "#B80058FF") + ) +}) + test_that("plot_corr_heatmap method dispatch works", { moo <- multiOmicDataSet( sample_metadata = as.data.frame(nidap_sample_metadata), @@ -261,3 +295,40 @@ test_that("plot_expr_heatmap works", { expect_equal(p_moo@matrix, p_dat@matrix) }) + +test_that("plot_expr_heatmap resolves annotation colors by first observed group order", { + counts_dat <- nidap_norm_counts[, c( + "Gene", + "B1", + "B2", + "B3", + "A1", + "A2", + "A3", + "C1", + "C2", + "C3" + )] + sample_metadata <- as.data.frame(nidap_sample_metadata) + sample_metadata <- sample_metadata[ + match(colnames(counts_dat)[-1], sample_metadata$Sample), + ] + + expect_message( + p <- plot_expr_heatmap( + as.data.frame(counts_dat), + sample_metadata = sample_metadata, + feature_id_colname = "Gene", + samples_to_include = colnames(counts_dat)[-1], + group_columns = "Group", + group_colors = c("#5954d6", "#e1562c", "#b80058") + ), + "total number of genes in heatmap", + fixed = FALSE + ) + + expect_equal( + p@top_annotation@anno_list$Group@color_mapping@colors[c("B", "A", "C")], + c(B = "#5954D6FF", A = "#E1562CFF", C = "#B80058FF") + ) +}) diff --git a/code/MOSuite/tests/testthat/test-plot_histogram.R b/code/MOSuite/tests/testthat/test-plot_histogram.R index 598289d..4a1623f 100644 --- a/code/MOSuite/tests/testthat/test-plot_histogram.R +++ b/code/MOSuite/tests/testthat/test-plot_histogram.R @@ -79,6 +79,62 @@ sample_meta <- structure( row.names = c("A1", "A2", "A3", "B1", "B2", "B3", "C1", "C2", "C3"), class = "data.frame" ) + +get_histogram_colour_guide_ncol <- function(plot) { + plot$guides$guides$colour$params$ncol +} + +test_that("plot_histogram wraps long top and bottom sample-name legends", { + counts_dat <- log_counts |> + as.data.frame() |> + tibble::rownames_to_column("Gene") + sample_columns <- setdiff(colnames(counts_dat), "Gene") + long_sample_names <- stats::setNames( + sprintf("SampleName%05d", seq_along(sample_columns)), + sample_columns + ) + colnames(counts_dat) <- ifelse( + colnames(counts_dat) %in% names(long_sample_names), + unname(long_sample_names[colnames(counts_dat)]), + colnames(counts_dat) + ) + sample_metadata <- sample_meta + sample_metadata$Sample <- unname(long_sample_names[as.character( + sample_metadata$Sample + )]) + sample_metadata$Label <- sample_metadata$Sample + + for (legend_position in c("top", "bottom")) { + plot <- plot_histogram( + counts_dat, + sample_metadata = sample_metadata, + sample_id_colname = "Sample", + feature_id_colname = "Gene", + color_by_group = FALSE, + legend_position = legend_position, + number_of_legend_columns = 6 + ) + + expect_equal(get_histogram_colour_guide_ncol(plot), 3) + } +}) + +test_that("plot_histogram legend columns target the colour guide", { + plot <- plot_histogram( + log_counts |> + as.data.frame() |> + tibble::rownames_to_column("Gene"), + sample_meta, + sample_id_colname = "Sample", + feature_id_colname = "Gene", + color_by_group = FALSE, + legend_position = "top", + number_of_legend_columns = 2 + ) + + expect_equal(get_histogram_colour_guide_ncol(plot), 2) +}) + test_that("plot_histogram works with rownames", { p <- plot_histogram( log_counts |> as.data.frame() |> tibble::rownames_to_column("Gene"), @@ -515,6 +571,108 @@ test_that("plot_histogram works with rownames", { ) }) +test_that("plot_histogram resolves group colors by first observed group order", { + color_values <- c("#5954d6", "#e1562c", "#b80058") + counts_dat <- nidap_filtered_counts[, c( + "Gene", + "B1", + "B2", + "B3", + "A1", + "A2", + "A3", + "C1", + "C2", + "C3" + )] + plot <- plot_histogram( + counts_dat, + sample_metadata = nidap_sample_metadata, + sample_id_colname = "Sample", + feature_id_colname = "Gene", + group_colname = "Group", + color_values = color_values, + color_by_group = TRUE + ) + scales <- ggplot2::ggplot_build(plot)$plot$scales$scales + colour_scale <- scales[[which(vapply( + scales, + function(scale) "colour" %in% scale$aesthetics, + logical(1) + ))[[1]]]] + + expect_equal( + colour_scale$palette.cache, + c(B = "#5954d6", A = "#e1562c", C = "#b80058") + ) +}) + +test_that("plot_histogram resolves group colors by factor level order", { + color_values <- c("#5954d6", "#e1562c", "#b80058") + counts_dat <- nidap_filtered_counts[, c( + "Gene", + "B1", + "B2", + "B3", + "A1", + "A2", + "A3", + "C1", + "C2", + "C3" + )] + sample_metadata <- nidap_sample_metadata + sample_metadata$Group <- factor( + sample_metadata$Group, + levels = c("C", "A", "B") + ) + + plot <- plot_histogram( + counts_dat, + sample_metadata = sample_metadata, + sample_id_colname = "Sample", + feature_id_colname = "Gene", + group_colname = "Group", + color_values = color_values, + color_by_group = TRUE + ) + scales <- ggplot2::ggplot_build(plot)$plot$scales$scales + colour_scale <- scales[[which(vapply( + scales, + function(scale) "colour" %in% scale$aesthetics, + logical(1) + ))[[1]]]] + + expect_equal( + colour_scale$palette.cache, + c(C = "#5954d6", A = "#e1562c", B = "#b80058") + ) +}) + +test_that("plot_histogram resolves sample colors by first observed sample order", { + color_values <- c("#5954d6", "#e1562c", "#b80058", "#00c6f8") + counts_dat <- nidap_filtered_counts[, c("Gene", "B1", "A1", "C1", "A2")] + plot <- plot_histogram( + counts_dat, + sample_metadata = nidap_sample_metadata, + sample_id_colname = "Sample", + feature_id_colname = "Gene", + color_values = color_values, + color_by_group = FALSE + ) + scales <- ggplot2::ggplot_build(plot)$plot$scales$scales + colour_scale <- scales[[which(vapply( + scales, + function(scale) "colour" %in% scale$aesthetics, + logical(1) + ))[[1]]]] + + expect_equal( + colour_scale$palette.cache, + c(B1 = "#5954d6", A1 = "#e1562c", C1 = "#b80058", A2 = "#00c6f8") + ) +}) + test_that("plot_histogram works with tibbles", { p <- plot_histogram( nidap_filtered_counts, diff --git a/code/MOSuite/tests/testthat/test-plot_pca.R b/code/MOSuite/tests/testthat/test-plot_pca.R index db003f3..39ea8f3 100644 --- a/code/MOSuite/tests/testthat/test-plot_pca.R +++ b/code/MOSuite/tests/testthat/test-plot_pca.R @@ -189,6 +189,216 @@ test_that("plot_pca layers are expected", { expect_s3_class(p$layers[[1]]$geom, "GeomPoint") }) +normalize_color_values <- function(colors) { + vapply( + colors, + function(color) { + if (grepl("^rgba\\(", color)) { + color_parts <- strsplit(gsub("^rgba\\(|\\)$", "", color), ",")[[1]] + color_parts <- as.numeric(color_parts[seq_len(3)]) + grDevices::rgb( + color_parts[1], + color_parts[2], + color_parts[3], + maxColorValue = 255 + ) + } else { + rgb_value <- grDevices::col2rgb(color) + grDevices::rgb( + rgb_value[1, 1], + rgb_value[2, 1], + rgb_value[3, 1], + maxColorValue = 255 + ) + } + }, + character(1), + USE.NAMES = FALSE + ) +} + +get_colour_scale <- function(plot) { + scales <- ggplot2::ggplot_build(plot)$plot$scales$scales + scales[[which(vapply( + scales, + function(scale) "colour" %in% scale$aesthetics, + logical(1) + ))[[1]]]] +} + +get_colour_guide_ncol <- function(plot) { + plot$guides$guides$colour$params$ncol +} + +test_that("2D PCA wraps long top and bottom sample-name legends", { + sample_columns <- setdiff(colnames(nidap_filtered_counts), "Gene") + long_sample_names <- stats::setNames( + sprintf("SampleName%05d", seq_along(sample_columns)), + sample_columns + ) + counts_dat <- nidap_filtered_counts + colnames(counts_dat) <- ifelse( + colnames(counts_dat) %in% names(long_sample_names), + unname(long_sample_names[colnames(counts_dat)]), + colnames(counts_dat) + ) + sample_metadata <- nidap_sample_metadata + sample_metadata$Sample <- unname(long_sample_names[as.character( + sample_metadata$Sample + )]) + sample_metadata$Label <- sample_metadata$Sample + + for (legend_position in c("top", "bottom")) { + pca_2d <- plot_pca_2d( + counts_dat, + sample_metadata = sample_metadata, + sample_id_colname = "Sample", + feature_id_colname = "Gene", + group_colname = "Sample", + label_colname = "Label", + legend_position = legend_position, + add_label = FALSE, + print_plots = FALSE, + save_plots = FALSE + ) + + expect_equal(get_colour_guide_ncol(pca_2d), 3) + } +}) + +test_that("2D and 3D PCA resolve unnamed colors by first observed group order", { + color_values <- c("#5954d6", "#e1562c", "#b80058") + expected_colors <- c(B = "#5954d6", A = "#e1562c", C = "#b80058") + counts_dat <- nidap_filtered_counts[, c( + "Gene", + "B1", + "B2", + "B3", + "A1", + "A2", + "A3", + "C1", + "C2", + "C3" + )] + + pca_2d <- plot_pca_2d( + counts_dat, + sample_metadata = nidap_sample_metadata, + feature_id_colname = "Gene", + color_values = color_values, + add_label = FALSE, + print_plots = FALSE, + save_plots = FALSE + ) + pca_2d_colors <- get_colour_scale(pca_2d)$palette.cache[names( + expected_colors + )] + + pca_3d <- plot_pca_3d( + counts_dat, + sample_metadata = nidap_sample_metadata, + feature_id_colname = "Gene", + color_values = color_values, + print_plots = FALSE, + save_plots = FALSE + ) + pca_3d_traces <- plotly::plotly_build(pca_3d)$x$data + pca_3d_colors <- stats::setNames( + normalize_color_values(vapply( + pca_3d_traces, + function(trace) trace$marker$color, + character(1) + )), + vapply(pca_3d_traces, function(trace) trace$name, character(1)) + )[names(expected_colors)] + + expected_colors <- stats::setNames( + normalize_color_values(expected_colors), + names(expected_colors) + ) + + expect_equal(normalize_color_values(pca_2d_colors), unname(expected_colors)) + expect_equal(pca_3d_colors, expected_colors) +}) + +test_that("2D and 3D PCA resolve unnamed colors by factor level order", { + color_values <- c("#5954d6", "#e1562c", "#b80058") + expected_colors <- c(C = "#5954d6", A = "#e1562c", B = "#b80058") + counts_dat <- nidap_filtered_counts[, c( + "Gene", + "B1", + "B2", + "B3", + "A1", + "A2", + "A3", + "C1", + "C2", + "C3" + )] + sample_metadata <- nidap_sample_metadata + sample_metadata$Group <- factor( + sample_metadata$Group, + levels = c("C", "A", "B") + ) + + pca_2d <- plot_pca_2d( + counts_dat, + sample_metadata = sample_metadata, + feature_id_colname = "Gene", + color_values = color_values, + add_label = FALSE, + print_plots = FALSE, + save_plots = FALSE + ) + pca_2d_colors <- get_colour_scale(pca_2d)$palette.cache[names( + expected_colors + )] + + pca_3d <- plot_pca_3d( + counts_dat, + sample_metadata = sample_metadata, + feature_id_colname = "Gene", + color_values = color_values, + print_plots = FALSE, + save_plots = FALSE + ) + pca_3d_traces <- plotly::plotly_build(pca_3d)$x$data + pca_3d_colors <- stats::setNames( + normalize_color_values(vapply( + pca_3d_traces, + function(trace) trace$marker$color, + character(1) + )), + vapply(pca_3d_traces, function(trace) trace$name, character(1)) + )[names(expected_colors)] + + expected_colors <- stats::setNames( + normalize_color_values(expected_colors), + names(expected_colors) + ) + + expect_equal(normalize_color_values(pca_2d_colors), unname(expected_colors)) + expect_equal(pca_3d_colors, expected_colors) +}) + +test_that("2D PCA preserves named color mappings", { + color_values <- c(C = "#5954d6", A = "#e1562c", B = "#b80058") + + pca_2d <- plot_pca_2d( + nidap_filtered_counts, + sample_metadata = nidap_sample_metadata, + feature_id_colname = "Gene", + color_values = color_values, + add_label = FALSE, + print_plots = FALSE, + save_plots = FALSE + ) + + expect_equal(get_colour_scale(pca_2d)$palette.cache, color_values) +}) + test_that("2D & 3D PCA method dispatch works", { moo <- multiOmicDataSet( diff --git a/code/MOSuite/tests/testthat/test-plot_read_depth.R b/code/MOSuite/tests/testthat/test-plot_read_depth.R index df2f4e1..d7a5ae5 100644 --- a/code/MOSuite/tests/testthat/test-plot_read_depth.R +++ b/code/MOSuite/tests/testthat/test-plot_read_depth.R @@ -28,3 +28,63 @@ test_that("plot_read_depth accepts extra args via moo dispatch without error", { ) ) }) + +test_that("plot_read_depth can color samples by group", { + moo <- multiOmicDataSet( + sample_metadata = nidap_sample_metadata, + anno_dat = data.frame(), + counts_lst = list("raw" = nidap_raw_counts) + ) + plot <- plot_read_depth( + moo, + count_type = "raw", + sample_id_colname = "Sample", + group_colname = "Group", + color_values = c("A" = "blue", "B" = "green", "C" = "orange") + ) + + built <- ggplot2::ggplot_build(plot) + expect_equal( + unique(built$data[[1]]$fill), + c("blue", "green", "orange") + ) +}) + +test_that("plot_read_depth keeps single-color bars when group_colname is blank", { + moo <- multiOmicDataSet( + sample_metadata = nidap_sample_metadata, + anno_dat = data.frame(), + counts_lst = list("raw" = nidap_raw_counts) + ) + plot <- plot_read_depth( + moo, + count_type = "raw", + sample_id_colname = "Sample", + group_colname = "", + color_values = c("A" = "blue", "B" = "green", "C" = "orange") + ) + + built <- ggplot2::ggplot_build(plot) + expect_equal(unique(built$data[[1]]$fill), "blue") +}) + +test_that("plot_read_depth extends undersupplied group colors", { + moo <- multiOmicDataSet( + sample_metadata = nidap_sample_metadata, + anno_dat = data.frame(), + counts_lst = list("raw" = nidap_raw_counts) + ) + plot <- plot_read_depth( + moo, + count_type = "raw", + sample_id_colname = "Sample", + group_colname = "Group", + color_values = c("blue") + ) + + built <- ggplot2::ggplot_build(plot) + expect_equal( + unique(built$data[[1]]$fill), + c("blue", "#e1562c", "#b80058") + ) +})