From 8695a665325fc252d5f11357a40c961e43a056b4 Mon Sep 17 00:00:00 2001 From: Benjamin Demaille Date: Thu, 27 Nov 2025 14:43:47 +0100 Subject: [PATCH 01/12] Fix ggplot2 v4 compatibility: DarkTheme() and LabelClusters() - Replace deprecated 'size' parameter with 'linewidth' in element_rect() and element_line() - Fix LabelClusters() color retrieval to work with ggplot2 v4 S7 objects - Extract colors from plot scales instead of built plot data - Resolves issues #10156 and #10176 --- R/visualization.R | 54 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/R/visualization.R b/R/visualization.R index 52033b3a1..1a8d7d903 100644 --- a/R/visualization.R +++ b/R/visualization.R @@ -6132,12 +6132,45 @@ LabelClusters <- function( } } - # Retrieve colour from built data - col_choice <- intersect(c("colour", "color"), names(pb$data[[1]])) - if (length(col_choice) > 0) { - data <- cbind(data, color = pb$data[[1]][[col_choice[1]]]) - } else { - data <- cbind(data, color = NA_character_) + # Retrieve colour mapping for each group + # Extract colors from the plot's scales + color_scale <- NULL + if (!is.null(pb$plot$scales$get_scales("colour"))) { + color_scale <- pb$plot$scales$get_scales("colour") + } else if (!is.null(pb$plot$scales$get_scales("color"))) { + color_scale <- pb$plot$scales$get_scales("color") + } + + # Create color mapping for groups + group_colors <- setNames(rep(NA_character_, length(groups)), groups) + if (!is.null(color_scale) && !is.null(color_scale$palette)) { + if (is.function(color_scale$palette)) { + # For discrete scales + n_colors <- length(groups) + palette_colors <- color_scale$palette(n_colors) + group_colors <- setNames(palette_colors[seq_along(groups)], groups) + } + } + + # If no colors from scale, try to get from built data + if (all(is.na(group_colors))) { + col_choice <- intersect(c("colour", "color"), names(pb$data[[1]])) + if (length(col_choice) > 0) { + data <- cbind(data, color = pb$data[[1]][[col_choice[1]]]) + # Try to map colors from data + for (group in groups) { + group_data <- if (inherits(data, "sf")) data[data[[id]] == group, , drop = FALSE] else data[data[, id] == group, , drop = FALSE] + if (nrow(group_data) > 0) { + group_color <- group_data$color[1] + # Check if it's a valid color + if (!is.na(group_color) && !is.numeric(group_color) && grepl("^#[0-9A-Fa-f]{6}", group_color)) { + group_colors[group] <- group_color + } + } + } + } else { + data <- cbind(data, color = NA_character_) + } } labels.loc <- lapply( @@ -6189,7 +6222,8 @@ LabelClusters <- function( ))) } data.medians[, id] <- group - data.medians$color <- data.use$color[1] + # Assign color from group_colors mapping + data.medians$color <- group_colors[as.character(group)] return(data.medians) } ) @@ -6430,7 +6464,7 @@ CenterTitle <- function(...) { DarkTheme <- function(...) { # Some constants for easier changing in the future black.background <- element_rect(fill = 'black') - black.background.no.border <- element_rect(fill = 'black', size = 0) + black.background.no.border <- element_rect(fill = 'black', linewidth = 0) font.margin <- 4 white.text <- element_text( colour = 'white', @@ -6441,8 +6475,8 @@ DarkTheme <- function(...) { l = font.margin ) ) - white.line <- element_line(colour = 'white', size = 1) - no.line <- element_line(size = 0) + white.line <- element_line(colour = 'white', linewidth = 1) + no.line <- element_line(linewidth = 0) # Create the dark theme dark.theme <- theme( # Set background colors From 6355ba9f445a54875ded4db8c0e6cf6033a1fe58 Mon Sep 17 00:00:00 2001 From: Benjamin Demaille Date: Thu, 27 Nov 2025 14:45:33 +0100 Subject: [PATCH 02/12] Add documentation for ggplot2 v4 compatibility issues Documents fixed issues, outstanding problems, and workarounds for ggplot2 v4 S7 migration --- GGPLOT2_V4_KNOWN_ISSUES.md | 90 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 GGPLOT2_V4_KNOWN_ISSUES.md diff --git a/GGPLOT2_V4_KNOWN_ISSUES.md b/GGPLOT2_V4_KNOWN_ISSUES.md new file mode 100644 index 000000000..2d24289e1 --- /dev/null +++ b/GGPLOT2_V4_KNOWN_ISSUES.md @@ -0,0 +1,90 @@ +# ggplot2 v4.0.0 Known Issues in Seurat + +## Overview +ggplot2 version 4.0.0 (released September 2025) introduced breaking changes by migrating from S3 to S7 object system. This document tracks known compatibility issues and their status. + +## Fixed Issues + +### 1. DarkTheme() - Deprecated `size` parameter ✅ +**Status**: FIXED +**Files**: `R/visualization.R` (lines 6433-6445) +**Changes**: +- Replaced `element_rect(fill = 'black', size = 0)` with `linewidth = 0` +- Replaced `element_line(colour = 'white', size = 1)` with `linewidth = 1` +- Replaced `element_line(size = 0)` with `linewidth = 0` + +### 2. LabelClusters() - S7 object color retrieval ✅ +**Status**: FIXED +**Issues**: #10156, #10176 +**Files**: `R/visualization.R` (lines 6134-6227) +**Problem**: With S7 objects, `pb$data` returns numeric coordinates instead of valid colors +**Solution**: Extract colors directly from plot scales using `pb$plot$scales$get_scales()` +**Changes**: +- Added color scale extraction from plot scales +- Created group_colors mapping with validation +- Fixed data.medians$color assignment to use explicit group_colors mapping + +## Outstanding Issues (Require ggplot2 Package Fix) + +### 3. VlnPlot - S4SXP Error 🔴 +**Status**: NEEDS GGPLOT2 FIX +**Issues**: #10101, #10188, #10160 +**Files**: Not Seurat-specific code +**Error**: +``` +Error in deparse(substitute(e2, env = caller_env(2))) : +'S4SXP': should not happen - please report +``` +**Root Cause**: Deep incompatibility between ggplot2 v4 S7 objects and R's `substitute()` function calls within ggplot2 internals +**Workaround**: Downgrade to ggplot2 3.5.2 +```r +remotes::install_version("ggplot2", version = "3.5.2", repos = "https://cran.r-project.org") +``` +**Note**: Some packages (e.g., clusterProfiler >= 4.12.0) require ggplot2 >= 4.0.0, creating dependency conflicts + +## Potential Issues (To Monitor) + +### 4. ggplot_build()$plot$data Access Pattern ⚠️ +**Status**: MONITORING +**Files**: `R/visualization.R` (lines 5948, 7795) +**Functions**: `GGpointToPlotly()`, `GGpointToPlotlyBuild()` +**Concern**: Direct `$plot$data` access may behave differently with S7 objects +**Impact**: Unknown - needs testing with real-world usage + +### 5. plot$layers Access Pattern ⚠️ +**Status**: MONITORING +**Files**: `R/visualization.R` (multiple locations) +**Concern**: `plot$layers[[i]]$data` and `plot$layers[[i]]$mapping` access may need S7 updates +**Impact**: Unknown - backward compatibility should handle this, but monitor for issues + +## Migration Notes + +### What Changed in ggplot2 v4 +- **S7 Object System**: ggplot objects now use S7 instead of S3 +- **Deprecated Parameters**: + - `size` → `linewidth` in `element_rect()` and `element_line()` + - Keep `size` in `element_text()` (NOT deprecated) +- **S7 Backward Compatibility**: `$data` and `$layers` access still works, but internal data structures changed +- **New Features**: + - Theme improvements (ink/paper/accent, palette.*, theme_sub_*) + - `geom_label()` new aesthetics (linewidth, border.colour, text.colour) + +### Testing Recommendations +1. Test all visualization functions with ggplot2 v4 +2. Verify label.box functionality in DimPlot variants +3. Check VlnPlot alternatives if S4SXP persists +4. Monitor Plotly integration functions for S7 issues + +### Dependencies +- **Minimum ggplot2 version**: 3.5.2 (for compatibility) +- **Target ggplot2 version**: 4.0.0+ (once S4SXP issue resolved upstream) + +## References +- ggplot2 v4 blog post: https://www.tidyverse.org/blog/2025/09/ggplot2-4-0-0/ +- Issue #10101: VlnPlot S4SXP error +- Issue #10156: DimPlot label.box color error +- Issue #10176: label.box returning numeric values +- Issue #10188: Temporary S4SXP solution thread + +## Last Updated +2025-11-27 From f1e7fac0256e54641ad9d20c98f3148ed79d78f0 Mon Sep 17 00:00:00 2001 From: Benjamin Demaille Date: Thu, 27 Nov 2025 14:48:46 +0100 Subject: [PATCH 03/12] Fix SCTransform do.call slowdown by optimizing Parenting() Avoid serialization of large objects in sys.calls() by extracting only function names. This fixes massive performance degradation when using do.call(SCTransform, list(...)). Resolves #10153 --- R/utilities.R | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/R/utilities.R b/R/utilities.R index 59668765d..431d4023a 100644 --- a/R/utilities.R +++ b/R/utilities.R @@ -2625,12 +2625,21 @@ Online <- function(url, strict = FALSE, seconds = 5L) { # Parenting(parent.find = 'Seurat', features = features[features > 7]) # Parenting <- function(parent.find = 'Seurat', ...) { - calls <- as.character(x = sys.calls()) - calls <- lapply( - X = strsplit(x = calls, split = '(', fixed = TRUE), - FUN = '[', - 1 - ) + # Extract function names from call stack without serializing entire objects + # This avoids the performance issue when large objects are passed via do.call + calls <- vapply(sys.calls(), function(call) { + if (is.call(call) && length(call) > 0) { + # Get just the function name, not the full call with arguments + func <- call[[1]] + if (is.name(func)) { + return(as.character(func)) + } else if (is.call(func)) { + # Handle cases like pkg::function + return(paste(as.character(func), collapse = "")) + } + } + return("") + }, character(1)) parent.index <- grep(pattern = parent.find, x = calls) if (length(x = parent.index) != 1) { warning( From aedb10ae2490a5c705deb19fd10e1449bebc4526 Mon Sep 17 00:00:00 2001 From: Benjamin Demaille Date: Thu, 27 Nov 2025 14:49:56 +0100 Subject: [PATCH 04/12] Fix ReadXenium QV filtering regression Restore mols.qv.threshold functionality that was removed in commit 3dad59d. - Add 'qv' column to cols.use when reading transcripts - Apply QV threshold filter after loading transcripts - Remove qv column from output after filtering Resolves #10155 --- R/preprocessing.R | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/R/preprocessing.R b/R/preprocessing.R index fa95b7e2e..afb4033c2 100644 --- a/R/preprocessing.R +++ b/R/preprocessing.R @@ -2846,7 +2846,8 @@ ReadXenium <- function( col.use = c( x_location = letters[24+flip.xy], y_location = letters[25-flip.xy], - feature_name = 'gene' + feature_name = 'gene', + qv = 'qv' ) for(option in Filter(function(x) x$req, list( @@ -2879,6 +2880,14 @@ ReadXenium <- function( colnames(transcripts) <- col.use transcripts$gene <- binary_to_string(transcripts$gene) + + # Apply QV threshold filtering + if (!is.null(mols.qv.threshold) && 'qv' %in% colnames(transcripts)) { + transcripts <- transcripts[transcripts$qv >= mols.qv.threshold, ] + } + + # Remove qv column after filtering (not needed in output) + transcripts <- transcripts[, setdiff(colnames(transcripts), 'qv')] pmicrons(type = 'finish') From ca3c0ee30951faaf1a28b3eb8026acbd9358918a Mon Sep 17 00:00:00 2001 From: Benjamin Demaille Date: Thu, 27 Nov 2025 14:50:28 +0100 Subject: [PATCH 05/12] Add validation for scale.data in FindTransferAnchors Check if scale.data exists and contains features before using it with SCT normalization. Provide clear error message when scale.data is missing or empty, directing users to run ScaleData(). Resolves #10165 --- R/integration.R | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/R/integration.R b/R/integration.R index 55569c943..23e285fa3 100644 --- a/R/integration.R +++ b/R/integration.R @@ -823,10 +823,26 @@ FindTransferAnchors <- function( features = features, verbose = FALSE )) + # Check if scale.data exists and has features + if (is.null(reference[[reference.assay]]$scale.data) || + nrow(reference[[reference.assay]]$scale.data) == 0) { + stop( + "No scale.data found in the ", reference.assay, " assay of the reference object. ", + "Please run ScaleData() on the reference object before using FindTransferAnchors with SCT normalization.", + call. = FALSE + ) + } features <- intersect( x = features, y = rownames(reference[[reference.assay]]$scale.data) ) + if (length(features) == 0) { + stop( + "No features remaining after intersecting with scale.data in ", reference.assay, " assay. ", + "Please ensure ScaleData() has been run on the reference object with appropriate features.", + call. = FALSE + ) + } VariableFeatures(reference) <- features } if (IsSCT(assay = query[[query.assay]])) { From 1c3ce508769c14b879b50086e05b2bd8c2c062e2 Mon Sep 17 00:00:00 2001 From: Benjamin Demaille Date: Thu, 27 Nov 2025 14:51:22 +0100 Subject: [PATCH 06/12] Fix TransferData crash when prediction.scores contain NaN Replace which.max with safe_which_max that handles NaN values and edge cases. Add warning when NaN values detected in prediction scores, indicating potential issues with normalization, k.weight parameter, or data quality. Resolves #10003 --- R/integration.R | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/R/integration.R b/R/integration.R index 23e285fa3..6c16ea22f 100644 --- a/R/integration.R +++ b/R/integration.R @@ -3633,7 +3633,31 @@ TransferData <- function( prediction.scores <- (prediction.scores + bridge.prediction.scores)/2 prediction.scores <- as.matrix(x = prediction.scores) } - prediction.ids <- possible.ids[apply(X = prediction.scores, MARGIN = 1, FUN = which.max)] + + # Check for NaN values in prediction scores and handle them + if (any(is.nan(prediction.scores))) { + warning( + "NaN values detected in prediction scores. This may indicate issues with ", + "normalization, k.weight parameter, or data quality. NaN scores will be replaced with 0.", + call. = FALSE, + immediate. = TRUE + ) + prediction.scores[is.nan(prediction.scores)] <- 0 + } + + # Use a safer version of which.max that handles edge cases + safe_which_max <- function(x) { + if (all(is.na(x)) || all(x == 0)) { + return(1L) # Default to first class if all are NA or 0 + } + result <- which.max(x) + if (length(result) == 0) { + return(1L) # Default to first class if which.max returns empty + } + return(result) + } + + prediction.ids <- possible.ids[apply(X = prediction.scores, MARGIN = 1, FUN = safe_which_max)] prediction.ids <- as.character(prediction.ids) prediction.max <- apply(X = prediction.scores, MARGIN = 1, FUN = max) if (is.null(x = query)) { From af7adc419c4b449aa0ddd4fab2129e14fbc3c570 Mon Sep 17 00:00:00 2001 From: Benjamin Demaille Date: Thu, 27 Nov 2025 14:53:13 +0100 Subject: [PATCH 07/12] Fix VariableFeaturePlot crash with multiple assays Add validation to handle cases where HVFInfo returns fewer than 3 columns, which occurs when different normalization methods (RNA + SCT) are used. Provide informative warning and use available columns for plotting. Resolves #9743 --- R/visualization.R | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/R/visualization.R b/R/visualization.R index 1a8d7d903..41c5de4d5 100644 --- a/R/visualization.R +++ b/R/visualization.R @@ -2179,7 +2179,19 @@ VariableFeaturePlot <- function( ) status.col <- colnames(hvf.info)[grepl("variable", colnames(hvf.info))][[1]] var.status <- c('no', 'yes')[unlist(hvf.info[[status.col]]) + 1] - if (colnames(x = hvf.info)[3] == 'dispersion.scaled') { + + # Handle cases where hvf.info may have different numbers of columns + if (ncol(hvf.info) < 3) { + warning( + "HVFInfo returned fewer than 3 columns. ", + "This may occur when using different normalization methods on the same object. ", + "Using available columns for plotting.", + call. = FALSE, + immediate. = TRUE + ) + # Use first two columns (typically mean and variance-related) + hvf.info <- hvf.info[, 1:min(2, ncol(hvf.info)), drop = FALSE] + } else if (colnames(x = hvf.info)[3] == 'dispersion.scaled') { hvf.info <- hvf.info[, c(1, 2)] } else if (colnames(x = hvf.info)[3] == 'variance.expected') { hvf.info <- hvf.info[, c(1, 4)] From 760a0918c558856725fadfc884e39f35a8b87eea Mon Sep 17 00:00:00 2001 From: Benjamin Demaille <41732816+BenjaminDEMAILLE@users.noreply.github.com> Date: Thu, 27 Nov 2025 17:10:27 +0100 Subject: [PATCH 08/12] Update R/utilities.R Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- R/utilities.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/utilities.R b/R/utilities.R index 431d4023a..39fb1d785 100644 --- a/R/utilities.R +++ b/R/utilities.R @@ -2635,7 +2635,7 @@ Parenting <- function(parent.find = 'Seurat', ...) { return(as.character(func)) } else if (is.call(func)) { # Handle cases like pkg::function - return(paste(as.character(func), collapse = "")) + return(paste(as.character(func), collapse = "::")) } } return("") From 3b2614a2910eaa6e5969cba03a1ad701023e8a2d Mon Sep 17 00:00:00 2001 From: Benjamin Demaille <41732816+BenjaminDEMAILLE@users.noreply.github.com> Date: Thu, 27 Nov 2025 17:11:01 +0100 Subject: [PATCH 09/12] Update R/visualization.R Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- R/visualization.R | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/R/visualization.R b/R/visualization.R index 41c5de4d5..33a76385d 100644 --- a/R/visualization.R +++ b/R/visualization.R @@ -6156,11 +6156,10 @@ LabelClusters <- function( # Create color mapping for groups group_colors <- setNames(rep(NA_character_, length(groups)), groups) if (!is.null(color_scale) && !is.null(color_scale$palette)) { - if (is.function(color_scale$palette)) { - # For discrete scales - n_colors <- length(groups) - palette_colors <- color_scale$palette(n_colors) - group_colors <- setNames(palette_colors[seq_along(groups)], groups) + if (is.function(color_scale$map)) { + # Use the scale's map function to get the correct color for each group + mapped_colors <- color_scale$map(groups) + group_colors <- setNames(mapped_colors, groups) } } From 5b4e3e2e217e75045a5ec9762552c7ed061499f9 Mon Sep 17 00:00:00 2001 From: Benjamin Demaille <41732816+BenjaminDEMAILLE@users.noreply.github.com> Date: Thu, 27 Nov 2025 17:11:25 +0100 Subject: [PATCH 10/12] Update R/visualization.R Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- R/visualization.R | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/R/visualization.R b/R/visualization.R index 33a76385d..5e0ac6c0c 100644 --- a/R/visualization.R +++ b/R/visualization.R @@ -2191,6 +2191,12 @@ VariableFeaturePlot <- function( ) # Use first two columns (typically mean and variance-related) hvf.info <- hvf.info[, 1:min(2, ncol(hvf.info)), drop = FALSE] + if (ncol(hvf.info) < 2) { + stop( + "HVFInfo returned fewer than 2 columns. ", + "Cannot generate plot. Please check your normalization and feature selection methods." + ) + } } else if (colnames(x = hvf.info)[3] == 'dispersion.scaled') { hvf.info <- hvf.info[, c(1, 2)] } else if (colnames(x = hvf.info)[3] == 'variance.expected') { From d1e9d46023ca39b5d5a50e5d8ee253f7b3c3703c Mon Sep 17 00:00:00 2001 From: Benjamin Demaille <41732816+BenjaminDEMAILLE@users.noreply.github.com> Date: Thu, 27 Nov 2025 17:11:50 +0100 Subject: [PATCH 11/12] Update R/visualization.R Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- R/visualization.R | 1 - 1 file changed, 1 deletion(-) diff --git a/R/visualization.R b/R/visualization.R index 5e0ac6c0c..b42a3d34a 100644 --- a/R/visualization.R +++ b/R/visualization.R @@ -2189,7 +2189,6 @@ VariableFeaturePlot <- function( call. = FALSE, immediate. = TRUE ) - # Use first two columns (typically mean and variance-related) hvf.info <- hvf.info[, 1:min(2, ncol(hvf.info)), drop = FALSE] if (ncol(hvf.info) < 2) { stop( From 3e4fa08a9b60e5f95589476a89cb24e8af7c4946 Mon Sep 17 00:00:00 2001 From: Benjamin Demaille <41732816+BenjaminDEMAILLE@users.noreply.github.com> Date: Thu, 27 Nov 2025 17:14:14 +0100 Subject: [PATCH 12/12] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- GGPLOT2_V4_KNOWN_ISSUES.md | 2 +- R/integration.R | 11 +++++++---- R/preprocessing.R | 5 ++--- R/visualization.R | 2 +- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/GGPLOT2_V4_KNOWN_ISSUES.md b/GGPLOT2_V4_KNOWN_ISSUES.md index 2d24289e1..42f2297ac 100644 --- a/GGPLOT2_V4_KNOWN_ISSUES.md +++ b/GGPLOT2_V4_KNOWN_ISSUES.md @@ -7,7 +7,7 @@ ggplot2 version 4.0.0 (released September 2025) introduced breaking changes by m ### 1. DarkTheme() - Deprecated `size` parameter ✅ **Status**: FIXED -**Files**: `R/visualization.R` (lines 6433-6445) +**Files**: `R/visualization.R` (lines 6476-6491) **Changes**: - Replaced `element_rect(fill = 'black', size = 0)` with `linewidth = 0` - Replaced `element_line(colour = 'white', size = 1)` with `linewidth = 1` diff --git a/R/integration.R b/R/integration.R index 6c16ea22f..3baca56ca 100644 --- a/R/integration.R +++ b/R/integration.R @@ -828,7 +828,8 @@ FindTransferAnchors <- function( nrow(reference[[reference.assay]]$scale.data) == 0) { stop( "No scale.data found in the ", reference.assay, " assay of the reference object. ", - "Please run ScaleData() on the reference object before using FindTransferAnchors with SCT normalization.", + "For SCT normalization, please run GetResidual() on the reference object before using FindTransferAnchors. ", + "ScaleData() is not required for SCT assays.", call. = FALSE ) } @@ -839,7 +840,7 @@ FindTransferAnchors <- function( if (length(features) == 0) { stop( "No features remaining after intersecting with scale.data in ", reference.assay, " assay. ", - "Please ensure ScaleData() has been run on the reference object with appropriate features.", + "For SCT normalization, please ensure GetResidual() has been run on the reference object with appropriate features.", call. = FALSE ) } @@ -3646,9 +3647,11 @@ TransferData <- function( } # Use a safer version of which.max that handles edge cases + # A safer version of which.max that handles the all-NA case. + # Negative and zero values are handled by which.max as usual. safe_which_max <- function(x) { - if (all(is.na(x)) || all(x == 0)) { - return(1L) # Default to first class if all are NA or 0 + if (all(is.na(x))) { + return(1L) # Default to first class if all are NA } result <- which.max(x) if (length(result) == 0) { diff --git a/R/preprocessing.R b/R/preprocessing.R index afb4033c2..0f1937357 100644 --- a/R/preprocessing.R +++ b/R/preprocessing.R @@ -2884,10 +2884,9 @@ ReadXenium <- function( # Apply QV threshold filtering if (!is.null(mols.qv.threshold) && 'qv' %in% colnames(transcripts)) { transcripts <- transcripts[transcripts$qv >= mols.qv.threshold, ] + # Remove qv column after filtering (not needed in output) + transcripts <- transcripts[, setdiff(colnames(transcripts), 'qv')] } - - # Remove qv column after filtering (not needed in output) - transcripts <- transcripts[, setdiff(colnames(transcripts), 'qv')] pmicrons(type = 'finish') diff --git a/R/visualization.R b/R/visualization.R index b42a3d34a..a70e9e7e5 100644 --- a/R/visualization.R +++ b/R/visualization.R @@ -6179,7 +6179,7 @@ LabelClusters <- function( if (nrow(group_data) > 0) { group_color <- group_data$color[1] # Check if it's a valid color - if (!is.na(group_color) && !is.numeric(group_color) && grepl("^#[0-9A-Fa-f]{6}", group_color)) { + if (!is.na(group_color) && !is.numeric(group_color) && grepl("^#[0-9A-Fa-f]{6,8}$", group_color)) { group_colors[group] <- group_color } }