mirror of
https://github.com/rstudio/shiny.git
synced 2026-01-10 15:38:19 -05:00
500 lines
46 KiB
HTML
500 lines
46 KiB
HTML
<!DOCTYPE html>
|
||
<!-- Generated by pkgdown: do not edit by hand --><html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Add caching with reactivity to an object — bindCache • shiny</title><!-- jquery --><script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js" integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script><!-- Bootstrap --><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" integrity="sha256-bZLfwXAP04zRMK2BjiO8iu9pf4FbLqX6zitd+tIvLhE=" crossorigin="anonymous"><script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js" integrity="sha256-nuL8/2cJ5NDSSwnKD8VqreErSWHtnEP9E7AySL+1ev4=" crossorigin="anonymous"></script><!-- bootstrap-toc --><link rel="stylesheet" href="../bootstrap-toc.css"><script src="../bootstrap-toc.js"></script><!-- Font Awesome icons --><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.12.1/css/all.min.css" integrity="sha256-mmgLkCYLUQbXn0B1SRqzHar6dCnv9oZFPEC1g1cwlkk=" crossorigin="anonymous"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.12.1/css/v4-shims.min.css" integrity="sha256-wZjR52fzng1pJHwx4aV2AO3yyTOXrcDW7jBpJtTwVxw=" crossorigin="anonymous"><!-- clipboard.js --><script src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.6/clipboard.min.js" integrity="sha256-inc5kl9MA1hkeYUt+EC3BhlIgyp/2jDIyBLS6k3UxPI=" crossorigin="anonymous"></script><!-- headroom.js --><script src="https://cdnjs.cloudflare.com/ajax/libs/headroom/0.11.0/headroom.min.js" integrity="sha256-AsUX4SJE1+yuDu5+mAVzJbuYNPHj/WroHuZ8Ir/CkE0=" crossorigin="anonymous"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/headroom/0.11.0/jQuery.headroom.min.js" integrity="sha256-ZX/yNShbjqsohH1k95liqY9Gd8uOiE1S4vZc+9KQ1K4=" crossorigin="anonymous"></script><!-- pkgdown --><link href="../pkgdown.css" rel="stylesheet"><script src="../pkgdown.js"></script><meta property="og:title" content="Add caching with reactivity to an object — bindCache"><meta property="og:description" content="bindCache() adds caching reactive() expressions and render* functions
|
||
(like renderText(), renderTable(), ...).
|
||
Ordinary reactive() expressions automatically cache their most recent
|
||
value, which helps to avoid redundant computation in downstream reactives.
|
||
bindCache() will cache all previous values (as long as they fit in the
|
||
cache) and they can be shared across user sessions. This allows
|
||
bindCache() to dramatically improve performance when used correctly."><meta property="og:image" content="/logo.png"><!-- mathjax --><script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js" integrity="sha256-nvJJv9wWKEm88qvoQl9ekL2J+k/RWIsaSScxxlsrv8k=" crossorigin="anonymous"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/config/TeX-AMS-MML_HTMLorMML.js" integrity="sha256-84DKXVJXs0/F8OTMzX4UR909+jtl4G7SPypPavF+GfA=" crossorigin="anonymous"></script><!--[if lt IE 9]>
|
||
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
|
||
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
|
||
<![endif]--></head><body data-spy="scroll" data-target="#toc">
|
||
|
||
|
||
<div class="container template-reference-topic">
|
||
<header><div class="navbar navbar-default navbar-fixed-top" role="navigation">
|
||
<div class="container">
|
||
<div class="navbar-header">
|
||
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false">
|
||
<span class="sr-only">Toggle navigation</span>
|
||
<span class="icon-bar"></span>
|
||
<span class="icon-bar"></span>
|
||
<span class="icon-bar"></span>
|
||
</button>
|
||
<span class="navbar-brand">
|
||
<a class="navbar-link" href="../index.html">shiny</a>
|
||
<span class="version label label-default" data-toggle="tooltip" data-placement="bottom" title="">1.12.0</span>
|
||
</span>
|
||
</div>
|
||
|
||
<div id="navbar" class="navbar-collapse collapse">
|
||
<ul class="nav navbar-nav"><li>
|
||
<a href="../reference/index.html">Reference</a>
|
||
</li>
|
||
<li>
|
||
<a href="../news/index.html">Changelog</a>
|
||
</li>
|
||
</ul><ul class="nav navbar-nav navbar-right"><li>
|
||
<a href="https://github.com/rstudio/shiny/" class="external-link">
|
||
<span class="fab fa-github fa-lg"></span>
|
||
|
||
</a>
|
||
</li>
|
||
</ul></div><!--/.nav-collapse -->
|
||
</div><!--/.container -->
|
||
</div><!--/.navbar -->
|
||
|
||
|
||
|
||
</header><div class="row">
|
||
<div class="col-md-9 contents">
|
||
<div class="page-header">
|
||
<h1>Add caching with reactivity to an object</h1>
|
||
<small class="dont-index">Source: <a href="https://github.com/rstudio/shiny/blob/rc-v1.12.0/R/bind-cache.R" class="external-link"><code>R/bind-cache.R</code></a></small>
|
||
<div class="hidden name"><code>bindCache.Rd</code></div>
|
||
</div>
|
||
|
||
<div class="ref-description">
|
||
<p><code>bindCache()</code> adds caching <code><a href="reactive.html">reactive()</a></code> expressions and <code>render*</code> functions
|
||
(like <code><a href="renderPrint.html">renderText()</a></code>, <code><a href="renderTable.html">renderTable()</a></code>, ...).</p>
|
||
<p>Ordinary <code><a href="reactive.html">reactive()</a></code> expressions automatically cache their <em>most recent</em>
|
||
value, which helps to avoid redundant computation in downstream reactives.
|
||
<code>bindCache()</code> will cache all previous values (as long as they fit in the
|
||
cache) and they can be shared across user sessions. This allows
|
||
<code>bindCache()</code> to dramatically improve performance when used correctly.</p>
|
||
</div>
|
||
|
||
<div id="ref-usage">
|
||
<div class="sourceCode"><pre class="sourceCode r"><code><span><span class="fu">bindCache</span><span class="op">(</span><span class="va">x</span>, <span class="va">...</span>, cache <span class="op">=</span> <span class="st">"app"</span><span class="op">)</span></span></code></pre></div>
|
||
</div>
|
||
|
||
<div id="arguments">
|
||
<h2>Arguments</h2>
|
||
|
||
|
||
<dl><dt id="arg-x">x<a class="anchor" aria-label="anchor" href="#arg-x"></a></dt>
|
||
<dd><p>The object to add caching to.</p></dd>
|
||
|
||
|
||
<dt id="arg--">...<a class="anchor" aria-label="anchor" href="#arg--"></a></dt>
|
||
<dd><p>One or more expressions to use in the caching key.</p></dd>
|
||
|
||
|
||
<dt id="arg-cache">cache<a class="anchor" aria-label="anchor" href="#arg-cache"></a></dt>
|
||
<dd><p>The scope of the cache, or a cache object. This can be <code>"app"</code>
|
||
(the default), <code>"session"</code>, or a cache object like a
|
||
<code><a href="https://cachem.r-lib.org/reference/cache_disk.html" class="external-link">cachem::cache_disk()</a></code>. See the Cache Scoping section for more information.</p></dd>
|
||
|
||
</dl></div>
|
||
<div id="details">
|
||
<h2>Details</h2>
|
||
<p><code>bindCache()</code> requires one or more expressions that are used to generate a
|
||
<strong>cache key</strong>, which is used to determine if a computation has occurred
|
||
before and hence can be retrieved from the cache. If you're familiar with the
|
||
concept of memoizing pure functions (e.g., the <span class="pkg">memoise</span> package), you
|
||
can think of the cache key as the input(s) to a pure function. As such, one
|
||
should take care to make sure the use of <code>bindCache()</code> is <em>pure</em> in the same
|
||
sense, namely:</p><ol><li><p>For a given key, the return value is always the same.</p></li>
|
||
<li><p>Evaluation has no side-effects.</p></li>
|
||
</ol><p>In the example here, the <code>bindCache()</code> key consists of <code>input$x</code> and
|
||
<code>input$y</code> combined, and the value is <code>input$x * input$y</code>. In this simple
|
||
example, for any given key, there is only one possible returned value.</p>
|
||
<p></p><div class="sourceCode"><pre><code><span id="cb1-1"><a href="#cb1-1" tabindex="-1"></a>r <span class="ot"><-</span> <span class="fu">reactive</span>({ input<span class="sc">$</span>x <span class="sc">*</span> input<span class="sc">$</span>y }) <span class="sc">%>%</span></span>
|
||
<span id="cb1-2"><a href="#cb1-2" tabindex="-1"></a> <span class="fu">bindCache</span>(input<span class="sc">$</span>x, input<span class="sc">$</span>y)</span></code></pre><p></p></div>
|
||
<p>The largest performance improvements occur when the cache key is fast to
|
||
compute and the reactive expression is slow to compute. To see if the value
|
||
should be computed, a cached reactive evaluates the key, and then serializes
|
||
and hashes the result. If the resulting hashed key is in the cache, then the
|
||
cached reactive simply retrieves the previously calculated value and returns
|
||
it; if not, then the value is computed and the result is stored in the cache
|
||
before being returned.</p>
|
||
<p>To compute the cache key, <code>bindCache()</code> hashes the contents of <code>...</code>, so it's
|
||
best to avoid including large objects in a cache key since that can result in
|
||
slow hashing. It's also best to avoid reference objects like environments and
|
||
R6 objects, since the serialization of these objects may not capture relevant
|
||
changes.</p>
|
||
<p>If you want to use a large object as part of a cache key, it may make sense
|
||
to do some sort of reduction on the data that still captures information
|
||
about whether a value can be retrieved from the cache. For example, if you
|
||
have a large data set with timestamps, it might make sense to extract the
|
||
most recent timestamp and return that. Then, instead of hashing the entire
|
||
data object, the cached reactive only needs to hash the timestamp.</p>
|
||
<p></p><div class="sourceCode"><pre><code><span id="cb1-1"><a href="#cb1-1" tabindex="-1"></a>r <span class="ot"><-</span> <span class="fu">reactive</span>({ <span class="fu">compute</span>(<span class="fu">bigdata</span>()) } <span class="sc">%>%</span></span>
|
||
<span id="cb1-2"><a href="#cb1-2" tabindex="-1"></a> <span class="fu">bindCache</span>({ <span class="fu">extract_most_recent_time</span>(<span class="fu">bigdata</span>()) })</span></code></pre><p></p></div>
|
||
<p>For computations that are very slow, it often makes sense to pair
|
||
<code>bindCache()</code> with <code><a href="bindEvent.html">bindEvent()</a></code> so that no computation is performed until
|
||
the user explicitly requests it (for more, see the Details section of
|
||
<code><a href="bindEvent.html">bindEvent()</a></code>).</p>
|
||
</div>
|
||
<div id="cache-keys-and-reactivity">
|
||
<h2>Cache keys and reactivity</h2>
|
||
|
||
|
||
|
||
<p>Because the <strong>value</strong> expression (from the original <code><a href="reactive.html">reactive()</a></code>) is
|
||
cached, it is not necessarily re-executed when someone retrieves a value,
|
||
and therefore it can't be used to decide what objects to take reactive
|
||
dependencies on. Instead, the <strong>key</strong> is used to figure out which objects
|
||
to take reactive dependencies on. In short, the key expression is reactive,
|
||
and value expression is no longer reactive.</p>
|
||
<p>Here's an example of what not to do: if the key is <code>input$x</code> and the value
|
||
expression is from <code>reactive({input$x + input$y})</code>, then the resulting
|
||
cached reactive will only take a reactive dependency on <code>input$x</code> – it
|
||
won't recompute <code>{input$x + input$y}</code> when just <code>input$y</code> changes.
|
||
Moreover, the cache won't use <code>input$y</code> as part of the key, and so it could
|
||
return incorrect values in the future when it retrieves values from the
|
||
cache. (See the examples below for an example of this.)</p>
|
||
<p>A better cache key would be something like <code>input$x, input$y</code>. This does
|
||
two things: it ensures that a reactive dependency is taken on both
|
||
<code>input$x</code> and <code>input$y</code>, and it also makes sure that both values are
|
||
represented in the cache key.</p>
|
||
<p>In general, <code>key</code> should use the same reactive inputs as <code>value</code>, but the
|
||
computation should be simpler. If there are other (non-reactive) values
|
||
that are consumed, such as external data sources, they should be used in
|
||
the <code>key</code> as well. Note that if the <code>key</code> is large, it can make sense to do
|
||
some sort of reduction on it so that the serialization and hashing of the
|
||
cache key is not too expensive.</p>
|
||
<p>Remember that the key is <em>reactive</em>, so it is not re-executed every single
|
||
time that someone accesses the cached reactive. It is only re-executed if
|
||
it has been invalidated by one of the reactives it depends on. For
|
||
example, suppose we have this cached reactive:</p>
|
||
<p></p><div class="sourceCode"><pre><code><span id="cb1-1"><a href="#cb1-1" tabindex="-1"></a>r <span class="ot"><-</span> <span class="fu">reactive</span>({ input<span class="sc">$</span>x <span class="sc">*</span> input<span class="sc">$</span>y }) <span class="sc">%>%</span></span>
|
||
<span id="cb1-2"><a href="#cb1-2" tabindex="-1"></a> <span class="fu">bindCache</span>(input<span class="sc">$</span>x, input<span class="sc">$</span>y)</span></code></pre><p></p></div>
|
||
<p>In this case, the key expression is essentially <code>reactive(list(input$x, input$y))</code> (there's a bit more to it, but that's a good enough
|
||
approximation). The first time <code>r()</code> is called, it executes the key, then
|
||
fails to find it in the cache, so it executes the value expression, <code>{ input$x + input$y }</code>. If <code>r()</code> is called again, then it does not need to
|
||
re-execute the key expression, because it has not been invalidated via a
|
||
change to <code>input$x</code> or <code>input$y</code>; it simply returns the previous value.
|
||
However, if <code>input$x</code> or <code>input$y</code> changes, then the reactive expression will
|
||
be invalidated, and the next time that someone calls <code>r()</code>, the key
|
||
expression will need to be re-executed.</p>
|
||
<p>Note that if the cached reactive is passed to <code><a href="bindEvent.html">bindEvent()</a></code>, then the key
|
||
expression will no longer be reactive; instead, the event expression will be
|
||
reactive.</p>
|
||
</div>
|
||
<div id="cache-scope">
|
||
<h2>Cache scope</h2>
|
||
|
||
|
||
|
||
<p>By default, when <code>bindCache()</code> is used, it is scoped to the running
|
||
application. That means that it shares a cache with all user sessions
|
||
connected to the application (within the R process). This is done with the
|
||
<code>cache</code> parameter's default value, <code>"app"</code>.</p>
|
||
<p>With an app-level cache scope, one user can benefit from the work done for
|
||
another user's session. In most cases, this is the best way to get
|
||
performance improvements from caching. However, in some cases, this could
|
||
leak information between sessions. For example, if the cache key does not
|
||
fully encompass the inputs used by the value, then data could leak between
|
||
the sessions. Or if a user sees that a cached reactive returns its value
|
||
very quickly, they may be able to infer that someone else has already used
|
||
it with the same values.</p>
|
||
<p>It is also possible to scope the cache to the session, with
|
||
<code>cache="session"</code>. This removes the risk of information leaking between
|
||
sessions, but then one session cannot benefit from computations performed in
|
||
another session.</p>
|
||
<p>It is possible to pass in caching objects directly to
|
||
<code>bindCache()</code>. This can be useful if, for example, you want to use a
|
||
particular type of cache with specific cached reactives, or if you want to
|
||
use a <code><a href="https://cachem.r-lib.org/reference/cache_disk.html" class="external-link">cachem::cache_disk()</a></code> that is shared across multiple processes and
|
||
persists beyond the current R session.</p>
|
||
<p>To use different settings for an application-scoped cache, you can call
|
||
<code><a href="shinyOptions.html">shinyOptions()</a></code> at the top of your app.R, server.R, or
|
||
global.R. For example, this will create a cache with 500 MB of space
|
||
instead of the default 200 MB:</p>
|
||
<p></p><div class="sourceCode"><pre><code><span id="cb1-1"><a href="#cb1-1" tabindex="-1"></a><span class="fu">shinyOptions</span>(<span class="at">cache =</span> cachem<span class="sc">::</span><span class="fu">cache_mem</span>(<span class="at">max_size =</span> <span class="fl">500e6</span>))</span></code></pre><p></p></div>
|
||
<p>To use different settings for a session-scoped cache, you can set
|
||
<code>session$cache</code> at the top of your server function. By default, it will
|
||
create a 200 MB memory cache for each session, but you can replace it with
|
||
something different. To use the session-scoped cache, you must also call
|
||
<code>bindCache()</code> with <code>cache="session"</code>. This will create a 100 MB cache for
|
||
the session:</p>
|
||
<p></p><div class="sourceCode"><pre><code><span id="cb1-1"><a href="#cb1-1" tabindex="-1"></a><span class="cf">function</span>(input, output, session) {</span>
|
||
<span id="cb1-2"><a href="#cb1-2" tabindex="-1"></a> session<span class="sc">$</span>cache <span class="ot"><-</span> cachem<span class="sc">::</span><span class="fu">cache_mem</span>(<span class="at">max_size =</span> <span class="fl">100e6</span>)</span>
|
||
<span id="cb1-3"><a href="#cb1-3" tabindex="-1"></a> ...</span>
|
||
<span id="cb1-4"><a href="#cb1-4" tabindex="-1"></a>}</span></code></pre><p></p></div>
|
||
<p>If you want to use a cache that is shared across multiple R processes, you
|
||
can use a <code><a href="https://cachem.r-lib.org/reference/cache_disk.html" class="external-link">cachem::cache_disk()</a></code>. You can create a application-level shared
|
||
cache by putting this at the top of your app.R, server.R, or global.R:</p>
|
||
<p></p><div class="sourceCode"><pre><code><span id="cb1-1"><a href="#cb1-1" tabindex="-1"></a><span class="fu">shinyOptions</span>(<span class="at">cache =</span> cachem<span class="sc">::</span><span class="fu">cache_disk</span>(<span class="fu">file.path</span>(<span class="fu">dirname</span>(<span class="fu">tempdir</span>()), <span class="st">"myapp-cache"</span>)))</span></code></pre><p></p></div>
|
||
<p>This will create a subdirectory in your system temp directory named
|
||
<code>myapp-cache</code> (replace <code>myapp-cache</code> with a unique name of
|
||
your choosing). On most platforms, this directory will be removed when
|
||
your system reboots. This cache will persist across multiple starts and
|
||
stops of the R process, as long as you do not reboot.</p>
|
||
<p>To have the cache persist even across multiple reboots, you can create the
|
||
cache in a location outside of the temp directory. For example, it could
|
||
be a subdirectory of the application:</p>
|
||
<p></p><div class="sourceCode"><pre><code><span id="cb1-1"><a href="#cb1-1" tabindex="-1"></a><span class="fu">shinyOptions</span>(<span class="at">cache =</span> cachem<span class="sc">::</span><span class="fu">cache_disk</span>(<span class="st">"./myapp-cache"</span>))</span></code></pre><p></p></div>
|
||
<p>In this case, resetting the cache will have to be done manually, by deleting
|
||
the directory.</p>
|
||
<p>You can also scope a cache to just one item, or selected items. To do that,
|
||
create a <code><a href="https://cachem.r-lib.org/reference/cache_mem.html" class="external-link">cachem::cache_mem()</a></code> or <code><a href="https://cachem.r-lib.org/reference/cache_disk.html" class="external-link">cachem::cache_disk()</a></code>, and pass it
|
||
as the <code>cache</code> argument of <code>bindCache()</code>.</p>
|
||
</div>
|
||
<div id="computing-cache-keys">
|
||
<h2>Computing cache keys</h2>
|
||
|
||
|
||
|
||
<p>The actual cache key that is used internally takes value from evaluating
|
||
the key expression(s) (from the <code>...</code> arguments) and combines it with the
|
||
(unevaluated) value expression.</p>
|
||
<p>This means that if there are two cached reactives which have the same
|
||
result from evaluating the key, but different value expressions, then they
|
||
will not need to worry about collisions.</p>
|
||
<p>However, if two cached reactives have identical key and value expressions
|
||
expressions, they will share the cached values. This is useful when using
|
||
<code>cache="app"</code>: there may be multiple user sessions which create separate
|
||
cached reactive objects (because they are created from the same code in the
|
||
server function, but the server function is executed once for each user
|
||
session), and those cached reactive objects across sessions can share
|
||
values in the cache.</p>
|
||
</div>
|
||
<div id="async-with-cached-reactives">
|
||
<h2>Async with cached reactives</h2>
|
||
|
||
|
||
|
||
<p>With a cached reactive expression, the key and/or value expression can be
|
||
<em>asynchronous</em>. In other words, they can be promises — not regular R
|
||
promises, but rather objects provided by the
|
||
<a href="https://rstudio.github.io/promises/" class="external-link"><span class="pkg">promises</span></a> package, which
|
||
are similar to promises in JavaScript. (See <code><a href="https://rstudio.github.io/promises/reference/promise.html" class="external-link">promises::promise()</a></code> for more
|
||
information.) You can also use <code><a href="https://mirai.r-lib.org/reference/mirai.html" class="external-link">mirai::mirai()</a></code> or <code><a href="https://future.futureverse.org/reference/future.html" class="external-link">future::future()</a></code>
|
||
objects to run code in a separate process or even on a remote machine.</p>
|
||
<p>If the value returns a promise, then anything that consumes the cached
|
||
reactive must expect it to return a promise.</p>
|
||
<p>Similarly, if the key is a promise (in other words, if it is asynchronous),
|
||
then the entire cached reactive must be asynchronous, since the key must be
|
||
computed asynchronously before it knows whether to compute the value or the
|
||
value is retrieved from the cache. Anything that consumes the cached
|
||
reactive must therefore expect it to return a promise.</p>
|
||
</div>
|
||
<div id="developing-render-functions-for-caching">
|
||
<h2>Developing render functions for caching</h2>
|
||
|
||
|
||
|
||
<p>If you've implemented your own <code>render*()</code> function, it may just work with
|
||
<code>bindCache()</code>, but it is possible that you will need to make some
|
||
modifications. These modifications involve helping <code>bindCache()</code> avoid
|
||
cache collisions, dealing with internal state that may be set by the,
|
||
<code>render</code> function, and modifying the data as it goes in and comes out of
|
||
the cache.</p>
|
||
<p>You may need to provide a <code>cacheHint</code> to <code><a href="createRenderFunction.html">createRenderFunction()</a></code> (or
|
||
<code><a href="https://rdrr.io/pkg/htmlwidgets/man/htmlwidgets-shiny.html" class="external-link">htmlwidgets::shinyRenderWidget()</a></code>, if you've authored an htmlwidget) in
|
||
order for <code>bindCache()</code> to correctly compute a cache key.</p>
|
||
<p>The potential problem is a cache collision. Consider the following:</p>
|
||
<p></p><div class="sourceCode"><pre><code><span id="cb1-1"><a href="#cb1-1" tabindex="-1"></a>output<span class="sc">$</span>x1 <span class="ot"><-</span> <span class="fu">renderText</span>({ input<span class="sc">$</span>x }) <span class="sc">%>%</span> <span class="fu">bindCache</span>(input<span class="sc">$</span>x)</span>
|
||
<span id="cb1-2"><a href="#cb1-2" tabindex="-1"></a>output<span class="sc">$</span>x2 <span class="ot"><-</span> <span class="fu">renderText</span>({ input<span class="sc">$</span>x <span class="sc">*</span> <span class="dv">2</span> }) <span class="sc">%>%</span> <span class="fu">bindCache</span>(input<span class="sc">$</span>x)</span></code></pre><p></p></div>
|
||
<p>Both <code>output$x1</code> and <code>output$x2</code> use <code>input$x</code> as part of their cache key,
|
||
but if it were the only thing used in the cache key, then the two outputs
|
||
would have a cache collision, and they would have the same output. To avoid
|
||
this, a <em>cache hint</em> is automatically added when <code><a href="renderPrint.html">renderText()</a></code> calls
|
||
<code><a href="createRenderFunction.html">createRenderFunction()</a></code>. The cache hint is used as part of the actual
|
||
cache key, in addition to the one passed to <code>bindCache()</code> by the user. The
|
||
cache hint can be viewed by calling the internal Shiny function
|
||
<code>extractCacheHint()</code>:</p>
|
||
<p></p><div class="sourceCode"><pre><code><span id="cb1-1"><a href="#cb1-1" tabindex="-1"></a>r <span class="ot"><-</span> <span class="fu">renderText</span>({ input<span class="sc">$</span>x })</span>
|
||
<span id="cb1-2"><a href="#cb1-2" tabindex="-1"></a>shiny<span class="sc">:::</span><span class="fu">extractCacheHint</span>(r)</span></code></pre><p></p></div>
|
||
<p>This returns a nested list containing an item, <code>$origUserFunc$body</code>, which
|
||
in this case is the expression which was passed to <code><a href="renderPrint.html">renderText()</a></code>:
|
||
<code>{ input$x }</code>. This (quoted) expression is mixed into the actual cache
|
||
key, and it is how <code>output$x1</code> does not have collisions with <code>output$x2</code>.</p>
|
||
<p>For most developers of render functions, nothing extra needs to be done;
|
||
the automatic inference of the cache hint is sufficient. Again, you can
|
||
check it by calling <code>shiny:::extractCacheHint()</code>, and by testing the
|
||
render function for cache collisions in a real application.</p>
|
||
<p>In some cases, however, the automatic cache hint inference is not
|
||
sufficient, and it is necessary to provide a cache hint. This is true
|
||
for <code><a href="renderPrint.html">renderPrint()</a></code>. Unlike <code><a href="renderPrint.html">renderText()</a></code>, it wraps the user-provided
|
||
expression in another function, before passing it to <code><a href="createRenderFunction.html">createRenderFunction()</a></code>
|
||
(instead of <code><a href="createRenderFunction.html">createRenderFunction()</a></code>). Because the user code is wrapped in
|
||
another function, <code><a href="createRenderFunction.html">createRenderFunction()</a></code> is not able to automatically
|
||
extract the user-provided code and use it in the cache key. Instead,
|
||
<code>renderPrint</code> calls <code><a href="createRenderFunction.html">createRenderFunction()</a></code>, it explicitly passes along a
|
||
<code>cacheHint</code>, which includes a label and the original user expression.</p>
|
||
<p>In general, if you need to provide a <code>cacheHint</code>, it is best practice to
|
||
provide a <code>label</code> id, the user's <code>expr</code>, as well as any other arguments
|
||
that may influence the final value.</p>
|
||
<p>For <span class="pkg">htmlwidgets</span>, it will try to automatically infer a cache hint;
|
||
again, you can inspect the cache hint with <code>shiny:::extractCacheHint()</code> and
|
||
also test it in an application. If you do need to explicitly provide a
|
||
cache hint, pass it to <code>shinyRenderWidget</code>. For example:</p>
|
||
<p></p><div class="sourceCode"><pre><code><span id="cb1-1"><a href="#cb1-1" tabindex="-1"></a>renderMyWidget <span class="ot"><-</span> <span class="cf">function</span>(expr) {</span>
|
||
<span id="cb1-2"><a href="#cb1-2" tabindex="-1"></a> q <span class="ot"><-</span> rlang<span class="sc">::</span><span class="fu">enquo0</span>(expr)</span>
|
||
<span id="cb1-3"><a href="#cb1-3" tabindex="-1"></a></span>
|
||
<span id="cb1-4"><a href="#cb1-4" tabindex="-1"></a> htmlwidgets<span class="sc">::</span><span class="fu">shinyRenderWidget</span>(</span>
|
||
<span id="cb1-5"><a href="#cb1-5" tabindex="-1"></a> q,</span>
|
||
<span id="cb1-6"><a href="#cb1-6" tabindex="-1"></a> myWidgetOutput,</span>
|
||
<span id="cb1-7"><a href="#cb1-7" tabindex="-1"></a> <span class="at">quoted =</span> <span class="cn">TRUE</span>,</span>
|
||
<span id="cb1-8"><a href="#cb1-8" tabindex="-1"></a> <span class="at">cacheHint =</span> <span class="fu">list</span>(<span class="at">label =</span> <span class="st">"myWidget"</span>, <span class="at">userQuo =</span> q)</span>
|
||
<span id="cb1-9"><a href="#cb1-9" tabindex="-1"></a> )</span>
|
||
<span id="cb1-10"><a href="#cb1-10" tabindex="-1"></a>}</span></code></pre><p></p></div>
|
||
<p>If your <code>render</code> function sets any internal state, you may find it useful
|
||
in your call to <code><a href="createRenderFunction.html">createRenderFunction()</a></code> to use
|
||
the <code>cacheWriteHook</code> and/or <code>cacheReadHook</code> parameters. These hooks are
|
||
functions that run just before the object is stored in the cache, and just
|
||
after the object is retrieved from the cache. They can modify the data
|
||
that is stored and retrieved; this can be useful if extra information needs
|
||
to be stored in the cache. They can also be used to modify the state of the
|
||
application; for example, it can call <code><a href="createWebDependency.html">createWebDependency()</a></code> to make
|
||
JS/CSS resources available if the cached object is loaded in a different R
|
||
process. (See the source of <code><a href="https://rdrr.io/pkg/htmlwidgets/man/htmlwidgets-shiny.html" class="external-link">htmlwidgets::shinyRenderWidget</a></code> for an example
|
||
of this.)</p>
|
||
</div>
|
||
<div id="uncacheable-objects">
|
||
<h2>Uncacheable objects</h2>
|
||
|
||
|
||
|
||
<p>Some render functions cannot be cached, typically because they have side
|
||
effects or modify some external state, and they must re-execute each time
|
||
in order to work properly.</p>
|
||
<p>For developers of such code, they should call <code><a href="createRenderFunction.html">createRenderFunction()</a></code> (or
|
||
<code><a href="markRenderFunction.html">markRenderFunction()</a></code>) with <code>cacheHint = FALSE</code>.</p>
|
||
</div>
|
||
<div id="caching-with-renderplot-">
|
||
<h2>Caching with <code><a href="renderPlot.html">renderPlot()</a></code></h2>
|
||
|
||
|
||
|
||
<p>When <code>bindCache()</code> is used with <code><a href="renderPlot.html">renderPlot()</a></code>, the <code>height</code> and <code>width</code>
|
||
passed to the original <code><a href="renderPlot.html">renderPlot()</a></code> are ignored. They are superseded by
|
||
<code>sizePolicy</code> argument passed to `bindCache. The default is:</p>
|
||
<p></p><div class="sourceCode"><pre><code><span id="cb1-1"><a href="#cb1-1" tabindex="-1"></a>sizePolicy <span class="ot">=</span> <span class="fu">sizeGrowthRatio</span>(<span class="at">width =</span> <span class="dv">400</span>, <span class="at">height =</span> <span class="dv">400</span>, <span class="at">growthRate =</span> <span class="fl">1.2</span>)</span></code></pre><p></p></div>
|
||
<p><code>sizePolicy</code> must be a function that takes a two-element numeric vector as
|
||
input, representing the width and height of the <code><img></code> element in the
|
||
browser window, and it must return a two-element numeric vector, representing
|
||
the pixel dimensions of the plot to generate. The purpose is to round the
|
||
actual pixel dimensions from the browser to some other dimensions, so that
|
||
this will not generate and cache images of every possible pixel dimension.
|
||
See <code><a href="sizeGrowthRatio.html">sizeGrowthRatio()</a></code> for more information on the default sizing policy.</p>
|
||
</div>
|
||
<div id="see-also">
|
||
<h2>See also</h2>
|
||
<div class="dont-index"><p><code><a href="bindEvent.html">bindEvent()</a></code>, <code><a href="renderCachedPlot.html">renderCachedPlot()</a></code> for caching plots.</p></div>
|
||
</div>
|
||
|
||
<div id="ref-examples">
|
||
<h2>Examples</h2>
|
||
<div class="sourceCode"><pre class="sourceCode r"><code><span class="r-in"><span><span class="kw">if</span> <span class="op">(</span><span class="cn">FALSE</span><span class="op">)</span> <span class="op">{</span> <span class="co"># \dontrun{</span></span></span>
|
||
<span class="r-in"><span><span class="va">rc</span> <span class="op"><-</span> <span class="fu">bindCache</span><span class="op">(</span></span></span>
|
||
<span class="r-in"><span> x <span class="op">=</span> <span class="fu"><a href="reactive.html">reactive</a></span><span class="op">(</span><span class="op">{</span></span></span>
|
||
<span class="r-in"><span> <span class="fu"><a href="https://rdrr.io/r/base/Sys.sleep.html" class="external-link">Sys.sleep</a></span><span class="op">(</span><span class="fl">2</span><span class="op">)</span> <span class="co"># Pretend this is expensive</span></span></span>
|
||
<span class="r-in"><span> <span class="va">input</span><span class="op">$</span><span class="va">x</span> <span class="op">*</span> <span class="fl">100</span></span></span>
|
||
<span class="r-in"><span> <span class="op">}</span><span class="op">)</span>,</span></span>
|
||
<span class="r-in"><span> <span class="va">input</span><span class="op">$</span><span class="va">x</span></span></span>
|
||
<span class="r-in"><span><span class="op">)</span></span></span>
|
||
<span class="r-in"><span></span></span>
|
||
<span class="r-in"><span><span class="co"># Can make it prettier with the %>% operator</span></span></span>
|
||
<span class="r-in"><span><span class="kw"><a href="https://rdrr.io/r/base/library.html" class="external-link">library</a></span><span class="op">(</span><span class="va"><a href="https://magrittr.tidyverse.org" class="external-link">magrittr</a></span><span class="op">)</span></span></span>
|
||
<span class="r-in"><span></span></span>
|
||
<span class="r-in"><span><span class="va">rc</span> <span class="op"><-</span> <span class="fu"><a href="reactive.html">reactive</a></span><span class="op">(</span><span class="op">{</span></span></span>
|
||
<span class="r-in"><span> <span class="fu"><a href="https://rdrr.io/r/base/Sys.sleep.html" class="external-link">Sys.sleep</a></span><span class="op">(</span><span class="fl">2</span><span class="op">)</span></span></span>
|
||
<span class="r-in"><span> <span class="va">input</span><span class="op">$</span><span class="va">x</span> <span class="op">*</span> <span class="fl">100</span></span></span>
|
||
<span class="r-in"><span><span class="op">}</span><span class="op">)</span> <span class="op"><a href="https://magrittr.tidyverse.org/reference/pipe.html" class="external-link">%>%</a></span></span></span>
|
||
<span class="r-in"><span> <span class="fu">bindCache</span><span class="op">(</span><span class="va">input</span><span class="op">$</span><span class="va">x</span><span class="op">)</span></span></span>
|
||
<span class="r-in"><span></span></span>
|
||
<span class="r-in"><span><span class="op">}</span> <span class="co"># }</span></span></span>
|
||
<span class="r-in"><span></span></span>
|
||
<span class="r-in"><span><span class="co">## Only run app examples in interactive R sessions</span></span></span>
|
||
<span class="r-in"><span><span class="kw">if</span> <span class="op">(</span><span class="fu"><a href="https://rdrr.io/r/base/interactive.html" class="external-link">interactive</a></span><span class="op">(</span><span class="op">)</span><span class="op">)</span> <span class="op">{</span></span></span>
|
||
<span class="r-in"><span></span></span>
|
||
<span class="r-in"><span><span class="co"># Basic example</span></span></span>
|
||
<span class="r-in"><span><span class="fu"><a href="shinyApp.html">shinyApp</a></span><span class="op">(</span></span></span>
|
||
<span class="r-in"><span> ui <span class="op">=</span> <span class="fu"><a href="fluidPage.html">fluidPage</a></span><span class="op">(</span></span></span>
|
||
<span class="r-in"><span> <span class="fu"><a href="sliderInput.html">sliderInput</a></span><span class="op">(</span><span class="st">"x"</span>, <span class="st">"x"</span>, <span class="fl">1</span>, <span class="fl">10</span>, <span class="fl">5</span><span class="op">)</span>,</span></span>
|
||
<span class="r-in"><span> <span class="fu"><a href="sliderInput.html">sliderInput</a></span><span class="op">(</span><span class="st">"y"</span>, <span class="st">"y"</span>, <span class="fl">1</span>, <span class="fl">10</span>, <span class="fl">5</span><span class="op">)</span>,</span></span>
|
||
<span class="r-in"><span> <span class="fu"><a href="https://rstudio.github.io/htmltools/reference/builder.html" class="external-link">div</a></span><span class="op">(</span><span class="st">"x * y: "</span><span class="op">)</span>,</span></span>
|
||
<span class="r-in"><span> <span class="fu"><a href="textOutput.html">verbatimTextOutput</a></span><span class="op">(</span><span class="st">"txt"</span><span class="op">)</span></span></span>
|
||
<span class="r-in"><span> <span class="op">)</span>,</span></span>
|
||
<span class="r-in"><span> server <span class="op">=</span> <span class="kw">function</span><span class="op">(</span><span class="va">input</span>, <span class="va">output</span><span class="op">)</span> <span class="op">{</span></span></span>
|
||
<span class="r-in"><span> <span class="va">r</span> <span class="op"><-</span> <span class="fu"><a href="reactive.html">reactive</a></span><span class="op">(</span><span class="op">{</span></span></span>
|
||
<span class="r-in"><span> <span class="co"># The value expression is an _expensive_ computation</span></span></span>
|
||
<span class="r-in"><span> <span class="fu"><a href="https://rdrr.io/r/base/message.html" class="external-link">message</a></span><span class="op">(</span><span class="st">"Doing expensive computation..."</span><span class="op">)</span></span></span>
|
||
<span class="r-in"><span> <span class="fu"><a href="https://rdrr.io/r/base/Sys.sleep.html" class="external-link">Sys.sleep</a></span><span class="op">(</span><span class="fl">2</span><span class="op">)</span></span></span>
|
||
<span class="r-in"><span> <span class="va">input</span><span class="op">$</span><span class="va">x</span> <span class="op">*</span> <span class="va">input</span><span class="op">$</span><span class="va">y</span></span></span>
|
||
<span class="r-in"><span> <span class="op">}</span><span class="op">)</span> <span class="op"><a href="https://magrittr.tidyverse.org/reference/pipe.html" class="external-link">%>%</a></span></span></span>
|
||
<span class="r-in"><span> <span class="fu">bindCache</span><span class="op">(</span><span class="va">input</span><span class="op">$</span><span class="va">x</span>, <span class="va">input</span><span class="op">$</span><span class="va">y</span><span class="op">)</span></span></span>
|
||
<span class="r-in"><span></span></span>
|
||
<span class="r-in"><span> <span class="va">output</span><span class="op">$</span><span class="va">txt</span> <span class="op"><-</span> <span class="fu"><a href="renderPrint.html">renderText</a></span><span class="op">(</span><span class="fu">r</span><span class="op">(</span><span class="op">)</span><span class="op">)</span></span></span>
|
||
<span class="r-in"><span> <span class="op">}</span></span></span>
|
||
<span class="r-in"><span><span class="op">)</span></span></span>
|
||
<span class="r-in"><span></span></span>
|
||
<span class="r-in"><span></span></span>
|
||
<span class="r-in"><span><span class="co"># Caching renderText</span></span></span>
|
||
<span class="r-in"><span><span class="fu"><a href="shinyApp.html">shinyApp</a></span><span class="op">(</span></span></span>
|
||
<span class="r-in"><span> ui <span class="op">=</span> <span class="fu"><a href="fluidPage.html">fluidPage</a></span><span class="op">(</span></span></span>
|
||
<span class="r-in"><span> <span class="fu"><a href="sliderInput.html">sliderInput</a></span><span class="op">(</span><span class="st">"x"</span>, <span class="st">"x"</span>, <span class="fl">1</span>, <span class="fl">10</span>, <span class="fl">5</span><span class="op">)</span>,</span></span>
|
||
<span class="r-in"><span> <span class="fu"><a href="sliderInput.html">sliderInput</a></span><span class="op">(</span><span class="st">"y"</span>, <span class="st">"y"</span>, <span class="fl">1</span>, <span class="fl">10</span>, <span class="fl">5</span><span class="op">)</span>,</span></span>
|
||
<span class="r-in"><span> <span class="fu"><a href="https://rstudio.github.io/htmltools/reference/builder.html" class="external-link">div</a></span><span class="op">(</span><span class="st">"x * y: "</span><span class="op">)</span>,</span></span>
|
||
<span class="r-in"><span> <span class="fu"><a href="textOutput.html">verbatimTextOutput</a></span><span class="op">(</span><span class="st">"txt"</span><span class="op">)</span></span></span>
|
||
<span class="r-in"><span> <span class="op">)</span>,</span></span>
|
||
<span class="r-in"><span> server <span class="op">=</span> <span class="kw">function</span><span class="op">(</span><span class="va">input</span>, <span class="va">output</span><span class="op">)</span> <span class="op">{</span></span></span>
|
||
<span class="r-in"><span> <span class="va">output</span><span class="op">$</span><span class="va">txt</span> <span class="op"><-</span> <span class="fu"><a href="renderPrint.html">renderText</a></span><span class="op">(</span><span class="op">{</span></span></span>
|
||
<span class="r-in"><span> <span class="fu"><a href="https://rdrr.io/r/base/message.html" class="external-link">message</a></span><span class="op">(</span><span class="st">"Doing expensive computation..."</span><span class="op">)</span></span></span>
|
||
<span class="r-in"><span> <span class="fu"><a href="https://rdrr.io/r/base/Sys.sleep.html" class="external-link">Sys.sleep</a></span><span class="op">(</span><span class="fl">2</span><span class="op">)</span></span></span>
|
||
<span class="r-in"><span> <span class="va">input</span><span class="op">$</span><span class="va">x</span> <span class="op">*</span> <span class="va">input</span><span class="op">$</span><span class="va">y</span></span></span>
|
||
<span class="r-in"><span> <span class="op">}</span><span class="op">)</span> <span class="op"><a href="https://magrittr.tidyverse.org/reference/pipe.html" class="external-link">%>%</a></span></span></span>
|
||
<span class="r-in"><span> <span class="fu">bindCache</span><span class="op">(</span><span class="va">input</span><span class="op">$</span><span class="va">x</span>, <span class="va">input</span><span class="op">$</span><span class="va">y</span><span class="op">)</span></span></span>
|
||
<span class="r-in"><span> <span class="op">}</span></span></span>
|
||
<span class="r-in"><span><span class="op">)</span></span></span>
|
||
<span class="r-in"><span></span></span>
|
||
<span class="r-in"><span></span></span>
|
||
<span class="r-in"><span><span class="co"># Demo of using events and caching with an actionButton</span></span></span>
|
||
<span class="r-in"><span><span class="fu"><a href="shinyApp.html">shinyApp</a></span><span class="op">(</span></span></span>
|
||
<span class="r-in"><span> ui <span class="op">=</span> <span class="fu"><a href="fluidPage.html">fluidPage</a></span><span class="op">(</span></span></span>
|
||
<span class="r-in"><span> <span class="fu"><a href="sliderInput.html">sliderInput</a></span><span class="op">(</span><span class="st">"x"</span>, <span class="st">"x"</span>, <span class="fl">1</span>, <span class="fl">10</span>, <span class="fl">5</span><span class="op">)</span>,</span></span>
|
||
<span class="r-in"><span> <span class="fu"><a href="sliderInput.html">sliderInput</a></span><span class="op">(</span><span class="st">"y"</span>, <span class="st">"y"</span>, <span class="fl">1</span>, <span class="fl">10</span>, <span class="fl">5</span><span class="op">)</span>,</span></span>
|
||
<span class="r-in"><span> <span class="fu"><a href="actionButton.html">actionButton</a></span><span class="op">(</span><span class="st">"go"</span>, <span class="st">"Go"</span><span class="op">)</span>,</span></span>
|
||
<span class="r-in"><span> <span class="fu"><a href="https://rstudio.github.io/htmltools/reference/builder.html" class="external-link">div</a></span><span class="op">(</span><span class="st">"x * y: "</span><span class="op">)</span>,</span></span>
|
||
<span class="r-in"><span> <span class="fu"><a href="textOutput.html">verbatimTextOutput</a></span><span class="op">(</span><span class="st">"txt"</span><span class="op">)</span></span></span>
|
||
<span class="r-in"><span> <span class="op">)</span>,</span></span>
|
||
<span class="r-in"><span> server <span class="op">=</span> <span class="kw">function</span><span class="op">(</span><span class="va">input</span>, <span class="va">output</span><span class="op">)</span> <span class="op">{</span></span></span>
|
||
<span class="r-in"><span> <span class="va">r</span> <span class="op"><-</span> <span class="fu"><a href="reactive.html">reactive</a></span><span class="op">(</span><span class="op">{</span></span></span>
|
||
<span class="r-in"><span> <span class="fu"><a href="https://rdrr.io/r/base/message.html" class="external-link">message</a></span><span class="op">(</span><span class="st">"Doing expensive computation..."</span><span class="op">)</span></span></span>
|
||
<span class="r-in"><span> <span class="fu"><a href="https://rdrr.io/r/base/Sys.sleep.html" class="external-link">Sys.sleep</a></span><span class="op">(</span><span class="fl">2</span><span class="op">)</span></span></span>
|
||
<span class="r-in"><span> <span class="va">input</span><span class="op">$</span><span class="va">x</span> <span class="op">*</span> <span class="va">input</span><span class="op">$</span><span class="va">y</span></span></span>
|
||
<span class="r-in"><span> <span class="op">}</span><span class="op">)</span> <span class="op"><a href="https://magrittr.tidyverse.org/reference/pipe.html" class="external-link">%>%</a></span></span></span>
|
||
<span class="r-in"><span> <span class="fu">bindCache</span><span class="op">(</span><span class="va">input</span><span class="op">$</span><span class="va">x</span>, <span class="va">input</span><span class="op">$</span><span class="va">y</span><span class="op">)</span> <span class="op"><a href="https://magrittr.tidyverse.org/reference/pipe.html" class="external-link">%>%</a></span></span></span>
|
||
<span class="r-in"><span> <span class="fu"><a href="bindEvent.html">bindEvent</a></span><span class="op">(</span><span class="va">input</span><span class="op">$</span><span class="va">go</span><span class="op">)</span></span></span>
|
||
<span class="r-in"><span> <span class="co"># The cached, eventified reactive takes a reactive dependency on</span></span></span>
|
||
<span class="r-in"><span> <span class="co"># input$go, but doesn't use it for the cache key. It uses input$x and</span></span></span>
|
||
<span class="r-in"><span> <span class="co"># input$y for the cache key, but doesn't take a reactive dependency on</span></span></span>
|
||
<span class="r-in"><span> <span class="co"># them, because the reactive dependency is superseded by addEvent().</span></span></span>
|
||
<span class="r-in"><span></span></span>
|
||
<span class="r-in"><span> <span class="va">output</span><span class="op">$</span><span class="va">txt</span> <span class="op"><-</span> <span class="fu"><a href="renderPrint.html">renderText</a></span><span class="op">(</span><span class="fu">r</span><span class="op">(</span><span class="op">)</span><span class="op">)</span></span></span>
|
||
<span class="r-in"><span> <span class="op">}</span></span></span>
|
||
<span class="r-in"><span><span class="op">)</span></span></span>
|
||
<span class="r-in"><span></span></span>
|
||
<span class="r-in"><span><span class="op">}</span></span></span>
|
||
<span class="r-in"><span></span></span>
|
||
</code></pre></div>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-3 hidden-xs hidden-sm" id="pkgdown-sidebar">
|
||
<nav id="toc" data-toggle="toc" class="sticky-top"><h2 data-toc-skip>Contents</h2>
|
||
</nav></div>
|
||
</div>
|
||
|
||
|
||
<footer><div class="copyright">
|
||
<p></p><p>Developed by Winston Chang, Joe Cheng, JJ Allaire, Carson Sievert, Barret Schloerke, Garrick Aden-Buie, Yihui Xie, Jeff Allen, Jonathan McPherson, Alan Dipert, Barbara Borges, Posit Software, PBC.</p>
|
||
</div>
|
||
|
||
<div class="pkgdown">
|
||
<p></p><p>Site built with <a href="https://pkgdown.r-lib.org/" class="external-link">pkgdown</a> 2.2.0.</p>
|
||
</div>
|
||
|
||
</footer></div>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
</body></html>
|
||
|