Skip to content

Commit dfd4795

Browse files
committed
Working on graphics
1 parent 8bfa162 commit dfd4795

File tree

2 files changed

+56
-24
lines changed

2 files changed

+56
-24
lines changed

action-graphics.Rmd

Lines changed: 55 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -122,22 +122,35 @@ If you want to change the colour of the brush, you'll need to use `brushOpts()`.
122122

123123
### Modifying data
124124

125-
So far we've displayed the results of the interaction in another output. But the true elegance of interactivity comes when you display the changes in the same plot you're interacting with. Unfortunately this requires an advanced reactivity technique that you have yet learned about: `reactiveVal()`. We'll come back to `reactiveVal()` in Chapter XYZ, but I wanted to show it here because it's such a useful technique. You'll probably need to re-read this section after you've read that chapter, but hopefully even without all the theory you'll get a sense of the potential applications.
125+
So far we've displayed the results of the interaction in another output. But the true elegance of interactivity comes when you display the changes in the same plot you're interacting with. Unfortunately this requires an advanced reactivity technique that you have yet learned about: `reactiveVal()`. We'll come back to `reactiveVal()` in Chapter \@ref(reactivity-components), but I wanted to show it here because it's such a useful technique. You'll probably need to re-read this section after you've read that chapter, but hopefully even without all the theory you'll get a sense of the potential applications.
126126

127-
`reactiveValue()` is similarly to `reactive()` except that you can modify it by supplying an argument when you call it. The following code shows the basic idea:
127+
As you might guess from the name, `reactiveVal()` is rather similar to `reactive()`. You create a reactive value by calling `reactiveVal()` with its initial value, and retrieve that value in the same way as a reactive:
128128

129129
```{r, eval = FALSE}
130130
val <- reactiveVal(10)
131131
val()
132132
#> [1] 10
133+
```
134+
135+
However, it has a big difference: you can also update it, and all reactive consumers that refer to it will recompute. It uses a special syntax for updating --- you call it like a function (like retrieving the value) but supply an argument that is the new value:
136+
137+
```{r, eval = FALSE}
133138
val(20)
134139
val()
135140
#> [1] 20
136141
```
137142

138-
One of the challenges of learning `reactiveVal()` is that doesn't work in the console because it must be run in an reactive environment. This is one of the challenges we'll come back to later in the book.
143+
So updating a reactive value based on its current value looks something like this:
139144

140-
But lets see how we might use it. For example, imagine that you want to visualise the distance between a click and the points on the plot. First we create a reactive value that initialises the distance to a constant (that'll be used before we click anything). Then we use `observeEvent()` to update the reactive value when the mouse is clicked. All up, this looks something like:
145+
```{r, eval = FALSE}
146+
val(val() + 1)
147+
val()
148+
#> [1] 21
149+
```
150+
151+
Unfortunately if you actually try to run this code in the console you'll get an error because be run in an reactive environment. That makes it challenging to understand and debug, because you'll need to add a `browser()` call to your Shiny app to get into a state where you can explore what's happening. This is one of the challenges we'll come back to later in Chapter \@ref(reactivity-components).
152+
153+
But for now, lets put the challenges of learning `reactiveVal()` aside, and show you why you might bother. Imagine that you want to visualise the distance between a click and the points on the plot. In the app below, we start by creating a reactive value to store those distances, initialising it with a constant that will be used before we click anything. Then we use `observeEvent()` to update the reactive value when the mouse is clicked, and a ggplot that visualises the distance with point size. All up, this looks something like:
141154

142155
```{r}
143156
df <- data.frame(x = rnorm(100), y = rnorm(100))
@@ -163,37 +176,38 @@ server <- function(input, output, session) {
163176
There are two important ggplot2 techniques to note here:
164177

165178
- I add the distances to the data frame before plotting it. It's good practice to put everything that you're visualising in a single data frame.
166-
- I set the `limits` to `scale_size_area()` to ensure that sizes are comparable over time. I just did a little interactive experimentation to find the range, but in an exercise you'll work out the exact details.
179+
- I set the `limits` to `scale_size_area()` to ensure that sizes are comparable over time. Here, I did a little interactive experimentation to determine right the range, but you can work out the exact details if needed (see exercises below).
167180

168-
Here's a more complicated idea. I'm going to allow the user to click points to add or remove them from a model fit. I use `nearPoint()` to find which points are near the click, then `ifelse()` to toggle their values: if they were previously excluded they'll be included; if they were previously included, they'll be excluded.
181+
Here's a more complicated idea. I want to use a brush to select (and deselect) points on a plot. I just display them using colours on the plot, but you could imagine many other applications. To make this work, I initialise the `reactiveVal()` to a vector of `FALSE`s, then use `brushedPoints()` and `ifelse()` toggle their values: if they were previously excluded they'll be included; if they were previously included, they'll be excluded.
169182

170183
```{r}
171184
ui <- fluidPage(
172-
plotOutput("plot", click = clickOpts("click")),
185+
plotOutput("plot", brush = "plot_brush"),
173186
tableOutput("data")
174187
)
175188
server <- function(input, output, session) {
176189
selected <- reactiveVal(rep(TRUE, nrow(mtcars)))
177-
190+
191+
observeEvent(input$plot_brush, {
192+
brushed <- brushedPoints(mtcars, input$plot_brush, allRows = TRUE)$selected_
193+
selected(ifelse(brushed, !selected(), selected()))
194+
})
195+
178196
output$plot <- renderPlot({
179197
mtcars$sel <- selected()
180198
ggplot(mtcars, aes(wt, mpg)) +
181199
geom_point(aes(colour = sel)) +
182200
scale_colour_discrete(limits = c("TRUE", "FALSE"))
183201
}, res = 96)
184-
185-
observeEvent(input$click, {
186-
clicked <- nearPoints(mtcars, input$click, allRows = TRUE)$selected_
187-
selected(ifelse(clicked, !selected(), selected()))
188-
})
202+
189203
}
190204
```
191205

192206
Again, I set the limits of the scale to ensure that the legend (and colours) don't change after the first click.
193207

194208
### Data flow
195209

196-
It's important to understand the basic data flow in interactive plots in order to understand their limitations.
210+
Before we move on, it's important to understand the basic data flow in interactive plots in order to understand their limitations. The basic flow is something like this:
197211

198212
1. Javascript captures mouse event.
199213
2. Shiny sends the javascript mouse event back to R, invalidating the input.
@@ -204,11 +218,11 @@ For local apps, the bottleneck tends to be the time taken to draw the plot. Depe
204218

205219
In general, this means that it's not possible to create Shiny apps where action and response is percieved as instanteous (i.e. the plot updates simultaneously with your action). If you need this level of speed, you'll need to perform more computation in javascript.
206220

207-
### Dynamic height and width
221+
## Dynamic height and width
208222

209-
Before we move on to plot caching, there's one other handy trick I wanted to mention: you can make the size of a plot reactive, so it changes size based on the user's interaction with other controls. To do this, you supply zero-argument functions that return the size in pixels to `height` and `width`. These are evaluated in a reactive environment so that you can make the size of your plot dynamic.
223+
The rest of this chapter is less exciting than interactive graphics, but important to cover somewhere. First, you can make the size of a plot reactive, so it changes size based on the user's interaction with your app. To do this, you supply functions to the `width` and `height` argument. These functions should have no argument and return the desired size in pixels.They are evaluated in a reactive environment so that you can make the size of your plot dynamic.
210224

211-
The following app illustrates the basic idea. It provides two slides that directly control the size of the plot.
225+
The following app illustrates the basic idea. It provides two sliders that directly control the size of the plot:
212226

213227
```{r}
214228
ui <- fluidPage(
@@ -229,22 +243,40 @@ server <- function(input, output, session) {
229243
}
230244
```
231245

232-
Note that the plot is re-drawn, but the code is not rerun (i.e. the random values say the same). This is the same behaviour as when you resize a Shiny app that contains a plot with a dynamic height/width (e.g. the default width of 100%).
246+
Note that when you resize the plot, the data stays the same. This is the same behaviour as when you resize a Shiny app that contains a plot with a dynamic height/width (e.g. the default width of 100%).
233247

234248
In real cases, you'd use more complicated expressions in the `width` and `height` functions. For example, if you're using a faceted plot in ggplot2, you might use it to increase the size of the plot to keep the individual facet sizes roughly the same (unfortunately there's no easy way to keep them exactly the same because it's currently not possible to find out the size of the fixed elements around the borders of the plot.)
235249

236-
## `renderCachedPlot()`
250+
## Cached plots
251+
252+
If you have an app with complicated plots that take a while to draw, and the same plots are seen by many people, you can get some major performance advantages by used plot caching. This is most a matter of changing `renderPlot()` to `renderCachedPlot()`, but there are a few other issues that you need to consider. Here we'll focus on the big picture; full the full details you can refer to the [Shiny website](https://shiny.rstudio.com/articles/plot-caching.html).
237253

238-
Really useful if a plot is seen by multiple users,
254+
```{r}
255+
ui <- fluidPage(
256+
selectInput("x", "X", choices = names(diamonds), selected = "carat"),
257+
selectInput("y", "Y", choices = names(diamonds), selected = "price"),
258+
plotOutput("diamonds")
259+
)
239260
240-
<https://shiny.rstudio.com/articles/plot-caching.html>
261+
server <- function(input, output, session) {
262+
output$diamonds <- renderCachedPlot({
263+
ggplot(diamonds, aes(.data[[input$x]], .data[[input$y]])) +
264+
geom_point()
265+
},
266+
cacheKeyExpr = list(input$x, input$y))
267+
}
268+
```
241269

242-
Mostly a matter of changing `renderPlot()` to `renderCachedPlot()`. But you also need to supply a `cacheKeyExpr`. This is some code that returns an object that basically represents the "state" of the plot; whenever that value changes, the plot will be recomputed.
270+
You can use the plot cache with what you now know. But there are three topics that will Three important topics we need to cover:
243271

244-
BASIC EXAMPLE
272+
- The cache key, which determines when the plot needs to be recomputed.
273+
- The sizing policy, which ensures that plot is shared even when the sizes are a little different.
274+
- The scoping, which controls how frequently the cache is used.
245275

246276
### Cache key
247277

278+
But you also need to supply a `cacheKeyExpr`. This is some code that returns an object that basically represents the "state" of the plot; whenever that value changes, the plot will be recomputed.
279+
248280
Best to keep it as simple as possible - should be a list of vectors.
249281

250282
- Input parameters.

reactivity-components.Rmd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Reactive components
1+
# Reactive components {#reactivity-components}
22

33
```{r setup, include=FALSE}
44
source("common.R")

0 commit comments

Comments
 (0)