Filtering Data with the dataFilter Module

Introduction

The dataFilter module renders an interactive table with a filter box on every column and returns only the rows the user has left visible. Because it hands that filtered data back as a reactive, you can plug it in front of any plotting module so the plot always reflects the current table filters.

Like every module in VizModules, it comes in two halves that share an id:

What the module does

The server is small, and its job is easy to describe:

  1. Prepare the data. By default (factor.char.cols = TRUE) every character column is converted to a factor. This is purely cosmetic: it makes DT show a drop-down select filter for those columns instead of a free-text search box.
  2. Render the table. A DT table is drawn with a filter row at the top of each column (filter = "top").
  3. Return the visible rows. DT continuously reports which rows survive the current filters via input$table_rows_all. The module subsets the data to those rows, calls droplevels() so filtered-out categories don’t linger in plot legends or axes, and returns the result as a reactive.

Conceptually:

data (reactive) --> character cols to factors --> DT table (user filters)
                                                        |
                                     input$table_rows_all (visible rows)
                                                        v
                                              filtered data (reactive)

Arguments

Argument Default Purpose
id Module id; must match the id given to dataFilterUI().
data A reactive containing the data frame to display and filter.
factor.char.cols TRUE Convert character columns to factors so they get drop-down filters.
page.length 10 Rows shown per page.
col.visibility FALSE Add a “Columns” button so users can show/hide individual columns.
hide.columns NULL Column names (or positions) to hide when the table is first drawn.

Return value

A reactive that evaluates to the filtered subset of data. This is the key to the module’s flexibility: a reactive can be read by as many consumers as you like, so the same filtered data can drive one plot or several.

Showing fewer columns

Wide data frames make for a busy table and a wall of filter boxes. Pass the columns you don’t want on screen to hide.columns and the table is drawn without them – no column, no filter box:

filtered <- dataFilterServer("filter", reactive(example_bar),
    hide.columns = c("internal_id", "batch")
)

Two things to keep in mind:

filtered <- dataFilterServer("filter", reactive(example_bar),
    hide.columns = c("internal_id", "batch"),
    col.visibility = TRUE
)

The name-to-position lookup behind hide.columns is exported as resolve_column_targets(), so you can reach for it in your own tables too. DataTables addresses columns in columnDefs by zero-based position; this turns column names into those positions (pass rownames = TRUE if your table shows a row-names column, which shifts everything over by one). Hiding is just one use - the same targets drive width, orderable, className, and friends:

DT::datatable(
    example_bar,
    rownames = FALSE,
    options = list(
        columnDefs = list(list(
            visible = FALSE,
            targets = resolve_column_targets(example_bar, c("internal_id", "batch"))
        ))
    )
)

A minimal example

Pass the returned reactive straight into a plotting module’s data argument. When the user filters the table, the plot redraws automatically.

library(shiny)
library(VizModules)

ui <- fluidPage(
    plotthis_BarPlotOutputUI("bar"),
    dataFilterUI("filter"),
    plotthis_BarPlotInputsUI("bar", example_bar)
)

server <- function(input, output, session) {
    # Returns a reactive of only the currently-visible rows.
    filtered <- dataFilterServer("filter", reactive(example_bar))

    # The plot tracks the filter automatically.
    plotthis_BarPlotServer("bar", data = filtered)
}

if (interactive()) shinyApp(ui, server)

Note the shared id: "bar" is used for the module’s OutputUI, InputsUI, and Server, while "filter" ties dataFilterUI() to dataFilterServer().

Linking several modules to one table

Because the filtered data is just a reactive, you don’t need any special machinery to connect it to more than one plot – feed the same filtered reactive into each module’s server. Every module reading it re-renders whenever the filter changes, so a single table keeps all of the plots in sync.

library(shiny)
library(shinyjs)
library(VizModules)

ui <- fluidPage(
    useShinyjs(),
    fluidRow(
        column(6, plotthis_BarPlotOutputUI("bar")),
        column(6, plotthis_BoxPlotOutputUI("box"))
    ),
    dataFilterUI("filter"),
    fluidRow(
        column(6, plotthis_BarPlotInputsUI("bar", example_bar)),
        column(6, plotthis_BoxPlotInputsUI("box", example_bar))
    )
)

server <- function(input, output, session) {
    # One filtered reactive, shared by both modules.
    filtered <- dataFilterServer("filter", reactive(example_bar))

    plotthis_BarPlotServer("bar", data = filtered)
    plotthis_BoxPlotServer("box", data = filtered)
}

if (interactive()) shinyApp(ui, server)

The only rule to remember is namespacing: each plotting module’s InputsUI, OutputUI, and Server must share the same top-level id ("bar" and "box" above). Nothing wraps the module servers, so their ids resolve to the top-level namespace where their UI lives, and everything connects.

A ready-to-run version of this app ships with the package:

shiny::runApp(
    system.file("examples", "linked-filter", package = "VizModules")
)

How it compares to createModuleApp()

The *App() helpers and createModuleApp() already wire a dataFilter table to a single plot for you (see the Quick Start vignette). Reach for dataFilterUI() / dataFilterServer() directly when you want to control the layout yourself – for example, to share one table across multiple plots as shown above, or to embed the table inside a larger custom module.

Summary