glin / reactable

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

"total with filtering" example doesn't work (change of state.data object) #271

Closed namarkus closed 1 year ago

namarkus commented 1 year ago

Hi and thanks for this wonderful package. It makes creating fancy tables from R really fun.

I found out that the example about column total with filtering in the custom rendering article doesn't work (anymore, probably since version 0.3.0).

summarizing the values from state.data gives always total of whole data instead of filtered data. According to the documentation, this is correct, stat.data contains the "original array of row data values in the table".

To make this example work, I guess one has to rebuild filtering of the data inside JS-function. e.g. like this:

data <- MASS::Cars93[20:24, c("Manufacturer", "Model", "Type", "Price")]

reactable(
  data,
  filterable = TRUE,
  columns = list(
    Price = colDef(
      html=TRUE,
      footer = JS("function(column, state) {
                const values = state.data.map(function(row) {
                let filtered = true
                  state.filters.forEach(function(filter) {
                    if(row[filter.id].toUpperCase().indexOf(filter.value.toUpperCase()) == -1) filtered = false
                  })
                  if(filtered) return row[column.id]
                  return 0
                })
                let total = 0
                values.forEach(function(value) {
                  total += value
                })
                return '<b>$' + total.toFixed(2) + '</b>'
      }")    ),
    Manufacturer = colDef(html = TRUE, footer = "<b>Total</b>")
  ))

of course additional filtering by "search" isn't yet included in this example. Is this the way to go, or is there an easier way to obtain the actual displayed subset of rows of the whole dataset?

glin commented 1 year ago

Thanks for the report, this is now fixed in https://github.com/glin/reactable/commit/2131c8597745665c52c07467cbbc622ac8d5f08a. I apparently messed up converting a bunch of examples to not use the deprecated colInfo.data anymore 😅. Instead of state.data, which you're right, is the original raw data, the examples should've used state.sortedData, which is the current row data after sorting, filtering, and grouping.

So just replace state.data with state.sortedData, and those examples should calculate totals dynamically again. I also tweaked a couple other things while here:

reactable( data, searchable = TRUE, columns = list( Price = colDef( html = TRUE, footer = JS("function(column, state) { let total = 0 state.sortedData.forEach(function(row) { total += row[column.id] }) return '$' + total.toFixed(2) + '' }") ), Manufacturer = colDef(html = TRUE, footer = "Total") ) )



- Fixed the v0.3.0 NEWS breaking changes note:

> replace usages of ... `colInfo.data` with `state.sortedData` (current rows after sorting and filtering) or `state.data` (the original data).
>
> https://glin.github.io/reactable/news/index.html#breaking-changes-0-3-0

- And wrote a better description of `sortedData` in the docs. I think the name is still confusing though, as it's really more like "current rows" in the table after sorting, filter, grouping, etc., so it may eventually be replaced with "rows" or "rowData"
namarkus commented 1 year ago

Awesome! Thanks for the explanation and the quick fix. It's good to have both options state.sortedData and state.data!