Skip to content

Commit 370d9e5

Browse files
authored
Merge pull request #122 from janithwanni/feat/add-points-and-edges
Feature: Add points and edges through a proxy
2 parents 215b8b9 + 94b5c4f commit 370d9e5

File tree

16 files changed

+509
-32
lines changed

16 files changed

+509
-32
lines changed

DESCRIPTION

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ Imports:
1818
rlang,
1919
purrr,
2020
viridisLite,
21-
grDevices
21+
grDevices,
22+
cli
2223
RoxygenNote: 7.3.2
2324
Roxygen: list(markdown = TRUE)
2425
Suggests:

NAMESPACE

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
# Generated by roxygen2: do not edit by hand
22

33
S3method(as.list,detour)
4+
export(add_edges)
5+
export(add_points)
46
export(dependence_tour)
57
export(detour)
68
export(displayScatter2dOutput)
79
export(displayScatter3dOutput)
10+
export(display_scatter_proxy)
811
export(frozen_guided_tour)
912
export(frozen_tour)
1013
export(grand_tour)

R/shiny_bindings.r

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,95 @@ shinyRenderDisplayScatter3d <- function(expr, env = parent.frame(), quoted = FAL
6363
}
6464
htmlwidgets::shinyRenderWidget(expr, displayScatter3dOutput, quoted = TRUE, env = env)
6565
}
66+
67+
#' Send commands to a detourr instance in a Shiny app
68+
#'
69+
#' Creates a proxy object that can be used to add
70+
#' or remove points to a detour instance that has
71+
#' already being rendered using \code{\link{shinyRenderDisplayScatter3d}}.
72+
#' To be used in Shiny apps only.
73+
#' @param id output id of the detourr instance
74+
#' @param session the Shiny session object used in the app.
75+
#' Default should work for most cases
76+
#'
77+
#' @rdname detour-shiny
78+
#' @export
79+
display_scatter_proxy <- function(id, session = shiny::getDefaultReactiveDomain()) { #nolint
80+
structure(list(id = id, session = session), class = "detourr_proxy")
81+
}
82+
83+
#' @title Add a set of points to an existing detourr instance in Shiny
84+
#' @param proxy Proxy object created by \code{\link{display_scatter_proxy}}
85+
#' @param points Data.frame of points
86+
#' @param .data Original dataset used in creating the detourr instance
87+
#' @param .col_means Vector of column means of the original dataset.
88+
#' Defaults to the result of `attributes(scale(.data))[["scaled:center"]]`
89+
#' @param .scale_factor Numeric value to multiply the centered data.
90+
#' Defaults to `1 / max(sqrt(rowSums(scale(.data)^2)))`
91+
#' @return Proxy object to be used for piping
92+
#' @rdname detour-shiny
93+
#' @export
94+
add_points <- function(
95+
proxy,
96+
points,
97+
.data = NULL,
98+
.col_means = NULL,
99+
.scale_factor = NULL,
100+
colour = "black",
101+
size = 1,
102+
alpha = 1
103+
) {
104+
if (is.null(.data)) {
105+
if (is.null(.col_means) || is.null(.scale_factor)) {
106+
cli::cli_abort(c(
107+
"Either {.var .data} or both {.var .col_means} and {.var .scale_factor} should be given",
108+
"i" = "Pass the data used to create the detourr instance as {.var .data}"
109+
))
110+
}
111+
} else {
112+
scaled_data <- scale(.data, scale = FALSE)
113+
.col_means <- attributes(scaled_data)[["scaled:center"]]
114+
.scale_factor <- 1 / max(sqrt(rowSums(scaled_data^2)))
115+
}
116+
points <- unname(as.matrix(points)) |>
117+
scale(
118+
center = .col_means,
119+
scale = FALSE
120+
)
121+
points <- points * .scale_factor
122+
message <- list(
123+
id = proxy$id,
124+
data = apply(points, 1, as.list),
125+
config = list(
126+
colour = colour,
127+
size = size,
128+
alpha = alpha
129+
)
130+
)
131+
if (!is.null(proxy$message)) {
132+
# previous proxy message exists
133+
proxy$message$data <- message$data
134+
proxy$message$config <- message$config
135+
} else {
136+
proxy$message <- message
137+
}
138+
proxy$session$sendCustomMessage("add-points", proxy$message)
139+
return(proxy)
140+
}
141+
142+
#' @title Function to add a bunch of lines to existing shiny instance
143+
#'
144+
#' @param proxy Proxy object created by \code{\link{display_scatter_proxy}}
145+
#' @param edge_list Data.frame with two columns with the `from` node at first.
146+
#' The indexing of points starts with the original dataset.
147+
#' If \code{\link{add_points}} has been called before hand,
148+
#' the indexing of these points starts from the end of the original dataset.
149+
#' @return Proxy object to be used for piping
150+
#' @rdname detour-shiny
151+
#' @export
152+
add_edges <- function(proxy, edge_list) {
153+
edge_list <- edge_list |> as.matrix() |> unname()
154+
proxy$message$edges <- apply(edge_list, 1, as.list)
155+
proxy$session$sendCustomMessage("add-edges", proxy$message)
156+
return(proxy)
157+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
library(shiny)
2+
library(detourr)
3+
4+
dataset <- tourr::flea |>
5+
dplyr::mutate(id = dplyr::row_number())
6+
7+
create_fake_box <- function(datum) {
8+
# expected tibble needs to have two rows with ncols(datum) + 1 columns
9+
box_dist <- rnorm(1, mean = 3)
10+
bounds_list <- rbind(datum + box_dist, datum - box_dist) |>
11+
as.list()
12+
do.call(tidyr::expand_grid, bounds_list)
13+
}
14+
15+
main_ui <- function(id) {
16+
ns <- NS(id)
17+
fluidPage(
18+
column(6,
19+
displayScatter2dOutput(
20+
ns("detourr_out"), width = "100%", height = "400px"
21+
),
22+
textOutput(ns("detour_click_output"))
23+
),
24+
column(6,
25+
h1("Adding points and edges to detourr through Shiny"),
26+
p(
27+
"In this demonstration,",
28+
"clicking on points on the detourr instance to the left",
29+
"adds a box around the point.",
30+
"Click on the play button to view the box in different projections"
31+
)
32+
)
33+
)
34+
}
35+
36+
main_server <- function(id) {
37+
moduleServer(id, function(input, output, session){
38+
output$detourr_out <- shinyRenderDisplayScatter2d({
39+
detour(dataset,
40+
tour_aes(projection = -c(id, species), colour = species, label = id)
41+
) |>
42+
tour_path(grand_tour(2), fps = 60) |>
43+
show_scatter(
44+
alpha = 0.7,
45+
axes = TRUE
46+
)
47+
})
48+
49+
output$detour_click_output <- renderText({
50+
input$detourr_out_detour_click
51+
})
52+
53+
observeEvent(input$detourr_out_detour_click, {
54+
req(
55+
!is.null(input$detourr_out_detour_click),
56+
input$detourr_out_detour_click != -1
57+
)
58+
data_to_send <- dataset |>
59+
dplyr::select(-species) |>
60+
dplyr::filter(id == input$detourr_out_detour_click) |>
61+
dplyr::select(-id)
62+
63+
box_to_send <- data_to_send |> create_fake_box()
64+
65+
cube_box <- geozoo::cube.iterate(p = ncol(data_to_send))
66+
67+
display_scatter_proxy(session$ns("detourr_out")) |>
68+
add_points(
69+
box_to_send,
70+
.data = dataset |> dplyr::select(-c(id, species))
71+
) |>
72+
add_edges(
73+
edge_list = cube_box$edges
74+
)
75+
})
76+
77+
})
78+
}
79+
80+
ui <- function() {
81+
main_ui("main")
82+
}
83+
84+
server <- function(input, output, session) {
85+
main_server("main")
86+
}
87+
88+
shinyApp(ui, server, options = list(port = 5534))

inst/htmlwidgets/lib/show_sage_2d.bundle.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

inst/htmlwidgets/lib/show_sage_3d.bundle.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

inst/htmlwidgets/lib/show_scatter_2d.bundle.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

inst/htmlwidgets/lib/show_scatter_3d.bundle.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

inst/htmlwidgets/lib/show_slice_2d.bundle.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

inst/htmlwidgets/lib/show_slice_3d.bundle.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)