Skip to content

Commit 68d212f

Browse files
committed
feat: improve the trigger tooltips
1 parent a840f29 commit 68d212f

File tree

5 files changed

+223
-39
lines changed

5 files changed

+223
-39
lines changed

R/tm_g_spiderplot.R

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ ui_g_spiderplot <- function(id, height) {
160160
bslib::card(
161161
full_screen = TRUE,
162162
tags$div(
163-
ui_trigger_tooltips(ns("show_tooltips")),
163+
trigger_tooltips_deps(),
164164
plotly::plotlyOutput(ns("plot"), height = "100%")
165165
)
166166
),
@@ -266,11 +266,36 @@ srv_g_spiderplot <- function(id,
266266
)
267267
})
268268

269-
output$plot <- plotly::renderPlotly(plotly::event_register(plotly_q()$p, "plotly_selected"))
269+
output$plot <- output$plot <- plotly::renderPlotly(plotly::event_register(
270+
{
271+
plotly_q()$p |>
272+
setup_trigger_tooltips(session$ns, input)
273+
},
274+
"plotly_selected"
275+
))
270276

271277
plotly_selected <- reactive(plotly::event_data("plotly_selected", source = "spiderplot"))
272278

273-
srv_trigger_tooltips("show_tooltips", plotly_selected, session$ns("plot"))
279+
observeEvent(input$show_tooltips, {
280+
sel <- plotly_selected()
281+
282+
if (!is.null(sel) && nrow(sel) > 0) {
283+
tooltip_points <- lapply(seq_len(nrow(sel)), function(i) {
284+
list(
285+
curve = sel$curveNumber[i],
286+
index = sel$pointNumber[i]
287+
)
288+
})
289+
290+
session$sendCustomMessage(
291+
"triggerTooltips",
292+
list(
293+
plotID = session$ns("plot"),
294+
tooltipPoints = jsonlite::toJSON(tooltip_points, auto_unbox = TRUE)
295+
)
296+
)
297+
}
298+
})
274299

