---
title: "Introduction to perspectiveR"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Introduction to perspectiveR}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)
```

## Overview

perspectiveR provides an R interface to the [FINOS Perspective](https://perspective-dev.github.io/) 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

```{r setup}
library(perspectiveR)
```

The simplest usage is to pass a data frame directly:

```{r basic, eval=FALSE}
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:
```{r config, eval=FALSE}
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

```{r filter-sort, eval=FALSE}
perspective(iris,
  filter = list(c("Species", "==", "setosa")),
  sort = list(c("Sepal.Length", "desc"))
)
```

### Computed Expressions

```{r expressions, eval=FALSE}
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:

```{r arrow, eval=FALSE}
# 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:

```{r shiny, eval=FALSE}
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`:

```{r filter-op, eval=FALSE}
# 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:
```{r limit, eval=FALSE}
# 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.

```{r indexed, eval=FALSE}
# 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:

```{r export, eval=FALSE}
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:

```{r save-restore, eval=FALSE}
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:

```{r on-update, eval=FALSE}
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:

```{r metadata, eval=FALSE}
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:

```{r windowed-export, eval=FALSE}
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:

```{r validate-expr, eval=FALSE}
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:

```{r themes, eval=FALSE}
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"`.
