Open nistara opened 4 years ago
Only text filtering is supported for now, but I've always wanted different filter types like a range input or dropdown list in the table. I don't know when I'll get to it, but it's definitely high on the to-do list! Thanks for the feedback.
Found this after Twitter discussion. Lines disallow any custom filterMethod
. I changed to
if(!col.hasOwnProperty("filterMethod")) {
col.filterMethod = (filter, rows) => {
const id = filter.id
const match = col.createMatcher(filter.value)
return rows.filter(row => {
const value = row[id]
if (value === undefined) {
return true
}
// Don't filter on aggregated cells
if (row._subRows) {
return true
}
return match(value)
})
}
}
and assuming user is very JS literate we can then define a custom filter with the code below
library(reactable)
library(htmltools)
rt <- reactable(
iris,
filterable = TRUE
)
rt$x$tag$attribs$columns[[2]]$filterMethod <- htmlwidgets::JS("filterLessThan")
rt$x$tag$attribs$columns[[2]]$Filter <- htmlwidgets::JS("inputFilter")
browsable(
tagList(
tags$script(HTML("
function filterLessThan(filter, rows) {
debugger
return rows.filter(function(row) {
return row[filter.id] <= +filter.value;
})
}
function inputFilter(filter) {
var _onChange = filter.onChange;
return React.createElement(
React.Fragment,
null,
[
React.createElement(
'span',
{
style: {fontSize: '20px'}
},
'<='
),
React.createElement(
'input',
{
type: 'number',
min: 0,
max: 5,
style: {width: '70%'},
onChange: function onChange(event) {return _onChange(event.target.value)}
}
)
]
)
}
")),
rt
)
)
@nistara I added an example of a range slider using material-ui.
# remotes::install_github("timelyportfolio/reactable")
library(reactable)
library(htmltools)
# not good practice since big dependency and all we want is slider
# but for demonstration purposes do it this way
material_dep <- htmlDependency(
name = "material-ui",
version = "4.6.1",
src = c(href = "https://unpkg.com/@material-ui/core/umd/"),
script = "material-ui.production.min.js"
)
rt <- reactable(
iris,
filterable = TRUE
)
rt$x$tag$attribs$columns[[2]]$filterMethod <- htmlwidgets::JS("filterRange")
rt$x$tag$attribs$columns[[2]]$Filter <- htmlwidgets::JS("inputFilter")
browsable(
tagList(
reactR::html_dependency_react(),
reactR::html_dependency_reacttools(),
htmlwidgets::getDependency("reactable","reactable"),
material_dep,
tags$style("
.rt-thead.-filters .rt-tr {align-items: flex-end; height: 60px;}
.rt-thead.-filters .rt-th {overflow: visible;}
"),
tags$script(HTML("
function filterRange(filter, rows) {
return rows.filter(function(row) {
// Don't filter on aggregated cells
if (row._subRows) {
return true
}
return row[filter.id] >= filter.value[0] && row[filter.id] <= filter.value[1];
})
}
function inputFilter(filter) {
var _onChange = filter.onChange;
return React.createElement(
'div',
null,
React.createElement(
MaterialUI.Slider,
{
defaultValue: [0,5],
min: 0,
max: 5,
step: 0.5,
valueLabelDisplay: 'auto',
onChange: function onChange(event, newValue) {return _onChange(newValue)}
}
)
)
}
")),
rt
)
)
Thanks @glin, much appreciated!!!
@timelyportfolio Thanks a lot for showing the examples above! I'm going to try to get this working with my data. This slider looks nicer than the one from the DT
package, though I really like that I can enter numbers manually with DT (helpful when the range is large and I want to subset a really small bit of it). I'll try to reproduce it and get back to you :)
@timelyportfolio Nice examples! Looks like exposing filterMethod
and Filter
would be a quick way to at least enable custom filter implementations.
Hope there's future capability to do multi-entity filtering on character columns too 🤩
@glin - Thanks for the amazing package. Really nice output! Love it!
@timelyportfolio & @glin - Do you believe that the approach that @timelyportfolio took for the value filter could be used for factors?
But this time doing the selection via drop down. Possible example here : https://material-ui.com/components/selects/
Do you believe that it could be possible the approach of @timelyportfolio and do you believe it could work? I would love your view on the feasibility:)
Just wanted to say that this would be a great addition. If could set filterable = TRUE
then filterType = "dropdown"
it would simplify some of the apps I have developed that instead use shiny::selectInputs
to filter the table.
Hey @tyluRp
Can I see the implementation of the dropdown with shiny::selectInputs
?
@shahreyar-abeer To clarify, I use selectInput
outside of the reactable
, unlike the slider shown in @timelyportfolio‘s example (which is what I think you’re looking for).
Oh, yeah I am looking for a dropdown inside the reactable. Looks like it isn't getting much attention here! Thanks for the quick reply though. Appreciate it.
I hope to get back to this, but unfortunately have not had the time or a project that requires it.
@glin This package is indeed amazing.
Maybe I am rehashing the past but I see that the CRAN radiant package has the filters folks here are working on.
+1 for this feature request. I like using this package (thanks @glin ) because it doesn't have those slider filters like we have in DT
. Problem there was if you had a wide table with a horizontal scroller, then whenever you filter a column, those range-sliders would push the scroller back to table's first column...very annoying. Please I know you're thinking of implementing this, but beware of that issue.
Just played a little with flat-ui
. and the filters are very nice, but reactable
far more powerful. Perhaps we could borrow the filters components https://github.com/githubocto/flat-ui/tree/main/src/components/filters.
I'd like to +1 this feature request. A new reactable adopter and loving it, but this keeps me from making the switch from DT in a lot of places.
+1 for the dropdown filter. This is also preventing me from switching from DT
Far from perfect, but I updated the example to work with newest reactable
to add a slider for hp
column in mtcars
. As before we need to make a slight change in the source code to allow for custom filtering https://github.com/timelyportfolio/reactable/commit/fae87a5d525a555b2f18383a02b726e3b81f00a7#diff-4735897a0722c2357dfd440bf89a02e8e13d008249940a4218a3cad6317f63fa.
#remotes::install_github("timelyportfolio/reactable@filters")
library(htmltools)
library(magrittr)
library(reactable)
mui <- htmlDependency(
name = "mui",
version = "5.2.7",
src = c(href = "https://unpkg.com/@mui/material@5.2.7/umd/"),
script = "material-ui.development.js"
)
rt <- reactable(
mtcars,
columns = list(
hp = colDef(filterable = TRUE) %>%
{
.$filterFun = JS('filterRange')
.$Filter = JS('inputFilter')
.
}
)
)
browsable(
tagList(
reactR::html_dependency_react(),
reactR::html_dependency_reacttools(),
htmlwidgets::getDependency("reactable","reactable"),
tags$style(
"
.rt-td-filter {
align-items: flex-end;
height: 80px;
}
.rt-td-filter .rt-td-inner {
overflow: visible;
}
"
),
mui,
rt,
tags$script(HTML(
sprintf(
"
const inputFilter = ({ value, setValue, className, ...props }) => {
const range = %s;
return React.createElement(
'div',
{style: {margin: '0 5px'}},
[
React.createElement(
'div',
null,
JSON.stringify(value ? value : range)
),
React.createElement(
MaterialUI.Slider,
{
defaultValue: range,
min: range[0],
max: range[1],
step: 10,
valueLabelDisplay: 'auto',
onChange: (e, val) => {setValue(val)}
}
)
]
)
}
const filterRange = (rng) => {
return value => (value >= rng[0] && value <= rng[1])
}
",
jsonlite::toJSON(c(0, max(mtcars$hp)), auto_unbox = TRUE)
)
))
)
)
Just wanted to express my wish for allowing different column-based filter methods based on the column type (e.g., dropdown for character/factor, range for numeric). Would make this already invaluable package even more perfect!
+1 on this! slider range filters on continuous data as in DT
would be a much welcomed feature!
Thanks all for the feedback and @timelyportfolio for those examples. Custom filtering is now first-class supported in the development version. See the Custom Filtering article for usage and a bunch of examples. There are a few examples about range filtering specifically:
There's still no built-in range filter though, so I'm keeping this issue open. That might be added some day, but it's not in my short-term priorities because of the required effort and lack of time. Unfortunately, the native <input type="range">
is probably too limited to be useful enough for most cases, and most users will want a multi-range slider that can filter both min and max values. That'll probably have to come from an external library because of how complicated multi-range sliders are, especially if you want them to be accessible and usable from a table cell with limited space.
Hey, I am currently switching from DT
to reactable
due to some issues with DT
. I am really impressed by reactable
so far. I wanted to ask if you could add an example for a range slider for a date column. Thanks a lot
@glin this is amazing, and I really appreciate all the efforts that you have so generously expended on reactable
. I thought what if we use the filter slot for something else. Here is a little example using a dataui
sparkline. I think with a little more hacking we could connect the sparkline to the filter if desired.
library(htmltools)
library(reactable)
library(dataui) # remotes::install_github("timelyportfolio/dataui")
js <- tags$script(HTML(
"
// Custom range filter with value label
function rangeFilter(column, state) {
// Get min and max values from raw table data
const range = React.useMemo(() => {
let min = Infinity
let max = -Infinity
state.data.forEach(row => {
const value = row[column.id]
if (value < min) {
min = Math.floor(value)
} else if (value > max) {
max = Math.ceil(value)
}
})
return [min, max]
}, [state.data])
const {pageRows} = state
const data = {
data: pageRows.map( d => ({
x: { },
y: d[column.id]
}))
}
const sparklineProps = {
ariaLabel: 'sparkline bar plot of price',
height: 100,
margin: {left:20, top: 10, right: 40, bottom: 10},
min: range[0],
max: range[1]
};
// use hydrate from R reactR js tools to convert JSON object to React element
// for a more friendly R experience we could use dataui functions and sprintf
const spk_hydrate = window.reactR.hydrate(
dataui,
{
name: 'SparklineResponsive',
attribs: {...sparklineProps, ...data},
children: [
{name: 'SparklineBarSeries', attribs: {fill: '#eebefa'}, children: []},
{
name: 'TooltipComponent',
attribs: {},
children: [
{
name: 'HorizontalReferenceLine',
attribs: {
'stroke': '#9c36b5',
'strokeWidth': 1,
'strokeDasharray': '3,3',
'labelPosition': 'right',
'labelOffset': 12,
'renderLabel': d => d.toFixed(1)
},
children: []
}
]
}
]
}
)
return spk_hydrate
}
// Filter method that filters numeric columns by minimum value
function filterMinValue(rows, columnId, filterValue) {
// return all since not using filter cell for filtering
return rows
/* old filter mechanism that we leave for legacy but will not use
return rows.filter(function(row) {
return row.values[columnId] >= filterValue
})
*/
}
"
))
data <- MASS::Cars93[, c("Manufacturer", "Model", "Type", "Price")]
rt <- reactable(
data,
filterable = TRUE,
columns = list(
# we will use the filter cell for a sparkline
Price = colDef(
filterMethod = JS("filterMinValue"),
filterInput = JS("rangeFilter")
)
),
defaultPageSize = 20
)
rt$dependencies <- list(html_dependency_dataui())
browsable(
tagList(
js,
rt
)
)
@timelyportfolio Nice example. And yeah, using the filter slot for just arbitrary custom rendering is totally valid, and that had occurred to me as well. A better named feature might be a separate row of "sub headers", where you can render whatever you want just below the table headers. I could imagine wanting to show sparklines there while also keeping the default filter inputs.
I like code provided by @timelyportfolio, however, I've encountered an issue with it. In some cases, filter range is invalid - instead of real max value from the provided data, it shows Infinity or some other (lower) value from the column. Here's an example:
library(reactable)
library(htmltools)
example_data <- read.csv2("data.csv", sep = ",")
material_ui_range_filter_dependency_function <- function() {
list(
# Material UI requires React
reactR::html_dependency_react(),
# Material UI dependency
htmltools::htmlDependency(
name = "mui",
version = "5.6.3",
src = c(href = "https://unpkg.com/@mui/material@5.6.3/umd/"),
script = "material-ui.production.min.js"
),
# filter functions written in javascript
htmltools::htmlDependency(
name = "material_ui_range_filter",
version = "0.1.0",
src = c(file = here::here("inst/material_ui_range_filter")),
script = "material-ui-range-filter.js",
all_files = TRUE
)
)
}
browsable(
tagList(
material_ui_range_filter_dependency_function(),
reactable(
example_data,
columns = list(
Visibility.Score = colDef(
filterable = TRUE,
filterMethod = JS("filterRange"),
filterInput = JS("muiRangeFilter")
)
),
defaultPageSize = 5,
minRows = 5
)
))
example_data$Visibility.Score |> summary()
Min. 1st Qu. Median Mean 3rd Qu. Max.
0.0 10.0 40.0 511.1 187.0 28081.0
class(example_data$Visibility.Score)
[1] "integer"
File for error reproduction: data.csv material-ui-range-filter.js is copied from https://glin.github.io/reactable/articles/custom-filtering-extra.html
Thanks a lot for this great package! Is it possible to implement filtering by a range of values? For e.g. filtering rows with prices ranging from 15 to 30, instead of a single value filter. I'm not quite sure if there's another option I could use for this in addition to
filterable = TRUE
. Thanks again!