275300
tables_selected_q <- .plotly_selected_filter_children(
276301
data = plotly_q,

R/tm_g_swimlane.R

Lines changed: 81 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -140,13 +140,15 @@ ui_g_swimlane <- function(id, height) {
140140
selectInput(ns("group_var"), label = "Group by:", choices = NULL, selected = NULL, multiple = FALSE),
141141
selectInput(ns("sort_var"), label = "Sort by:", choices = NULL, selected = NULL, multiple = FALSE),
142142
colour_picker_ui(ns("colors")),
143-
sliderInput(ns("plot_height"), "Plot Height (px)", height[2], height[3], height[1])
143+
sliderInput(ns("plot_height"), "Plot Height (px)", height[2], height[3], height[1]),
144+
selectInput(ns("subjects"), "Subjects", choices = NULL, selected = NULL, multiple = TRUE),
145+
actionButton(ns("subject_tooltips"), "Show Subject Tooltips")
144146
),
145147
tags$div(
146148
bslib::card(
147149
full_screen = TRUE,
148150
tags$div(
149-
ui_trigger_tooltips(ns("show_tooltips")),
151+
trigger_tooltips_deps(),
150152
plotly::plotlyOutput(ns("plot"), height = "100%")
151153
)
152154
),
@@ -188,6 +190,7 @@ srv_g_swimlane <- function(id,
188190

189191
plotly_q <- reactive({
190192
req(data(), input$time_var, input$subject_var, input$color_var, input$group_var, input$sort_var, color_inputs())
193+
print(input$subject_var)
191194
adjusted_symbols <- .shape_palette_discrete(
192195
levels = unique(data()[[plot_dataname]][[input$color_var]]),
193196
symbol = point_symbols
@@ -225,14 +228,69 @@ srv_g_swimlane <- function(id,
225228
)
226229
})
227230

228-
output$plot <- plotly::renderPlotly(plotly::event_register(plotly_q()$p, "plotly_selected"))
231+
output$plot <- plotly::renderPlotly(plotly::event_register(
232+
{
233+
plotly_q()$p |>
234+
set_plot_data(session$ns("plot_data")) |>
235+
setup_trigger_tooltips(session$ns)
236+
},
237+
"plotly_selected"
238+
))
239+
240+
plotly_data <- reactive({
241+
data.frame(
242+
x = unlist(input$plot_data$x),
243+
y = unlist(input$plot_data$y),
244+
customdata = unlist(input$plot_data$customdata),
245+
curve = unlist(input$plot_data$curveNumber),
246+
index = unlist(input$plot_data$pointNumber)
247+
)
248+
})
229249

230250
plotly_selected <- reactive({
231251
plotly::event_data("plotly_deselect", source = "swimlane") # todo: deselect doesn't work
232252
plotly::event_data("plotly_selected", source = "swimlane")
233253
})
234254

235-
srv_trigger_tooltips("show_tooltips", plotly_selected, session$ns("plot"))
255+
observeEvent(input$show_tooltips, {
256+
sel <- plotly_selected()
257+
258+
if (!is.null(sel) && nrow(sel) > 0) {
259+
tooltip_points <- lapply(seq_len(nrow(sel)), function(i) {
260+
list(
261+
curve = sel$curveNumber[i],
262+
index = sel$pointNumber[i]
263+
)
264+
})
265+
266+
session$sendCustomMessage(
267+
"triggerTooltips",
268+
list(
269+
plotID = session$ns("plot"),
270+
tooltipPoints = jsonlite::toJSON(tooltip_points, auto_unbox = TRUE)
271+
)
272+
)
273+
}
274+
})
275+
276+
observeEvent(input$subject_tooltips, {
277+
hovervalues <- data()[[plot_dataname]] |>
278+
dplyr::mutate(customdata = dplyr::row_number()) |>
279+
dplyr::filter(!!rlang::sym(input$subject_var) %in% input$subjects) |>
280+
dplyr::pull(customdata)
281+
282+
283+
hovertips <- plotly_data() |>
284+
dplyr::filter(customdata %in% hovervalues)
285+
286+
session$sendCustomMessage(
287+
"triggerTooltips",
288+
list(
289+
plotID = session$ns("plot"),
290+
tooltipPoints = jsonlite::toJSON(hovertips)
291+
)
292+
)
293+
})
236294

237295
tables_selected_q <- .plotly_selected_filter_children(
238296
data = plotly_q,
@@ -243,6 +301,19 @@ srv_g_swimlane <- function(id,
243301
children_datanames = table_datanames
244302
)
245303

304+
305+
observeEvent(data(), {
306+
if (class(subject_var) == "choices_selected") {
307+
subject_col <- subject_var$selected
308+
} else {
309+
subject_col <- subject_var
310+
}
311+
updateSelectInput(
312+
inputId = "subjects",
313+
choices = data()[[plot_dataname]][[subject_col]]
314+
)
315+
})
316+
246317
srv_t_reactables(
247318
"subtables",
248319
data = tables_selected_q,
@@ -260,6 +331,8 @@ swimlanely <- function(
260331
colors, symbols, height, tooltip_vars = NULL, size_var = NULL, point_size = 10) {
261332
subject_var_label <- .get_column_label(data, subject_var)
262333
time_var_label <- .get_column_label(data, time_var)
334+
data <- data |>
335+
dplyr::mutate(customdata = dplyr::row_number())
263336

264337
if (is.null(size_var)) {
265338
size <- point_size
@@ -307,7 +380,8 @@ swimlanely <- function(
307380
source = "swimlane",
308381
colors = colors,
309382
symbols = symbols,
310-
height = height
383+
height = height,
384+
customdata = ~customdata
311385
) %>%
312386
plotly::add_markers(
313387
x = stats::as.formula(sprintf("~%s", time_var)),
@@ -327,7 +401,8 @@ swimlanely <- function(
327401
dplyr::group_by(!!as.name(subject_var), !!as.name(group_var)) |>
328402
dplyr::summarise(study_day = max(!!as.name(time_var))),
329403
line = list(width = 2, color = "grey"),
330-
showlegend = FALSE
404+
showlegend = FALSE,
405+
customdata = NULL
331406
) %>%
332407
plotly::layout(
333408
xaxis = list(title = time_var_label),

R/utils.R

Lines changed: 68 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -485,44 +485,82 @@ trigger_tooltips_deps <- function() {
485485
name = "teal-modules-general-trigger-tooltips",
486486
version = utils::packageVersion("teal.modules.general"),
487487
package = "teal.modules.general",
488-
src = "js",
489-
script = "triggerTooltips.js"
488+
src = "triggerTooltips",
489+
script = "triggerTooltips.js",
490+
stylesheet = "triggerTooltips.css"
490491
)
491492
}
492493

494+
493495
#' @keywords internal
494496
#' @noRd
495-
ui_trigger_tooltips <- function(id) {
496-
ns <- NS(id)
497-
tags$div(
498-
trigger_tooltips_deps(),
499-
actionButton(ns("show_tooltips"), "Show Selected Tooltips")
497+
setup_trigger_tooltips <- function(plot, ns) {
498+
htmlwidgets::onRender(
499+
plot,
500+
paste0(
501+
"function(el) {
502+
const targetDiv = document.querySelector('#", ns("plot"), " .modebar-group:nth-child(4)');
503+
console.log(el.data);
504+
if (targetDiv) {
505+
const button = document.createElement('button');
506+
button.setAttribute('data-count', '0');
507+
button.className = 'teal-modules-general trigger-tooltips-button';
508+
509+
button.onclick = function () {
510+
const current = parseInt(this.getAttribute('data-count'));
511+
const next = current + 1;
512+
this.setAttribute('data-count', next);
513+
console.log('Button clicked ' + next + ' times');
514+
Shiny.setInputValue('", ns("show_tooltips"), "', next);
515+
};
516+
517+
const icon = document.createElement('i');
518+
icon.className = 'fas fa-message';
519+
icon.setAttribute('role', 'presentation');
520+
icon.setAttribute('aria-label', 'info icon');
521+
522+
const tooltip = document.createElement('span');
523+
tooltip.className = 'plotly-icon-tooltip';
524+
tooltip.textContent = 'Hover selection';
525+
526+
button.appendChild(icon);
527+
button.appendChild(tooltip);
528+
targetDiv.appendChild(button);
529+
}
530+
}"
531+
)
500532
)
501533
}
502534

503535
#' @keywords internal
504536
#' @noRd
505-
srv_trigger_tooltips <- function(id, plotly_selected, plot_id) {
506-
moduleServer(id, function(input, output, session) {
507-
observeEvent(input$show_tooltips, {
508-
sel <- plotly_selected()
509-
510-
if (!is.null(sel) && nrow(sel) > 0) {
511-
tooltip_points <- lapply(seq_len(nrow(sel)), function(i) {
512-
list(
513-
curve = sel$curveNumber[i],
514-
index = sel$pointNumber[i]
515-
)
516-
})
517-
518-
session$sendCustomMessage(
519-
"triggerTooltips",
520-
list(
521-
plotID = plot_id,
522-
tooltipPoints = jsonlite::toJSON(tooltip_points, auto_unbox = TRUE)
523-
)
524-
)
525-
}
526-
})
527-
})
537+
set_plot_data <- function(plot, data_id) {
538+
htmlwidgets::onRender(
539+
plot,
540+
paste0(
541+
"
542+
function(el) {
543+
slicedData = el.data.slice(0, -1).map(({ x, y, customdata }) => ({ x, y, customdata }));
544+
plotData = {
545+
x: [],
546+
y: [],
547+
customdata: [],
548+
curveNumber: [],
549+
pointNumber: []
550+
};
551+
552+
slicedData.forEach((item, curveNumber) => {
553+
for (let i = 0; i < item.x.length; i++) {
554+
plotData.pointNumber.push(i);
555+
plotData.x.push(item.x[i]);
556+
plotData.y.push(item.y[i]);
557+
plotData.customdata.push(item.customdata[i]);
558+
plotData.curveNumber.push(curveNumber);
559+
}
560+
});
561+
Shiny.setInputValue('", data_id, "', plotData);
562+
}
563+
"
564+
)
565+
)
528566
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
.teal-modules-general.trigger-tooltips-button {
2+
border: none;
3+
background: white;
4+
opacity: 0.2;
5+
}
6+
7+
.teal-modules-general.trigger-tooltips-button:hover {
8+
opacity: 0.6;
9+
}
10+
11+
.teal-modules-general.trigger-tooltips-button i {
12+
font-size: 0.85em;
13+
}
14+
15+
.teal-modules-general.trigger-tooltips-button {
16+
position: relative;
17+
}
18+
19+
.teal-modules-general.trigger-tooltips-button > .plotly-icon-tooltip {
20+
visibility: hidden;
21+
position: absolute;
22+
top: 125%;
23+
right: 0;
24+
transform: translateX(0);
25+
background-color: #121f3d;
26+
color: #fff;
27+
padding: 6px 10px;
28+
border-radius: 3px;
29+
z-index: 1000;
30+
font-size: 12px;
31+
}
32+
33+
.teal-modules-general.trigger-tooltips-button > .plotly-icon-tooltip::after {
34+
content: "";
35+
position: absolute;
36+
bottom: 100%;
37+
right: 10px;
38+
border-width: 5px;
39+
border-style: solid;
40+
border-color: transparent transparent #121f3d transparent;
41+
}
42+
43+
.teal-modules-general.trigger-tooltips-button:hover > .plotly-icon-tooltip {
44+
visibility: visible;
45+
opacity: 1;
46+
}

0 commit comments

Comments
 (0)