---
title: "Filter Panel for Developers"
author: "NEST CoreDev"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Filter Panel for Developers}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

## Filter Panel API

### Getting and setting filter states

All filter panel classes have dedicated methods to set and get the current filter state.
These methods include:

- `get_filter_state` - returns current state of filters as `teal_slices` object
- `set_filter_state` - adds or modifies filters based on `teal_slices` object
- `remove_filter_state` - removes particular filter states based on `teal_slices` object
- `clear_filter_states` - removes all filter states

Setting and getting filter states are done through `teal_slices` object which is a collection of `teal_slice` objects.
Think of a `teal_slice` as a quantum of information that fully describes the filter state of one variable.

In order to tell `FilteredData` to set a filter for a specific variable,
one must call the `set_filter_state` method with a `teal_slices` object containing a `teal_slice`
that refers to the variable of interest.
To remove a particular `FilterState` object, one must call the `remove_filter_state` method
using a `teal_slices` containing a `teal_slice` that refers to the respective variable.

Each `teal_slice` object contains all the information necessary to:

1. Determine the column in the data set on which to apply the filter expression:

   - `dataname` - name of the data set
   - `varname` - name of the column
   - `experiment` (only for `MultiAssayExperiment` objects) - name of the experiment
   - `arg` (only for `SummarizedExperiment` objects, e.g within a `MultiAssayExperiment`) -
   name of the argument in the call to `subset` (`subset` of `select`)

2. Express or store the current selection state:

   - `selected` - selected values or limits of the selected range
   - `keep_inf` - determines if `Inf` values should be dropped
   - `keep_na` - determines if `NA` values should be dropped
   - `expr` - explicit logical expression

3. Control the behavior and appearance of the `FilterState` object:

   - `choices` - determines set of values or range that can be selected from
   - `multiple` (only for `ChoiceFilterState`) - allows multiple values to be selected
   - `fixed` - forbids changing state of the `FilterState`
   - `anchored` - forbids removing the `FilterState`
   - `title` - displayed title of the filter card

In addition, every `teal_slice` object has an `id`.

It is impossible to create `FilteredData` with slices with duplicated `id`s.
This is because filter states are both created and modified with the `set_filter_state` method
so if two consecutive calls to `set_filter_state` are passed a `teal_slice` with the same id,
the first call will instantiate a `FilterState`, and the second call will modify it.

Creating `teal_slices` with slices with duplicated `id`s is forbidden and will raise an error.


#### 1. Setting the filter state
```{r, message=FALSE, warning=FALSE}
library(teal.slice)

datasets <- init_filtered_data(list(iris = iris, mtcars = mtcars))

set_filter_state(
  datasets = datasets,
  filter = teal_slices(
    teal_slice(dataname = "iris", varname = "Species", selected = "virginica", keep_na = FALSE),
    teal_slice(dataname = "mtcars", id = "4 cyl", title = "4 Cylinders", expr = "cyl == 4"),
    teal_slice(dataname = "mtcars", varname = "mpg", selected = c(20.0, 25.0), keep_na = FALSE, keep_inf = FALSE),
    include_varnames = list(iris = c("Species", "Sepal.Length")),
    exclude_varnames = list(mtcars = "cyl")
  )
)
```

#### 2. Updating filter states. *Works only in the `shiny` reactive context.
```{r, message=FALSE, warning=FALSE}
set_filter_state(
  datasets = datasets,
  filter = teal_slices(
    teal_slice(dataname = "mtcars", varname = "mpg", selected = c(22.0, 25.0))
  )
)
```

#### 3. Getting the filter state
```{r, message=FALSE, warning=FALSE}
get_filter_state(datasets)
```

#### 4. Removing filter states
```{r, message=FALSE, warning=FALSE}
remove_filter_state(
  datasets = datasets,
  filter = teal_slices(
    teal_slice(dataname = "iris", varname = "Species")
  )
)
```

#### 5. Clearing the filter state
```{r, message=FALSE, warning=FALSE}
clear_filter_states(datasets)
```

