glin / reactable

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

Bar charts appear to be forcing `Reactable.setData()` function to Index when trying to use OJS filter #356

Open mrworthington opened 5 months ago

mrworthington commented 5 months ago

Hi Greg,

I originally filed this issue on the {reactablefmtr} repository, but have since tried this with the bar chart example you provided in your site and am experiencing the same behavior where the filters aren't working as expected.

FWIW, I also tried this with the formatting changes example you provided and the filter behaved as expected. When boxes were checked/unchecked, the values all reflected appropiately across the table.

Link to Original Issue Filed at {reactablefmtr}: data_bars() + color_tiles() forcing reactble's Javascript API Reactable.setData() function to Index when trying to filter #65

glin commented 4 months ago

I think it's a limitation from the way Observable JS filters are integrated here. One caveat of setData() that probably should be documented, is that it won't work well with R-based custom render functions and style functions. R render/style functions precalculate the results once at the beginning, and can't be rerun later when the user's looking at the table in their browser. So any modifications to the table data via setData() will be done without those R functions rerunning, causing the new set of data to be completely mismatched.

The way around this is to convert those R-based render/style functions to JavaScript functions instead, so they run dynamically in the browser based on the current table state, rather than statically in R at the beginning.

I know that's not always feasible though. Alternatively, we could find a better way to integrate Observable JS filters that doesn't use setData(), but native reactable filtering instead (i.e. setFilter() or setAllFilters()). I don't remember why I used setData() in my first OJS example, maybe because it was just the easiest path.

glin commented 4 months ago

Here's a modified example of filtering reactable from OJS with native reactable filtering instead. I'll most likely replace the current example with this.

So basically, move the JavaScript filter functions from Observable to reactable filterMethods, then change the setData() calls to use Reactable.setFilter() instead. And filtering should work as you'd expect with R-based render functions.

## 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:" }
)
//| include: false
data = FileAttachment("palmer-penguins.csv").csv({ typed: true })

// Update table filters when filtered data changes
Reactable.setFilter('tbl', 'bill_length', billLengthMin)
Reactable.setFilter('tbl', 'island', islands)
library(reactable)

data <- read.csv("palmer-penguins.csv")

reactable(
  data,
  wrap = FALSE,
  resizable = TRUE,
  minRows = 10,
  columns = list(
    bill_length = colDef(
      filterMethod = JS("(rows, columnId, billLengthMin) => {
        return rows.filter(row => row.values[columnId] > billLengthMin)
      }")
    ),
    island = colDef(
      filterMethod = JS("(rows, columnId, islands) => {
        return rows.filter(row => islands.includes(row.values[columnId]))
      }")
    )
  ),
  elementId = "tbl"
)
mrworthington commented 4 months ago

Hi, Greg. First of all, thanks for going through this issue and providing some sample code. I just tried your code in quarto doc and while the table rendered, none of the filters applied worked.

Not a JS expert (still learning alot), but I did notice in both the JS filterMethod functions and the Reactable.setFilter functions, you didn't call the data object with the penguins data. Am I wrong to assume there's an explicit call to that missing or does it automatically get picked up by the R reactable() table object?

glin commented 4 months ago

Oh right, without filtering data in OJS, that OJS data = FileAttachment(...) object is unused. But the filters should still work with the data read in from R. I updated the published doc example: https://glin.quarto.pub/observable-reactable/

The second example is now broken though, so I'll have to do some more work on it. But the first one with OJS filtering reactable is working.

glin commented 4 months ago

Updated the example again and removed the unused data = FileAttachment(...). Full source at https://github.com/glin/reactable/blob/main/vignettes/quarto/observable-reactable.qmd and also updated on Quarto Pub.

The second example breaking was just me having an old version of htmlwidgets with the JS() bug, so that's also fixed now.

mrworthington commented 4 months ago

Hey Greg. Just following up to say the updated filter method worked seamlessly. Thank you for your help on this. There's truly an opportunity to make some neat stuff with a quarto dashboard, R's reactable, and OJS plot charts.