Skip to content

rewrite scatterplotmatrix so it uses patchwork#986

Open
m7pr wants to merge 28 commits intomainfrom
patchwork-scatterplotmatrix
Open

rewrite scatterplotmatrix so it uses patchwork#986
m7pr wants to merge 28 commits intomainfrom
patchwork-scatterplotmatrix

Conversation

@m7pr
Copy link
Copy Markdown
Contributor

@m7pr m7pr commented Feb 24, 2026

Part of

Satisfies last comment from 2025

Alternative to

This PR rewrite scatterplotmatrix so it uses patchwork

Coe do generate plots
library(ggplot2)
library(patchwork)
library(dplyr)

set.seed(42)
n <- 200
demo_data <- data.frame(
  AGE = rnorm(n, mean = 55, sd = 12),
  BMRKR1 = rlnorm(n, meanlog = 1.5, sdlog = 0.8),
  AVAL = rnorm(n, mean = 100, sd = 25),
  BMRKR2 = sample(c("LOW", "MEDIUM", "HIGH"), n, replace = TRUE, prob = c(0.3, 0.4, 0.3))
) %>%
  mutate(BMRKR2 = factor(BMRKR2, levels = c("LOW", "MEDIUM", "HIGH")))

demo_data$AGE[sample(n, 10)] <- NA
demo_data$BMRKR1[sample(n, 5)] <- NA

build_scatterplot_matrix <- function(data, varnames, alpha = 0.5, size = 1.5,
                                     add_cor = FALSE, cor_method = "pearson",
                                     cor_use = "pairwise.complete.obs") {
  col_names <- names(data)
  n_vars <- length(col_names)
  plot_list <- list()

  for (i in seq_len(n_vars)) {
    for (j in seq_len(n_vars)) {
      vi <- col_names[i]
      vj <- col_names[j]

      if (i == j) {
        if (is.numeric(data[[vi]])) {
          p <- ggplot(data, aes(x = .data[[vi]])) +
            geom_density(fill = "steelblue", alpha = 0.5) +
            labs(x = NULL, y = NULL) +
            ggtitle(varnames[i]) +
            theme_minimal() +
            theme(
              plot.title = element_text(hjust = 0.5, size = 9, face = "bold"),
              axis.text = element_text(size = 7)
            )
        } else {
          p <- ggplot(data, aes(x = .data[[vi]])) +
            geom_bar(fill = "steelblue", alpha = 0.5) +
            labs(x = NULL, y = NULL) +
            ggtitle(varnames[i]) +
            theme_minimal() +
            theme(
              plot.title = element_text(hjust = 0.5, size = 9, face = "bold"),
              axis.text = element_text(size = 7),
              axis.text.x = element_text(angle = 45, hjust = 1)
            )
        }
      } else if (add_cor && i < j) {
        if (is.numeric(data[[vi]]) && is.numeric(data[[vj]])) {
          cor_val <- tryCatch(
            cor(data[[vi]], data[[vj]], method = cor_method, use = cor_use),
            error = function(e) NA_real_
          )
          cor_label <- if (is.na(cor_val)) "NA" else sprintf("%.3f", cor_val)
          cor_size <- max(3, abs(cor_val) * 8 + 3)
          cor_color <- if (is.na(cor_val)) "grey50" else if (cor_val > 0) "firebrick" else "steelblue"
        } else {
          cor_label <- "-"
          cor_size <- 4
          cor_color <- "grey50"
        }
        p <- ggplot() +
          annotate("text", x = 0.5, y = 0.5, label = cor_label,
                   size = cor_size, fontface = "bold", color = cor_color) +
          xlim(0, 1) + ylim(0, 1) +
          theme_void()
      } else {
        if (is.numeric(data[[vj]]) && is.numeric(data[[vi]])) {
          p <- ggplot(data, aes(x = .data[[vj]], y = .data[[vi]])) +
            geom_point(alpha = alpha, size = size, color = "steelblue") +
            labs(x = NULL, y = NULL) +
            theme_minimal() +
            theme(axis.text = element_text(size = 7))
        } else if (is.factor(data[[vj]]) && is.numeric(data[[vi]])) {
          p <- ggplot(data, aes(x = .data[[vj]], y = .data[[vi]])) +
            geom_boxplot(fill = "steelblue", alpha = 0.5) +
            labs(x = NULL, y = NULL) +
            theme_minimal() +
            theme(axis.text = element_text(size = 7),
                  axis.text.x = element_text(angle = 45, hjust = 1))
        } else if (is.numeric(data[[vj]]) && is.factor(data[[vi]])) {
          p <- ggplot(data, aes(x = .data[[vi]], y = .data[[vj]])) +
            geom_boxplot(fill = "steelblue", alpha = 0.5) +
            labs(x = NULL, y = NULL) +
            theme_minimal() +
            theme(axis.text = element_text(size = 7),
                  axis.text.x = element_text(angle = 45, hjust = 1)) +
            coord_flip()
        } else {
          p <- ggplot(data, aes(x = .data[[vj]], fill = .data[[vi]])) +
            geom_bar(position = "dodge", alpha = 0.5) +
            labs(x = NULL, y = NULL, fill = NULL) +
            theme_minimal() +
            theme(axis.text = element_text(size = 7),
                  axis.text.x = element_text(angle = 45, hjust = 1),
                  legend.position = "none")
        }
      }
      plot_list[[(i - 1) * n_vars + j]] <- p
    }
  }
  wrap_plots(plot_list, ncol = n_vars, nrow = n_vars)
}

