Skip to content

Commit 89fa5be

Browse files
chlebowagogonzo
andauthored
conflict resolution (#197)
Merging `main` branch into `filter_panel_refactor` following #189. --------- Co-authored-by: chlebowa <chlebowa@users.noreply.github.com> Co-authored-by: Dawid Kałędkowski <dawid.kaledkowski@gmail.com>
1 parent 64f492c commit 89fa5be

18 files changed

+409
-316
lines changed

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
### Bug fixes
1818

1919
* Fixed an error where the `RangeFilterState` produced an error when using `bootstrap 4`.
20+
* Fixed a bug that caused the range slider to omit values selected programmatically through the filter API.
2021

2122
### Miscellaneous
2223

R/FilterState-utils.R

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ init_filter_state.POSIXlt <- function(x,
265265
#' @keywords internal
266266
#'
267267
#' @examples
268-
#' \dontrun{
268+
#' \donttest{
269269
#' check_in_range(c(3, 1), c(1, 3))
270270
#' check_in_range(c(0, 3), c(1, 3))
271271
#' check_in_range(
@@ -313,13 +313,15 @@ check_in_range <- function(subinterval, range, pre_msg = "") {
313313
#' @keywords internal
314314
#'
315315
#' @examples
316-
#' check_in_subset <- teal.slice:::check_in_subset
316+
#' \donttest{
317+
#' check_in_subset <- check_in_subset
317318
#' check_in_subset(c("a", "b"), c("a", "b", "c"))
318319
#' \dontrun{
319320
#' check_in_subset(c("a", "b"), c("b", "c"), pre_msg = "Error: ")
320321
#' # truncated because too long
321322
#' check_in_subset("a", LETTERS, pre_msg = "Error: ")
322323
#' }
324+
#' }
323325
check_in_subset <- function(subset, choices, pre_msg = "") {
324326
checkmate::assert_string(pre_msg)
325327

@@ -336,3 +338,46 @@ check_in_subset <- function(subset, choices, pre_msg = "") {
336338
}
337339
return(invisible(NULL))
338340
}
341+
342+
#' Find containing limits for interval.
343+
#'
344+
#' Given an interval and a numeric vector,
345+
#' find the smallest interval within the numeric vector that contains the interval.
346+
#'
347+
#' This is a helper function for `RangeFilterState` that modifies slider selection
348+
#' so that the _subsetting call_ includes the value specified by the filter API call.
349+
#'
350+
#' Regardless of the underlying numeric data, the slider always presents 100 steps.
351+
#' The ticks on the slider do not represent actual observations but rather borders between virtual bins.
352+
#' Since the value selected on the slider is passed to `private$selected` and that in turn
353+
#' updates the slider selection, programmatic selection of arbitrary values may inadvertently shift
354+
#' the selection to the closest tick, thereby dropping the actual value set (if it exists in the data).
355+
#'
356+
#' This function purposely shifts the selection to the closest ticks whose values form an interval
357+
#' that will contain the interval defined by the filter API call.
358+
#'
359+
#' @param x `numeric(2)` interval to contain
360+
#' @param range `numeric(>=2)` vector of values to contain `x` in
361+
#'
362+
#' @return Numeric vector of length 2 that lies within `range`.
363+
#'
364+
#' @keywords internal
365+
#'
366+
#' @examples
367+
#' \donttest{
368+
#' ticks <- 1:10
369+
#' values1 <- c(3, 5)
370+
#' contain_interval(values1, ticks)
371+
#' values2 <- c(3.1, 5.7)
372+
#' contain_interval(values2, ticks)
373+
#' values3 <- c(0, 20)
374+
#' contain_interval(values3, ticks)
375+
#'}
376+
contain_interval <- function(x, range) {
377+
checkmate::assert_numeric(x, len = 2L, any.missing = FALSE, sorted = TRUE)
378+
checkmate::assert_numeric(range, min.len = 2L, any.missing = FALSE, sorted = TRUE)
379+
380+
x[1] <- Find(function(i) i <= x[1], range, nomatch = min(range), right = TRUE)
381+
x[2] <- Find(function(i) i >= x[2], range, nomatch = max(range))
382+
x
383+
}

R/FilterState.R

Lines changed: 35 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
#' @docType class
33
#'
44
#'
5-
#' @title Abstract class to encapsulate filter states
5+
#' @title FilterState Abstract Class
6+
#'
7+
#' @description Abstract class to encapsulate filter states
68
#'
79
#' @details
810
#' This class is responsible for managing single filter item within
@@ -288,7 +290,7 @@ FilterState <- R6::R6Class( # nolint
288290
#' @param value (`vector`)\cr
289291
#' value(s) that come from filter selection; values are set in the
290292
#' module server after a selection is made in the app interface;
291-
#' values are stored in `private$selected`n which is reactive;
293+
#' values are stored in `private$selected` which is reactive;
292294
#' value types have to be the same as `private$choices`
293295
#'
294296
#' @return NULL invisibly
@@ -430,11 +432,12 @@ FilterState <- R6::R6Class( # nolint
430432
x_reactive = NULL, # reactive containing the filtered variable, used for updating counts and histograms
431433
filtered_na_count = NULL, # reactive containing the count of NA in the filtered dataset
432434

433-
#' description
434-
#' Adds `is.na(varname)` before existing condition calls if `keep_na` is selected.
435-
#' Otherwise, if missings are found in the variable `!is.na` will be added
436-
#' only if `private$na_rm = TRUE`
437-
#' return (`call`)
435+
# private methods ----
436+
# @description
437+
# Adds `is.na(varname)` before existing condition calls if `keep_na` is selected.
438+
# Otherwise, if missings are found in the variable `!is.na` will be added
439+
# only if `private$na_rm = TRUE`
440+
# @return (`call`)
438441
add_keep_na_call = function(filter_call) {
439442
if (isTRUE(self$get_keep_na())) {
440443
call(
@@ -453,14 +456,14 @@ FilterState <- R6::R6Class( # nolint
453456
}
454457
},
455458

456-
#' description
457-
#' Prefixed (or not) variable
458-
#'
459-
#' Return variable name needed to condition call.
460-
#' If `isTRUE(private$use_dataset)` variable is prefixed by
461-
#' dataname to be evaluated as extracted object, for example
462-
#' `data$var`
463-
#' return (`name` or `call`)
459+
# @description
460+
# Prefixed (or not) variable
461+
#
462+
# Return variable name needed to condition call.
463+
# If `isTRUE(private$use_dataset)` variable is prefixed by
464+
# dataname to be evaluated as extracted object, for example
465+
# `data$var`
466+
# @return (`name` or `call`)
464467
get_varname_prefixed = function() {
465468
if (isTRUE(private$extract_type == "list")) {
466469
call_extract_list(private$dataname, private$varname)
@@ -474,13 +477,20 @@ FilterState <- R6::R6Class( # nolint
474477
}
475478
},
476479

477-
#' Set choices
478-
#'
479-
#' Set choices is supposed to be executed once in the constructor
480-
#' to define set/range which selection is made from.
481-
#' parameter choices (`vector`)\cr
482-
#' class of the vector depends on the `FilterState` class.
483-
#' return a `NULL`
480+
481+
# Sets `keep_na` field according to observed `input$keep_na`
482+
# If `keep_na = TRUE` `is.na(varname)` is added to the returned call.
483+
# Otherwise returned call excludes `NA` when executed.
484+
observe_keep_na = function(input) {
485+
486+
},
487+
488+
# @description
489+
# Set choices is supposed to be executed once in the constructor
490+
# to define set/range which selection is made from.
491+
# parameter choices (`vector`)\cr
492+
# class of the vector depends on the `FilterState` class.
493+
# @return `NULL`
484494
set_choices = function(choices) {
485495
private$choices <- choices
486496
invisible(NULL)
@@ -528,11 +538,11 @@ FilterState <- R6::R6Class( # nolint
528538
},
529539

530540
# shiny modules -----
531-
#' module with inputs
541+
# module with inputs
532542
ui_inputs = function(id) {
533543
stop("abstract class")
534544
},
535-
#' module with inputs
545+
# module with inputs
536546
server_inputs = function(id) {
537547
stop("abstract class")
538548
},
@@ -589,7 +599,7 @@ FilterState <- R6::R6Class( # nolint
589599
}
590600
)
591601
private$observers$keep_na <- observeEvent(
592-
ignoreNULL = FALSE, # ignoreNULL: we don't want to ignore NULL when nothing is selected in the `selectInput`,
602+
ignoreNULL = FALSE, # ignoreNULL: we don't want to ignore NULL when nothing is selected in the `selectInput`
593603
ignoreInit = TRUE, # ignoreInit: should not matter because we set the UI with the desired initial state
594604
eventExpr = input$value,
595605
handlerExpr = {

R/FilterStateChoices.R

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
ChoicesFilterState <- R6::R6Class( # nolint
1919
"ChoicesFilterState",
2020
inherit = FilterState,
21+
22+
# public methods ----
23+
2124
public = list(
2225

2326
#' @description
@@ -139,8 +142,13 @@ ChoicesFilterState <- R6::R6Class( # nolint
139142
super$set_selected(value)
140143
}
141144
),
145+
146+
# private members ----
147+
142148
private = list(
143149
histogram_data = data.frame(),
150+
151+
# private methods ----
144152
validate_selection = function(value) {
145153
if (!is.character(value)) {
146154
stop(
@@ -229,6 +237,8 @@ ChoicesFilterState <- R6::R6Class( # nolint
229237
}
230238
},
231239

240+
# shiny modules ----
241+
232242
# @description
233243
# UI Module for `ChoicesFilterState`.
234244
# This UI element contains available choices selection and

R/FilterStateDate.R

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
DateFilterState <- R6::R6Class( # nolint
2020
"DateFilterState",
2121
inherit = FilterState,
22+
23+
# public methods ----
24+
2225
public = list(
2326

2427
#' @description
@@ -70,12 +73,13 @@ DateFilterState <- R6::R6Class( # nolint
7073
format = function(indent = 0) {
7174
checkmate::assert_number(indent, finite = TRUE, lower = 0)
7275

76+
vals <- self$get_selected()
7377
sprintf(
7478
"%sFiltering on: %s\n%1$s Selected range: %s - %s\n%1$s Include missing values: %s",
7579
format("", width = indent),
7680
private$varname,
77-
format(self$get_selected()[1], nsmall = 3),
78-
format(self$get_selected()[2], nsmall = 3),
81+
format(vals[1], nsmall = 3),
82+
format(vals[2], nsmall = 3),
7983
format(self$get_keep_na())
8084
)
8185
},
@@ -132,6 +136,9 @@ DateFilterState <- R6::R6Class( # nolint
132136
super$set_selected(value)
133137
}
134138
),
139+
140+
# private methods ----
141+
135142
private = list(
136143
validate_selection = function(value) {
137144
if (!is(value, "Date")) {
@@ -180,6 +187,8 @@ DateFilterState <- R6::R6Class( # nolint
180187
values
181188
},
182189

190+
# shiny modules ----
191+
183192
# @description
184193
# UI Module for `DateFilterState`.
185194
# This UI element contains two date selections for `min` and `max`

R/FilterStateDatettime.R

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
DatetimeFilterState <- R6::R6Class( # nolint
2020
"DatetimeFilterState",
2121
inherit = FilterState,
22+
23+
# public methods ----
24+
2225
public = list(
2326

2427
#' @description
@@ -82,12 +85,15 @@ DatetimeFilterState <- R6::R6Class( # nolint
8285
#'
8386
format = function(indent = 0) {
8487
checkmate::assert_number(indent, finite = TRUE, lower = 0)
88+
89+
90+
vals <- self$get_selected()
8591
sprintf(
8692
"%sFiltering on: %s\n%1$s Selected range: %s - %s\n%1$s Include missing values: %s",
8793
format("", width = indent),
8894
private$varname,
89-
format(self$get_selected(), nsmall = 3)[1],
90-
format(self$get_selected(), nsmall = 3)[2],
95+
format(vals[1], nsmall = 3),
96+
format(vals[2], nsmall = 3),
9197
format(self$get_keep_na())
9298
)
9399
},
@@ -144,8 +150,14 @@ DatetimeFilterState <- R6::R6Class( # nolint
144150
super$set_selected(value)
145151
}
146152
),
153+
154+
# private fields ----
155+
147156
private = list(
148157
timezone = Sys.timezone(),
158+
159+
# private methods ----
160+
149161
validate_selection = function(value) {
150162
if (!(is(value, "POSIXct") || is(value, "POSIXlt"))) {
151163
stop(
@@ -194,6 +206,8 @@ DatetimeFilterState <- R6::R6Class( # nolint
194206
values
195207
},
196208

209+
# shiny modules ----
210+
197211
# @description
198212
# UI Module for `DatetimeFilterState`.
199213
# This UI element contains two date-time selections for `min` and `max`

R/FilterStateLogical.R

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
LogicalFilterState <- R6::R6Class( # nolint
2020
"LogicalFilterState",
2121
inherit = FilterState,
22+
23+
# public methods ----
2224
public = list(
2325

2426
#' @description
@@ -126,8 +128,14 @@ LogicalFilterState <- R6::R6Class( # nolint
126128
super$set_selected(value)
127129
}
128130
),
131+
132+
# private fields ----
133+
129134
private = list(
130135
histogram_data = data.frame(),
136+
137+
# private methods ----
138+
131139
validate_selection = function(value) {
132140
if (!(checkmate::test_logical(value, max.len = 1, any.missing = FALSE))) {
133141
stop(
@@ -187,6 +195,9 @@ LogicalFilterState <- R6::R6Class( # nolint
187195
})
188196
},
189197

198+
199+
# shiny modules ----
200+
190201
# @description
191202
# UI Module for `EmptyFilterState`.
192203
# This UI element contains available choices selection and

0 commit comments

Comments
 (0)