Filter definitions
# Render a bar chart in the background of the cell
bar_style <- function(width = 1, fill = "#e6e6e6", height = "75%",
align = c("left", "right"), color = NULL) {
align <- match.arg(align)
if (align == "left") {
position <- paste0(width * 100, "%")
image <- sprintf("linear-gradient(90deg, %1$s %2$s, transparent %2$s)", fill, position)
} else {
position <- paste0(100 - width * 100, "%")
image <- sprintf("linear-gradient(90deg, transparent %1$s, %2$s %1$s)", position, fill)
}
list(
backgroundImage = image,
backgroundSize = paste("100%", height),
backgroundRepeat = "no-repeat",
backgroundPosition = "center",
color = color
)
}
bar_chart <- function(label, width = "100%", height = "1rem", fill = "#00bfc4", background = NULL) {
bar <- div(style = list(background = fill, width = width, height = height))
chart <- div(style = list(height = height,flexGrow = 1, marginLeft = "0rem",marginRight = "0.5rem", background = background), bar)
# div(style = list(display = "flex", alignItems = "left"), label, chart)
div(div(style = list(display = "flex",
align = "right"), paste0(label," %")),div(style = list(display = "flex",
alignItems = "center"),chart))
}
# Source: https://glin.github.io/reactable/articles/custom-filtering.html
# 2022-03-23
# Custom range input filter with label and value
rangeFilter1 <- function(tableId, columnId, label, min, max, value = NULL, step = NULL, width = "200px") {
value <- if (!is.null(value)) value else min
inputId <- sprintf("filter_%s_%s", tableId, columnId)
valueId <- sprintf("filter_%s_%s__value", tableId, columnId)
oninput <- paste(
sprintf("document.getElementById('%s').textContent = this.value;", valueId),
sprintf("Reactable.setFilter('%s', '%s', this.value)", tableId, columnId)
)
div(
tags$label(`for` = inputId, label),
div(
style = sprintf("display: flex; align-items: center; width: %s", validateCssUnit(width)),
tags$input(
id = inputId,
type = "range",
min = min,
max = max,
step = step,
value = value,
oninput = oninput,
onchange = oninput, # For IE11 support
style = "width: 100%;"
),
span(id = valueId, style = "margin-left: 8px;", value)
)
)
}
# Source: https://glin.github.io/reactable/articles/custom-filtering.html
rangeFilter2 <- 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 = "", "Alle"),
lapply(unique(values), tags$option),
"aria-label" = sprintf("Filter %s", name),
style = "width: 100%; height: 28px;"
)
}
# Custom range input filter with label and value
rangeFilter <- function(tableId, columnId, label, min, max, value = NULL, step = NULL, width = "200px") {
value <- if (!is.null(value)) value else min
inputId <- sprintf("filter_%s_%s", tableId, columnId)
valueId <- sprintf("filter_%s_%s__value", tableId, columnId)
oninput <- paste(
sprintf("document.getElementById('%s').textContent = this.value;", valueId),
sprintf("Reactable.setFilter('%s', '%s', this.value)", tableId, columnId)
)
div(
tags$label(`for` = inputId, label),
div(
style = sprintf("display: flex; align-items: center; width: %s", validateCssUnit(width)),
tags$input(
id = inputId,
type = "range",
min = min,
max = max,
step = step,
value = value,
oninput = oninput,
onchange = oninput, # For IE11 support
style = "width: 100%;"
),
span(id = valueId, style = "margin-left: 8px;", value)
)
)
}
# Filter method that filters numeric columns by minimum value
filterMinValue <- JS("function(rows, columnId, filterValue) {
return rows.filter(function(row) {
return row.values[columnId] >= filterValue
})
}")
# Filter method that filters numeric columns by minimum value
filterMinValue <- JS("function(rows, columnId, filterValue) {
return rows.filter(function(row) {
return row.values[columnId] >= filterValue
})
}")
# Source: https://github.com/glin/reactable/blob/HEAD/vignettes/popular-movies/popular-movies.Rmd
# 2023-03-23
select_filter <- function(id, label, shared_data, group, choices = NULL,
width = "100%", class = "filter-input") {
values <- shared_data$data()[[group]]
keys <- shared_data$key()
if (is.list(values)) {
# Multiple values per row
flat_keys <- unlist(mapply(rep, keys, sapply(values, length)))
keys_by_value <- split(flat_keys, unlist(values), drop = TRUE)
choices <- if (is.null(choices)) sort(unique(unlist(values))) else choices
} else {
# Single value per row
keys_by_value <- split(seq_along(keys), values, drop = TRUE)
choices <- if (is.null(choices)) sort(unique(values)) else choices
}
script <- sprintf("
window['__ct__%s'] = (function() {
const handle = new window.crosstalk.FilterHandle('%s')
const keys = %s
return {
filter: function(value) {
if (!value) {
handle.clear()
} else {
handle.set(keys[value])
}
}
}
})()
", id, shared_data$groupName(), toJSON(keys_by_value))
div(
class = class,
tags$label(`for` = id, label),
tags$select(
id = id,
onchange = sprintf("window['__ct__%s'].filter(this.value)", id),
style = sprintf("width: %s", validateCssUnit(width)),
tags$option(value = "", "Alle"),
lapply(choices, function(value) tags$option(value = value, value))
),
tags$script(HTML(script))
)
}
# https://glin.github.io/reactable/articles/custom-filtering.html
# 2023-02-23
# Creates a data list column filter for a table with the given ID
dataListFilter <- function(tableId, style = "width: 100%; height: 28px;") {
function(values, name) {
dataListId <- sprintf("%s-%s-list", tableId, name)
tagList(
tags$input(
type = "text",
list = dataListId,
oninput = sprintf("Reactable.setFilter('%s', '%s', event.target.value || undefined)", tableId, name),
"aria-label" = sprintf("Filter %s", name),
style = style
),
tags$datalist(
id = dataListId,
lapply(unique(values), function(value) tags$option(value = value))
)
)
}
}
with_tooltip <- function(value, tooltip, ...) {
div(style = "text-decoration: underline; text-decoration-style: dotted; cursor: help",
tippy(value, tooltip, ...))
}
Shared HTML Table
# nur Amibition > 0
tmp<-d |>
filter(Ambition>0)|>
group_by(School,Name,District_Sport,Sex,Club,Sport,Inclusion)|>
reframe(auswertbar = length(unique(Child)),across())|>
group_by(School,Name,District_Sport,Sex,Club,Sport,Inclusion,Performance_total,auswertbar)|>
reframe(n = length(unique(Child)))|>
mutate(Performance_total = factor(Performance_total,levels=c("R","Y","G","B")),
Sex = recode(Sex,m="Jungen",w="Mädchen"))|>
#mutate(Y )
pivot_wider(names_from = Performance_total,values_from=n)|>
relocate(any_of(c("B", "G","Y","R")),.after = last_col());head(tmp)
# NA to 0
tmp$B[is.na(tmp$B)] <- 0
tmp$G[is.na(tmp$G)] <- 0
tmp$Y[is.na(tmp$Y)] <- 0
tmp$R[is.na(tmp$R)] <- 0
tmp
#tmp$District[tmp$District=="Eisenach, Stadt"] <- "Wartburgkreis"
# shared table
shared_data <- SharedData$new(tmp)
# styles
sticky_style <- list(backgroundColor = "#f7f7f7")
sticky_style_r <- list(borderTop = "0px solid #eee",borderLeft = "0px solid #eee",borderRight = "0px solid #eee")
rotate_header_style<- list(
`white-space` = "nowrap",
`transform-origin` = "0% 50%",
transform = "rotate(-90deg)",
`margin-top` = "10px",
`margin-bottom` = "10px",
borderColor = "#ffffff"
)
small_header_style <- list(`font-size` = "10px")
sticky_style_l <- list(borderLeft = "0px solid #eee")
# reactable
tbl<-reactable(
shared_data,
defaultColDef = colDef(
# headerStyle = sticky_style_r,
align = "right",
footerStyle = list(fontWeight = "bold"),
# maxWidth = 120,
vAlign="top"),
searchable = TRUE,
resizable = TRUE,
wrap = TRUE,
pagination=TRUE,
paginateSubRows = TRUE,
# paginationType = "jump",
defaultPageSize = 15,
showSortable = TRUE,
bordered = FALSE,
compact=TRUE,
# details = function(index) {
#t1$Name[index]},
# onClick = "select",
#height = 800,
# width=600,
# defaultPageSize = 20,
striped = TRUE,
#elementId = "cars-vis-table",
elementId = "cars-grouping-table",
highlight = TRUE,
filterable = FALSE,
groupBy = c("Name"),
columns = list(
Sport = colDef(name = "Sport-AG",
align="left",
width=70,
headerStyle = small_header_style,
filterable = FALSE),
#cohort = colDef(name = "Jahr",
# align="left",
# minWidth=60,
# headerStyle = small_header_style,
# filterable = FALSE),
Sex = colDef(name="Geschlecht",
align="left",
width=70,
sortable = FALSE,
# width = 50,
headerStyle = small_header_style,
filterable=FALSE),
Club = colDef(name="Verein",
sortable = FALSE,
filterable=FALSE,
width=60,
headerStyle = small_header_style,
align="left"),
Inclusion = colDef(name="Inklusion",
sortable = FALSE,
filterable=FALSE,
width=60,
headerStyle = small_header_style,
align="left"),
Name = colDef(name = "Schule",
sortable = TRUE,
show=TRUE,
minWidth = 100,
align="left",
headerStyle = small_header_style,
#style = "font-weight: 100",
# Show species under character names
cell = function(value, index) {
School<- tmp$School[index]
School <- if (!is.na(School)) School else "Unknown"
div(
div(style = "font-weight: 100", value),
div(style = "font-size: 0.75rem", School)
)
}),
School = colDef(show=FALSE,
minWidth = 100,
headerStyle = small_header_style,
sortable = TRUE), #Schulnummer
# School = colDef(show=FALSE),
District_Sport = colDef(name="Kreissportbund",
minWidth = 80,
headerStyle = small_header_style,
show=TRUE,align="left",sortable = TRUE),
# Gesamtzahl= colDef(name="N",
# width=90,
# align = "right",
# aggregate = "sum",
# html=TRUE,
#
# # footer = function(values)
# # sprintf("∑ %.0f",
# # #sum(shared_data$Gesamtzahl)
# # sum(values)
#
# # )
# footer = JS("function(colInfo) {
# var total = 0
# colInfo.data.forEach(function(row) {
# total += row['Gesamtzahl']
# })
# return '∑ ' + total.toFixed(0)
# }")
# ),
auswertbar= colDef(name="N<sub>a</sub>",
html=TRUE,
width=90,
headerStyle = small_header_style,
header = with_tooltip("Gesamtzahl <div style=\"font-size: 10px; font-weight:normal\"> (Anzahl auswertbar)</div>","vollständig auswertbare Datensätze für diese Darstellungsform (Teilnahme an mind. 4 Testaufgaben, Information zu Geschlecht, Verein UND Sport-AG vorhanden)"),
align = "right",
sticky = "right",
aggregate = "sum",
# footer = function(values)
# sprintf("∑ %.0f", sum(values))
#https://github.com/glin/reactable/issues/78
footer = JS("function(colInfo) {
var total = 0
colInfo.data.forEach(function(row) {
total += row['auswertbar']
})
return '∑ ' + total.toFixed(0)
}")
),
B= colDef(align = "right",
name="++",
html=TRUE,
width=80,
headerStyle = small_header_style,
header = with_tooltip("++ <div style=\"font-size: 10px; font-weight:normal\"> (Anzahl)</div>","Anzahl an Schülerinnen und Schülern, die zwischen 20 und 24 von 24 Punkten erreichten. In den Schulämtern Nord-, West-, Ost- und Mittelthüringen erhielten diese eine Einladung zur Talentiade im jeweiligen Schulamt."),
aggregate = "sum",
# footer = function(values)
# sprintf("%.0f", sum(values))
footer = JS("function(colInfo) {
var total = 0
colInfo.data.forEach(function(row) {
total += row['B']
})
return '∑ ' + total.toFixed(0)
}")
),
R= colDef(align = "right",
name="--",
html=TRUE,
headerStyle = small_header_style,
header = with_tooltip("-- <div style=\"font-size: 10px; font-weight:normal\"> (Anzahl)</div>","Anzahl an Schülerinnen und Schülern, die zwischen 1 und 9 von 24 Punkten erreichten."),
width=60,
aggregate = "sum",
# footer = function(values)
# sprintf("%.0f", sum(values))
footer = JS("function(colInfo) {
var total = 0
colInfo.data.forEach(function(row) {
total += row['R']
})
return '∑ ' + total.toFixed(0)
}")
),
G= colDef(align = "right",
name="+",
html=TRUE,
headerStyle = small_header_style,
sticky = "right",
header = with_tooltip("+ <div style=\"font-size: 10px; font-weight:normal\"> (Anzahl)</div>","Anzahl an Schülerinnen und Schülern, die zwischen 15 und 19 von 24 Punkten erreichten."),
width=70,
aggregate = "sum",
# footer = function(values)
# sprintf("%.0f", sum(values))
footer = JS("function(colInfo) {
var total = 0
colInfo.data.forEach(function(row) {
total += row['G']
})
return '∑ ' + total.toFixed(0)
}")
),
Y= colDef(align = "right",
name="-",
html=TRUE,
headerStyle = small_header_style,
sticky = "right",
header = with_tooltip("- <div style=\"font-size: 10px; font-weight:normal\"> (Anzahl)</div>","'Anzahl an Schülerinnen und Schülern, die zwischen 10 und 14 von 24 Punkten erreichten."),
width=70,
aggregate = "sum",
# footer = function(values)
# sprintf("%.0f", sum(values))
footer = JS("function(colInfo) {
var total = 0
colInfo.data.forEach(function(row) {
total += row['Y']
})
return '∑ ' + total.toFixed(0)
}")
)
),
columnGroups = list(
colGroup(
name = "Punktzahlbereiche",
sticky = "right",
columns = c(
"B","G","Y","R"))
# colGroup(name = "Gesamtzahl",
# sticky="left",
# columns = c("Gesamtzahl",
# "auswertbar"))
)
)