varnames <- c("Age", "Biomarker 1", "Analysis Value", "Biomarker 2")

plot_with_cor <- build_scatterplot_matrix(
  demo_data, varnames, add_cor = TRUE, cor_method = "pearson"
) + plot_annotation(title = "With Correlation (Pearson)")

plot_without_cor <- build_scatterplot_matrix(
  demo_data, varnames, add_cor = FALSE
) + plot_annotation(title = "Without Correlation")

plot_spearman <- build_scatterplot_matrix(
  demo_data, varnames, add_cor = TRUE, cor_method = "spearman"
) + plot_annotation(title = "With Correlation (Spearman)")

numeric_data <- demo_data %>% select(AGE, BMRKR1, AVAL)
plot_numeric_only <- build_scatterplot_matrix(
  numeric_data, c("Age", "Biomarker 1", "Analysis Value"),
  add_cor = TRUE, cor_method = "pearson"
) + plot_annotation(title = "Numeric Only - Correlation Matrix Style")

output_dir <- file.path("dev", "demo_plots")
dir.create(output_dir, showWarnings = FALSE, recursive = TRUE)

ggsave(file.path(output_dir, "1_with_correlation_pearson.png"), plot_with_cor, width = 10, height = 10, dpi = 150)
ggsave(file.path(output_dir, "2_without_correlation.png"), plot_without_cor, width = 10, height = 10, dpi = 150)
ggsave(file.path(output_dir, "3_with_correlation_spearman.png"), plot_spearman, width = 10, height = 10, dpi = 150)
ggsave(file.path(output_dir, "4_numeric_only_correlation.png"), plot_numeric_only, width = 8, height = 8, dpi = 150)

1_with_correlation_pearson 2_without_correlation 3_with_correlation_spearman 4_numeric_only_correlation

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Feb 24, 2026

Unit Tests Summary

    1 files     37 suites   26m 51s ⏱️
  684 tests   683 ✅ 1 💤 0 ❌
1 318 runs  1 317 ✅ 1 💤 0 ❌

Results for commit 2594752.

♻️ This comment has been updated with latest results.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Feb 24, 2026

Unit Test Performance Difference

