---
title: "Using Observable with reactable in Quarto"
format:
html:
echo: false
code-tools: true
---
Examples of using Observable with reactable in Quarto, based on the [ Observable JS Penguins example ](https://quarto.org/docs/interactive/ojs/examples/penguins.html)
from the Quarto documentation.
:::{.callout-note}
This is a work in progress, and requires new features in reactable v0.4.0.
:::
Source code: [ observable-reactable.qmd ](https://github.com/glin/reactable/blob/main/vignettes/quarto/observable-reactable.qmd)
## Using Observable Inputs to filter reactable
```{ojs}
//| panel: input
viewof billLengthMin = Inputs.range(
[32, 50],
{ value: 35, step: 1, label: "Bill length (min):" }
)
viewof islands = Inputs.checkbox(
["Torgersen", "Biscoe", "Dream"],
{ value: ["Torgersen", "Biscoe"], label: "Islands:" }
)
```
```{ojs}
// Update table filters when filtered data changes
Reactable.setFilter('tbl', 'bill_length', billLengthMin)
Reactable.setFilter('tbl', 'island', islands)
```
```{r}
library (reactable)
data <- read.csv ("palmer-penguins.csv" )
# Min value filter method
filterMinValue <- JS ("(rows, columnId, filterValue) => {
return rows.filter(row => row.values[columnId] >= filterValue)
}" )
# Filters rows included within a list of values like ['Torgersen', 'Biscoe']
filterIncludesValue <- JS ("(rows, columnId, filterValue) => {
return rows.filter(row => filterValue.includes(row.values[columnId]))
}" )
reactable (
data,
wrap = FALSE ,
resizable = TRUE ,
minRows = 10 ,
columns = list (
bill_length = colDef (
filterMethod = filterMinValue
),
island = colDef (
filterMethod = filterIncludesValue
)
),
elementId = "tbl"
)
```
## Using reactable to filter Observable charts
```{ojs}
// Create an Observable value that automatically tracks the table's filtered data
filteredData = Generators.observe(change => {
return Reactable.onStateChange('tbl-input', state => {
change(state.sortedData)
})
})
```
```{r}
library (reactable)
library (htmltools)
data <- read.csv ("palmer-penguins.csv" )
# Select input filter with an "All" default option
selectFilter <- function (tableId, style = "width: 100%; height: 100%;" ) {
function (values, name) {
tags$ select (
# Set to undefined to clear the filter
onchange = sprintf ("
const value = event.target.value
Reactable.setFilter('%s', '%s', value === '__ALL__' ? undefined : value)
" , tableId, name),
# "All" has a special value to clear the filter, and is the default option
tags$ option (value = "__ALL__" , "All" ),
lapply (unique (values), tags$ option),
"aria-label" = sprintf ("Filter %s" , name),
style = style
)
}
}
# Min range filter input that handles NaNs
minRangeFilter <- function (tableId, style = "width: 100%;" ) {
function (values, name) {
values <- na.omit (values)
oninput <- sprintf ("Reactable.setFilter('%s', '%s', this.value)" , tableId, name)
tags$ input (
type = "range" ,
min = floor (min (values)),
max = ceiling (max (values)),
value = floor (min (values)),
oninput = oninput,
style = style,
"aria-label" = sprintf ("Filter by minimum %s" , name)
)
}
}
# Min value filter method that handles NaNs
filterMinValue <- JS ("(rows, columnId, filterValue) => {
return rows.filter(row => {
const value = row.values[columnId]
return !isNaN(value) && value >= filterValue
})
}" )
reactable (
data,
columns = list (
species = colDef (
filterInput = selectFilter ("tbl-input" )
),
island = colDef (
filterInput = selectFilter ("tbl-input" )
),
bill_length = colDef (
filterMethod = filterMinValue,
filterInput = minRangeFilter ("tbl-input" )
),
sex = colDef (
filterInput = selectFilter ("tbl-input" ),
# Exact match filter method
filterMethod = JS ("(rows, columnId, filterValue) => {
return rows.filter(row => row.values[columnId] === filterValue)
}" )
)
),
filterable = TRUE ,
wrap = FALSE ,
resizable = TRUE ,
defaultPageSize = 5 ,
minRows = 5 ,
elementId = "tbl-input"
)
```
### Penguin body mass by sex and species
```{ojs}
Plot.rectY(filteredData,
Plot.binX(
{ y: "count" },
{ x: "body_mass", fill: "species", thresholds: 20 }
))
.plot({
facet: {
data: filteredData,
x: "sex",
y: "species",
marginRight: 80
},
marks: [
Plot.frame(),
]
}
)
```