Turn ggplot formulas into interactive Shiny apps instantly
ggpaintr turns a plain ggplot2 call into a running Shiny app - drop in placeholder tokens like ppVar/ppNum and each becomes an input widget.
0.11.1Add to Favorites
Why it matters
Transform static ggplot2 visualizations into interactive Shiny applications without writing UI or server code, enabling rapid prototyping and deployment of data exploration tools.
Outcomes
What it gets done
Convert ggplot expressions into live Shiny apps with placeholder keywords
Generate input widgets automatically from ppVar, ppNum, ppText, and ppExpr tokens
Scaffold interactive plot controls with RStudio addin command palette
Reuse plot formulas across static and interactive contexts with identical syntax
Source
Get it from source
Spark does not host a copy of it.
Open sourceReports
Agent outcome reports
No reports yet
Overview
Ggpaintr
ggpaintr turns a ggplot2 formula into a running Shiny app by placing placeholder tokens (ppVar, ppNum, ppText, ppExpr, ppUpload) where a value would normally go, each becoming an input widget, with an RStudio addin to insert them and a documented safety model governing which expressions run. Use it to add interactivity to an existing ggplot2 chart without writing Shiny code by hand; the same formula without ptr_app() remains ordinary, runnable ggplot2.
What it does
ggpaintr turns a ggplot-like formula into a running Shiny app, with no Shiny UI or server code required. You write a single ggplot() call, either passed directly as an unquoted expression (the primary form) or as a string (a fallback), and drop placeholder keywords anywhere a value would normally go: ppVar (column pickers), ppText (text input), ppNum (numeric input), ppExpr (a code box for expressions like a facet spec), and ppUpload. Each keyword becomes one input widget, the same parsed object drives the UI, the plot, and a live code pane, and editing any widget re-renders the plot. Calling library(ggpaintr) also attaches ggplot2, so bare ggplot()/aes()/geom_*() calls work directly inside the formula expression.
Each pp* token is also a plain identity function - ppVar(Sepal.Length) evaluates to Sepal.Length, ppNum(3) to 3 - so the exact same formula without ptr_app() is ordinary, runnable ggplot2. That means a plot can be sketched as a static chart first and only wrapped in ptr_app() once widgets are wanted, and the value inside each placeholder becomes that widget's initial selection when the app launches, rendering the same figure immediately.
When to use - and when NOT to
Use it when you want to turn a ggplot2 chart into an interactive exploration tool without writing Shiny UI or server code by hand - anywhere a static chart's axis, color, size, title, or facet spec should become something a viewer can change live. A formula can be captured once with rlang::expr() and reused: ptr_app() resolves a formula stored in a variable by name directly, or spliced in with !!, so it can be built, inspected, or shared before launching.
An RStudio addin removes the need to type pp* tokens by hand: highlight a piece of code, run the addin, and pick a placeholder from a command palette that orders suggestions by what's highlighted (a bare name surfaces ppVar, a number surfaces ppNum, a string surfaces ppText); its "Wrap in app" button scaffolds the whole selection into a runnable ptr_app() call.
Inputs and outputs
Beyond the five built-in placeholder keywords, custom placeholders can be defined via the package's three constructors: ptr_define_placeholder_value() and its _consumer() and _source() counterparts. Several plots can be embedded in a hand-written Shiny app with ptr_ui()/ptr_server(), sharing one control across them via ptr_shared()/ptr_shared_panel()/ptr_shared_server(). A documented safety model (expr_check, a denylist plus an AST-walker, and an upload trust boundary) governs what expressions are allowed to run.
Integrations
install.packages("ggpaintr")
Or the development version via pak::pkg_install("willju-wangqian/ggpaintr"). A documented integration with the ellmer package wires ggpaintr up as an LLM-callable tool - registering a primer and topic-lookup tool, inspecting what the model sees, and testing without spending tokens.
Who it's for
R/ggplot2 users who want to turn an existing static chart into an interactive Shiny app - or expose plotting as an LLM-callable tool via ellmer - without hand-writing Shiny UI and server code.
Source README
ggpaintr 
Overview
ggpaintr turns a ggplot-like formula into a running Shiny app. You write
a single ggplot() call - passed directly as an unquoted expression
(the primary form), or as a string (the fallback) - and drop placeholder
keywords (ppVar, ppText, ppNum, ppExpr, ppUpload) anywhere a
value would normally go. ggpaintr does the rest: each keyword becomes an
input widget, the same parsed object drives the UI, the plot, and a live
code pane, and editing any widget re-renders the plot.
No Shiny UI or server code required. If you can write a ggplot call, you
can ship an interactive version of it.
Installation
# Install the released version from CRAN:
install.packages("ggpaintr")
# Or the development version from GitHub:
# install.packages("pak")
pak::pkg_install("willju-wangqian/ggpaintr")
Usage
library(ggpaintr)
# Primary form: pass the ggplot() call directly as an unquoted expression.
ptr_app(
ggplot(data = iris, aes(x = ppVar, y = ppVar)) +
geom_point(aes(color = ppVar), size = ppNum) +
labs(title = ppText) +
facet_wrap(ppExpr)
)
That single call returns a running Shiny app. Each placeholder in the
formula becomes one widget:
- the three
ppVartokens → column pickers populated fromiris, ppNum→ a numeric input (point size),ppText→ a text input (plot title),ppExpr→ a code box for the facet spec (e.g.vars(Species)).
library(ggpaintr) also attaches ggplot2, so bare ggplot() /aes() / geom_*() calls work directly in the formula expression. To
reuse a formula across calls, store it with rlang::expr() and splice
it in with !! - f <- rlang::expr(ggplot(...)); ptr_app(!!f). The
string form (ptr_app("ggplot(...)")) remains a supported fallback,
handy when a formula is built or fetched as text. To swap in a custom
page shell or theme, write a thin wrapper on top of the public
primitives.
The ptr_app() call above needs a Shiny session to run, so it is not
evaluated here. But each pp* token is a plain identity function, so
the same formula without ptr_app() is ordinary, runnable ggplot2
- drop the placeholders into your plot, sketch it as a static chart
first, then wrap it inptr_app()when you want the widgets:
ggplot(iris, aes(x = ppVar(Sepal.Length), y = ppVar(Sepal.Width), color = ppVar(Species))) +
geom_point(size = ppNum(3)) +
labs(x = "Sepal.Length", y = "Sepal.Width", color = "Species")
Here ppVar(Sepal.Length) evaluates to Sepal.Length and ppNum(3) to3, so the chart renders exactly as plain ggplot2 - the tokens only
become widgets once the expression is handed to ptr_app().
Handing that exact expression to ptr_app() launches the interactive
app, and the value inside each placeholder becomes that widget’s
initial selection - so the app boots with x = Sepal.Length,y = Sepal.Width, color = Species, size = 3 pre-selected and
renders the very same figure straight away, ready to be re-pointed at
other columns:
ptr_app(
ggplot(iris, aes(x = ppVar(Sepal.Length),
y = ppVar(Sepal.Width),
color = ppVar(Species))) +
geom_point(size = ppNum(3))
)
The expression need not be inline. Capture it once with rlang::expr()
and reuse it - ptr_app() resolves a formula stored in a variable by
name (no !! needed), so you can build, inspect, or share the formula
before launching the app:
formula_iris <- rlang::expr(
ggplot(iris, aes(x = ppVar(Sepal.Length),
y = ppVar(Sepal.Width),
color = ppVar(Species))) +
geom_point(size = ppNum(3))
)
ptr_app(formula_iris)
RStudio addin
ggpaintr ships an RStudio addin that turns a plain ggplot expression
into a placeholder formula without typing the pp* tokens by hand.
Highlight a piece of your code, run the addin, and pick a placeholder
from a command palette - the selection is rewritten in place (mpg →ppVar(mpg); nothing highlighted inserts ppVar() with the cursor
between the parens). The list is ordered by what you highlighted - a
bare name surfaces the column pickers (ppVar), a number ppNum, a
string ppText, a call the layer/verb toggles (ppLayerOff /ppVerbSwitch) - and any placeholder you registered this session
appears automatically. The palette’s Wrap in app button wraps the
whole selection in { … } |> ptr_app() to scaffold a runnable app.
Run it from the Addins menu ▸ ggpaintr placeholder. To bind a
keyboard shortcut (the addin must be installed, not justload_all()-ed, to appear in the dialog):
- Tools ▸ Addins ▸ Browse Addins…, then the Keyboard Shortcuts…
button. (Or Tools ▸ Modify Keyboard Shortcuts… and type “ggpaintr”
in the search box.) - Click the Shortcut cell on the ggpaintr placeholder row.
- Press your combination - e.g. Cmd+Shift+G (macOS) or
Ctrl+Shift+G (Windows/Linux). Any free combination works. - Apply.
More topics
vignette("ggpaintr-tutorial")- Tutorial. A guided walk from
a one-lineptr_app()formula, through defining your own
placeholders (every argument of the three constructorsptr_define_placeholder_value()/_consumer()/_source()), to
embedding several plots in your own Shiny app withptr_ui()/ptr_server()and sharing one control across them viaptr_shared()/ptr_shared_panel()/ptr_shared_server(). Opens
with a tour of the five built-in placeholder keywords.**Safety
- chapter of the ggpaintr book (development-version docs).** What
expr_checkdoes and when (never) to turn it off, the denylist +
AST-walker safety model, and the upload trust boundary.
- chapter of the ggpaintr book (development-version docs).** What
Using ggpaintr from an LLM with
ellmer -
chapter of the ggpaintr book (development-version docs). Wire
ggpaintr up as an LLM-callable tool with ellmer: register the primer
and the topic-lookup tool, inspect what the model sees, swap
providers/models, and test without spending tokens.
The pkgdown reference
lists every exported function. The ggpaintr
book is the longer,
book-length introduction (documents the development version).
FAQ
Common questions
Discussion
Questions & comments · 0
Sign In Sign in to leave a comment.