### Controlling settings of the filter panel

In addition to controlling individual filter states through `set_filter_state`,
one can also manage some general behaviors of the whole filter panel.
This can be done with arguments of the `teal_slices` function:

1. `include_varnames` defines which columns in the used data sets are allowed to be filtered on.
In the following example only two columns of `iris` and two columns of `mtcars` will be able to have filters set.
```{r}
set_filter_state(
  datasets,
  teal_slices(
    include_varnames = list(
      iris = c("Species", "Sepal.Length"),
      mtcard = c("cyl", "mpg")
    )
  )
)
```

2. `exclude_varnames` defines which columns in the used data sets are **not** allowed to be filtered on.
In the following example all variables except the four will be available to choose from.
```{r}
set_filter_state(
  datasets,
  teal_slices(
    exclude_varnames = list(
      iris = c("Species", "Sepal.Length"),
      mtcard = c("cyl", "mpg")
    )
  )
)
```

3. `count_type` defines how observation counts are displayed in filter cards

| "none" | "all" |
|--------|-------|
| Distribution in unfiltered data | Filtered vs. unfiltered distribution  |
| ![](./images/filter_panel/count_type_none.jpeg){width=300} | ![](./images/filter_panel/count_type_all.jpeg){width=300} |

4. `allow_add` determines whether the "Add Filter Variables" module will be displayed
to allow the user to add new filters.



## Filter panel as a module

All the instructions herein can be utilized to build a `shiny` app.

```{r warning=FALSE}
library(shiny)
library(bslib)

# initializing FilteredData
datasets <- init_filtered_data(list(iris = iris, mtcars = mtcars))

# setting initial filters
set_filter_state(
  datasets = datasets,
  filter = teal_slices(
    teal_slice(dataname = "iris", varname = "Species", selected = "virginica", keep_na = FALSE),
    teal_slice(dataname = "mtcars", id = "4 cyl", title = "4 Cylinders", expr = "cyl == 4"),
    teal_slice(dataname = "mtcars", varname = "mpg", selected = c(20.0, 25.0), keep_na = FALSE, keep_inf = FALSE),
    include_varnames = list(iris = c("Species", "Sepal.Length")),
    exclude_varnames = list(mtcars = "cyl"),
    count_type = "all",
    allow_add = TRUE
  )
)

ui <- bslib::page_fluid(
  bslib::layout_column_wrap(
    style = htmltools::css(grid_template_columns = "2fr 1fr"),
    tags$div(
      tags$div(
        actionButton("add_species_filter", "Set iris$Species filter"),
        actionButton("remove_species_filter", "Remove iris$Species filter"),
        actionButton("remove_all_filters", "Remove all filters")
      ),
      verbatimTextOutput("rcode"),
      verbatimTextOutput("filter_state")
    ),
    datasets$ui_filter_panel("filter_panel")
  )
)

server <- function(input, output, session) {
  # calling filter panel module
  datasets$srv_filter_panel("filter_panel")

  # displaying actual filter states
  output$filter_state <- renderPrint(print(get_filter_state(datasets), trim = FALSE))

  # displaying reproducible filter call
  output$rcode <- renderText(
    paste(
      sapply(c("iris", "mtcars"), datasets$get_call),
      collapse = "\n"
    )
  )

  # programmatic interaction with FilteredData
  observeEvent(input$add_species_filter, {
    set_filter_state(
      datasets,
      teal_slices(
        teal_slice(dataname = "iris", varname = "Species", selected = c("setosa", "versicolor"))
      )
    )
  })

  # programmatic removal of the FilterState
  observeEvent(input$remove_species_filter, {
    remove_filter_state(
      datasets,
      teal_slices(
        teal_slice(dataname = "iris", varname = "Species")
      )
    )
  })
  observeEvent(input$remove_all_filters, clear_filter_states(datasets))
}

if (interactive()) {
  shinyApp(ui, server)
}
```
