Skip to content

Commit 8f5be40

Browse files
authored
Allow highlight() selectize to contain selectize.js options (#2217)
* Close #2216: allow highlight() selectize to contain selectize.js options * Update shinytest screenshots * Update shinytest baseline * Close #2218: properly construct mapping between crosstalk sets and keys when constructing selectize payload * document the new feature * update news * Fix tests * Revert "Update shinytest baseline" This reverts commit fa5a1d5.
1 parent 3a33b1a commit 8f5be40

File tree

11 files changed

+125
-45
lines changed

11 files changed

+125
-45
lines changed

DESCRIPTION

+1-1
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,6 @@ Suggests:
7979
reticulate,
8080
rsvg
8181
LazyData: true
82-
RoxygenNote: 7.2.1
82+
RoxygenNote: 7.2.3
8383
Encoding: UTF-8
8484
Roxygen: list(markdown = TRUE)

NEWS.md

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
# 4.10.1.9000
22

3+
## New features
4+
5+
* Closed #2216: Additional selectize.js options can now be passed along to `highlight()`'s `selectize` argument. (#2217)
6+
37
## Bug fixes
48

5-
* `ggplotly()` no longer errors given a `geom_area()` with 1 or less data points (error introduced by new behavior in ggplot2 v3.4.0). (#2209)
9+
* Closed #2218: `highlight(selectize = TRUE)` no longer yields an incorrect selectize.js result when there is a combination of crosstalk and non-crosstalk traces. (#2217)
10+
* Closed #2208: `ggplotly()` no longer errors given a `geom_area()` with 1 or less data points (error introduced by new behavior in ggplot2 v3.4.0). (#2209)
611

712

813
# 4.10.1

R/highlight.R

+5-3
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,10 @@
3636
#' highlighting selections. See [toRGB()] for valid color
3737
#' specifications. If `NULL` (the default), the color of selected marks
3838
#' are not altered.
39-
#' @param selectize provide a selectize.js widget for selecting keys? Note that
40-
#' the label used for this widget derives from the groupName of the SharedData object.
39+
#' @param selectize whether or not to render a selectize.js widget for selecting
40+
#' [highlight_key()] values. A list of additional selectize.js options may
41+
#' also be provided. The label used for this widget should be set via the
42+
#' `groupName` argument of [highlight_key()].
4143
#' @param defaultValues a vector of values for setting a "default selection".
4244
#' These values should match the key attribute.
4345
#' @param opacityDim a number between 0 and 1 used to reduce the
@@ -115,7 +117,7 @@ highlight <- function(p, on = "plotly_click", off,
115117

116118
# attach HTML dependencies (these libraries are used in the HTMLwidgets.renderValue() method)
117119
# TODO: only attach these when keys are present!
118-
if (selectize) {
120+
if (!identical(selectize, FALSE)) {
119121
p$dependencies <- c(p$dependencies, list(selectizeLib()))
120122
}
121123
if (dynamic) {

R/utils.R

+55-30
Original file line numberDiff line numberDiff line change
@@ -366,31 +366,32 @@ supply_highlight_attrs <- function(p) {
366366
# set "global" options via crosstalk variable
367367
p$x$highlight <- p$x$highlight %||% highlight_defaults()
368368

369-
# defaults are now populated, allowing us to populate some other
370-
# attributes such as the selectize widget definition
371-
sets <- unlist(lapply(p$x$data, "[[", "set"))
372-
keys <- setNames(lapply(p$x$data, "[[", "key"), sets)
373-
p$x$highlight$ctGroups <- i(unique(sets))
369+
# Grab the special "crosstalk set" (i.e., group) for each trace
370+
sets <- lapply(p$x$data, "[[", "set")
371+
noSet <- vapply(sets, is.null, logical(1))
372+
373+
# If no sets are present, there's nothing more to do
374+
if (all(noSet)) {
375+
return(p)
376+
}
377+
378+
# Store the unique set of crosstalk sets (which gets looped over client-side)
379+
p$x$highlight$ctGroups <- i(unique(unlist(sets)))
380+
381+
# Build a set -> key mapping for each relevant trace, which we'll use
382+
# to set default values and/or build the selectize.js payload (if relevant)
383+
setDat <- p$x$data[!noSet]
384+
keys <- setNames(lapply(setDat, "[[", "key"), sets[!noSet])
374385

375-
# TODO: throw warning if we don't detect valid keys?
376-
hasKeys <- FALSE
377386
for (i in p$x$highlight$ctGroups) {
387+
388+
# Get all the keys for this crosstalk group
378389
k <- unique(unlist(keys[names(keys) %in% i], use.names = FALSE))
379-
if (is.null(k)) next
380390
k <- k[!is.null(k)]
381-
hasKeys <- TRUE
382-
383-
# include one selectize dropdown per "valid" SharedData layer
384-
if (isTRUE(p$x$highlight$selectize)) {
385-
# Hash i (the crosstalk group id) so that it can be used
386-
# as an HTML id client-side (i.e., key shouldn't contain spaces)
387-
p$x$selectize[[rlang::hash(i)]] <- list(
388-
items = data.frame(value = k, label = k), group = i
389-
)
390-
}
391+
if (length(k) == 0) next
391392

392393
# set default values via crosstalk api
393-
vals <- p$x$highlight$defaultValues[p$x$highlight$defaultValues %in% k]
394+
vals <- intersect(p$x$highlight$defaultValues, k)
394395
if (length(vals)) {
395396
p <- htmlwidgets::onRender(
396397
p, sprintf(
@@ -399,20 +400,44 @@ supply_highlight_attrs <- function(p) {
399400
)
400401
)
401402
}
403+
404+
# include one selectize dropdown per "valid" SharedData layer
405+
selectize <- p$x$highlight$selectize %||% FALSE
406+
if (!identical(selectize, FALSE)) {
407+
options <- list(items = data.frame(value = k, label = k), group = i)
408+
if (!is.logical(selectize)) {
409+
options <- modify_list(options, selectize)
410+
}
411+
# Hash i (the crosstalk group id) so that it can be used
412+
# as an HTML id client-side (i.e., key shouldn't contain spaces)
413+
groupId <- rlang::hash(i)
414+
415+
# If the selectize payload has already been built, use that already built payload
416+
# (since it may have been modified at this point), unless there are new keys to consider
417+
oldSelectize <- p$x$selectize[[groupId]]
418+
if (length(oldSelectize) > 0) {
419+
missingKeys <- setdiff(k, oldSelectize$items$value)
420+
if (length(missingKeys) > 0) {
421+
warning("Overwriting the existing selectize payload for group '", i, "'. If you've previously modified this payload in some way, consider modifying it again.")
422+
} else {
423+
options <- oldSelectize
424+
}
425+
}
426+
427+
p$x$selectize[[groupId]] <- options
428+
}
402429
}
403430

404-
# add HTML dependencies, set a sensible dragmode default, & throw messages
405-
if (hasKeys) {
406-
p$x$layout$dragmode <- p$x$layout$dragmode %|D|%
407-
default(switch(p$x$highlight$on %||% "", plotly_selected = "select", plotly_selecting = "select") %||% "zoom")
408-
if (is.default(p$x$highlight$off)) {
409-
message(
410-
sprintf(
411-
"Setting the `off` event (i.e., '%s') to match the `on` event (i.e., '%s'). You can change this default via the `highlight()` function.",
412-
p$x$highlight$off, p$x$highlight$on
413-
)
431+
# set a sensible dragmode default, & throw messages
432+
p$x$layout$dragmode <- p$x$layout$dragmode %|D|%
433+
default(switch(p$x$highlight$on %||% "", plotly_selected = "select", plotly_selecting = "select") %||% "zoom")
434+
if (is.default(p$x$highlight$off)) {
435+
message(
436+
sprintf(
437+
"Setting the `off` event (i.e., '%s') to match the `on` event (i.e., '%s'). You can change this default via the `highlight()` function.",
438+
p$x$highlight$off, p$x$highlight$on
414439
)
415-
}
440+
)
416441
}
417442

418443
p
Loading
Loading
Loading
Loading

inst/htmlwidgets/plotly.js

+10-8
Original file line numberDiff line numberDiff line change
@@ -521,15 +521,17 @@ HTMLWidgets.widget({
521521
// communication between the widget and direct manipulation events
522522
if (x.selectize) {
523523
var selectizeID = Object.keys(x.selectize)[i];
524-
var items = x.selectize[selectizeID].items;
524+
var options = x.selectize[selectizeID];
525525
var first = [{value: "", label: "(All)"}];
526-
var opts = {
527-
options: first.concat(items),
528-
searchField: "label",
529-
valueField: "value",
530-
labelField: "label",
531-
maxItems: 50
532-
};
526+
var opts = $.extend({
527+
options: first.concat(options.items),
528+
searchField: "label",
529+
valueField: "value",
530+
labelField: "label",
531+
maxItems: 50
532+
},
533+
options
534+
);
533535
var select = $("#" + selectizeID).find("select")[0];
534536
var selectize = $(select).selectize(opts)[0].selectize;
535537
// NOTE: this callback is triggered when *directly* altering

man/highlight.Rd

+4-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/test-animate-highlight.R

+44
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,50 @@ test_that("group_by.plotly() retains crosstalk set", {
8181
expect_true(all(b$x$data[[1]]$key == row.names(mtcars)))
8282
})
8383

84+
test_that("highlight(selectize) produces a sensible payload", {
85+
p <- plot_ly() %>%
86+
add_lines(data = mtcars, x = ~wt, y = ~mpg) %>%
87+
add_markers(
88+
data = highlight_key(mtcars, ~cyl, "Choose cylinder"),
89+
x = ~wt, y = ~mpg
90+
)
91+
92+
# Builds basic payload when selectize=TRUE
93+
b <- p %>%
94+
highlight(selectize = TRUE) %>%
95+
plotly_build()
96+
97+
selectize <- list(
98+
items = data.frame(value = c(6, 4, 8), label = c(6, 4, 8)),
99+
group = "Choose cylinder"
100+
)
101+
102+
expect_length(b$x$selectize, 1)
103+
expect_equal(b$x$selectize[[1]], selectize)
104+
105+
# Copies over any list() options
106+
b2 <- p %>%
107+
highlight(selectize = list(plugins = list("remove_button"))) %>%
108+
plotly_build()
109+
110+
selectize$plugins <- list("remove_button")
111+
112+
expect_length(b2$x$selectize, 1)
113+
expect_equal(b2$x$selectize[[1]], selectize)
114+
115+
# Can also tack on options after building, and plotly_build() won't overwrite
116+
b2$x$selectize[[1]] <- modifyList(
117+
b2$x$selectize[[1]], list(foo = "bar")
118+
)
119+
120+
b2 <- plotly_build(b2)
121+
122+
selectize$foo <- "bar"
123+
124+
expect_equal(b2$x$selectize[[1]], selectize)
125+
126+
})
127+
84128

85129

86130
# Ignore for now https://github.com/ggobi/ggally/issues/264

0 commit comments

Comments
 (0)