Introduction to perspectiveR

Overview

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.

Quick Start

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:

Configuration

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.

Available Chart Types

Filtering and Sorting

perspective(iris,
  filter = list(c("Species", "==", "setosa")),
  sort = list(c("Sepal.Length", "desc"))
)

Computed Expressions

perspective(mtcars,
  expressions = c('"hp" / "wt"'),
  columns = c("mpg", "hp", "wt", '"hp" / "wt"')
)

Arrow IPC for Large Datasets

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)

Shiny Integration

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)

Proxy Functions

Filter Operator

When 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.

Rolling Window Tables

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.

Indexed Tables

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

Exporting Data

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).

Saving and Restoring State

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)
})

Update Notifications

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)

Table Metadata

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 = ", "))
})

Windowed Export

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
)

Validating Expressions

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)
})

Themes

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".