Test Suite $Status$ Time on main $±Time$ $±Tests$ $±Skipped$ $±Failures$ $±Errors$
scatterplotmatrix_get_stats 💀 $0.02$ $-0.02$ $-6$ $0$ $0$ $0$
shinytest2-tm_a_pca 💚 $233.71$ $-5.10$ $0$ $0$ $0$ $0$
shinytest2-tm_a_regression 💚 $93.07$ $-3.11$ $0$ $0$ $0$ $0$
shinytest2-tm_file_viewer 💚 $46.11$ $-1.63$ $0$ $0$ $0$ $0$
shinytest2-tm_g_bivariate 💚 $104.61$ $-1.50$ $0$ $0$ $0$ $0$
shinytest2-tm_g_scatterplotmatrix 💔 $46.02$ $+25.09$ $0$ $0$ $0$ $0$
shinytest2-tm_variable_browser 💔 $98.46$ $+3.03$ $0$ $0$ $0$ $0$
tm_g_scatterplot 💚 $8.40$ $-1.47$ $0$ $0$ $0$ $0$
tm_g_scatterplotmatrix 💔 $1.29$ $+4.71$ $0$ $0$ $0$ $0$
tm_missing_data 💔 $10.33$ $+1.01$ $0$ $0$ $0$ $0$
variable_browser 💔 $65.43$ $+3.37$ $0$ $0$ $0$ $0$
Additional test case details
Test Suite $Status$ Time on main $±Time$ Test Case
examples 💀 $0.02$ $-0.02$ example_get_scatterplotmatrix_stats.Rd
scatterplotmatrix_get_stats 💀 $0.01$ $-0.01$ get_scatterplotmatrix_stats_x_y_character
scatterplotmatrix_get_stats 💀 $0.01$ $-0.01$ get_scatterplotmatrix_stats_x_y_numeric
shinytest2-tm_a_pca 💔 $12.51$ $+1.23$ e2e_tm_a_pca_Module_is_initialised_with_the_specified_defaults_in_function_call.
shinytest2-tm_g_scatterplotmatrix 💔 $13.60$ $+6.74$ e2e_tm_g_scatterplotmatrix_Change_plot_settings
shinytest2-tm_g_scatterplotmatrix 💔 $9.59$ $+4.11$ e2e_tm_g_scatterplotmatrix_Initializes_without_errors
shinytest2-tm_g_scatterplotmatrix 💔 $11.05$ $+10.10$ e2e_tm_g_scatterplotmatrix_Verify_default_values_and_settings_data_extracts_for_data_selection
shinytest2-tm_g_scatterplotmatrix 💔 $11.78$ $+4.13$ e2e_tm_g_scatterplotmatrix_Verify_module_displays_data_table
tm_g_scatterplotmatrix 💔 $0.62$ $+2.94$ tm_g_scatterplotmatrix_module_server_behavior_server_function_executes_successfully_through_module_interface
tm_g_scatterplotmatrix 💔 $0.57$ $+1.78$ tm_g_scatterplotmatrix_module_server_behavior_server_function_generates_scatterplot_matrix_with_two_variables

Results for commit 620595b

♻️ This comment has been updated with latest results.

@donyunardi donyunardi requested review from a team and removed request for donyunardi March 6, 2026 00:48
Copy link
Copy Markdown
Contributor

@gogonzo gogonzo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This branch needs a proper code reducing.

Comment on lines +407 to +408
vi <- col_names[i]
vj <- col_names[j]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about more descriptive object names?

Suggested change
vi <- col_names[i]
vj <- col_names[j]
colname_i <- col_names[i]
colname_j <- col_names[j]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently I changed to

            xi <- ANL[[col_names[i]]]
            xj <- ANL[[col_names[j]]]

so that you can understand it's an x-th variable in a matrix.

@gogonzo gogonzo self-assigned this Mar 6, 2026
Copy link
Copy Markdown
Contributor

@llrs-roche llrs-roche left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Plots generated with lots of duplicate code. I think there are several ways to simplify them:

  1. Reuse a base plot and then update it for each specific purpose.
  2. Apply the theme and general characteristics after the plots are wrapped by patchwork ( use the right operator to apply to all plots).

Sorry I didn't assign myself and we have duplicated efforts but I agree with the previous review

I ran the example and text is too small to read:

image

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 6, 2026

badge

Code Coverage Summary

