Author: NEST CoreDev

# Filter Panel for Developers

#### NEST CoreDev

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

    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.

    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

    get_filter_state(datasets)

    ## {
    ##   "slices": [
    ##     {
    ##       "dataname"       : "iris",
    ##       "varname"        : "Species",
    ##       "id"             : "iris Species",
    ##       "choices"        : ["setosa", "versicolor", "virgin...
    ##       "selected"       : ["virginica"],
    ##       "keep_na"        : false,
    ##       "fixed"          : false,
    ##       "anchored"       : false,
    ##       "multiple"       : true
    ##     },
    ##     {
    ##       "dataname"       : "mtcars",
    ##       "id"             : "4 cyl",
    ##       "expr"           : "cyl == 4",
    ##       "fixed"          : true,
    ##       "anchored"       : false,
    ##       "title"          : "4 Cylinders"
    ##     },
    ##     {
    ##       "dataname"       : "mtcars",
    ##       "varname"        : "mpg",
    ##       "id"             : "mtcars mpg",
    ##       "choices"        : [10.4, 34],
    ##       "selected"       : [22, 25],
    ##       "keep_na"        : false,
    ##       "keep_inf"       : false,
    ##       "fixed"          : false,
    ##       "anchored"       : false,
    ##       "multiple"       : true
    ##     }
    ##   ],
    ##   "attributes": {
    ##     "exclude_varnames" : {
    ##       "mtcars"         : "cyl"
    ##     },
    ##     "include_varnames" : {
    ##       "iris"           : ["Species", "Sepal.Length"]
    ##     },
    ##     "count_type"       : "none",
    ##     "allow_add"        : true
    ##   }
    ## }

#### 4. Removing filter states

    remove_filter_state(
      datasets = datasets,
      filter = teal_slices(
        teal_slice(dataname = "iris", varname = "Species")
      )
    )

#### 5. Clearing the filter state

    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.

    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.

    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

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.

    library(shiny)
    library(bslib)

    ##
    ## Attaching package: 'bslib'

    ## The following object is masked from 'package:utils':
    ##
    ##     page

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