glin / reactable

Interactive data tables for R
https://glin.github.io/reactable
Other
627 stars 80 forks source link

Include more examples of filterMethod in the documentation (multi-value filter, numeric range filter, date range filter) #318

Open novotny1akub opened 1 year ago

novotny1akub commented 1 year ago

reactable is really great. One minor thing that I feel could be improved in the documentation are some additional examples of the basic custom filtering. Below are examples of multi-value filter, numeric range filter and date range filter. I know these things may seem obvious to someone JavaScript literate, but some R folks may find them useful.

library(reactable)

data <- data.frame(
  letters = letters[1:24],
  decimals = round(runif(24), 2),
  dates = c(as.Date("2022-12-31") + 1:24)
)

reactable(
  data,
  columns = list(
    letters = colDef(
      filterable = TRUE,
      # filtering for multiple values using regex partial match
      # a|b|c to get a, b and c
      # ^a$|b to get exact match for a and partial match for b
      filterMethod = JS("function(rows, columnId, filterValue) {
        const pattern = new RegExp(filterValue, 'i')

        return rows.filter(function(row) {
          return pattern.test(row.values[columnId])
        })
      }")
    ),
    decimals = colDef(
      filterable = TRUE,
      # Filter by a single number (greater than or equal to)
      # 0.4 filters value greater than or equal to 0.4
      # filter by a range
      # 0.4 0.5 filters values between 0.4 and 0.5 (inclusive)
      filterMethod = JS("function(rows, columnId, filterValue) {
        var range = filterValue.split(' ');
        range[0] = Number(range[0]) || -Infinity;
        range[1] = Number(range[1]) || Infinity;
        return rows.filter(function(row) {
          return row.values[columnId] >= range[0] &&
            row.values[columnId] <= range[1]
        })
      }")
    ),
    dates = colDef(
      filterable = TRUE,
      # string filtering for dates
      # 2023-01-02 for dates 2023-01-02 or later
      # 2023-01-02 2023-01-03 for dates between 2023-01-02 and 2023-01-03 (inclusive)
      # beware that if only one date provided, dates are filtered using the date to
      # of 9999-12-31 (this date can  be changed in the JavaScript code)
      filterMethod = JS("function(rows, columnId, filterValue) {
        var test = filterValue.split(' ');
        test[1] = test[1] || '9999-12-31';

        return rows.filter(function(row) {
          return row.values[columnId] >= test[0] &&
            row.values[columnId] <= test[1]
        })
      }")
    )
  ),
  defaultPageSize = 5
)
novotny1akub commented 1 year ago

The numeric range filter was asked in #9 . I am well aware the implementation above is far from ideal though.

glin commented 1 year ago

Agreed, and thanks for the suggestions. The only thing preventing me from adding more complex examples like this are that they're rough to use when you just have a free-form text box. Ideally, these would come with a multi-select input, dual range slider, and date picker input, but those are also complicated examples to create, and I haven't had the time. Some of these may so useful that they're worth documenting without a corresponding input though.

Fluke95 commented 7 months ago

Hey guys, Maybe someone could integrate noUiSlider into provided decimals example? I would do it myself, but I don't know javascript at all :(

timothoms commented 4 months ago

I have a closely related question. I have a table with dropdown select filters for some columns, based on this example. This seems to match rows based on a regular expression match, and does not match exact values. For instance, when "transition", "conflict", and "transition & conflict" are possible values, selecting the "conflict" option, this will filter both "conflict" and "transition & conflict". Is there a way to make this match only to exact values. I assume that it requires a custom filterMethod function, but I'm not JS literate, and have not found an example to adapt in the Reactable documentation.

novotny1akub commented 4 months ago

@timothoms you are right. A custom filterMethod should accomplish what you want. You can try something along these lines to make this match only to exact values:

function filterRows(rows, columnId, filterValue) {
  return rows.filter(function(row) {
    return row.values[columnId] === filterValue;
  });
}

Here is a very simple runnable example:

library(htmltools)

data <- data.frame(
  col1 = c("transition", "conflict", "transition & conflict")
)

reactable(
  data,
  filterable = TRUE,
  columns = list(
    col1 = colDef(
      filterMethod = JS("function(rows, columnId, filterValue) {
        return rows.filter(function(row) {
          return row.values[columnId] === filterValue;
        })
      }"),
      filterInput = function(values, name) {
        tags$select(
          # Set to undefined to clear the filter
          onchange = sprintf("Reactable.setFilter('cars-select', '%s', event.target.value || undefined)", name),
          # "All" has an empty value to clear the filter, and is the default option
          tags$option(value = "", "All"),
          lapply(unique(values), tags$option),
          "aria-label" = sprintf("Filter %s", name),
          style = "width: 100%; height: 28px;"
        )
      }
    )
  ),
  defaultPageSize = 5,
  elementId = "cars-select"
)
timothoms commented 4 months ago

Thank you very much! That is so simple, now I feel silly to have asked.