# 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:

- Drag columns in/out of the view
- Switch between chart types (datagrid, bar, line, scatter, heatmap, etc.)
- Add group-by (row pivot) and split-by (column pivot) dimensions
- Create filters, sort, and change aggregations
- Write computed expressions

## 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

- `"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"` — heatmap

### 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

- `psp_update(proxy, data)` — append new rows (upserts when table has an index)
- `psp_replace(proxy, data)` — replace all data
- `psp_clear(proxy)` — clear all rows
- `psp_restore(proxy, config)` — apply a config
- `psp_reset(proxy)` — reset to defaults
- `psp_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 state
- `psp_on_update(proxy, enable)` — subscribe/unsubscribe to data change events
- `psp_schema(proxy)` — get table schema (column names and types)
- `psp_size(proxy)` — get table row count
- `psp_columns(proxy)` — get table column names
- `psp_validate_expressions(proxy, expressions)` — validate expression strings

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