Filename                      Stmts    Miss  Cover    Missing
--------------------------  -------  ------  -------  ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
R/geom_mosaic.R                  73       0  100.00%
R/tm_a_pca.R                    852       0  100.00%
R/tm_a_regression.R             750     391  47.87%   496-523, 530-533, 540-541, 545-555, 559, 563-575, 580-595, 600-622, 625-633, 637-665, 670-680, 683-709, 716-740, 743-768, 775-782, 785-811, 818-846, 849-874, 881-900, 903-929, 936-950, 953-979, 1004, 1008
R/tm_data_table.R               204       8  96.08%   110, 115-120, 330
R/tm_file_viewer.R              172      50  70.93%   141-152, 156-163, 165-167, 172-182, 184, 204, 229-235, 237-238, 240, 244-250
R/tm_front_page.R               143       0  100.00%
R/tm_g_association.R            320      72  77.50%   226-294, 319, 325, 474, 488
R/tm_g_bivariate.R              672     196  70.83%   332-471, 505, 511-514, 585, 590, 595, 616-618, 655-658, 668-682, 684-685, 712-721, 763, 829, 940, 987, 989, 991, 998-1008
R/tm_g_distribution.R          1106      60  94.58%   412-420, 426-429, 468-469, 471, 473, 482, 484, 488-491, 495-498, 501, 513-514, 569, 580, 594, 889, 1073-1077, 1146-1150, 1152-1158, 1289-1292, 1310-1312, 1398-1399
R/tm_g_response.R               345      87  74.78%   184-185, 192, 198, 259-325, 348-352, 425, 430, 447-453, 531, 537, 550
R/tm_g_scatterplot.R            709     259  63.47%   359-501, 534-537, 575, 624, 636, 650, 667, 672, 698-711, 787-798, 837, 845-882, 894-896, 906-912, 919-921, 932-940, 942-943, 945, 949, 983-996, 1040, 1060
R/tm_g_scatterplotmatrix.R      327     143  56.27%   251-349, 424-430, 444-447, 449, 472-475, 487, 490-502, 508-509, 515-517, 569-577
R/tm_gtsummary.R                148       0  100.00%
R/tm_missing_data.R            1180     167  85.85%   129, 488, 494, 507, 512, 532-538, 547-553, 620-635, 676, 680, 721, 739-746, 771, 787-790, 848-927, 931-933, 964-971, 1104, 1241, 1280, 1332-1334, 1344-1364, 1472, 1474, 1477-1478
R/tm_outliers.R                1029     186  81.92%   400, 428, 438-439, 441-442, 517-531, 533, 613, 616, 654-706, 709-747, 759, 790, 809, 812-827, 888-891, 965-993, 1117, 1220-1223, 1227, 1230-1233, 1239-1248, 1250, 1254-1263, 1266-1267, 1269
R/tm_rmarkdown.R                159       0  100.00%
R/tm_t_crosstable.R             263      56  78.71%   227-269, 292-293, 309, 318, 425, 438-447
R/tm_variable_browser.R         887      27  96.96%   395, 597, 812-826, 956, 984, 986, 1058-1059, 1067, 1165, 1246, 1278, 1310
R/utils.R                       148      15  89.86%   146, 177, 227, 288-291, 300-306, 314, 350, 353
R/zzz.R                           2       2  0.00%    2-3
TOTAL                          9489    1719  81.88%

Diff against main

Filename                      Stmts    Miss  Cover
--------------------------  -------  ------  -------
R/tm_a_regression.R              -1       0  -0.07%
R/tm_g_scatterplotmatrix.R      +55     +33  -3.29%
TOTAL                           +54     +33  -0.25%

Results for commit: 2594752

Minimum allowed coverage is 80%

♻️ This comment has been updated with latest results

@m7pr m7pr marked this pull request as draft March 9, 2026 08:28
@m7pr
Copy link
Copy Markdown
Contributor Author

m7pr commented Mar 9, 2026

Hey @gogonzo and @llrs-roche - thanks for the review. I mainly aimed to get a quick prototype of the output, so we could compare different approaches. Will work on improving the PR and making it more polished and production-ready.
Until then I will mark this one as draft.

@gogonzo
Copy link
Copy Markdown
Contributor

gogonzo commented Mar 9, 2026

I just run an app and:

  1. Selected 5 variables. Visuals need serious improvements:
image
  1. Loop to make k-ggplots also impacts the performance. I selected 10 variables and It took around 10 seconds before I saw it rendered.

  2. R code is looooong

Skärmavbild 2026-03-09 kl  12 27 17

IMO This visualization is a candidate for a separate package to optimize geom creations.

checkmate::assert_multi_class(post_output, c("shiny.tag", "shiny.tag.list", "html"), null.ok = TRUE)
checkmate::assert_choice(default_plot_type, seq.int(1L, length(plot_choices)))
checkmate::assert_string(default_outlier_label)
assert_decorators(decorators)
Copy link
Copy Markdown
Contributor Author

