knitr::opts_chunk$set( collapse = TRUE, comment = "#>" )
perspectiveR provides an R interface to the FINOS Perspective library, a high-performance WebAssembly-powered data visualization engine. It offers interactive pivot tables, cross-tabulations, and multiple chart types that run entirely in the browser.
library(perspectiveR)
The simplest usage is to pass a data frame directly:
perspective(mtcars)
This opens a fully interactive viewer with a settings panel where you can:
You can set an initial configuration programmatically:
perspective(mtcars, group_by = "cyl", columns = c("mpg", "hp", "wt"), plugin = "Y Bar", theme = "Pro Dark" )
Users can still modify the view interactively even after initial configuration.
"Datagrid" — interactive data grid (default)"Y Bar" / "X Bar" — vertical / horizontal bar charts"Y Line" — line chart"X/Y Line" — line chart with explicit X axis"Y Area" — area chart"Y Scatter" / "XY Scatter" — scatter plots"Treemap" — treemap"Sunburst" — sunburst chart"Heatmap" — heatmapperspective(iris, filter = list(c("Species", "==", "setosa")), sort = list(c("Sepal.Length", "desc")) )
perspective(mtcars, expressions = c('"hp" / "wt"'), columns = c("mpg", "hp", "wt", '"hp" / "wt"') )
For datasets with 100k+ rows, use Arrow IPC serialization for better performance:
# Requires the arrow package big_data <- data.frame( x = rnorm(100000), y = rnorm(100000), group = sample(letters, 100000, replace = TRUE) ) perspective(big_data, use_arrow = TRUE)
perspectiveR includes full Shiny support with a proxy interface for streaming updates:
library(shiny) ui <- fluidPage( perspectiveOutput("viewer", height = "600px"), actionButton("add", "Add Data") ) server <- function(input, output, session) { output$viewer <- renderPerspective({ perspective(mtcars, plugin = "Y Bar", group_by = "cyl") }) observeEvent(input$add, { proxy <- perspectiveProxy(session, "viewer") new_data <- mtcars[sample(nrow(mtcars), 5), ] psp_update(proxy, new_data) }) # Capture user's interactive config changes observeEvent(input$viewer_config, { message("User changed config: ", str(input$viewer_config)) }) } shinyApp(ui, server)
psp_update(proxy, data) — append new rows (upserts when table has an index)psp_replace(proxy, data) — replace all datapsp_clear(proxy) — clear all rowspsp_restore(proxy, config) — apply a configpsp_reset(proxy) — reset to defaultspsp_remove(proxy, keys) — remove rows by primary key (indexed tables only)psp_export(proxy, format) — export data as JSON, CSV, columns, or Arrow (supports windowed export)psp_save(proxy) — retrieve current viewer statepsp_on_update(proxy, enable) — subscribe/unsubscribe to data change eventspsp_schema(proxy) — get table schema (column names and types)psp_size(proxy) — get table row countpsp_columns(proxy) — get table column namespsp_validate_expressions(proxy, expressions) — validate expression stringsWhen using multiple filters, you can control how they are combined using filter_op:
# Match rows where Species is "setosa" OR Sepal.Length > 6 perspective(iris, filter = list( c("Species", "==", "setosa"), c("Sepal.Length", ">", "6") ), filter_op = "or" )
The default is "and" (all filters must match). Set filter_op = "or" to match rows that satisfy any filter.
Use the limit parameter to create a rolling-window table that automatically drops the oldest rows when new rows are added beyond the limit:
# Keep only the last 100 rows perspective(streaming_data, limit = 100)
Note that limit and index are mutually exclusive.
Use the index parameter to create a keyed table. When an index is set,
psp_update() performs upserts—rows with matching keys are updated instead of
appended—and psp_remove() can delete rows by key.
# Create an indexed table keyed on "cyl" perspective(mtcars, index = "cyl", plugin = "Datagrid") # In a Shiny server: proxy <- perspectiveProxy(session, "viewer") psp_update(proxy, updated_rows) # upserts by "cyl" psp_remove(proxy, keys = c(4, 8)) # remove rows where cyl == 4 or 8
Request the current view's data from the browser:
proxy <- perspectiveProxy(session, "viewer") psp_export(proxy, format = "csv") # Result arrives asynchronously: observeEvent(input$viewer_export, { cat("Format:", input$viewer_export$format, "\n") cat("Data:", input$viewer_export$data, "\n") })
Supported formats: "json", "csv", "columns", "arrow" (base64-encoded).
Retrieve the current viewer configuration and restore it later:
proxy <- perspectiveProxy(session, "viewer") # Save current state psp_save(proxy) observeEvent(input$viewer_state, { saved <- input$viewer_state # Later, restore it: psp_restore(proxy, saved) })
Subscribe to table data changes to react when data is updated:
proxy <- perspectiveProxy(session, "viewer") psp_on_update(proxy, enable = TRUE) observeEvent(input$viewer_update, { info <- input$viewer_update message("Update at ", info$timestamp, " from ", info$source) }) # To unsubscribe: psp_on_update(proxy, enable = FALSE)
Query the table for its schema, size, or column names:
proxy <- perspectiveProxy(session, "viewer") # Get column types psp_schema(proxy) observeEvent(input$viewer_schema, { str(input$viewer_schema) # list(col1 = "float", col2 = "string", ...) }) # Get row count psp_size(proxy) observeEvent(input$viewer_size, { message("Table has ", input$viewer_size, " rows") }) # Get column names psp_columns(proxy) observeEvent(input$viewer_columns, { message("Columns: ", paste(input$viewer_columns, collapse = ", ")) })
Export a subset of rows/columns by specifying a window:
proxy <- perspectiveProxy(session, "viewer") # Export only the first 50 rows psp_export(proxy, format = "json", start_row = 0, end_row = 50) # Export rows 100-200, columns 0-3 psp_export(proxy, format = "csv", start_row = 100, end_row = 200, start_col = 0, end_col = 3 )
Check whether expression strings are valid before applying them:
proxy <- perspectiveProxy(session, "viewer") psp_validate_expressions(proxy, c('"hp" / "wt"', '"invalid_col" + 1')) observeEvent(input$viewer_validate_expressions, { result <- input$viewer_validate_expressions # Contains validation info for each expression str(result) })
Set the visual theme with the theme parameter:
perspective(mtcars, theme = "Pro Dark") perspective(mtcars, theme = "Dracula") perspective(mtcars, theme = "Gruvbox Dark")
Available themes: "Pro Light" (default), "Pro Dark", "Monokai",
"Solarized Light", "Solarized Dark", "Vaporwave", "Dracula",
"Gruvbox", "Gruvbox Dark".
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.