From b66724f2ee41e307a5fe6030fc0c889c89ef9bf2 Mon Sep 17 00:00:00 2001 From: Tuomas Borman Date: Fri, 7 Aug 2026 14:05:57 +0300 Subject: [PATCH 1/5] Add plotHeatmap --- R/AllGenerics.R | 5 + R/plotHeatmap.R | 470 ++++++++++++++++++++++++++++++ tests/testthat/test-plotHeatmap.R | 309 ++++++++++++++++++++ 3 files changed, 784 insertions(+) create mode 100644 R/plotHeatmap.R create mode 100644 tests/testthat/test-plotHeatmap.R diff --git a/R/AllGenerics.R b/R/AllGenerics.R index 1058ba59..30b98be9 100644 --- a/R/AllGenerics.R +++ b/R/AllGenerics.R @@ -166,3 +166,8 @@ setGeneric("plotOrdination", signature = c("x"), function(x, ...) #' @export setGeneric("plotJointRPCA", signature = c("x"), function(x, ...) standardGeneric("plotJointRPCA")) + +#' @rdname plotHeatmap +#' @export +setGeneric("plotHeatmap", signature = c("x"), function(x, ...) + standardGeneric("plotHeatmap")) diff --git a/R/plotHeatmap.R b/R/plotHeatmap.R new file mode 100644 index 00000000..08971464 --- /dev/null +++ b/R/plotHeatmap.R @@ -0,0 +1,470 @@ +#' @name +#' plotHeatmap +#' +#' @title +#' Visualize assay values as a heatmap +#' +#' @description +#' Creates a heatmap from an assay stored in a +#' \code{SummarizedExperiment} or +#' \code{TreeSummarizedExperiment}. Features are shown as rows and +#' samples as columns. +#' +#' @details +#' \code{plotHeatmap} visualizes values from an assay as a heatmap. Values +#' can be optionally centered and/or scaled across samples for each feature, +#' which is useful for highlighting relative abundance patterns rather than +#' absolute abundances. +#' +#' Additional variables from \code{rowData(x)} and \code{colData(x)} can be +#' used to facet the heatmap. When multiple row or column variables are +#' provided, nested facets are created using +#' \pkg{ggh4x}. +#' +#' For \code{TreeSummarizedExperiment} objects, a row tree can be displayed +#' alongside the heatmap. When a tree is shown, only leaf nodes are plotted +#' and the heatmap rows are reordered to match the tree tip order. +#' +#' @return +#' A \code{ggplot2} object. If \code{show.tree = TRUE}, the returned object is +#' a combined \code{patchwork} object containing the row tree and the heatmap. +#' +#' @inheritParams plotAbundance +#' +#' @param row.var \code{NULL} or \code{character vector}. Variables from +#' \code{rowData(x)} used for row facetting. +#' (Default: \code{NULL}) +#' +#' @param col.var \code{NULL} or \code{character vector}. Variables from +#' \code{colData(x)} used for column facetting. +#' (Default: \code{NULL}) +#' +#' @param scale \code{Logical scalar}. Should assay values be scaled for each +#' feature across samples? +#' (Default: \code{FALSE}) +#' +#' @param center \code{Logical scalar}. Should assay values be centered for +#' each feature across samples? +#' (Default: \code{FALSE}) +#' +#' @param tree.name \code{Character scalar}. Name of the row tree to display +#' when \code{x} is a \code{TreeSummarizedExperiment}. +#' (Default: \code{"phylo"}) +#' +#' @param show.tree \code{Logical scalar}. Should the row tree be displayed? +#' Only available for \code{TreeSummarizedExperiment}. +#' (Default: \code{TRUE}) +#' +#' @param ... Additional parameters controlling the visualization. When +#' \code{show.tree = TRUE}, additional arguments are passed to +#' \code{\link{plotRowTree}} to control the appearance of the tree. +#' Additional parameters include, for example: +#' \itemize{ +#' \item \code{scales}: Facet scaling passed to +#' \code{ggplot2::facet_grid()} or +#' \code{ggh4x::facet_nested()}. +#' (Default: \code{"free"}) +#' \item \code{tree.width}: Relative width of the tree panel when +#' \code{show.tree = TRUE}. +#' (Default: \code{0.2}) +#' } +#' +#' @examples +#' data(GlobalPatterns) +#' tse <- GlobalPatterns +#' +#' tse <- agglomerateByPrevalence(tse, rank = "Class") +#' +#' # Plot raw counts +#' plotHeatmap( +#' tse, +#' assay.type = "counts" +#' ) +#' +#' # Scale and center each feature +#' plotHeatmap( +#' tse, +#' assay.type = "counts", +#' scale = TRUE, +#' center = TRUE +#' ) +#' +#' # Facet samples by metadata +#' plotHeatmap( +#' tse, +#' assay.type = "counts", +#' col.var = "SampleType" +#' ) +#' +#' @seealso +#' \code{\link[=plotRowTree]{plotRowTree}} +#' +NULL + +#' @rdname plotHeatmap +#' @export +setMethod( + "plotHeatmap", + signature = c(x = "TreeSummarizedExperiment"), + function( + x, + assay.type = NULL, + row.var = NULL, + col.var = NULL, + scale = FALSE, + center = FALSE, + tree.name = "phylo", + show.tree = FALSE, + ... + ) { + + # Validate tree-related arguments + if (!.is_a_bool(show.tree)) { + stop("'show.tree' must be TRUE or FALSE.", call. = FALSE) + } + + # Prepare the TreeSummarizedExperiment for plotting + if (show.tree) { + + # Row facets are incompatible with the row tree + if (!is.null(row.var)) { + stop( + "'row.var' cannot be specified when 'show.tree = TRUE'.", + call. = FALSE + ) + } + + # Check that the requested row tree is available + .check_rowTree_present(tree.name, x) + + tree_plot <- plotRowTree( + x, + tree.name = tree.name, + layout = "rectangular", + branch.length = "none", + show.label = TRUE, + levels.rm = TRUE + ) + + tree_data <- ggplot_build(tree_plot) + tips <- tree_data$data[[5]][, c("y", "label")] + tips <- tips[order(tips$y), , drop = FALSE] + + tax_order <- match( + tips$label, + rowLinks(x)$nodeLab + ) + + x <- x[tax_order, ] + # + # # Keep only features represented by tree leaves + # x <- subsetByLeaf(x, whichRowTree = tree.name) + # + # # Reorder rows to match the tree tip order + # links <- rowLinks(x) + # keep <- links$whichTree == tree.name & links$isLeaf + # links <- links[keep, "nodeNum"] |> order() + # x <- x[links, ] + } + + # Create the heatmap using the SummarizedExperiment method + p <- callNextMethod( + x = x, + assay.type = assay.type, + row.var = row.var, + col.var = col.var, + scale = scale, + center = center, + ... + ) + + # Add the row tree to the heatmap + if (show.tree) { + p <- .add_row_tree_to_heatmap( + tse = x, + p = p, + tree.name = tree.name, + ... + ) + } + + return(p) + } +) + + +#' @rdname plotHeatmap +#' @export +setMethod( + "plotHeatmap", + signature = c(x = "SummarizedExperiment"), + function( + x, + assay.type = NULL, + row.var = NULL, + col.var = NULL, + scale = FALSE, + center = FALSE, + ... + ) { + + # Validate input + .check_assay_present(assay.type, x) + .check_metadata_variable(x, row.var, row = TRUE, multiple = TRUE) + .check_metadata_variable(x, col.var, col = TRUE, multiple = TRUE) + + if (!.is_a_bool(scale)) { + stop("'scale' must be TRUE or FALSE.", call. = FALSE) + } + + if (!.is_a_bool(center)) { + stop("'center' must be TRUE or FALSE.", call. = FALSE) + } + + # Retrieve assay values together with the requested metadata + df <- .get_heatmap_data( + tse = x, + assay.type = assay.type, + row.var = row.var, + col.var = col.var, + scale = scale, + center = center + ) + + # Update the legend title to reflect any transformation + assay.name <- assay.type + if (scale && center) { + assay.type <- paste0(assay.type, " (scaled & centered)") + } else if (scale) { + assay.type <- paste0(assay.type, " (scaled)") + } else if (center) { + assay.type <- paste0(assay.type, " (centered)") + } + names(df)[names(df) == assay.name] <- assay.type + + # Create the heatmap + p <- .plot_heatmap( + df = df, + fill = assay.type, + scale = scale, + ... + ) + + # Add row and/or column facets + # No faceting requested + if (!is.null(row.var) || !is.null(col.var)) { + p <- .resolve_heatmap_facets( + p = p, + row.var = row.var, + col.var = col.var, + ... + ) + } + + return(p) + } +) + +############################### HELPER FUNCTIONS ############################### + +# Retrieve assay values together with the requested metadata. +#' @importFrom dplyr group_by mutate ungroup +.get_heatmap_data <- function( + tse, + assay.type, + row.var = NULL, + col.var = NULL, + scale = FALSE, + center = FALSE, + ... +) { + + df <- meltSE( + tse, + assay.type = assay.type, + add.row = row.var, + add.col = col.var + ) + + # Scale and/or center each feature across samples + if (scale || center) { + df <- df |> + group_by(FeatureID) |> + mutate( + !!assay.type := as.numeric( + scale( + .data[[assay.type]], + center = center, + scale = scale + ) + ) + ) |> + ungroup() + } + + return(df) +} + + +# Create the base heatmap. +#' @importFrom ggplot2 aes element_rect element_text geom_tile ggplot labs +#' scale_fill_gradient2 theme theme_minimal +.plot_heatmap <- function( + df, + fill, + scale = FALSE, + ... +) { + + # Create the heatmap + p <- ggplot( + df, + aes( + x = SampleID, + y = FeatureID, + fill = .data[[fill]] + ) + ) + + geom_tile( + width = 0.95, + height = 0.95, + colour = "white", + linewidth = 0.2 + ) + + # Apply the heatmap theme + p <- p + + theme_minimal() + + theme( + strip.background = element_rect( + fill = "white", + colour = "black", + linewidth = 0.5 + ), + strip.text = element_text( + face = "bold", + colour = "black" + ) + ) + + # Remove axis titles + p <- p + + labs( + x = NULL, + y = NULL + ) + + # Apply the fill scale + p <- .resolve_heatmap_scale( + p = p, + values = df[[fill]], + name = fill, + scale = scale + ) + + return(p) +} + + +# Apply the fill scale. + +#' @importFrom ggplot2 scale_fill_gradient2 +.resolve_heatmap_scale <- function( + p, + values, + name, + scale, + ... +) { + + if (scale) { + p <- p + + scale_fill_gradient2( + low = "#2166AC", + mid = "white", + high = "#B2182B", + midpoint = 0 + ) + } else { + p <- .resolve_plot_colours( + p, + values, + name, + fill = TRUE + ) + } + + return(p) +} + + +# Add row and/or column facets. +#' @importFrom ggplot2 facet_grid +.resolve_heatmap_facets <- function( + p, + row.var = NULL, + col.var = NULL, + scales = "free", + ... +) { + + # Construct the facet formula + rows <- if (is.null(row.var)) "." else paste(row.var, collapse = " + ") + cols <- if (is.null(col.var)) "." else paste(col.var, collapse = " + ") + + form <- stats::as.formula( + paste(rows, "~", cols) + ) + + # Use ggplot2 for simple facets and ggh4x for nested facets + if (length(row.var) <= 1L && length(col.var) <= 1L) { + p <- p + + facet_grid( + form, + scales = scales + ) + } else { + .require_package("ggh4x") + p <- p + + ggh4x::facet_nested( + form, + scales = scales + ) + } + + return(p) +} + + +# Combine the row tree and heatmap into a single plot. +#' @importFrom patchwork plot_layout +#' @importFrom ggplot2 theme +.add_row_tree_to_heatmap <- function( + tse, + p, + tree.name, + layout = "rectangular", + tree.width = 0.2, + ... +) { + + # Draw the row tree. Only rectangular layout is available. + p_tree <- plotRowTree( + tse, + tree.name = tree.name, + layout = "rectangular", + ... + ) + + # Combine the tree and heatmap + p <- (p_tree + p) + + plot_layout( + widths = c(tree.width, 1), + guides = "collect" + ) & + theme( + legend.position = "right" + ) + + return(p) +} diff --git a/tests/testthat/test-plotHeatmap.R b/tests/testthat/test-plotHeatmap.R new file mode 100644 index 00000000..d8186d30 --- /dev/null +++ b/tests/testthat/test-plotHeatmap.R @@ -0,0 +1,309 @@ +test_that("plotHeatmap works for SummarizedExperiment", { + + data(GlobalPatterns) + tse <- agglomerateByPrevalence(GlobalPatterns, rank = "Class") + + p <- plotHeatmap( + tse, + assay.type = "counts" + ) + + expect_s3_class(p, "ggplot") +}) + +test_that("plotHeatmap supports scaling", { + + data(GlobalPatterns) + tse <- agglomerateByPrevalence(GlobalPatterns, rank = "Class") + + expect_s3_class( + plotHeatmap( + tse, + assay.type = "counts", + scale = TRUE + ), + "ggplot" + ) + + expect_s3_class( + plotHeatmap( + tse, + assay.type = "counts", + center = TRUE + ), + "ggplot" + ) + + expect_s3_class( + plotHeatmap( + tse, + assay.type = "counts", + scale = TRUE, + center = TRUE + ), + "ggplot" + ) +}) + +test_that("plotHeatmap supports row and column facets", { + + data(GlobalPatterns) + tse <- agglomerateByPrevalence(GlobalPatterns, rank = "Class") + + expect_s3_class( + plotHeatmap( + tse, + assay.type = "counts", + col.var = "SampleType" + ), + "ggplot" + ) + + expect_s3_class( + plotHeatmap( + tse, + assay.type = "counts", + row.var = "Phylum" + ), + "ggplot" + ) + + expect_s3_class( + plotHeatmap( + tse, + assay.type = "counts", + row.var = "Phylum", + col.var = "SampleType" + ), + "ggplot" + ) +}) + +test_that("plotHeatmap validates arguments", { + + data(GlobalPatterns) + tse <- agglomerateByPrevalence(GlobalPatterns, rank = "Class") + + expect_error( + plotHeatmap( + tse, + assay.type = "foo" + ) + ) + + expect_error( + plotHeatmap( + tse, + assay.type = "counts", + scale = 1 + ), + "scale" + ) + + expect_error( + plotHeatmap( + tse, + assay.type = "counts", + center = 1 + ), + "center" + ) + + expect_error( + plotHeatmap( + tse, + assay.type = "counts", + row.var = "foo" + ) + ) + + expect_error( + plotHeatmap( + tse, + assay.type = "counts", + col.var = "foo" + ) + ) +}) + +test_that("plotHeatmap works for TreeSummarizedExperiment", { + + data(GlobalPatterns) + + tse <- agglomerateByPrevalence(GlobalPatterns, rank = "Class") + + p <- plotHeatmap( + tse, + assay.type = "counts" + ) + + expect_s3_class(p, "ggplot") +}) + +test_that("plotHeatmap adds row tree", { + + data(GlobalPatterns) + + tse <- agglomerateByPrevalence(GlobalPatterns, rank = "Class") + + p <- plotHeatmap( + tse, + assay.type = "counts", + show.tree = TRUE + ) + + expect_s3_class(p, "patchwork") +}) + +test_that("plotHeatmap validates tree arguments", { + + data(GlobalPatterns) + + tse <- GlobalPatterns + + expect_error( + plotHeatmap( + tse, + assay.type = "counts", + show.tree = 1 + ), + "show.tree" + ) + + expect_error( + plotHeatmap( + tse, + assay.type = "counts", + show.tree = TRUE, + row.var = "Phylum" + ), + "row.var" + ) + + expect_error( + plotHeatmap( + tse, + assay.type = "counts", + show.tree = TRUE, + tree.name = "foo" + ) + ) +}) + +test_that("Centering centers each feature to zero", { + + data(GlobalPatterns) + + tse <- agglomerateByRank(GlobalPatterns, rank = "Family") + + df <- .get_heatmap_data( + tse = tse, + assay.type = "counts", + center = TRUE, + scale = FALSE + ) + + means <- tapply( + df$counts, + df$FeatureID, + mean + ) + + expect_true( + all(abs(means) < 1e-10) + ) +}) + +test_that("Scaling and centering produce zero mean and unit variance", { + + data(GlobalPatterns) + + tse <- agglomerateByRank(GlobalPatterns, rank = "Family") + + df <- .get_heatmap_data( + tse = tse, + assay.type = "counts", + center = TRUE, + scale = TRUE + ) + + means <- tapply( + df$counts, + df$FeatureID, + mean + ) + + sds <- tapply( + df$counts, + df$FeatureID, + sd + ) + + expect_true( + all(abs(means) < 1e-10) + ) + + expect_true( + all(abs(sds - 1) < 1e-10) + ) +}) + +test_that("No transformation leaves assay values unchanged", { + + data(GlobalPatterns) + + tse <- agglomerateByRank(GlobalPatterns, rank = "Family") + + df <- .get_heatmap_data( + tse = tse, + assay.type = "counts" + ) + + expected <- meltSE( + tse, + assay.type = "counts" + ) + + expect_equal( + df$counts, + expected$counts, + check.attributes = FALSE + ) +}) + +test_that("row tree and heatmap use the same feature order", { + + data(GlobalPatterns) + + tse <- agglomerateByRank(GlobalPatterns, rank = "Family") + + p <- plotHeatmap( + tse, + assay.type = "counts", + show.tree = TRUE, + show.label = TRUE + ) + + tree_plot <- p[[1]] + heatmap_plot <- p[[2]] + + # Tip order in the tree + tree_build <- ggplot2::ggplot_build(tree_plot) + tree_tips <- tree_build$data[[5]][, c("y", "label")] + tree_tips <- tree_tips[order(tree_tips$y), ] + + # Feature order in the heatmap + heatmap_build <- ggplot2::ggplot_build(heatmap_plot) + + heatmap_data <- unique( + heatmap_build$plot$data[, c("FeatureID")] + ) + + heatmap_order <- as.character(heatmap_data$FeatureID) + + expect_identical( + heatmap_order, + tree_tips$label + ) + +}) From 7896b681d17598120418611f7b66bbd26388dc5d Mon Sep 17 00:00:00 2001 From: Tuomas Borman Date: Fri, 7 Aug 2026 14:06:51 +0300 Subject: [PATCH 2/5] up --- DESCRIPTION | 2 +- NEWS | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index a32e4d4d..f5afd928 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: miaViz Title: Microbiome Analysis Plotting and Visualization -Version: 1.21.3 +Version: 1.21.4 Authors@R: c(person(given = "Tuomas", family = "Borman", role = c("aut", "cre"), email = "tuomas.v.borman@utu.fi", diff --git a/NEWS b/NEWS index aa5b7d8a..99cbe083 100644 --- a/NEWS +++ b/NEWS @@ -54,3 +54,4 @@ Changes in version 1.19.x Changes in version 1.21.x + Added plotOrdination and plotJointRPCA (2026-08-04) ++ Added plotHeatmap (2026-08-xx) From 5218a52a1439f58198672b449023223035de52b6 Mon Sep 17 00:00:00 2001 From: Tuomas Borman Date: Sun, 9 Aug 2026 19:35:30 +0300 Subject: [PATCH 3/5] up --- NAMESPACE | 5 ++ R/plotHeatmap.R | 30 +++++----- R/plotTree.R | 6 +- man/plotHeatmap.Rd | 141 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 167 insertions(+), 15 deletions(-) create mode 100644 man/plotHeatmap.Rd diff --git a/NAMESPACE b/NAMESPACE index fa0a1614..30ac17e9 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -9,6 +9,7 @@ export(plotColTile) export(plotDMNFit) export(plotFeaturePrevalence) export(plotForest) +export(plotHeatmap) export(plotHistogram) export(plotJointRPCA) export(plotLoadings) @@ -40,6 +41,7 @@ exportMethods(plotColTree) exportMethods(plotDMNFit) exportMethods(plotFeaturePrevalence) exportMethods(plotForest) +exportMethods(plotHeatmap) exportMethods(plotHistogram) exportMethods(plotJointRPCA) exportMethods(plotLoadings) @@ -122,8 +124,10 @@ importFrom(ggplot2,annotate) importFrom(ggplot2,coord_cartesian) importFrom(ggplot2,coord_flip) importFrom(ggplot2,element_blank) +importFrom(ggplot2,element_rect) importFrom(ggplot2,element_text) importFrom(ggplot2,expansion) +importFrom(ggplot2,facet_grid) importFrom(ggplot2,facet_wrap) importFrom(ggplot2,geom_bar) importFrom(ggplot2,geom_errorbar) @@ -152,6 +156,7 @@ importFrom(ggplot2,scale_y_discrete) importFrom(ggplot2,theme) importFrom(ggplot2,theme_bw) importFrom(ggplot2,theme_classic) +importFrom(ggplot2,theme_minimal) importFrom(ggrepel,geom_label_repel) importFrom(ggrepel,geom_text_repel) importFrom(ggtree,geom_cladelab) diff --git a/R/plotHeatmap.R b/R/plotHeatmap.R index 08971464..2dfd7b79 100644 --- a/R/plotHeatmap.R +++ b/R/plotHeatmap.R @@ -96,6 +96,13 @@ #' col.var = "SampleType" #' ) #' +#' # Add phylogeny +#' plotHeatmap( +#' tse, +#' assay.type = "counts", +#' show.tree = TRUE +#' ) +#' #' @seealso #' \code{\link[=plotRowTree]{plotRowTree}} #' @@ -137,22 +144,19 @@ setMethod( # Check that the requested row tree is available .check_rowTree_present(tree.name, x) - tree_plot <- plotRowTree( - x, - tree.name = tree.name, - layout = "rectangular", - branch.length = "none", - show.label = TRUE, - levels.rm = TRUE - ) - + # Create a tree plot that we will use to extract order. We have to + # create a plot, because exact top-to-bottom ordering of tips is + # determined when the tree is plotted. For example, branches can be + # rotated around internal nodes without changing the tree. + tree <- rowTree(x, tree.name) + tree_plot <- ggtree(tree) + geom_tiplab() tree_data <- ggplot_build(tree_plot) - tips <- tree_data$data[[5]][, c("y", "label")] - tips <- tips[order(tips$y), , drop = FALSE] + tips <- tree_data[["data"]][[3]][, c("y", "label")] + tips <- tips[order(tips[["y"]]), , drop = FALSE] tax_order <- match( - tips$label, - rowLinks(x)$nodeLab + tips[["label"]], + rowLinks(x)[["nodeLab"]] ) x <- x[tax_order, ] diff --git a/R/plotTree.R b/R/plotTree.R index dad25ea4..25991fc6 100644 --- a/R/plotTree.R +++ b/R/plotTree.R @@ -1231,14 +1231,16 @@ setMethod("plotRowTree", signature = c(x = "TreeSummarizedExperiment"), # add tip labels plot_out <- plot_out + geom_tiplab( - mapping = aes_string(subset = f_tip, label = "node_label"), + data = data[f_tip, ], + mapping = aes(label = .data[["node_label"]]), offset = 0.01, size = label_font_size) } if(any(f_node)){ # add node labels plot_out <- plot_out + geom_nodelab( - mapping = aes_string(subset = f_node, label = "node_label"), + data = data[f_node, ], + mapping = aes(label = .data[["node_label"]]), size = label_font_size) } } diff --git a/man/plotHeatmap.Rd b/man/plotHeatmap.Rd new file mode 100644 index 00000000..4ce5ad29 --- /dev/null +++ b/man/plotHeatmap.Rd @@ -0,0 +1,141 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/AllGenerics.R, R/plotHeatmap.R +\name{plotHeatmap} +\alias{plotHeatmap} +\alias{plotHeatmap,TreeSummarizedExperiment-method} +\alias{plotHeatmap,SummarizedExperiment-method} +\title{Visualize assay values as a heatmap} +\usage{ +plotHeatmap(x, ...) + +\S4method{plotHeatmap}{TreeSummarizedExperiment}( + x, + assay.type = NULL, + row.var = NULL, + col.var = NULL, + scale = FALSE, + center = FALSE, + tree.name = "phylo", + show.tree = FALSE, + ... +) + +\S4method{plotHeatmap}{SummarizedExperiment}( + x, + assay.type = NULL, + row.var = NULL, + col.var = NULL, + scale = FALSE, + center = FALSE, + ... +) +} +\arguments{ +\item{x}{a +\code{\link[SummarizedExperiment:SummarizedExperiment-class]{SummarizedExperiment}} +object.} + +\item{...}{Additional parameters controlling the visualization. When +\code{show.tree = TRUE}, additional arguments are passed to +\code{\link{plotRowTree}} to control the appearance of the tree. +Additional parameters include, for example: +\itemize{ +\item \code{scales}: Facet scaling passed to +\code{ggplot2::facet_grid()} or +\code{ggh4x::facet_nested()}. +(Default: \code{"free"}) +\item \code{tree.width}: Relative width of the tree panel when +\code{show.tree = TRUE}. +(Default: \code{0.2}) +}} + +\item{assay.type}{\code{Character scalar} value defining which assay data to +use. (Default: \code{"relabundance"})} + +\item{row.var}{\code{NULL} or \code{character vector}. Variables from +\code{rowData(x)} used for row facetting. +(Default: \code{NULL})} + +\item{col.var}{\code{NULL} or \code{character vector}. Variables from +\code{colData(x)} used for column facetting. +(Default: \code{NULL})} + +\item{scale}{\code{Logical scalar}. Should assay values be scaled for each +feature across samples? +(Default: \code{FALSE})} + +\item{center}{\code{Logical scalar}. Should assay values be centered for +each feature across samples? +(Default: \code{FALSE})} + +\item{tree.name}{\code{Character scalar}. Name of the row tree to display +when \code{x} is a \code{TreeSummarizedExperiment}. +(Default: \code{"phylo"})} + +\item{show.tree}{\code{Logical scalar}. Should the row tree be displayed? +Only available for \code{TreeSummarizedExperiment}. +(Default: \code{TRUE})} +} +\value{ +A \code{ggplot2} object. If \code{show.tree = TRUE}, the returned object is +a combined \code{patchwork} object containing the row tree and the heatmap. +} +\description{ +Creates a heatmap from an assay stored in a +\code{SummarizedExperiment} or +\code{TreeSummarizedExperiment}. Features are shown as rows and +samples as columns. +} +\details{ +\code{plotHeatmap} visualizes values from an assay as a heatmap. Values +can be optionally centered and/or scaled across samples for each feature, +which is useful for highlighting relative abundance patterns rather than +absolute abundances. + +Additional variables from \code{rowData(x)} and \code{colData(x)} can be +used to facet the heatmap. When multiple row or column variables are +provided, nested facets are created using +\pkg{ggh4x}. + +For \code{TreeSummarizedExperiment} objects, a row tree can be displayed +alongside the heatmap. When a tree is shown, only leaf nodes are plotted +and the heatmap rows are reordered to match the tree tip order. +} +\examples{ +data(GlobalPatterns) +tse <- GlobalPatterns + +tse <- agglomerateByPrevalence(tse, rank = "Class") + +# Plot raw counts +plotHeatmap( + tse, + assay.type = "counts" +) + +# Scale and center each feature +plotHeatmap( + tse, + assay.type = "counts", + scale = TRUE, + center = TRUE +) + +# Facet samples by metadata +plotHeatmap( + tse, + assay.type = "counts", + col.var = "SampleType" +) + +# Add phylogeny +plotHeatmap( + tse, + assay.type = "counts", + show.tree = TRUE +) + +} +\seealso{ +\code{\link[=plotRowTree]{plotRowTree}} +} From e7db195a85d9a9a19b0c009bf1a8e2101f46986c Mon Sep 17 00:00:00 2001 From: Tuomas Borman Date: Sun, 9 Aug 2026 19:49:04 +0300 Subject: [PATCH 4/5] up --- R/plotHeatmap.R | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/R/plotHeatmap.R b/R/plotHeatmap.R index 2dfd7b79..0e8a8a03 100644 --- a/R/plotHeatmap.R +++ b/R/plotHeatmap.R @@ -144,6 +144,14 @@ setMethod( # Check that the requested row tree is available .check_rowTree_present(tree.name, x) + # Subset tree similarly what plotRowTree does, i.e., take only tips + # that are in data. + x <- subsetByLeaf( + x, + rowLeaf = rowLinks(x)[["nodeLab"]], + whichRowTree = tree.name + ) + # Create a tree plot that we will use to extract order. We have to # create a plot, because exact top-to-bottom ordering of tips is # determined when the tree is plotted. For example, branches can be @@ -160,15 +168,6 @@ setMethod( ) x <- x[tax_order, ] - # - # # Keep only features represented by tree leaves - # x <- subsetByLeaf(x, whichRowTree = tree.name) - # - # # Reorder rows to match the tree tip order - # links <- rowLinks(x) - # keep <- links$whichTree == tree.name & links$isLeaf - # links <- links[keep, "nodeNum"] |> order() - # x <- x[links, ] } # Create the heatmap using the SummarizedExperiment method From 1c1e7493a7261ec812bd3a1b69ad4aa8f3f481fa Mon Sep 17 00:00:00 2001 From: Tuomas Borman <60338854+TuomasBorman@users.noreply.github.com> Date: Mon, 10 Aug 2026 20:32:58 +0300 Subject: [PATCH 5/5] Update NEWS --- NEWS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 99cbe083..260f5963 100644 --- a/NEWS +++ b/NEWS @@ -54,4 +54,4 @@ Changes in version 1.19.x Changes in version 1.21.x + Added plotOrdination and plotJointRPCA (2026-08-04) -+ Added plotHeatmap (2026-08-xx) ++ Added plotHeatmap (2026-08-10)