@m7pr m7pr Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is repeated in line 245/246. so I deleted duplication

Comment on lines +363 to +364
~ if (length(.) <= 1) "Please select at least 2 columns.",
~ if (length(.) > 5) "Please select at most 5 columns."
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this limitation so we don't create too much plots. It already takes some time to create 25 plots when we have 5 variables and Add correlation is not checked.

Comment on lines +310 to +325
bslib::popover(
icon("circle-info"),
title = "NA handling options",
tags$dl(
tags$dt("Everything"),
tags$dd("Return NA for a pair if either variable contains any missing value."),
tags$dt("All observations"),
tags$dd("Assume no NAs are present; throws an error if any are found."),
tags$dt("Complete observations"),
tags$dd("Listwise deletion – only rows with no NAs across all selected variables."),
tags$dt("NA or complete"),
tags$dd("Like complete observations but returns NA instead of an error when no complete cases exist."),
tags$dt("Pairwise complete"),
tags$dd("Use all rows where both variables in a pair are non-missing (maximises available data).")
),
options = list(trigger = "hover focus")
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It;s this tooltip

Image

Comment on lines +296 to +300
title = "NA handling",
tags$p(tags$b("Checked:"), "use pairwise complete observations (each pair correlated over rows where both values are present)."),
tags$p(tags$b("Unchecked:"), "reveals a dropdown with all five", tags$code("stats::cor()"), "use= options."),
options = list(trigger = "hover focus")
)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Na omit explanation

Image

datasets = data,
selector_list = selector_list
)
) |> debounce(500)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added debounce, so that when 3 variables out of 5 are selected to be plotted, and when we want to quickly add the 4th and the 5th variable, we dont get a plot for 4 variables and then for 5 variables, but we get the plot for 5 variables

Comment on lines +513 to +517
add_cor_value = add_cor,
cor_method_value = cor_method,
cor_use_value = cor_use,
alpha_value = alpha_val,
varnames_value = varnames
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need to pass reactive values so that you see add_cor <- TRUE instead of code like i < j && TRUE

