--- title: "Linked visualization examples" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Linked visualization examples} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} # The examples are shown but not run when the vignette is built: each one # embeds a full interactive widget, which would add several MB to the package. # Run them in R to see the linked views. knitr::opts_chunk$set(collapse = TRUE, comment = "#>", eval = FALSE) ``` `mosaic_gl()` puts Mosaic views and GPU deck.gl layers in one browser scope over a single DuckDB-WASM coordinator and a single shared selection, so a brush in any panel filters every other panel. Each example below is self-contained: it builds its own data, so you can paste it straight into the console. The last example keeps its data in a DuckDB database and draws polygons. ```{r setup, eval = FALSE} library(dbVisuals) set.seed(1) ``` ## The three pieces A spec is an ordinary R list shaped like a Mosaic spec, with two additions: * `mgl_data()` declares a data source — either an R data frame shipped as Arrow IPC (`type = "table"`), or a query left in DuckDB (`type = "duckdb"`). * `mgl_deckgl()` declares a deck.gl panel as a Mosaic input, so it participates in the same selection as the Mosaic marks around it. `params = list(sel = list(select = "crossfilter"))` creates the shared selection; panels write to it with a `select` clause and read from it with `filterBy`. If you show several widgets on one page, give their tables different names: they share one DuckDB-WASM instance, so a reused name replaces the earlier table. ## 1. Cross-filtered scatter with a marginal histogram A deck.gl scatterplot beside a Mosaic histogram. Brushing the histogram filters the points; the interval brush writes to `$sel` and the deck panel reads it. ```{r ex1, eval = FALSE} cells <- data.frame( cell_ID = sprintf("cell_%04d", 1:4000), x = rnorm(4000, 0, 40), y = rnorm(4000, 0, 40), total_expr = rpois(4000, 60) ) spec1 <- mgl_spec( meta = list(title = "Points cross-filtered by expression"), data = list(cells1 = mgl_data(type = "table", df = cells)), params = list(sel = list(select = "crossfilter")), vconcat = list( list( plot = list( list( mark = "rectY", data = list(from = "cells1", filterBy = "$sel"), x = list(bin = "total_expr"), y = list(count = NULL), fill = "#60a5fa" ), list( select = "intervalX", as = "$sel", brush = list(stroke = "currentColor", fill = "transparent") ) ), width = 640, height = 110, marginLeft = 40 ), mgl_deckgl( id = "spatial", filter_by = "$sel", width = 640, height = 420, layers = list(list( `@@type` = "ScatterplotLayer", id = "pts", data = "cells1", getPosition = "@@=[x, y]", getFillColor = c(100L, 180L, 255L, 220L), getRadius = 2, radiusUnits = "pixels", coordinateSystem = "@@#COORDINATE_SYSTEM.CARTESIAN", positionFormat = "XY", pickable = TRUE )) ) ) ) mosaic_gl(spec1, width = "100%", height = 560) ``` `getPosition = "@@=[x, y]"` and `coordinateSystem = "@@#COORDINATE_SYSTEM.CARTESIAN"` are the two conventions worth remembering: `@@=` marks a JavaScript accessor expression and `@@#` marks a deck.gl constant. Cartesian is what you want for tissue coordinates — the default is geographic. ## 2. Dimension-reduction pairs linked to a categorical bar `mgl_deckgl(concat = ...)` lays out several panes that share one layer definition, each pane binding different columns to `x` and `y`. Toggling a bar in the Mosaic bar chart filters every pane at once. ```{r ex3, eval = FALSE} dimred <- data.frame( cell_ID = sprintf("cell_%04d", 1:2000), leiden_clus = sample(sprintf("c%02d", 1:8), 2000, replace = TRUE), umap_1 = rnorm(2000), umap_2 = rnorm(2000), umap_3 = rnorm(2000), pca_1 = rnorm(2000), pca_2 = rnorm(2000), pca_3 = rnorm(2000) ) spec3 <- mgl_spec( meta = list(title = "UMAP / PCA pairs linked to leiden_clus"), data = list(dimred = mgl_data(type = "table", df = dimred)), params = list(sel = list(select = "crossfilter")), vconcat = list( list( plot = list( list(mark = "barY", data = list(from = "dimred", filterBy = "$sel"), x = "leiden_clus", y = list(count = NULL), fill = "#60a5fa"), list(select = "toggleX", as = "$sel") ), width = 700, height = 140, marginLeft = 40 ), mgl_deckgl( id = "umap", filter_by = "$sel", width = 700, height = 210, concat = list(orient = "hconcat", panes = list( list(x = "umap_1", y = "umap_2", title = "UMAP 1 vs 2"), list(x = "umap_1", y = "umap_3", title = "UMAP 1 vs 3"), list(x = "umap_2", y = "umap_3", title = "UMAP 2 vs 3") )), layers = list(list( `@@type` = "ScatterplotLayer", id = "p", data = "dimred", getFillColor = c(100L, 180L, 255L, 220L), getRadius = 3, radiusUnits = "pixels", opacity = 0.85 )) ), mgl_deckgl( id = "pca", filter_by = "$sel", width = 700, height = 210, concat = list(orient = "hconcat", panes = list( list(x = "pca_1", y = "pca_2", title = "PC 1 vs 2"), list(x = "pca_1", y = "pca_3", title = "PC 1 vs 3"), list(x = "pca_2", y = "pca_3", title = "PC 2 vs 3") )), layers = list(list( `@@type` = "ScatterplotLayer", id = "p", data = "dimred", getFillColor = c(245L, 158L, 11L, 220L), getRadius = 3, radiusUnits = "pixels", opacity = 0.85 )) ) ) ) mosaic_gl(spec3, width = "100%", height = 620) ``` ## 3. Keeping data in DuckDB, and drawing polygons The examples above ship their data frames to the browser. For anything large, leave the data in DuckDB and send only the query: `mgl_data(type = "duckdb")` with a `con`. Polygons travel as GeoArrow rather than JSON, and are drawn with `GeoArrowPolygonLayer`. ```{r ex4, eval = FALSE} # A small spatial database, so this example runs anywhere. Substitute your own # file and table names for real work. db <- file.path(tempdir(), "cells.duckdb") unlink(db) con <- DBI::dbConnect(duckdb::duckdb(), db) DBI::dbExecute(con, "INSTALL spatial") DBI::dbExecute(con, "LOAD spatial") DBI::dbExecute(con, " CREATE TABLE cell_polygons AS SELECT i AS cell_ID, ((i - 1) % 25) * 12.0 + 6 * random() AS x, ((i - 1) // 25) * 12.0 + 6 * random() AS y, CAST(20 + 60 * random() AS INTEGER) AS total_expr FROM range(1, 501) t(i)") DBI::dbExecute(con, "ALTER TABLE cell_polygons ADD COLUMN geom GEOMETRY") DBI::dbExecute(con, "UPDATE cell_polygons SET geom = ST_Buffer(ST_Point(x, y), 4.5)") spec4 <- mgl_spec( meta = list(title = "Polygons and centroids from DuckDB"), data = list( cells4 = mgl_data( type = "duckdb", query = "SELECT cell_ID, x, y, total_expr FROM cell_polygons" ), polys4 = mgl_data( type = "duckdb", # The key column matters: the WKB encoder needs a key (cell_ID / # poly_ID / id) alongside the geometry, or it falls back to plain # Arrow and the polygon layer is silently skipped. query = "SELECT cell_ID, ST_AsWKB(geom) AS geometry FROM cell_polygons", format = "geoarrow" ) ), params = list(sel = list(select = "crossfilter")), vconcat = list( list( plot = list( list(mark = "rectY", data = list(from = "cells4", filterBy = "$sel"), x = list(bin = "total_expr"), y = list(count = NULL), fill = "#a78bfa"), list(select = "intervalX", as = "$sel", brush = list(stroke = "currentColor", fill = "transparent")) ), width = 640, height = 110, marginLeft = 40 ), mgl_deckgl( id = "insitu", filter_by = "$sel", width = 640, height = 440, layers = list( list( `@@type` = "GeoArrowPolygonLayer", id = "polys", data = "polys4", geometryColumn = "geometry", getFillColor = c(157L, 78L, 221L, 90L), stroked = FALSE, lineWidthMinPixels = 0.4, coordinateSystem = "@@#COORDINATE_SYSTEM.CARTESIAN", positionFormat = "XY" ), list( `@@type` = "ScatterplotLayer", id = "centroids", data = "cells4", getPosition = "@@=[x, y]", getFillColor = c(30L, 41L, 59L, 200L), getRadius = 2, radiusUnits = "pixels", coordinateSystem = "@@#COORDINATE_SYSTEM.CARTESIAN", positionFormat = "XY" ) ) ) ) ) mosaic_gl(spec4, con = con, width = "100%", height = 600) ``` Passing `con` also lets the widget issue follow-up queries against your R-side DuckDB as you interact, instead of materializing everything up front. Any spatial / nanoarrow / geoarrow extensions already loaded on that connection are reused. ## In Shiny ```{r shiny, eval = FALSE} ui <- shiny::fluidPage(mosaic_glOutput("viewer", height = "780px")) server <- function(input, output, session) { output$viewer <- renderMosaic_gl(mosaic_gl(spec1)) } shiny::shinyApp(ui, server) ``` ## See also `vignette("getting-started", package = "dbVisuals")` for the browser workspace, and `vignette("reproducing-paper-figures", package = "dbVisuals")` for the published figures.