# Transforms & Theming

## Transforms

Transforms derive new data from an existing layer before rendering. You specify a transform with the `transform` argument in `addIoLayer()`.

### Available Transforms

Transform | Description | Used by
--- | --- | ---
`"identity"` | Default. Passes data through unchanged. | All types
`"lm"` | Fits a linear model and returns fitted values. | `line`
`"loess"` | LOESS non-linear smoothing. Options: `span`, `degree`, `n_grid`. | `line`
`"polynomial"` | Polynomial regression. Options: `degree`, `n_grid`. | `line`
`"ci"` | Confidence or prediction interval band. Options: `method`, `level`, `interval`. | `area`
`"smooth"` | Simple or exponential moving average. Options: `method` (`"sma"`/`"ema"`), `window`, `alpha`. | `line`
`"mean"` | Group mean aggregation. | `point`, `bar`
`"mean_ci"` | Group mean ± confidence interval. Options: `level`, `method` (`"t"`/`"se"`). | `rangeBar`
`"residuals"` | Regression residuals vs. fitted values. Options: `method`. | `point`
`"summary"` | General aggregation. Options: `stat` (`"count"`/`"sum"`/`"sd"`/`"var"`/`"min"`/`"max"`). | `point`, `bar`
`"cumulative"` | Computes running totals with base/cumulative columns. | `waterfall` (auto-applied)
`"quantiles"` | Computes Q1, median, Q3, whisker bounds per group. | `boxplot` (internal)
`"median"` | Computes group medians. | `boxplot`, `violin` (internal)
`"outliers"` | Returns rows beyond 1.5x IQR fences. | `boxplot` (internal)
`"density"` | Kernel density estimation via `stats::density()`. | `violin`, `ridgeline` (internal)

Transforms marked “internal” are applied automatically by composite chart types. The first 10 transforms are user-facing — use them directly in `addIoLayer()`.

### Linear Model Trend Line

Add a trend line to a scatter plot by combining a `"point"` layer with a `"line"` layer that uses `transform = "lm"`:

    myIO() |>
      addIoLayer(
        type = "point",
        color = "#E69F00",
        label = "points",
        data = mtcars,
        mapping = list(x_var = "wt", y_var = "mpg")
      ) |>
      addIoLayer(
        type = "line",
        transform = "lm",
        color = "red",
        label = "trend",
        data = mtcars,
        mapping = list(x_var = "wt", y_var = "mpg")
      )

The `lm` transform fits `y ~ x` using `stats::lm()` and replaces the y values with the fitted values. The resulting line is sorted by x so it renders as a smooth trend.

### Cumulative Transform (Waterfall)

The `"cumulative"` transform is auto-applied when you use `type = "waterfall"`. It computes running totals and produces `_base_y` and `_cumulative_y` columns that the waterfall renderer uses for floating bars:

    df <- data.frame(
      step  = c("Start", "Sales", "Returns", "Total"),
      value = c(100, 50, -20, NA),
      is_total = c(FALSE, FALSE, FALSE, TRUE)
    )

    myIO() |>
      addIoLayer(
        type = "waterfall",
        label = "bridge",
        data = df,
        mapping = list(x_var = "step", y_var = "value", total = "is_total")
      ) |>
      defineCategoricalAxis(xAxis = TRUE)

### Confidence Interval Band

Overlay a confidence interval on a scatter plot:

    myIO(data = mtcars) |>
      addIoLayer(type = "point", color = "#4E79A7", label = "Data",
        mapping = list(x_var = "wt", y_var = "mpg")) |>
      addIoLayer(type = "line", color = "#E15759", label = "Trend",
        transform = "lm",
        mapping = list(x_var = "wt", y_var = "mpg")) |>
      addIoLayer(type = "area", color = "#E15759", label = "95% CI",
        transform = "ci",
        mapping = list(x_var = "wt", y_var = "mpg"),
        options = list(level = 0.95))

The `ci` transform accepts `method` (`"lm"` or `"loess"`), `level` (default 0.95), and `interval` (`"confidence"` or `"prediction"`). The mapping only needs `x_var` and `y_var` — `low_y`/`high_y` are auto-injected.

### LOESS Smoothing

Non-linear smoothing with adjustable span:

    myIO(data = mtcars) |>
      addIoLayer(type = "point", color = "#4E79A7", label = "Data",
        mapping = list(x_var = "wt", y_var = "mpg")) |>
      addIoLayer(type = "line", color = "#E15759", label = "LOESS",
        transform = "loess",
        mapping = list(x_var = "wt", y_var = "mpg"),
        options = list(span = 0.5))

### Mean ± CI Error Bars

Group means with confidence intervals:

    myIO(data = iris) |>
      addIoLayer(type = "rangeBar", color = "#4E79A7", label = "Mean ± 95% CI",
        transform = "mean_ci",
        mapping = list(x_var = "Species", y_var = "Sepal.Length"),
        options = list(level = 0.95)) |>
      defineCategoricalAxis(xAxis = TRUE)

### Regression Composite

One-call scatter + trend + CI + R²:

    myIO(data = mtcars) |>
      addIoLayer(type = "regression", label = "MPG vs Weight",
        mapping = list(x_var = "wt", y_var = "mpg"),
        options = list(method = "lm", showCI = TRUE, showStats = TRUE))

### Moving Average

    df <- data.frame(x = 1:100, y = cumsum(rnorm(100)))

    myIO(data = df) |>
      addIoLayer(type = "line", color = "#CCCCCC", label = "Raw",
        mapping = list(x_var = "x", y_var = "y")) |>
      addIoLayer(type = "line", color = "#E15759", label = "SMA-10",
        transform = "smooth",
        mapping = list(x_var = "x", y_var = "y"),
        options = list(method = "sma", window = 10))

Use `method = "ema"` with `alpha` for exponential moving average.

### Transform + Type Compatibility

Not every transform works with every chart type. The table below shows valid combinations:

Type | Supported Transforms
--- | ---
`"line"` | `"identity"`, `"lm"`, `"loess"`, `"polynomial"`, `"smooth"`
`"point"` | `"identity"`, `"mean"`, `"summary"`, `"residuals"`
`"bar"` | `"identity"`, `"mean"`, `"summary"`
`"area"` | `"identity"`, `"ci"`
`"rangeBar"` | `"identity"`, `"mean_ci"`
`"groupedBar"` | `"identity"`
`"histogram"` | `"identity"`
`"hexbin"` | `"identity"`
`"treemap"` | `"identity"`
`"donut"` | `"identity"`
`"gauge"` | `"identity"`
`"heatmap"` | `"identity"`
`"candlestick"` | `"identity"`
`"waterfall"` | `"identity"`, `"cumulative"`
`"sankey"` | `"identity"`
`"boxplot"` | `"identity"`
`"violin"` | `"identity"`
`"ridgeline"` | `"identity"`
`"text"` | `"identity"`

If you pass an incompatible combination, `addIoLayer()` will error with a clear message.

## Theming

Use `setTheme()` to customize chart appearance with CSS custom properties.

### Basic Theming

    myIO() |>
      addIoLayer(
        type = "point",
        color = "#56B4E9",
        label = "scatter",
        data = mtcars,
        mapping = list(x_var = "wt", y_var = "mpg")
      ) |>
      setTheme(
        text_color = "#e0e0e0",
        grid_color = "#333333",
        bg = "#1a1a2e",
        font = "Fira Code, monospace"
      )

### Theme Parameters

Parameter | CSS Property | Description
--- | --- | ---
`text_color` | `--chart-text-color` | Axis labels and tick text
`grid_color` | `--chart-grid-color` | Grid and axis lines
`bg` | `--chart-bg` | Chart background color
`font` | `--chart-font` | Font family

### Custom Properties

Pass additional CSS custom properties via `...` (the `chart-` prefix is added automatically):

    myIO() |>
      addIoLayer(
        type = "bar",
        color = "coral",
        label = "bars",
        data = mtcars,
        mapping = list(x_var = "cyl", y_var = "mpg")
      ) |>
      defineCategoricalAxis(xAxis = TRUE) |>
      setTheme(
        bg = "#fafafa",
        text_color = "#333",
        "border-radius" = "8px"
      )

### Dark Mode Example

Combine theming with color choices for a dark-mode chart:

    aq <- airquality
    aq$Month <- paste0("M", aq$Month)

    myIO() |>
      addIoLayer(
        type = "line",
        color = c("#00d2ff", "#ff6b6b", "#feca57", "#48dbfb", "#ff9ff3"),
        label = "Month",
        data = aq,
        mapping = list(x_var = "Day", y_var = "Temp", group = "Month")
      ) |>
      setTheme(
        text_color = "#b0b0b0",
        grid_color = "#2d2d2d",
        bg = "#0d1117",
        font = "Inter, system-ui, sans-serif"
      )