Comment on lines +478 to +482
if (is.numeric(xi)) {
p <- p + ggplot2::geom_density(fill = "steelblue", alpha = alpha)
} else {
p <- p + ggplot2::geom_bar(fill = "steelblue", alpha = alpha)
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be even shorter if we dont want to pass fill = "steelblue" or opactiy (alpha)

I was thinking if something like this is possible

custom_geom <- ifelse(is.numeric(x1), geom_density, geom_bar)
p + custom_geom(list_of_parameters)

or

p + do.call(custom_geom, list_of_parameters)

| `tm_g_response` | plot (ggplot) |
| `tm_g_scatterplot` | plot (ggplot) |
| `tm_g_scatterplotmatrix` | plot (trellis) |
| `tm_g_scatterplotmatrix` | plot (patchwork) |
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
| `tm_g_scatterplotmatrix` | plot (patchwork) |
| `tm_g_scatterplotmatrix` | plot (patchwork/ggplot) |

Comment on lines +492 to +497
p <- ggplot2::ggplot(data.frame(x = xj, y = xi)) + ggplot2::labs(x = NULL, y = NULL)
n_num <- is.numeric(xi) + is.numeric(xj)
if (n_num == 2) p <- p + ggplot2::aes(x = x, y = y) + ggplot2::geom_point(color = "steelblue", alpha = alpha)
if (n_num == 1) p <- p + ggplot2::aes(x = x, y = y) + ggplot2::geom_boxplot(fill = "steelblue", alpha = alpha)
if (n_num == 0) p <- p + ggplot2::aes(x = x, fill = y) + ggplot2::geom_bar(position = "dodge", alpha = alpha) + ggplot2::labs(fill = NULL)
p
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even though I don't like this part, this is the shortest I was able to come up with.
When code is parsed and shown in the Show R Code it looks like this - indentation is a bit different

Image

Comment on lines +484 to +490
cv <- if (!is.null(cor_mat) && is.numeric(xi) && is.numeric(xj)) cor_mat[col_names[i], col_names[j]] else NA_real_
col <- if (is.na(cv)) "grey50" else if (cv > 0) "firebrick" else "steelblue"
ggplot2::ggplot() +
ggplot2::annotate("text", x = 0.5, y = 0.5, fontface = "bold", color = col,
label = if (!is.na(cv)) sprintf("%.2f", cv) else if (is.numeric(xi) && is.numeric(xj)) "NA" else "-",
size = if (!is.na(cv)) max(3, abs(cv) * 8 + 3) else if (is.numeric(xi) && is.numeric(xj)) 3 else 4) +
ggplot2::xlim(0, 1) + ggplot2::ylim(0, 1) + ggplot2::theme_void()
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part is not that bad, but I think the indentation is different when we show the code in Show R Code.
Then it becomes a beast

Image

m7pr added 2 commits March 11, 2026 14:59
Signed-off-by: Marcin <133694481+m7pr@users.noreply.github.com>
Signed-off-by: Marcin <133694481+m7pr@users.noreply.github.com>
Comment on lines +10 to +13
#' @note When *Add Correlation* is enabled, a simple **Omit NAs** checkbox
#' controls NA handling (checked = `"pairwise.complete.obs"`, matching the
#' historical default). Unchecking it reveals a dropdown with all five
#' `stats::cor()` `use` options for advanced control.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can remove. Not really that important

@m7pr
Copy link
Copy Markdown
Contributor Author

m7pr commented Mar 11, 2026

Hey @llrs-roche and @gogonzo thanks for your time spent to review this.
I incorportated most of the feedback - there were many great points.

I revmoed labels from sub-plots so that we only see plots and legends dont take the whole space.
I added a validation so that we only allow 5 plots, so it's a tradeoff of generation time and visibility on the plot.
I tried to cleanup the code so it is more human friendly in terms of reading.

I added also some tooltips to explain the Add Correlation / NA omit options - how those are handled in cor::stats calcualtion.

I tested this branch with below app

devtools::load_all("teal")
devtools::load_all("teal.modules.general")

# ── Data ────────────────────────────────────────────────────────────────────
# 6 variables (4 numeric, 2 factor) covering every panel type:
#   numeric  × numeric  → scatter        (lower triangle)
#   factor   × numeric  → boxplot
#   numeric  × factor   → boxplot
#   factor   × factor   → grouped bar
#   diagonal numeric    → density
#   diagonal factor     → bar
#
# Only 5 are pre-selected to demonstrate the max-5 validation limit —
# the 6th variable ("height") is available in the dropdown to swap in.

set.seed(42)
data <- teal_data()
data <- within(data, {
  demo <- data.frame(
    age    = rnorm(200, mean = 50, sd = 10),
    weight = rnorm(200, mean = 75, sd = 15),
    bmi    = rnorm(200, mean = 25, sd = 4),
    height = rnorm(200, mean = 170, sd = 10),
    sex    = factor(sample(c("M", "F"), 200, replace = TRUE)),
    region = factor(sample(c("North", "South", "East", "West"), 200, replace = TRUE)),
    stringsAsFactors = FALSE
  )
})

# ── App ─────────────────────────────────────────────────────────────────────
app <- init(
  data = data,
  modules = modules(
    tm_g_scatterplotmatrix(
      label = "Scatterplot Matrix",
      variables = teal.transform::data_extract_spec(
        dataname = "demo",
        select = teal.transform::select_spec(
          label    = "Variables:",
          choices  = teal.transform::variable_choices("demo"),
          selected = c("age", "weight", "bmi", "sex", "region"),
          multiple = TRUE,
          ordered  = TRUE,
          fixed    = FALSE
        )
      )
    )
  )
)

shinyApp(app$ui, app$server)

@m7pr m7pr marked this pull request as ready for review March 11, 2026 14:37
@m7pr m7pr requested review from gogonzo and llrs-roche March 12, 2026 11:05
@m7pr
Copy link
Copy Markdown
Contributor Author

m7pr commented Mar 16, 2026

Hey @gogonzo last touches on the labels. I think we should just show the labels at the outside of the matrix for the categorical variables (no labels where red lines, labels only where the blue line is). This saves a lot of space. Also the labels for the categorical variables are trimmed, so they don't take the whole space of the plot

image

I would appreciate your opinion in general on the PR. I think it's ready in it's current state. The code being long in show r code could be tackled as a separate extension to teal via styler pacakge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants