Use Interdependent Filters on Table Columns in Shiny Apps.
Package shinyfilter

What shinyfilter does
With shinyfilter you can link selectizeInput widgets to a reactable table and use them as filters for the columns of that table. All filters are interdependent: When you change the selection in one filter not only is the table updated, of course, but also will the available filter values in the other filters adjust to the new data selection; each filter will only show those values that are available given the current selection defined by all the filters together. This mimics the behavior of column filters in spreadsheet applications like Microsoft Excel or LibreOffice Calc.
How you install shinyfilter
Execute install.packages("shinyfilter", dependencies = TRUE) in the R console to install the package including all packages it depends on.
How you work with shinyfilter
Cookbook recipe for the impatient
In your user interface:
- add the
selectizeInputwidgets that will serve as filters for thereactabletable; make sure they all have theironChangeproperty set to the same input variable - add the
reactabletable to present your data - if you want to use tooltips or popovers to show the currently (un)available filter options (given the current filter selection in all filters together), call
use_tooltips()(and change the appearance of the tooltips or popovers, if you like)
In your server function:
- call
define_filters()to configure whichselectizeInputwidget will filter which column of your table - handle the
onChangeevent of theselectizeInputwidgets withobserveEvent():- call
update_filters()to update the filter values;update_filters()will return the ‘new’, filtered dataframe. Ideally, this is captured in a reactive value so that thereactableupdates automatically - if you want to work with tooltips or popovers, call
update_tooltips()
- call
Comprehensive tutorial
There is a couple of simple steps to run through when you use shinyfilters. In the following, the process is shown using an example with cars, a subset of the used cars dataset by Austin Reese. This is also the example used in the online help for shinyfilter. Let us start with the UI.
User interface
Create your UI as usual and place the
reactablewidget and theselectizeInputwidgets for the filters on it. Make sure theselectizeInputwidgets all have an event handler function for theonChangeevent (which is triggered everytime the selection in that widget changes). All yourselectizeInputwidgets should use the same event handler for theonChangeevent. To set up such an event binding easily you can useshinyfilter’sevent()function which produces the required JavaScript code for you. The argument ofevent()is the name of the input value that you can process in the server function of your application usingobserveEvent()(more on that further down below).In our example, two filter widgets could then look like this:
selectizeInput(inputId = "sel_manufacturer", label = "Manufacturer", multiple = TRUE, options = list(onChange = event("ev_click")), choices = sort(unique(cars$manufacturer))) selectizeInput(inputId = "sel_fuel", label = "Fuel", multiple = TRUE, options = list(onChange = event("ev_click")), choices = sort(unique(cars$fuel))),If you want to use tooltips or popovers to show the user of your application the filter options that are currently not available (i.e. hidden) because they do not occur in the current selection that is shown in the
reactablethen you need to calluse_tooltips()from the UI. Here you can specify thebackground(default: black) andforeground(default: white) colors, thetextalignment (default: left), thefontsize(default: 100%) and theopacity(default: 0.8). A call ofuse_tooltips()could look like this:use_tooltips(background = "#1B3F8C", foreground = "#FFFFFF")
This is it. Now your UI is ready for shinyfilter. Let’s move on to the server function.
Server
In the server function you need to do three things:
Call
define_filters()to bind the filters to the columns of the dataframe you are presenting in thereactable. The arguments ofdefine_filters()are the following:- the
inputargument provided to the server function of your application - the
inputIdof thereactable - a named vector of the columns of the dataframe that will be filtered; the names of the vector elements are the
inputIds of theselectizeInputwidgets that represent the filters - the dataframe shown in the reactable
A call of
define_filters()in our example could look this (assuming, the dataframe which is presented in the reactable is calledcarsand thereactableitself is namedtbl_cars):define_filters(input, "tbl_cars", c(sel_manufacturer = "manufacturer", sel_fuel = "fuel"), cars)- the
An
observeEvent()call to handle the filter event (ev_clickin our example). In the expression to execute when the event is triggered (thehandleExprargument ofobserveEvent()) you need to callupdate_filters()with the input and session variables (the arguments of the server function), and theinputIdof thereactableas arguments.update_filters()will return a filtered dataframe that can be used to update yourreactable.In our example, the data for the
reactableis stored in a reactive objectrwhich had been created with:r <- reactiveValues(mycars = cars)The
reactableis rendered based on this data:output$tbl_cars <- renderReactable({ reactable(data = r$mycars, filterable = TRUE, rownames = FALSE, selection = "multiple", showPageSizeOptions = TRUE, paginationType = "jump", showSortable = TRUE, highlight = TRUE, resizable = TRUE, rowStyle = list(cursor = "pointer"), onClick = "select" ) })To update the
reactablewe only need to assign the return value ofupdate_filters()to the reactive variable:r$mycars <- update_filters(input, session, "tbl_cars")So far, the
observeEvent()call looks like this:observeEvent(input$ev_click, { r$mycars <- update_filters(input, session, "tbl_cars") })If you want to use tooltips or popovers to show the hidden (currently not available) filter options then you need an additional call of
update_tooltips()inobserveEvent(). Here, you can specify if you want to show not only the unavailable but the available filter options as well (argumentshow_avail), how many filter options you want to show at most (argumentsmax.availandmax.nonavail- default for both isNULLwhich means all filter values are shown), how the available (title_avail) and unavailable (title_unavail) filter options shall be captioned, and what to show if the list of filter values exceedsmax.avail/max.nonavail; default for the latter arguments (more.nonavailandmore.avail) is"... (# more)"where#is a placeholder for the number of values not shown any more. You can provide any text you like and use#to show the number of filter options not listed in the tooltip/popover.If you want to show popovers instead of tooltips you need to set the
tooltipsargument ofupdate_tooltips()toFALSE. In this case you can specify an additionalpopover_title.In our example, embedded in the
observeEvent()call, this could look like this:observeEvent(input$ev_click, { r$mycars <- update_filters(input, session, "tbl_cars") update_tooltips("tbl_cars", session, tooltip = TRUE, title_avail = "Available is:", title_nonavail = "Currently not available is:", max_avail = 10, max_nonavail = 10) })
Full code of the example application
This is how the application looks like (here, we use some more filters than just the two from above):
And here is the code:
library(shiny)
library(reactable)
library(shinyfilter)
cars_csv <- system.file("cars.csv", package="shinyfilter")
cars <- read.csv(cars_csv, stringsAsFactors = FALSE, header = TRUE, encoding = "UTF-8")
ui <- fluidPage(
titlePanel("Cars Database"),
sidebarLayout(
sidebarPanel(
width = 2,
selectizeInput(inputId = "sel_manufacturer", label = "Manufacturer",
multiple = TRUE, options = list(onChange = event("ev_click")),
choices = sort(unique(cars$manufacturer))),
selectizeInput(inputId = "sel_year", label = "Year",
multiple = TRUE, options = list(onChange = event("ev_click")),
choices = sort(unique(cars$year))),
selectizeInput(inputId = "sel_fuel", label = "Fuel",
multiple = TRUE, options = list(onChange = event("ev_click")),
choices = sort(unique(cars$fuel))),
selectizeInput(inputId = "sel_condition", label = "Condition",
multiple = TRUE, options = list(onChange = event("ev_click")),
choices = sort(unique(cars$condition))),
selectizeInput(inputId = "sel_size", label = "Size",
multiple = TRUE, options = list(onChange = event("ev_click")),
choices = sort(unique(cars$size))),
selectizeInput(inputId = "sel_transmission", label = "Transmission",
multiple = TRUE, options = list(onChange = event("ev_click")),
choices = sort(unique(cars$transmission))),
selectizeInput(inputId = "sel_color", label = "Color",
multiple = TRUE, options = list(onChange = event("ev_click")),
choices = sort(unique(cars$paint_color))),
selectizeInput(inputId = "sel_type", label = "Type",
multiple = TRUE, options = list(onChange = event("ev_click")),
choices = sort(unique(cars$type))),
use_tooltips(background = "#1B3F8C", foreground = "#FFFFFF")
),
mainPanel(
reactableOutput(outputId = "tbl_cars")
)
)
)
server <- function(input, output, session) {
r <- reactiveValues(mycars = cars)
define_filters(input,
"tbl_cars",
c(sel_manufacturer = "manufacturer",
sel_year = "year",
sel_fuel = "fuel",
sel_condition = "condition",
sel_size = "size",
sel_transmission = "transmission",
sel_color = "paint_color",
sel_type = "type"),
cars)
observeEvent(input$ev_click, {
r$mycars <- update_filters(input, session, "tbl_cars")
update_tooltips("tbl_cars",
session,
tooltip = TRUE,
title_avail = "Available is:",
title_nonavail = "Currently not available is:",
popover_title = "My filters",
max_avail = 10,
max_nonavail = 10)
})
output$tbl_cars <- renderReactable({
reactable(data = r$mycars,
filterable = TRUE,
rownames = FALSE,
selection = "multiple",
showPageSizeOptions = TRUE,
paginationType = "jump",
showSortable = TRUE,
highlight = TRUE,
resizable = TRUE,
rowStyle = list(cursor = "pointer"),
onClick = "select"
)
})
}
shinyApp(ui = ui, server = server)
Contact the author
Joachim Zuckarelli
Twitter: [@jsugarelli](https://twitter.com/jsugarelli)