Reactive Web Dashboards with Shiny Transcripts
Chapter: Reactivity
Lecture: Basic reactivity
Login or
purchase this course
to watch this video and the rest of the course contents.
0:00
So now that we've learned a little bit about how to build a basic Shiny app, let's take a step back and learn how this actually works.
0:07
So when I open up a Shiny application like this one, the first thing you'll notice is that it's reactive.
0:13
So when I change this top-level account filter, both of these plots change. But when I change this metric one, this is the only plot that recalculates.
0:22
Because this plot over here, the training scores, doesn't depend on the metric selector,
0:27
it doesn't need to recalculate, and so Shiny doesn't recalculate it. But when you look at the actual code, this is a little confusing.
0:34
So here's the code that I used to draw those two plots. And the thing that pops out is that there's no state management in this code.
0:43
I'm not writing a callback function, I'm not using any state variables. All I'm doing is referring to the value of this input, the value of this input,
0:52
and Shiny is figuring out how to update these plots in response to those inputs.
0:58
One way of putting this is we told Shiny what to do, we told it how to draw these plots, but we didn't tell Shiny when to do it.
1:05
So how does Shiny know? Shiny uses a system called runtime tracing. And this is a very simple algorithm when it's explained to you,
1:13
but it's incredibly powerful and scales very well to large applications.
1:18
The basic idea is that all these components have code that asks for values from other parts of the application, that's for inputs for the most part.
1:29
So we can watch when those components ask for those inputs, and then keep track of those relationships.
1:34
And if we do that, we can use those relationships to build a directed acyclic graph,
1:39
which will let us figure out what things in our application need to re-render. So that's the basic idea.
1:46
We want to have Shiny automatically build up an execution graph for our application and use that to minimally re-render it.
1:54
So let's just take a look at the simplest possible application. This app just echoes back the value of a slider.
2:00
And so we have an output here and we have an input. And what happens is, first thing it does is when you open up the application, it says,
2:09
Okay, I need to go render that output. And we have a rendering function here, which tells it how to render that output.
2:15
So Shiny goes through and tries to render that function. When it does that, it discovers that it needs the value of input n.
2:22
And then it goes and gets that value of input n from the DOM, the website. When it does that, it can create a little graph here,
2:31
which is this output asked for that input. And for the purpose of this course, we're going to look at a lot of graphs like this,
2:38
and we're always going to use ovals for outputs and squares for inputs. So let's take a look at this graph.
2:44
We have two inputs, this account input and this metric selector input. And we have two outputs, the metrics plot and the scores plot.
2:54
When Shiny starts up, the only thing it knows is that there's four things. It doesn't know the relationships between any of these things.
3:02
It just knows that there's a metric plot, there's a distribution plot. This is the metrics plot, which can be one of two plots.
3:09
And this is the distribution plot. But it's okay that Shiny doesn't know the relationship between these things,
3:16
because it's going to be able to discover it as the application runs. The first thing it does is it goes and it tries to calculate the metric plot.
3:23
And in doing so, it realizes, okay, when I calculate the metrics plot, I need the metric selector and the account selector.
3:30
So I'm going to go get those values, and I'm going to draw a dependency between those inputs and that output.
3:35
Then it'll go and calculate the distribution plot. And again, when it calculates the distribution plot, it realizes I need the account selector.
3:42
So it goes and gets that value. But notice, since it didn't reach out for the metric selector
3:47
when it was calculating the distribution plot, it didn't draw a line there. We know that this selector is irrelevant to the value of this output,
3:55
because it wasn't in the code that we used to define that plot.
3:57
And right there, we have a complete graph without you, the user, needing to do anything. This is an accurate and minimal representation
4:05
of how things need to change when the application updates. So let's take a look at what happens when that updates.
4:11
When the user changes the account selector, changes this thing over here, Shiny does something called ""invalidate.
4:19
And what invalidation means is it follows these arrows down, and it tells anything immediately downstream of this selector that it's invalid.
4:27
It needs to be recalculated. The second thing that invalidation does is it causes those elements to forget all of their old dependencies.
4:35
Again, this is happening at runtime. So even though those were the dependencies last time this application runs,
4:40
they might not be the dependencies this time. Something might have changed. So it'll start from scratch and do the same thing.
4:47
Draw the metric plot, get its dependencies, draw the distribution plot, get its dependencies. And we're back to the complete graph.
4:54
When the metric input changes, though, something different happens. Or the same thing happens, but the effect on the application is different.
5:01
So we take this metric plot, follow its dependencies down to invalidate, and it'll only invalidate this metric plot.
5:09
So this needs to recalculate, and it forgets all of its dependencies. But importantly, since distribution plot wasn't dependent on the metric selector,
5:18
it's not invalidated. So it doesn't need to be recalculated, and its dependencies don't need to change. So Shiny just goes and draws the metric plot.
5:26
So it draws the thing that was invalid, goes and gets its inputs, and we're back. So this is what happens when I make a change here.
5:35
The only one that depends on this is this graph here. So that's the only one that gets invalidated, and none of this code runs again.
5:42
What's great about this system is that it scales really well. So when we have a simple graph like this, a simple application,
5:49
you know, you can, it'll do that rendering without you needing to take any action. But that's also true for very, very complicated ones.
5:56
So you can imagine this is a, you know, an application with dozens or hundreds of nodes,
6:01
and we're still going to have this quality of when things change, the application minimally re-renders in response to that change.
6:09
And all of this is accomplished without you, the developer of the application, needing to do very much work.
6:14
You don't need to define callback functions. You don't need to manually define this execution graph.
6:20
All you need to do is refer to inputs within rendering functions, and you'll get this graph for free. craft for free.