Skip to content

Commit 9b3b736

Browse files
committed
add shiny-based crossfiltering example apps
1 parent f3cf60b commit 9b3b736

File tree

2 files changed

+217
-0
lines changed
  • inst/examples/shiny

2 files changed

+217
-0
lines changed
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
library(shiny)
2+
library(plotly)
3+
library(dplyr)
4+
library(nycflights13)
5+
# install from https://github.com/hadley/ggstat
6+
library(ggstat)
7+
8+
# Collect numeric variables to be shown as histograms in the crossfilter
9+
d <- select(flights, arr_time, dep_time, arr_delay, dep_delay, air_time, distance)
10+
11+
# Generate an output container per variable
12+
ui <- fluidPage(
13+
lapply(names(d), function(nm) plotlyOutput(nm, height = 200, width = "49%", inline = TRUE))
14+
)
15+
16+
server <- function(input, output, session) {
17+
18+
# These reactive values track the set of active brushes
19+
# Each reactive value corresponds to a different variable
20+
brush_ranges <- reactiveValues()
21+
22+
# Filter the dataset based on every active brush range except for one (var)
23+
d_filter <- function(d, var = "arr_time") {
24+
for (nm in setdiff(names(d), var)) {
25+
rng <- brush_ranges[[nm]]
26+
if (is.null(rng)) next
27+
d <- filter(d, between(d[[nm]], min(rng), max(rng)))
28+
}
29+
d
30+
}
31+
32+
# Implement same render logic for each variable
33+
lapply(names(d), function(nm) {
34+
35+
counts <- d[[nm]] %>%
36+
bin_fixed(bins = 150) %>%
37+
compute_stat(d[[nm]]) %>%
38+
filter(!is.na(xmin_)) %>%
39+
mutate(xmid = (xmin_ + xmax_) / 2)
40+
41+
output[[nm]] <- renderPlotly({
42+
43+
plot_ly(counts, source = nm) %>%
44+
add_bars(x = ~xmid, y = ~count_) %>%
45+
layout(
46+
dragmode = "select",
47+
selectdirection = "h",
48+
xaxis = list(
49+
title = nm,
50+
range = range(d[[nm]], na.rm = TRUE)
51+
),
52+
yaxis = list(title = "")
53+
)
54+
})
55+
56+
observeEvent(event_data("plotly_brushing", source = nm), ignoreNULL = FALSE, {
57+
58+
# inform the world about the new brush range
59+
brush_ranges[[nm]] <- event_data("plotly_brushing", source = nm)$x
60+
61+
# update the bar heights of every view (except for the one being brushed)
62+
for (var in setdiff(names(d), nm)) {
63+
64+
# views respect every brush except for their own
65+
d_filtered <- d_filter(d, var)
66+
67+
# bin the filtered data based on the global binning definition
68+
counts_filter <- d[[var]] %>%
69+
bin_fixed(bins = 150) %>%
70+
compute_stat(d_filtered[[var]]) %>%
71+
filter(!is.na(xmin_)) %>%
72+
mutate(xmid = (xmin_ + xmax_) / 2)
73+
74+
# finally, update the bar heights
75+
plotlyProxy(var, session) %>%
76+
plotlyProxyInvoke("restyle", "y", list(counts_filter$count_), 0)
77+
}
78+
})
79+
80+
})
81+
82+
}
83+
84+
shinyApp(ui, server)
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
library(shiny)
2+
library(dplyr)
3+
library(nycflights13)
4+
library(colourpicker)
5+
# install from https://github.com/hadley/ggstat
6+
library(ggstat)
7+
8+
# Select all the (numeric) variables to be shown as histograms
9+
# in the crossfilter
10+
d <- select(flights, arr_time, dep_time, arr_delay, dep_delay, air_time, distance)
11+
12+
# Determing a color palette for the brush
13+
n_colors <- 5
14+
color_codes <- RColorBrewer::brewer.pal(n_colors, "Dark2")
15+
16+
# Generate an output container per variable
17+
ui <- fluidPage(
18+
fluidRow(
19+
tags$div(
20+
style = "display:inline-block; max-width:150px; margin-left:50px",
21+
colourInput("brush_color", "Pick a brush color", color_codes[1], palette = "limited", allowedCols = color_codes)
22+
),
23+
tags$div(
24+
style = "display:inline-block",
25+
actionButton("clear", "Clear Selection")
26+
)
27+
),
28+
lapply(names(d), function(nm) plotlyOutput(nm, height = 200, width = "49%", inline = TRUE))
29+
)
30+
31+
server <- function(input, output, session) {
32+
33+
# Implement same render logic for each variable
34+
lapply(names(d), function(nm) {
35+
36+
# By letting R handle the binning, there is less data to send over the
37+
# wire (and, thus, a more responsive app over back connections)
38+
counts <- d[[nm]] %>%
39+
bin_fixed(bins = 150) %>%
40+
compute_stat(d[[nm]]) %>%
41+
filter(!is.na(xmin_)) %>%
42+
mutate(
43+
xmid = (xmin_ + xmax_) / 2,
44+
prop_ = count_ / sum(count_),
45+
zeros = 0
46+
)
47+
48+
output[[nm]] <- renderPlotly({
49+
50+
# Draw two layers of bars, one for the overall (black)
51+
# distribution, and one for the filtered data (red)
52+
p <- plot_ly(
53+
counts, x = ~xmid, alpha = I(0.5),
54+
source = nm, unselected = list(marker = list(opacity = 1))
55+
) %>%
56+
add_bars(y = ~prop_, color = I("black")) %>%
57+
layout(
58+
dragmode = "select",
59+
selectdirection = "h",
60+
xaxis = list(
61+
title = nm,
62+
range = range(d[[nm]], na.rm = TRUE)
63+
),
64+
yaxis = list(
65+
title = "",
66+
showticks = FALSE,
67+
showticklabels = FALSE,
68+
showgrid = FALSE
69+
),
70+
barmode = "overlay",
71+
showlegend = FALSE
72+
)
73+
74+
for (col in color_codes) {
75+
p <- add_bars(p, y = ~zeros, color = I(col))
76+
}
77+
78+
p
79+
})
80+
81+
# when the selection is cleared, return the selection layer bars to 0
82+
observeEvent(input$clear, {
83+
plotlyProxy(nm, session) %>%
84+
plotlyProxyInvoke("restyle", "y", list(counts$zeros), seq_along(color_codes))
85+
})
86+
87+
# each brush color code corresponds to a different trace index
88+
trace_index <- reactive(match(input$brush_color, color_codes))
89+
90+
observe({
91+
b <- event_data("plotly_brushing", source = nm)$x
92+
in_bounds <- between(d[[nm]], min(b), max(b))
93+
94+
lapply(names(d), function(var) {
95+
p <- plotlyProxy(var, session)
96+
97+
if (is.null(b)) {
98+
99+
# brush has been cleared, return the selection bars to a zero height
100+
plotlyProxyInvoke(p, "restyle", "y", list(counts$zeros), trace_index())
101+
102+
} else {
103+
104+
# if the brush originates from the proxy target
105+
# then don't compute a new marginal distribution,
106+
# just highlight the range of interest
107+
props <- if (nm == var) {
108+
if_else(
109+
between(counts$xmin_, min(b), max(b)) &
110+
between(counts$xmax_, min(b), max(b)),
111+
counts$prop_,
112+
0
113+
)
114+
} else {
115+
d[[var]] %>%
116+
bin_fixed(bins = 150) %>%
117+
compute_stat(d[[var]][in_bounds]) %>%
118+
filter(!is.na(xmin_)) %>%
119+
mutate(prop_ = count_ / sum(count_)) %>%
120+
pull(prop_)
121+
}
122+
123+
plotlyProxyInvoke(p, "restyle", "y", list(props), trace_index())
124+
}
125+
})
126+
127+
})
128+
129+
})
130+
131+
}
132+
133+
shinyApp(ui, server)

0 commit comments

Comments
 (0)