--- title: "Getting Started with rDeckgl" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Getting Started with rDeckgl} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", eval = FALSE ) ``` ## Introduction **rDeckgl** provides R bindings for [deck.gl](https://deck.gl) 9.2.2, a WebGL-powered framework for visualizing large datasets. This vignette demonstrates the basic usage of rDeckgl with working examples. ## Installation ```{r install, eval=FALSE} # From CRAN: install.packages("rDeckgl") # Development version from GitHub: # remotes::install_github("TiRizvanov/rDeckgl") ``` ## Example 1: Basic Scatterplot This example demonstrates deck.gl rendering performance with ~10,000 points using a viridis color palette: ```{r scatterplot} library(rDeckgl) library(scales) # Generate a spaced-out grid of ~10K points around San Francisco set.seed(42) grid_size <- 100L lon_seq <- seq(-122.515, -122.355, length.out = grid_size) lat_seq <- seq(37.70, 37.82, length.out = grid_size) grid <- expand.grid(lon = lon_seq, lat = lat_seq) grid$value <- rnorm(nrow(grid), mean = 0, sd = 1) # Add light jitter jitter_strength <- 0.0007 grid$lon <- grid$lon + runif(nrow(grid), -jitter_strength, jitter_strength) grid$lat <- grid$lat + runif(nrow(grid), -jitter_strength, jitter_strength) grid$radius <- runif(nrow(grid), 25, 80) points_data <- grid # Map 'value' to viridis color palette domain_range <- range(points_data$value) palette_fun <- col_numeric(viridis_pal(option = "B")(256), domain_range) rgba <- col2rgb(palette_fun(points_data$value)) points_data$color_r <- rgba[1, ] points_data$color_g <- rgba[2, ] points_data$color_b <- rgba[3, ] spec <- list( `@@type` = "DeckGL", initialViewState = list( longitude = mean(range(points_data$lon)), latitude = mean(range(points_data$lat)), zoom = 11.5, pitch = 20, bearing = 0 ), tooltip = list( html = "
Value: {value}
Radius: {radius} m
", style = list( backgroundColor = "#0e1119", color = "#FFFFFF", fontSize = "12px" ) ), views = list( list( `@@type` = "MapView", controller = TRUE, mapStyle = "https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json" ) ), layers = list( list( `@@type` = "ScatterplotLayer", id = "scatterplot", data = list( type = "duckdb", query = "SELECT lon, lat, radius, value, color_r, color_g, color_b FROM points" ), getPosition = "@@=[lon, lat]", getRadius = "@@=radius", getFillColor = "@@=[color_r, color_g, color_b, 200]", pickable = TRUE, autoHighlight = TRUE, radiusUnits = "meters" ) ) ) deckgl( spec = spec, data = list(points = points_data), width = "100%", height = "600px" ) ``` ## Example 2: Hexagon Heatmap This example uses remote data from the deck.gl website to create a 3D hexagon heatmap: ```{r hexagon} library(rDeckgl) hexagon_spec <- list( initialViewState = list( longitude = -1.4157267858730052, latitude = 52.232395363869415, zoom = 6.6, pitch = 40.5, bearing = -27.396674584323023 ), views = list( list( `@@type` = "MapView", controller = TRUE, mapStyle = "https://basemaps.cartocdn.com/gl/dark-matter-nolabels-gl-style/style.json" ) ), layers = list( list( `@@type` = "HexagonLayer", id = "heatmap", data = "https://raw.githubusercontent.com/visgl/deck.gl-data/master/examples/3d-heatmap/heatmap-data.csv", coverage = 1, pickable = TRUE, elevationRange = c(0, 3000), elevationScale = 50, extruded = TRUE, getPosition = "@@=[lng,lat]", radius = 1000, colorRange = list( c(1, 152, 189), c(73, 227, 206), c(216, 254, 181), c(254, 237, 177), c(254, 173, 84), c(209, 55, 78) ) ) ) ) deckgl( spec = hexagon_spec, width = "100%", height = "600px" ) ``` ## Example 3: In-situ Cell Segmentation (GeoArrow polygons + centroids) This example reproduces the kind of visualization used in spatial omics analysis — cell-boundary polygons rendered via the fast GeoArrow path, with a centroid scatter layer coloured by simulated expression level. All data is generated in-memory; no external files are required. ```{r polygons} library(rDeckgl) library(DBI) library(duckdb) # ── 1. Generate mock spatial omics data ──────────────────────────────────────── set.seed(42) n_cells <- 300 field_w <- 8000 # microns field_h <- 6000 # Random centroids cx <- runif(n_cells, 200, field_w - 200) cy <- runif(n_cells, 200, field_h - 200) # Simulated per-cell metrics total_expr <- pmax(0, round(rnorm(n_cells, 200, 80))) cluster <- paste0("c", sample.int(6L, n_cells, replace = TRUE)) # Colour centroids by cluster pal <- grDevices::hcl.colors(6, "Dark 3") rgb_ <- grDevices::col2rgb(pal[as.integer(factor(cluster))]) cr <- as.integer(rgb_[1, ]) cg <- as.integer(rgb_[2, ]) cb <- as.integer(rgb_[3, ]) centroids <- data.frame( cell_ID = sprintf("CELL_%04d", seq_len(n_cells)), x = cx, y = cy, total_expr, cluster, r = cr, g = cg, b = cb ) # Build irregular hexagonal WKT polygons angles <- seq(0, 2 * pi, length.out = 7)[-7] # 6 vertices wkt_polygons <- vapply(seq_len(n_cells), function(i) { radius <- runif(1, 80, 200) jitter <- runif(6, 0.80, 1.20) vx <- cx[i] + radius * jitter * cos(angles) vy <- cy[i] + radius * jitter * sin(angles) pts <- paste(sprintf("%f %f", c(vx, vx[1]), c(vy, vy[1])), collapse = ", ") sprintf("POLYGON((%s))", pts) }, character(1)) polygon_df <- data.frame( cell_ID = centroids$cell_ID, wkt = wkt_polygons ) # ── 2. Load data into in-memory DuckDB with spatial extension ────────────────── con <- dbConnect(duckdb(), dbdir = ":memory:") for (sql in c("INSTALL spatial", "LOAD spatial", "INSTALL nanoarrow FROM community", "LOAD nanoarrow", "CALL register_geoarrow_extensions()")) { try(dbExecute(con, sql), silent = TRUE) } dbWriteTable(con, "poly_raw", polygon_df, overwrite = TRUE) dbExecute(con, " CREATE TABLE cells AS SELECT cell_ID, ST_GeomFromText(wkt) AS geometry FROM poly_raw WHERE wkt IS NOT NULL ") # ── 3. Build the deck.gl spec ───────────────────────────────────────────────── # Orthographic (Cartesian) view — same as spatial omics viewers cx_mid <- field_w / 2 cy_mid <- field_h / 2 zoom <- log2(600 / field_w) # fit ~600 px wide polygon_query <- "SELECT cell_ID, geometry FROM cells WHERE geometry IS NOT NULL" spec <- list( views = list( list( `@@type` = "OrthographicView", controller = TRUE ) ), initialViewState = list( target = list(cx_mid, cy_mid, 0), zoom = zoom, minZoom = -5, maxZoom = 8 ), layers = list( list( `@@type` = "GeoArrowSolidPolygonLayer", id = "cell-polygons", data = list( type = "duckdb", query = polygon_query, format = "geoarrow" ), geometryColumn = "geometry", getFillColor = c(100L, 80L, 220L, 100L), stroked = FALSE, coordinateSystem = "@@#COORDINATE_SYSTEM.CARTESIAN", positionFormat = "XY", pickable = TRUE ), list( `@@type` = "ScatterplotLayer", id = "cell-centroids", data = list( type = "duckdb", query = "SELECT cell_ID, x, y, r, g, b FROM centroids" ), getPosition = "@@=[x, y]", getFillColor = "@@=[r, g, b]", getRadius = 2, radiusUnits = "pixels", coordinateSystem = "@@#COORDINATE_SYSTEM.CARTESIAN", positionFormat = "XY", opacity = 0.85, pickable = TRUE ) ) ) widget <- deckgl( spec = spec, con = con, data = list(centroids = centroids), width = "100%", height = "600px" ) DBI::dbDisconnect(con) widget ``` ## Data Hydration with DuckDB rDeckgl automatically creates an in-memory DuckDB database for efficient data handling: 1. Pass a named list of data.frames via the `data` argument 2. Each entry becomes a DuckDB table 3. Reference tables using `type = "duckdb"` and SQL queries in your spec 4. Use DuckDB's powerful SQL features for data transformation ## Shiny Integration Use rDeckgl in Shiny applications with reactive bindings. Here's a complete working example: ```{r shiny, eval=FALSE} library(shiny) library(rDeckgl) library(scales) ui <- fluidPage( titlePanel("Deck.gl in Shiny"), deckglOutput("myDeckgl", width = "100%", height = "600px") ) server <- function(input, output, session) { output$myDeckgl <- renderDeckgl({ # Generate sample data set.seed(42) n_points <- 500 points_data <- data.frame( lon = runif(n_points, -122.5, -122.3), lat = runif(n_points, 37.7, 37.85), value = rnorm(n_points), radius = runif(n_points, 50, 150) ) # Add colors palette_fun <- col_numeric(viridis_pal(option = "B")(256), range(points_data$value)) rgba <- col2rgb(palette_fun(points_data$value)) points_data$color_r <- rgba[1, ] points_data$color_g <- rgba[2, ] points_data$color_b <- rgba[3, ] # Create spec spec <- list( `@@type` = "DeckGL", initialViewState = list( longitude = -122.4, latitude = 37.78, zoom = 11, pitch = 0, bearing = 0 ), views = list( list( `@@type` = "MapView", controller = TRUE, mapStyle = "https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json" ) ), layers = list( list( `@@type` = "ScatterplotLayer", id = "points", data = list( type = "duckdb", query = "SELECT lon, lat, radius, color_r, color_g, color_b FROM points" ), getPosition = "@@=[lon, lat]", getRadius = "@@=radius", getFillColor = "@@=[color_r, color_g, color_b, 180]", pickable = TRUE, radiusUnits = "meters" ) ) ) deckgl(spec = spec, data = list(points = points_data)) }) } shinyApp(ui, server) ``` **Note:** This example generates data within the server function. For reactive/dynamic visualizations, wrap your data generation in `reactive()` and use `observe()` or `observeEvent()` to update the visualization based on user input. ## GeoArrow examples The following self-contained scripts mirror our internal GeoArrow tests. Copy them into your own session—no local `Examples/` folder required. ### GeoArrow scatterplot (points) ```{r geoarrow-scatterplot, eval=FALSE} library(rDeckgl) set.seed(42) n_points <- 1000 points_data <- data.frame( id = 1:n_points, lon = runif(n_points, -122.5, -122.3), lat = runif(n_points, 37.7, 37.85), value = rnorm(n_points, mean = 0.5, sd = 0.3), radius = runif(n_points, 50, 200) ) points_data$value <- pmax(0, pmin(1, points_data$value)) spec <- list( `@@type` = "DeckGL", initialViewState = list( longitude = -122.4, latitude = 37.78, zoom = 12, pitch = 0, bearing = 0 ), views = list( list( `@@type` = "MapView", controller = TRUE, mapStyle = "https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json" ) ), layers = list( list( `@@type` = "ScatterplotLayer", id = "points", data = list( type = "duckdb", query = "SELECT id, lon, lat, value, radius FROM points_data" ), getPosition = "@@=[lon, lat]", getRadius = "@@=radius", getFillColor = "@@=[value * 255, (1 - value) * 255, 100, 200]", radiusUnits = "meters", pickable = TRUE, autoHighlight = TRUE ) ) ) deckgl( spec = spec, data = list(points_data = points_data), width = "100%", height = "600px" ) ``` ### Utah building footprints (GeoArrow) This script renders the Utah portion of the [Microsoft USBuildingFootprints](https://github.com/microsoft/USBuildingFootprints) dataset as GeoArrow polygons. Create a GeoParquet with GeoArrow-encoded geometries from the upstream ZIP: ```bash ogr2ogr \ Utah.parquet \ /vsizip/Utah.geojson.zip \ -dialect SQLite \ -sql "SELECT geometry FROM 'Utah.geojson'" \ -lco COMPRESSION=BROTLI \ -lco GEOMETRY_ENCODING=GEOARROW \ -lco POLYGON_ORIENTATION=COUNTERCLOCKWISE \ -lco ROW_GROUP_SIZE=9999999 ``` ```{r geoarrow-utah, eval=FALSE} library(rDeckgl) library(arrow) parquet_path <- "Utah.parquet" if (!file.exists(parquet_path)) { stop("Parquet file not found: ", parquet_path) } parquet_data <- arrow::read_parquet(parquet_path) max_rows <- 1000000 sample_data <- if (nrow(parquet_data) > max_rows) { head(parquet_data, max_rows) } else { parquet_data } # Locate the densest cluster of buildings to centre the initial view bbox_data <- sample_data$GEOMETRY_bbox if (!is.null(bbox_data)) { cx <- (as.numeric(bbox_data$xmin) + as.numeric(bbox_data$xmax)) / 2 cy <- (as.numeric(bbox_data$ymin) + as.numeric(bbox_data$ymax)) / 2 grid_key <- paste(floor(cx / 0.1), floor(cy / 0.1), sep = ",") densest_cell <- names(which.max(table(grid_key))) idx <- which(grid_key == densest_cell) center_lon <- mean(cx[idx], na.rm = TRUE) center_lat <- mean(cy[idx], na.rm = TRUE) } else { center_lon <- -110.4144 center_lat <- 39.4991 } # Encode the Arrow table as base64 IPC for inline delivery arrow_table <- arrow::arrow_table(sample_data) temp_file <- tempfile(fileext = ".arrows") arrow::write_ipc_stream(arrow_table, temp_file) arrow_bytes <- readBin(temp_file, "raw", n = file.info(temp_file)$size) unlink(temp_file) arrow_b64 <- base64enc::base64encode(arrow_bytes) if (length(arrow_bytes) / 1024 / 1024 > 50) { warning("Encoded payload exceeds 50 MB and may cause browser issues.") } spec <- list( `@@type` = "DeckGL", width = 1024, height = 768, initialViewState = list( longitude = center_lon, latitude = center_lat, zoom = 12, pitch = 0, bearing = 0 ), controller = TRUE, layers = list( list( `@@type` = "GeoArrowSolidPolygonLayer", id = "utah-buildings", data = list(`__arrow` = arrow_b64), geometryColumn = "GEOMETRY", getFillColor = c(255, 100, 0, 200), extruded = FALSE, pickable = TRUE, `_normalize` = FALSE, `_windingOrder` = "CCW" ) ) ) deckgl(spec = spec, width = 1024, height = 768) ``` ### Native GeoArrow in Shiny (no binary conversion) This example demonstrates rendering GeoArrow polygons in Shiny using the native `GeoArrowPolygonLayer`, which passes Arrow tables directly to the layer without converting to binary format: ```{r shiny-native-geoarrow, eval=FALSE} library(shiny) library(rDeckgl) library(arrow) # Prepare the data file parquet_path <- "Utah.parquet" if (!file.exists(parquet_path)) { stop("Parquet file not found: ", parquet_path) } # Read Parquet and create Arrow IPC file parquet_data <- arrow::read_parquet(parquet_path) # Sample for reasonable performance max_rows <- 1000000 if (nrow(parquet_data) > max_rows) { sample_data <- head(parquet_data, max_rows) } else { sample_data <- parquet_data } # Compute density-based center bbox_data <- sample_data$GEOMETRY_bbox if (!is.null(bbox_data)) { xmin <- as.numeric(bbox_data$xmin) xmax <- as.numeric(bbox_data$xmax) ymin <- as.numeric(bbox_data$ymin) ymax <- as.numeric(bbox_data$ymax) cx <- (xmin + xmax) / 2 cy <- (ymin + ymax) / 2 grid_size <- 0.1 grid_x <- floor(cx / grid_size) grid_y <- floor(cy / grid_size) grid_key <- paste(grid_x, grid_y, sep = ",") grid_counts <- table(grid_key) densest_cell <- names(which.max(grid_counts)) densest_indices <- which(grid_key == densest_cell) center_lon <- mean(cx[densest_indices], na.rm = TRUE) center_lat <- mean(cy[densest_indices], na.rm = TRUE) } else { center_lon <- -110.4144 center_lat <- 39.4991 } # Write Arrow IPC file to temporary location temp_arrow <- tempfile(fileext = ".arrows") arrow_table <- arrow::arrow_table(sample_data) arrow::write_ipc_stream(arrow_table, temp_arrow) # Set up static file serving static_root <- dirname(temp_arrow) static_name <- "utahdata" addResourcePath(static_name, static_root) arrow_url <- paste0(static_name, "/", basename(temp_arrow)) # UI ui <- fluidPage( titlePanel("Utah Buildings - Native GeoArrow Rendering"), deckglOutput("map", width = "100%", height = "600px") ) # Server server <- function(input, output, session) { output$map <- renderDeckgl({ spec <- list( `@@type` = "DeckGL", width = 1024, height = 600, initialViewState = list( longitude = center_lon, latitude = center_lat, zoom = 12, pitch = 0, bearing = 0 ), controller = TRUE, layers = list( list( # Use GeoArrowPolygonLayer for native rendering (no binary conversion) `@@type` = "GeoArrowPolygonLayer", id = "utah-buildings-native", data = list(`__arrow_url` = arrow_url), geometryColumn = "GEOMETRY", getFillColor = c(255, 100, 0, 200), getLineColor = c(255, 255, 255, 100), filled = TRUE, stroked = TRUE, extruded = FALSE, pickable = TRUE, autoHighlight = TRUE ) ) ) deckgl(spec = spec, width = "100%", height = "600px") }) # Cleanup on session end onSessionEnded(function() { if (file.exists(temp_arrow)) { unlink(temp_arrow) } }) } # Run app shinyApp(ui, server) ``` **Key differences from binary conversion:** - Uses `GeoArrowPolygonLayer` instead of `GeoArrowSolidPolygonLayer` - Arrow table passed directly to layer (no conversion to binary arrays) - Supports full data-driven styling with accessors - No `_normalize` or `_windingOrder` flags needed ## Next Steps - Build your own specs by swapping the data queries in these templates (DuckDB-backed or GeoArrow IPC/Parquet inputs). - For large datasets, prefer URL-based loading patterns to avoid embedding multi-megabyte payloads in HTML. - Read the [deck.gl documentation](https://deck.gl/docs) for the full catalog of layers, views, and accessors. - Follow the [rDeckgl GitHub](https://github.com/TiRizvanov/rDeckgl) for updates and additional recipes.