## ----include = FALSE----------------------------------------------------------
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>",
eval = FALSE
)
## ----install, eval=FALSE------------------------------------------------------
# # From CRAN:
# install.packages("rDeckgl")
#
# # Development version from GitHub:
# # remotes::install_github("TiRizvanov/rDeckgl")
## ----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"
# )
## ----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"
# )
## ----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
## ----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)
## ----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"
# )
## ----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)
## ----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)