---
title: "Shiny Integration"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Shiny Integration}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

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

## Overview

myIO provides standard `htmlwidgets` Shiny bindings, so charts work like
any other output in your Shiny app. This vignette covers the basics,
interactive I/O patterns, and common recipes.

## Minimal App

Use `myIOOutput()` in the UI and `renderMyIO()` in the server:
```{r minimal, eval = FALSE}
library(shiny)
library(myIO)

ui <- fluidPage(
  titlePanel("myIO + Shiny"),
  myIOOutput("chart", width = "100%", height = "400px")
)

server <- function(input, output) {
  output$chart <- renderMyIO({
    myIO() |>
      addIoLayer(
        type = "point",
        color = "steelblue",
        label = "scatter",
        data = mtcars,
        mapping = list(x_var = "wt", y_var = "mpg")
      )
  })
}

shinyApp(ui, server)
```

## Reactive Data

Because `renderMyIO()` is reactive, the chart updates automatically when
inputs change. Use standard Shiny reactivity to filter or transform data:

```{r reactive, eval = FALSE}
library(shiny)
library(myIO)

ui <- fluidPage(
  titlePanel("Reactive myIO Chart"),
  sidebarLayout(
    sidebarPanel(
      selectInput("cyl", "Cylinders:",
        choices = c("All", sort(unique(mtcars$cyl))),
        selected = "All"
      ),
      selectInput("chart_type", "Chart Type:",
        choices = c("point", "bar", "line"),
        selected = "point"
      )
    ),
    mainPanel(
      myIOOutput("chart", height = "500px")
    )
  )
)

server <- function(input, output) {
  filtered_data <- reactive({
    if (input$cyl == "All") mtcars else mtcars[mtcars$cyl == as.numeric(input$cyl), ]
  })

  output$chart <- renderMyIO({
    myIO() |>
      addIoLayer(
        type = input$chart_type,
        color = "#E69F00",
        label = "data",
        data = filtered_data(),
        mapping = list(x_var = "wt", y_var = "mpg")
      ) |>
      setAxisFormat(xLabel = "Weight (1000 lbs)", yLabel = "Miles per Gallon") |>
      setMargin(top = 20, bottom = 70, left = 60, right = 10)
  })
}

shinyApp(ui, server)
```

## Brush Selection

`setBrush()` enables rectangle selection on a chart. The selected data
points are available as a Shiny reactive input:

```{r brush, eval = FALSE}
library(shiny)
library(myIO)

ui <- fluidPage(
  titlePanel("Brush Selection"),
  fluidRow(
    column(8, myIOOutput("chart", height = "400px")),
    column(4,
      h4("Selected Points"),
      verbatimTextOutput("selected")
    )
  )
)

server <- function(input, output) {
  output$chart <- renderMyIO({
    myIO() |>
      addIoLayer(
        type = "point",
        color = "#4E79A7",
        label = "scatter",
        data = mtcars,
        mapping = list(x_var = "wt", y_var = "mpg")
      ) |>
      setBrush() |>
      setAxisFormat(xLabel = "Weight", yLabel = "MPG")
  })

  output$selected <- renderPrint({
    brushed <- input$`myIO-chart-brushed`
    if (is.null(brushed)) return("Drag to select points")
    sel <- jsonlite::fromJSON(brushed)
    if (length(sel$keys) == 0) return("No points selected")
    paste(length(sel$keys), "points selected")
  })
}

shinyApp(ui, server)
```

The brush input key follows the pattern `myIO-{outputId}-brushed`. The
payload includes:

- `data` — array of selected row objects
- `keys` — `_source_key` values for each selected point
- `extent` — brush bounds in data coordinates

Use `direction = "x"` or `direction = "y"` for single-axis brushing.

## Click-to-Annotate

`setAnnotation()` enables click-to-label mode. Each annotation is stored
as structured data and available as a Shiny reactive:

```{r annotate, eval = FALSE}
library(shiny)
library(myIO)

ui <- fluidPage(
  titlePanel("Data Annotation"),
  fluidRow(
    column(8, myIOOutput("chart", height = "400px")),
    column(4,
      h4("Annotations"),
      tableOutput("annotations")
    )
  )
)

server <- function(input, output) {
  output$chart <- renderMyIO({
    myIO() |>
      addIoLayer(
        type = "point",
        color = "#4E79A7",
        label = "scatter",
        data = mtcars,
        mapping = list(x_var = "wt", y_var = "mpg")
      ) |>
      setAnnotation(
        labels = c("outlier", "interesting", "normal"),
        colors = c(outlier = "#E63946", interesting = "#F4A261", normal = "#2A9D8F")
      ) |>
      setAxisFormat(xLabel = "Weight", yLabel = "MPG")
  })

  output$annotations <- renderTable({
    ann <- input$`myIO-chart-annotated`
    if (is.null(ann)) return(data.frame())
    parsed <- jsonlite::fromJSON(ann)
    if (length(parsed$annotations) == 0) return(data.frame())
    parsed$annotations[, c("label", "x", "y", "category")]
  })
}

shinyApp(ui, server)
```

The annotation input key is `myIO-{outputId}-annotated`. Each annotation
includes `_source_key`, `x`, `y`, `label`, `category` (color), and
`timestamp`.

## Linked Brushing

`setLinked()` connects two or more myIO charts via Crosstalk. Brushing
points in one chart highlights them in the other:

```{r linked, eval = FALSE}
library(shiny)
library(myIO)
library(crosstalk)

ui <- fluidPage(
  titlePanel("Linked Brushing"),
  fluidRow(
    column(6, myIOOutput("scatter1", height = "350px")),
    column(6, myIOOutput("scatter2", height = "350px"))
  )
)

server <- function(input, output) {
  shared <- crosstalk::SharedData$new(mtcars, key = ~rownames(mtcars))

  output$scatter1 <- renderMyIO({
    myIO() |>
      addIoLayer(
        type = "point",
        color = "#4E79A7",
        label = "wt vs mpg",
        data = shared$data(),
        mapping = list(x_var = "wt", y_var = "mpg")
      ) |>
      setBrush() |>
      setLinked(shared, mode = "source") |>
      setAxisFormat(xLabel = "Weight", yLabel = "MPG")
  })

  output$scatter2 <- renderMyIO({
    myIO() |>
      addIoLayer(
        type = "point",
        color = "#E15759",
        label = "hp vs mpg",
        data = shared$data(),
        mapping = list(x_var = "hp", y_var = "mpg")
      ) |>
      setLinked(shared, mode = "target") |>
      setAxisFormat(xLabel = "Horsepower", yLabel = "MPG")
  })
}

shinyApp(ui, server)
```

Both charts share the same `SharedData` object. The `mode` parameter
controls direction: `"source"` emits selections, `"target"` receives
them, and `"both"` (default) does both.

Set `filter = TRUE` to hide non-matching points instead of dimming them.

## Parameter Sliders

`setSlider()` adds a slider below the chart that sends its value as a
Shiny input. This is useful for adjusting transform parameters (confidence
level, smoothing span, polynomial degree) without building a separate
sidebar:

```{r slider, eval = FALSE}
library(shiny)
library(myIO)

ui <- fluidPage(
  titlePanel("Interactive Regression"),
  myIOOutput("chart", height = "450px")
)

server <- function(input, output) {
  output$chart <- renderMyIO({
    ci <- input$`myIO-chart-slider-ci_level` %||% 0.95

    myIO(data = mtcars) |>
      addIoLayer(
        type = "regression",
        label = "fit",
        mapping = list(x_var = "wt", y_var = "mpg"),
        options = list(method = "lm", showCI = TRUE, level = ci)
      ) |>
      setSlider("ci_level", "Confidence Level", 0.80, 0.99, ci, 0.01) |>
      setAxisFormat(xLabel = "Weight", yLabel = "MPG")
  })
}

shinyApp(ui, server)
```

The slider input key is `myIO-{outputId}-slider-{param}`. Use
`debounce` to control how often the slider fires (default 200ms).

## Multiple Charts

Add multiple `myIOOutput()` calls to display several charts side by side.
Each chart gets its own output ID:

```{r multiple, eval = FALSE}
library(shiny)
library(myIO)

ui <- fluidPage(
  titlePanel("Multiple Charts"),
  fluidRow(
    column(6, myIOOutput("scatter", height = "350px")),
    column(6, myIOOutput("bars", height = "350px"))
  )
)

server <- function(input, output) {
  output$scatter <- renderMyIO({
    myIO() |>
      addIoLayer(
        type = "point",
        color = "#56B4E9",
        label = "scatter",
        data = mtcars,
        mapping = list(x_var = "wt", y_var = "mpg")
      ) |>
      setAxisFormat(xLabel = "Weight", yLabel = "MPG")
  })

  output$bars <- renderMyIO({
    myIO() |>
      addIoLayer(
        type = "bar",
        color = "#E69F00",
        label = "bars",
        data = mtcars,
        mapping = list(x_var = "cyl", y_var = "mpg")
      ) |>
      defineCategoricalAxis(xAxis = TRUE) |>
      setAxisFormat(xLabel = "Cylinders", yLabel = "MPG")
  })
}

shinyApp(ui, server)
```

## Theming in Shiny

Theme tokens from `setTheme()` work the same way in Shiny:

```{r themed, eval = FALSE}
library(shiny)
library(myIO)

ui <- fluidPage(
  style = "background-color: #1a1a2e; color: #e0e0e0;",
  titlePanel("Dark Mode Dashboard"),
  myIOOutput("chart", height = "400px")
)

server <- function(input, output) {
  output$chart <- renderMyIO({
    myIO() |>
      addIoLayer(
        type = "line",
        color = c("#48dbfb", "#ff6b6b", "#feca57", "#ff9ff3", "#00d2ff"),
        label = "Month",
        data = within(airquality, Month <- paste0("M", Month)),
        mapping = list(x_var = "Day", y_var = "Temp", group = "Month")
      ) |>
      setTheme(
        text_color = "#b0b0b0",
        grid_color = "#2d2d2d",
        bg = "#1a1a2e",
        font = "Inter, system-ui, sans-serif"
      ) |>
      setAxisFormat(xLabel = "Day", yLabel = "Temperature")
  })
}

shinyApp(ui, server)
```

## Sizing

`myIOOutput()` accepts `width` and `height` as CSS units. Charts are
responsive by default and will adapt to their container:

```{r sizing, eval = FALSE}
# Fixed size
myIOOutput("chart1", width = "600px", height = "400px")

# Fill container width
myIOOutput("chart2", width = "100%", height = "500px")
```

## Shiny Input Reference

All myIO Shiny inputs follow the naming pattern `myIO-{outputId}-{event}`:

| Input key | Event | Payload |
|-----------|-------|---------|
| `myIO-{id}-rollover` | Hover on data element | JSON data point |
| `myIO-{id}-dragEnd` | Point drag completed | JSON `{ point, layerLabel }` |
| `myIO-{id}-brushed` | Brush selection | JSON `{ data, extent, keys }` |
| `myIO-{id}-annotated` | Annotation added/removed | JSON `{ annotations, action }` |
| `myIO-{id}-slider-{param}` | Slider value changed | Numeric value |
| `myIO-{id}-error` | Render error | Error message string |

## Tips

- **Transitions**: Use `setTransitionSpeed(speed = 0)` to disable animations
  if the chart updates frequently from reactive inputs.
- **Export**: Charts include built-in CSV and SVG export buttons that work in
  Shiny without extra configuration.
- **Drag points**: `dragPoints()` enables interactive point dragging. In a
  Shiny context, you can use this for exploratory what-if analysis.
- **Brush + annotation**: Both can be active simultaneously. Clicks on data
  points open the annotation popover; dragging on empty space activates the
  brush.
- **Slider debounce**: For heavy transforms, increase the debounce:
  `setSlider("span", "Span", 0.1, 1.0, 0.5, debounce = 500)`.

## Deep Dive

For a comprehensive walkthrough with the live demo app embedded, see the
package documentation at `vignette("shiny-integration", package = "myIO")`.
