| Title: | Build Formula-Driven 'shiny' Apps for 'ggplot2' |
|---|---|
| Description: | Turns a single 'ggplot2'-style formula string into a small 'shiny' application. Placeholder tokens in the formula become input widgets automatically; the package completes the expression with the current input values, renders the plot, shows the generated code, and supports uploaded datasets in different formats. |
| Authors: | Wangqian Ju [aut, cre] (ORCID: <https://orcid.org/0000-0002-9977-377X>), Jinji Pang [aut] (ORCID: <https://orcid.org/0000-0002-2267-2313>), Zhili Qiao [aut] (ORCID: <https://orcid.org/0000-0002-8154-0898>) |
| Maintainer: | Wangqian Ju <[email protected]> |
| License: | GPL-3 |
| Version: | 0.11.1 |
| Built: | 2026-07-07 17:19:52 UTC |
| Source: | https://github.com/cran/ggpaintr |
A placeholder-embellished ggplot expression must stay valid plain R that
renders the original plot with no app running. Each placeholder carries a
callable (the embellish_eval = argument of the
ptr_define_placeholder_*() constructors) that supplies its plain-R
meaning when the naked expression is evaluated outside ptr_app(). These
two factories are the built-in callables for that slot:
embellish_identity() embellish_symbol_to_string()embellish_identity() embellish_symbol_to_string()
embellish_identity() returns the identity function(x, ...) x — the
slot's default behaviour. The placeholder call becomes a no-op wrapper:
it returns its argument unchanged.
embellish_symbol_to_string() returns a function that captures its
argument unevaluated and turns column references into a character
vector of names. This is the pattern a column-selecting consumer needs
so the naked expression works inside a tidyselect verb: tidyselect
evaluates an unknown wrapper call in non-masked scope, where bare column
symbols throw "object not found"; returning the names as strings
sidesteps that because tidyselect accepts selection by name.
These helpers are author-controlled plain-R semantics, never derived — only the author knows the intended live-R meaning of a placeholder.
Each factory returns a function of signature function(x, ...).
embellish_identity()'s function returns its first argument x
unchanged. embellish_symbol_to_string()'s function returns a character
vector of column names captured from the unevaluated x.
f <- embellish_identity() f(5L) g <- embellish_symbol_to_string() g(c(mpg, hp)) g(mpg)f <- embellish_identity() f(5L) g <- embellish_symbol_to_string() g(c(mpg, hp)) g(mpg)
ADR-0020 structural keyword that marks a layer as off-by-default in
ptr_app(). Inside ptr_app() / ptr_server() the parser sees the
wrapper and unwraps it at translate time, stamping the boot-state
metadata on the resulting node: ppLayerOff(layer_expr, hide = TRUE)
becomes a ptr_layer with default_active = FALSE. The wrapper
itself never appears in the typed tree.
ppLayerOff(layer_expr, hide = TRUE)ppLayerOff(layer_expr, hide = TRUE)
layer_expr |
A ggplot2 layer expression (e.g. |
hide |
A length-1 logical literal ( |
Outside ptr_app() it behaves per its R semantics so naked-ggplot
scripts still render: ppLayerOff(geom_point(), TRUE) returns NULL
(so ggplot(mtcars, aes(x = mpg, y = wt)) + ppLayerOff(geom_point(), TRUE)
renders without the hidden layer); ppLayerOff(geom_point(), FALSE)
returns the layer.
For the pipeline-stage sibling that exposes a user-toggleable checkbox
(ADR-0021), see ppVerbSwitch().
Outside ptr_app(): NULL when hide = TRUE, otherwise the
evaluated layer_expr.
library(ggplot2) # Naked-R semantics: hide = TRUE drops the layer to NULL. p1 <- ggplot(mtcars, aes(x = mpg, y = wt)) + ppLayerOff(geom_point(), TRUE) # equivalent to no geom_point p2 <- ggplot(mtcars, aes(x = mpg, y = wt)) + ppLayerOff(geom_point(), FALSE) # the layer is added # Inside ptr_app(), the wrapper becomes a node-level default and a # boot-state-off checkbox: if (interactive()) { ptr_app(ggplot() + ppLayerOff(geom_point(aes(x = mpg, y = wt)), TRUE)) }library(ggplot2) # Naked-R semantics: hide = TRUE drops the layer to NULL. p1 <- ggplot(mtcars, aes(x = mpg, y = wt)) + ppLayerOff(geom_point(), TRUE) # equivalent to no geom_point p2 <- ggplot(mtcars, aes(x = mpg, y = wt)) + ppLayerOff(geom_point(), FALSE) # the layer is added # Inside ptr_app(), the wrapper becomes a node-level default and a # boot-state-off checkbox: if (interactive()) { ptr_app(ggplot() + ppLayerOff(geom_point(aes(x = mpg, y = wt)), TRUE)) }
These are the plain-R callables returned by registering the five
built-in ggpaintr placeholders (ppVar, ppNum, ppText, ppExpr,
ppUpload). Inside a formula passed to ptr_app() / ptr_server()
the parser recognises calls to these names as placeholder invocations
and binds them to Shiny widgets (see vignette("ggpaintr-tutorial")).
Outside ptr_app() they behave as plain R functions: the first four
return their argument unchanged (identity), so a formula such as
aes(x = ppVar(mpg)) evaluates identically to aes(x = mpg) under
ggplot2's tidy-eval. ppUpload is identical when called with an
argument (ppUpload(penguins) returns penguins), so a formula such
as ppUpload(penguins) |> filter(...) |> ggplot(...) evaluates as
plain R when penguins is in scope. The no-arg form ppUpload()
aborts outside ptr_app() — it is meaningful only as a placeholder
slot.
ppVar(x = NULL, ...) ppNum(x = NULL, ...) ppText(x = NULL, ...) ppExpr(x = NULL, ...) ppUpload(x, ...)ppVar(x = NULL, ...) ppNum(x = NULL, ...) ppText(x = NULL, ...) ppExpr(x = NULL, ...) ppUpload(x, ...)
x |
A column name ( |
... |
Additional arguments (e.g. named arguments consumed by a
custom placeholder's |
The input value unchanged. The no-arg form ppUpload() does
not return; it aborts with a guard message.
# Identity inside ggplot2's tidy-eval: library(ggplot2) p1 <- ggplot(mtcars, aes(x = mpg)) + geom_histogram(bins = 10) p2 <- ggplot(mtcars, aes(x = ppVar(mpg))) + geom_histogram(bins = 10) # p1 and p2 produce the same plot. # Inside ptr_app() / ptr_server(), the same call binds to a column picker: if (interactive()) { ptr_app(ggplot(mtcars, aes(x = ppVar(mpg), y = ppVar(wt))) + geom_point()) }# Identity inside ggplot2's tidy-eval: library(ggplot2) p1 <- ggplot(mtcars, aes(x = mpg)) + geom_histogram(bins = 10) p2 <- ggplot(mtcars, aes(x = ppVar(mpg))) + geom_histogram(bins = 10) # p1 and p2 produce the same plot. # Inside ptr_app() / ptr_server(), the same call binds to a column picker: if (interactive()) { ptr_app(ggplot(mtcars, aes(x = ppVar(mpg), y = ppVar(wt))) + geom_point()) }
ADR-0021 structural keyword that marks a pipeline stage as user-
toggleable in ptr_app(). The boolean argument is switch_on
(positive sense: TRUE applies the verb, FALSE skips it) and an optional
label carries the UI text for the resulting checkbox. Inside
ptr_app() / ptr_server()
the parser sees the wrapper and unwraps it at translate time, stamping
the boot-state metadata + UI label onto the resulting node. The
wrapper itself never appears in the typed tree.
ppVerbSwitch(.data, verb_expr, switch_on = TRUE, label = NULL)ppVerbSwitch(.data, verb_expr, switch_on = TRUE, label = NULL)
.data |
A data frame or pipe-supplied dataset (the implicit
|
verb_expr |
A data-pipeline verb call (e.g. |
switch_on |
A length-1 non-NA logical literal. In |
label |
Optional length-1 character used as the checkbox label
inside |
Outside ptr_app() it behaves per its R semantics so naked-dplyr
scripts still render: ppVerbSwitch(.data, mutate(x = 1), FALSE)
returns .data unchanged; ppVerbSwitch(.data, mutate(x = 1), TRUE)
routes .data through the verb call. label is metadata-only
outside ptr_app() (the naked-R path ignores it).
Outside ptr_app(): returns .data unchanged when
switch_on = FALSE, otherwise the result of verb_expr applied to
.data.
ppVerbSwitch(.data, verb_expr, switch_on = TRUE) inserts .data as
the first positional argument of verb_expr when switch_on is
TRUE. This matches the tidyverse convention and the translator's
pipeline-stage handling; non-tidyverse verbs that take data in a
later argument are not supported (use a lambda stage or a named
wrapper instead).
if (requireNamespace("dplyr", quietly = TRUE)) { # Naked-R semantics: switch_on = FALSE leaves the data unchanged. identical( ppVerbSwitch(mtcars, dplyr::mutate(mpg = mpg + 100), FALSE), mtcars ) # switch_on = TRUE routes .data through the verb. result <- ppVerbSwitch(mtcars, dplyr::filter(mpg > 20), TRUE) nrow(result) # 14 } # Inside ptr_app(), the wrapper becomes a node-level default + a # labelled boot-state-on checkbox: if (interactive()) { ptr_app( "mtcars |> ppVerbSwitch(dplyr::filter(mpg > 20), TRUE, label = 'Filter') |> ggplot(aes(x = mpg, y = wt)) + geom_point()" ) }if (requireNamespace("dplyr", quietly = TRUE)) { # Naked-R semantics: switch_on = FALSE leaves the data unchanged. identical( ppVerbSwitch(mtcars, dplyr::mutate(mpg = mpg + 100), FALSE), mtcars ) # switch_on = TRUE routes .data through the verb. result <- ppVerbSwitch(mtcars, dplyr::filter(mpg > 20), TRUE) nrow(result) # 14 } # Inside ptr_app(), the wrapper becomes a node-level default + a # labelled boot-state-on checkbox: if (interactive()) { ptr_app( "mtcars |> ppVerbSwitch(dplyr::filter(mpg > 20), TRUE, label = 'Filter') |> ggplot(aes(x = mpg, y = wt)) + geom_point()" ) }
ggpaintr FormulaTranslates formula into the typed AST, builds the per-layer panel UI,
and wires the server end-to-end. Returns a shiny.appobj ready to be run.
This page is the canonical reference for the formula grammar and the
empty-call cleanup rule used by every public entry point.
ptr_app( formula, envir = parent.frame(), ui_text = NULL, expr_check = TRUE, safe_to_remove = character(), css = NULL, spec = NULL )ptr_app( formula, envir = parent.frame(), ui_text = NULL, expr_check = TRUE, safe_to_remove = character(), css = NULL, spec = NULL )
formula |
Either a single character scalar containing a ggplot
expression with |
envir |
Environment used to resolve local data objects. |
ui_text |
Optional named list of copy overrides; see |
expr_check |
Controls formula-level |
safe_to_remove |
Character vector of additional function names whose
zero-argument calls should be dropped after placeholder substitution
leaves them empty. Defaults to |
css |
Optional character vector of paths to additional CSS files. Each
is served as a static resource and linked after |
spec |
An optional named list of fully-qualified Shiny input id -> value, used to override widget defaults at session boot. |
A shiny.appobj.
A ggpaintr formula is a single ggplot() call written as a string. Drop
one of five placeholder keywords anywhere a value would normally go, and
the runtime substitutes the user's input back into the expression at
render time.
ppVarColumn picker, data-aware. Renders as a selectInput
populated with the upstream data's column-name vector. Example:
aes(x = ppVar).
ppTextFree-text input. Renders as a textInput. Example:
labs(title = text).
ppNumNumeric input. Renders as a numericInput. Example:
geom_point(size = ppNum).
ppExprCode editor, validated by expr_check. The only keyword
that accepts arbitrary R code; for the safety model see the ggpaintr
book's safety chapter (development-version docs,
https://willju-wangqian.github.io/ggpaintr-book/safety.html).
Example: facet_wrap(ppExpr).
ppUploadFile picker, returns a data frame. Renders as a
fileInput plus an optional dataset-name textbox. Accepted formats:
.csv, .tsv, .rds, .xlsx, .xls, .json. Uploaded data is
normalized via ptr_normalize_column_names() automatically. Example:
ggplot(ppUpload, ...).
Any keyword occurrence may carry shared = "<id>" to lift the widget out
of its per-layer panel into a top-level shared section. Used by
ptr_app_grid() to drive multiple plots from one control. See
vignette("ggpaintr-tutorial") for worked examples of each keyword.
When a placeholder resolves to "missing" (an empty ppVar pick, a blank
ppText, a cleared ppNum, an unchecked layer checkbox), its argument is
dropped from the generated code. If the surrounding call is left empty
and its bare name is in the curated cleanup list, the whole call
disappears too. This rule applies to both placeholder-driven empties and
user-authored literal empty calls like + labs().
Curated ggplot2 names that are dropped when empty:
theme, labs, xlab, ylab, ggtitle, facet_wrap, facet_grid, facet_null, xlim, ylim, lims, expand_limits, guides, annotate, annotation_custom, annotation_map, annotation_raster, aes, aes_, aes_q, aes_string, element_text, element_line, element_rect, element_point, element_polygon, element_geom
Empty calls to similar no-op helpers from dplyr, tidyr, tibble,
pillar, purrr, stringr, forcats, lubridate, and hms are
covered by the same rule.
geom_*() and stat_*() layers are never dropped, regardless of
whether they end up empty — they inherit their aesthetics from
ggplot() and remain meaningful with no arguments.
element_blank() is intentionally not in the cleanup list: its
empty form is a meaningful "suppress" directive, not a no-op.
Third-party helpers (e.g. pcp_theme() from ggpcp) are not in the
cleanup list — being absent is the "removal safety unknown" signal.
Use safe_to_remove = c("pcp_theme") to opt a specific name in.
ptr_app_bslib() for the same contract with a bslib theme;
ptr_app_grid() for multi-plot apps with shared widgets;
ptr_define_placeholder_value() et al. for registering custom
keywords; ptr_ui_text() for copy overrides; ptr_css() for the
css = argument and themable CSS custom properties;
vignette("ggpaintr-tutorial") for tutorial examples.
if (interactive()) { # Expression mode (primary): pass the unquoted ggplot expression. ptr_app(ggplot(mtcars, aes(x = ppVar, y = ppVar)) + geom_point()) # `!!` splices a value into the expression. col <- rlang::sym("mpg") ptr_app(ggplot(mtcars, aes(x = !!col, y = ppVar)) + geom_point()) # String mode (fallback): the same formula as text. ptr_app("ggplot(mtcars, aes(x = ppVar, y = ppVar)) + geom_point()") }if (interactive()) { # Expression mode (primary): pass the unquoted ggplot expression. ptr_app(ggplot(mtcars, aes(x = ppVar, y = ppVar)) + geom_point()) # `!!` splices a value into the expression. col <- rlang::sym("mpg") ptr_app(ggplot(mtcars, aes(x = !!col, y = ppVar)) + geom_point()) # String mode (fallback): the same formula as text. ptr_app("ggplot(mtcars, aes(x = ppVar, y = ppVar)) + geom_point()") }
ptr_arg_*)These helpers are factories that return a closure of shape
function(arg_expr) -> canonical_value | abort(). The closure validates
the unevaluated R expression captured as a placeholder's positional default
argument and returns a canonical value, or aborts with a clear message.
ptr_arg_symbol_or_string(vector = FALSE) ptr_arg_string(vector = FALSE) ptr_arg_symbol(vector = FALSE) ptr_arg_numeric(vector = FALSE, length = NULL) ptr_arg_expression()ptr_arg_symbol_or_string(vector = FALSE) ptr_arg_string(vector = FALSE) ptr_arg_symbol(vector = FALSE) ptr_arg_numeric(vector = FALSE, length = NULL) ptr_arg_expression()
vector |
Logical scalar (default |
length |
Optional integer length required of the resulting numeric
vector; honored only when |
The validators operate on AST only: they do not call eval(), parse(),
or any deparse-and-reparse cycle on their input. The numeric helper
ptr_arg_numeric() (scalar by default, vector via vector = TRUE) walks
the AST
against the constant-fold allowlist registry (see
ptr_register_constant_fold()) and then evaluate in a sealed environment
whose only bindings are the registered names.
Symbol policy is per-helper:
ptr_arg_symbol_or_string() accepts a bareword symbol (returned as
its character name, preserving non-syntactic / backticked names) or any
single string literal (including the empty string).
ptr_arg_symbol() accepts only a bareword symbol (returned as its
character name); rejects string literals, numbers, and compound calls.
ptr_arg_string() accepts only a single string literal (including
the empty string); rejects symbols and numbers.
ptr_arg_numeric() accepts any AST whose every node is a syntactic
literal or a registered constant-fold name; in the default scalar mode
the result must be a length-one non-NA numeric.
For the vector form use ptr_arg_numeric(vector = TRUE, length = NULL).
ptr_arg_expression() is a verbatim store: it returns its input
unchanged so it can later be evaluated in the data context. As a
convenience it emits a one-shot warning if the user wraps the
expression in quote(), bquote(), rlang::ppExpr(), or rlang::quo()
(the wrapper is stored verbatim).
Each of ptr_arg_symbol_or_string(), ptr_arg_symbol(),
ptr_arg_string(), and ptr_arg_numeric() takes a vector flag. With
vector = FALSE (the default) the validator parses a single scalar element
and returns a length-one value. With vector = TRUE it parses a c(...)
literal element-by-element (each element subject to the helper's scalar
element rule) and returns the whole vector; a lone element is treated as a
length-one vector. For ptr_arg_numeric(vector = TRUE) the optional
length check (honored only in vector mode) asserts the parsed vector's
length.
A closure that takes an unevaluated expression and returns the canonical default value, or aborts.
is_symbol_ok <- ptr_arg_symbol_or_string() is_symbol_ok(quote(mpg)) is_symbol_ok("mpg") is_num <- ptr_arg_numeric() is_num(5) is_num(quote(2 * pi)) is_vec <- ptr_arg_numeric(vector = TRUE, length = 2L) is_vec(quote(c(0, 1)))is_symbol_ok <- ptr_arg_symbol_or_string() is_symbol_ok(quote(mpg)) is_symbol_ok("mpg") is_num <- ptr_arg_numeric() is_num(5) is_num(quote(2 * pi)) is_vec <- ptr_arg_numeric(vector = TRUE, length = 2L) is_vec(quote(c(0, 1)))
Unregisters placeholders added with ptr_define_placeholder_value(),
ptr_define_placeholder_consumer(), or ptr_define_placeholder_source().
The five built-in placeholders (ppVar, ppText, ppNum, ppExpr,
ppUpload) and the two structural keywords (ppLayerOff, ppVerbSwitch)
are never removed.
ptr_clear_placeholder(keyword = NULL)ptr_clear_placeholder(keyword = NULL)
keyword |
Optional single string. When supplied, only that placeholder is removed. When omitted (the default), every user-registered placeholder is removed. |
The character vector of keywords that were removed, invisibly.
ptr_define_placeholder_value( "demo_kw", build_ui = function(node, ...) shiny::textInput(node$id, "demo"), resolve_expr = function(value, node, ...) value ) ptr_clear_placeholder("demo_kw")ptr_define_placeholder_value( "demo_kw", build_ui = function(node, ...) shiny::textInput(node$id, "demo"), resolve_expr = function(value, node, ...) value ) ptr_clear_placeholder("demo_kw")
The numeric default-argument validator ptr_arg_numeric() (scalar by
default, vector via vector = TRUE) walks the placeholder's
default-argument
AST against an allowlist of function and constant names. Authors can
extend the allowlist with ptr_register_constant_fold() when their
placeholder definitions need additional pure operators.
ptr_register_constant_fold(name, value) ptr_clear_constant_fold(name = NULL) ptr_constant_fold_keywords()ptr_register_constant_fold(name, value) ptr_clear_constant_fold(name = NULL) ptr_constant_fold_keywords()
name |
Character scalar function or constant name. |
value |
Function or numeric constant to bind under |
Built-in entries seeded at package load:
Arithmetic: -, +, *, /, ^, %%, %/%
Sequence constructors: :, c, seq, seq.int, seq_len, seq_along
Syntactic literals (TRUE, FALSE, NA, NA_integer_, NA_real_,
Inf, NaN) are recognised by the walker directly and never need
registration. pi resolves through the registry's parent
(baseenv()).
ptr_register_constant_fold() and ptr_clear_constant_fold()
return invisible(NULL). ptr_constant_fold_keywords() returns a
character vector of currently registered names.
ptr_register_constant_fold("log10", log10) ptr_arg_numeric()(quote(log10(100))) ptr_clear_constant_fold("log10")ptr_register_constant_fold("log10", log10) ptr_arg_numeric()(quote(log10(100))) ptr_clear_constant_fold("log10")
A consumer placeholder is a value placeholder that additionally
receives the columns of the upstream data frame — typically a column
picker. The built-in example is ppVar. See
vignette("ggpaintr-tutorial") § "Defining your own placeholders"
(consumer role).
ptr_define_placeholder_consumer( keyword, build_ui, resolve_expr, validate_session_input = NULL, parse_positional_arg = NULL, parse_named_args = list(), embellish_eval = NULL, ui_text_defaults = list(label = "Pick a column for {param}") )ptr_define_placeholder_consumer( keyword, build_ui, resolve_expr, validate_session_input = NULL, parse_positional_arg = NULL, parse_named_args = list(), embellish_eval = NULL, ui_text_defaults = list(label = "Pick a column for {param}") )
keyword, ui_text_defaults
|
|
build_ui |
Widget-seeding contract — Consumer-specific rule: filter through
|
resolve_expr |
|
validate_session_input |
Optional |
parse_positional_arg, parse_named_args
|
See |
embellish_eval |
Optional |
The runtime callable (identity by default; override with
embellish_eval = ...). Also called for its registration side effect; use
ptr_clear_placeholder() to remove it.
vignette("ggpaintr-tutorial") § "Defining your own placeholders";
ptr_define_placeholder_value(), ptr_define_placeholder_source().
# A consumer that picks a numeric-only column. # Note: `selected = NULL` formal (per the seeding contract) plus # intersect() filter (per the consumer-specific rule). Empty input # renders empty; a stale pick after a data swap drops cleanly. ptr_define_placeholder_consumer( keyword = "numvar", build_ui = function(node, cols, data, label = NULL, selected = NULL, ...) { numeric_cols <- if (is.null(data)) character(0) else names(data)[vapply(data, is.numeric, logical(1))] retained <- intersect(selected %||% character(0), numeric_cols) shiny::selectInput(node$id, label = label %||% "Numeric column", choices = numeric_cols, selected = retained) }, resolve_expr = function(value, node, ...) { if (length(value) != 1L || !nzchar(value)) return(NULL) rlang::sym(value) }, validate_session_input = function(value, ctx) { if (length(value) == 1L && value %in% ctx$upstream_cols) TRUE else "Pick a column that exists in the upstream data." } ) ptr_clear_placeholder("numvar")# A consumer that picks a numeric-only column. # Note: `selected = NULL` formal (per the seeding contract) plus # intersect() filter (per the consumer-specific rule). Empty input # renders empty; a stale pick after a data swap drops cleanly. ptr_define_placeholder_consumer( keyword = "numvar", build_ui = function(node, cols, data, label = NULL, selected = NULL, ...) { numeric_cols <- if (is.null(data)) character(0) else names(data)[vapply(data, is.numeric, logical(1))] retained <- intersect(selected %||% character(0), numeric_cols) shiny::selectInput(node$id, label = label %||% "Numeric column", choices = numeric_cols, selected = retained) }, resolve_expr = function(value, node, ...) { if (length(value) != 1L || !nzchar(value)) return(NULL) rlang::sym(value) }, validate_session_input = function(value, ctx) { if (length(value) == 1L && value %in% ctx$upstream_cols) TRUE else "Pick a column that exists in the upstream data." } ) ptr_clear_placeholder("numvar")
A source placeholder produces a data frame the rest of the formula
reads from. Built-in example: ppUpload. Custom examples: database
tables, built-in datasets, URL fetches.
ptr_define_placeholder_source( keyword, build_ui, resolve_data, resolve_expr = NULL, shortcut = FALSE, parse_positional_arg = NULL, parse_named_args = list(), embellish_eval = NULL, ui_text_defaults = list(label = "Provide a data source for {param}") )ptr_define_placeholder_source( keyword, build_ui, resolve_data, resolve_expr = NULL, shortcut = FALSE, parse_positional_arg = NULL, parse_named_args = list(), embellish_eval = NULL, ui_text_defaults = list(label = "Provide a data source for {param}") )
keyword, ui_text_defaults
|
See |
build_ui |
Seeding — same opt-in shape as the other two helpers: declare an
optional |
resolve_data |
|
resolve_expr |
Optional. |
shortcut |
Single logical (default |
parse_positional_arg, parse_named_args
|
See |
embellish_eval |
Optional |
The runtime callable. Default for a source placeholder is a
guard that aborts when called outside an app context. Also called for
its registration side effect; use ptr_clear_placeholder() to remove
the entry.
spec= round-tripThe spec= mechanism (see ptr_app()) captures a sparse snapshot of
input values so the preserve-mode panel can publish a reproducible boot
state. For a source placeholder, ONE of two patterns must hold:
Shortcut pattern — set shortcut = TRUE. The shortcut input's
text value (typically the typed dataset name) carries the round-trip
identity; the source's own value at node$id is dropped from the
spec, because it is typically a per-session Shiny artifact (a
fileInput() data.frame whose datapath is a tempfile path that
does not survive the session). The built-in ppUpload uses this.
Data-loading entry point (ADR 0024). When shortcut = TRUE,
the shortcut sibling is more than a name override for an uploaded
frame — it is a typed-in shortcut for loading a data.frame from
the embedder's environment (envir passed to ptr_app() /
ptr_server()). Any valid R name typed into the shortcut input (or
seeded via spec = list(<shortcut-id> = "df_name")) is looked
up via get(name, envir, inherits = TRUE) and bound as the
resolved source frame, with OR without default= on the
placeholder. The downstream pipeline, generated code panel, and
consumer pickers all read the named frame from state$eval_env
as if it had been uploaded. Failures surface on the inline error
pane via set_resolve_error:
"Object 'x' not found in environment." /
"Object 'x' is not a data frame." Lookup uses inherits = TRUE,
so package exports become reachable — typing "plot" will resolve
to graphics::plot and then fail the "is not a data frame" check
(loudly, not silently).
Scalar pattern — shortcut = FALSE. The widget's value at
node$id must be a literal that round-trips through deparse() —
a length-1 string / number / logical, or a simple atomic vector.
The selectInput-style example above qualifies (its value is a
single string).
Source widgets whose primary value is a complex object (raw
fileInput() data.frame, environment, S4 instance, etc.) without
shortcut = TRUE cannot round-trip; opt into shortcut = TRUE and the
framework renders the sibling textInput(node$shortcut_id, ...) that
carries the binding name, mirroring ppUpload.
ptr_define_placeholder_value(), ptr_define_placeholder_consumer(),
ptr_clear_placeholder().
# A minimal in-memory dataset source (picks from pre-loaded data frames). ptr_define_placeholder_source( keyword = "dataset", build_ui = function(node, label, ...) { shiny::selectInput(node$id, label = label, choices = c("mtcars", "iris")) }, resolve_data = function(value, node, ...) { if (length(value) != 1L || !nzchar(value)) return(NULL) get(value, envir = as.environment("package:datasets")) } ) ptr_clear_placeholder("dataset")# A minimal in-memory dataset source (picks from pre-loaded data frames). ptr_define_placeholder_source( keyword = "dataset", build_ui = function(node, label, ...) { shiny::selectInput(node$id, label = label, choices = c("mtcars", "iris")) }, resolve_data = function(value, node, ...) { if (length(value) != 1L || !nzchar(value)) return(NULL) get(value, envir = as.environment("package:datasets")) } ) ptr_clear_placeholder("dataset")
Register a new keyword (e.g. pct, color, date) that ggpaintr will
recognise as a substitutable token in a formula. The keyword's widget
is built by build_ui; the widget's value is turned back into the R
code spliced into the rendered call by resolve_expr. See
vignette("ggpaintr-tutorial") § "Defining your own placeholders" for
the lifecycle walk-through, signatures table, and runnable
ptr_app() examples — this help page is reference.
ptr_define_placeholder_value( keyword, build_ui, resolve_expr, validate_session_input = NULL, parse_positional_arg = NULL, parse_named_args = list(), embellish_eval = NULL, ui_text_defaults = list(label = "Enter a value for {param}") )ptr_define_placeholder_value( keyword, build_ui, resolve_expr, validate_session_input = NULL, parse_positional_arg = NULL, parse_named_args = list(), embellish_eval = NULL, ui_text_defaults = list(label = "Enter a value for {param}") )
keyword |
Single non-empty string. Must be a syntactically valid R
name (passes |
build_ui |
Widget-seeding contract — Declare
Because the framework omits the argument on the first-render-no-
default path, a hook signature without a formal default for
Render empty when Never read |
resolve_expr |
|
validate_session_input |
Optional |
parse_positional_arg |
Optional validator closure for the (single) positional
argument the keyword accepts inside a formula. |
parse_named_args |
Named list of validator closures for additional named
arguments beyond the reserved |
embellish_eval |
Optional |
ui_text_defaults |
Named list of single non-NA character defaults
feeding the |
Three roles. Pick this constructor for a value placeholder (a
self-contained widget like a slider, color picker, or numeric input).
Use ptr_define_placeholder_consumer() when the widget needs the
upstream column names (column pickers). Use
ptr_define_placeholder_source() when the widget produces the data
the rest of the formula reads from (file upload, dataset chooser).
The runtime callable. Default for a value placeholder is the
identity function(x, ...) x; override with embellish_eval = .... The
helper is also called for its registration side effect — use
ptr_clear_placeholder() to remove the entry.
vignette("ggpaintr-tutorial") § "Defining your own placeholders";
ptr_define_placeholder_consumer(), ptr_define_placeholder_source(),
ptr_clear_placeholder().
# A percentage placeholder: user types a number 0-100; we splice # the fraction 0-1 into the rendered call. The hook reads `selected` # to honor a formula default like `pct(75)` at boot and to keep a # user-cleared widget empty across Update Plot fires (do NOT # substitute a hardcoded fallback when selected is empty). ptr_define_placeholder_value( keyword = "pct", build_ui = function(node, label = NULL, selected = NULL, ...) { n <- suppressWarnings(as.numeric(selected)) initial <- if (length(n) == 1L && is.finite(n)) n else NA_real_ shiny::numericInput(node$id, label = label %||% "Percent", value = initial, min = 0, max = 100, step = 1) }, resolve_expr = function(value, node, ...) { if (length(value) != 1L || !is.finite(value)) return(NULL) value / 100 }, parse_positional_arg = ptr_arg_numeric(), # accept ppPct(50)-style defaults ui_text_defaults = list(label = "Percent for {param}") ) ptr_clear_placeholder("pct")# A percentage placeholder: user types a number 0-100; we splice # the fraction 0-1 into the rendered call. The hook reads `selected` # to honor a formula default like `pct(75)` at boot and to keep a # user-cleared widget empty across Update Plot fires (do NOT # substitute a hardcoded fallback when selected is empty). ptr_define_placeholder_value( keyword = "pct", build_ui = function(node, label = NULL, selected = NULL, ...) { n <- suppressWarnings(as.numeric(selected)) initial <- if (length(n) == 1L && is.finite(n)) n else NA_real_ shiny::numericInput(node$id, label = label %||% "Percent", value = initial, min = 0, max = 100, step = 1) }, resolve_expr = function(value, node, ...) { if (length(value) != 1L || !is.finite(value)) return(NULL) value / 100 }, parse_positional_arg = ptr_arg_numeric(), # accept ppPct(50)-style defaults ui_text_defaults = list(label = "Percent for {param}") ) ptr_clear_placeholder("pct")
ptr_state
Read the latest plot object, error message, or generated code text from
the runtime result stored on a ptr_state. Use these to compose custom
UIs or to test the runtime in shiny::testServer.
ptr_extract_plot(state) ptr_extract_error(state) ptr_extract_code(state)ptr_extract_plot(state) ptr_extract_error(state) ptr_extract_code(state)
state |
A |
ptr_extract_plot returns a ggplot object (or NULL on
failure); ptr_extract_error returns a string or NULL;
ptr_extract_code returns a single string.
Each function wraps its read in shiny::isolate(), so it works in both
reactive and non-reactive contexts and returns the current value without
establishing a reactive dependency.
Do not call these inside a render*{} block if you want the output
to update when the plot rerenders. Because isolate() suppresses the
dependency on state$runtime(), the render block fires once on mount and
never again. Inside a reactive context, read state$runtime() directly —
that takes the dependency and re-fires on every Update plot click.
Reserve ptr_extract_* for non-reactive contexts: download handlers,
shiny::testServer() assertions, and one-shot reads outside any session.
shiny::isolate({ state <- ptr_init_state( "ggplot(mtcars, aes(x = wt, y = mpg)) + geom_point()" ) ptr_extract_code(state) })shiny::isolate({ state <- ptr_init_state( "ggplot(mtcars, aes(x = wt, y = mpg)) + geom_point()" ) ptr_extract_code(state) })
ggplot2 Layers ProgrammaticallyEvaluate one or more ggplot2 expressions and attach the results as
"extras" on the state. Extras are folded into the plot during the next
runtime cycle when state$runtime()$ok is TRUE. Eval failures leave
the existing extras untouched.
ptr_gg_extra(state, ...)ptr_gg_extra(state, ...)
state |
A |
... |
|
state, invisibly.
shiny::isolate({ state <- ptr_init_state( "ggplot(mtcars, aes(x = wt, y = mpg)) + geom_point()" ) state <- ptr_gg_extra(state, ggplot2::theme_minimal()) ptr_extract_code(state) })shiny::isolate({ state <- ptr_init_state( "ggplot(mtcars, aes(x = wt, y = mpg)) + geom_point()" ) state <- ptr_gg_extra(state, ggplot2::theme_minimal()) ptr_extract_code(state) })
ggpaintr Instance, Wired for Linked Selectionptr_ggplotly() is the state-first counterpart to plotly::ggplotly() for
the supported L3 custom-render pattern (see ptr_server(), section "Custom
render (L3)"). Call it from inside plotly::renderPlotly() on the live
instance returned by ptr_server(): it reads the drawn plot off
state$runtime()$plot, mints a per-draw row key (the plotly key
aesthetic, on a widget-only copy of the data), sets dragmode = "select",
registers the plotly_selected / plotly_deselect events, and returns a
plain plotly object so your own plotly verbs stay composable after it.
ptr_ggplotly(state, ..., source = NULL)ptr_ggplotly(state, ..., source = NULL)
state |
A |
... |
Forwarded verbatim to |
source |
Character scalar for plotly's |
The helper owns the shiny::req() pre-draw guard (it raises Shiny's silent
pre-draw condition before the first draw, exactly like a bare req(p)) and
derives plotly's source = channel from the instance namespace, so the
companion selection reader coordinates without extra wiring. The user's
drawn data is never mutated: keys are minted per draw on the widget copy
only.
plotly is an optional dependency (in Suggests). When it is not installed,
this function aborts, mirroring ptr_app_bslib()'s bslib guard.
A plain plotly htmlwidget object (inherits class "plotly"), with
dragmode = "select" set and the plotly_selected / plotly_deselect
events registered.
ptr_server() for the L3 custom-render contract;
plotly::ggplotly().
if (interactive()) { library(shiny) library(plotly) f <- rlang::expr( ggplot(mtcars, aes(x = ppVar(wt), y = ppVar(mpg))) + geom_point() ) ui <- ptr_ui_page( ptr_ui_controls(formula = f, id = "p"), plotly::plotlyOutput("plt") ) server <- function(input, output, session) { state <- ptr_server(f, "p") output$plt <- plotly::renderPlotly({ # state-first: req() guard + key minting + select wiring are internal ptr_ggplotly(state, tooltip = "all") |> plotly::layout(legend = list(orientation = "h")) }) } shinyApp(ui, server) }if (interactive()) { library(shiny) library(plotly) f <- rlang::expr( ggplot(mtcars, aes(x = ppVar(wt), y = ppVar(mpg))) + geom_point() ) ui <- ptr_ui_page( ptr_ui_controls(formula = f, id = "p"), plotly::plotlyOutput("plt") ) server <- function(input, output, session) { state <- ptr_server(f, "p") output$plt <- plotly::renderPlotly({ # state-first: req() guard + key minting + select wiring are internal ptr_ggplotly(state, tooltip = "all") |> plotly::layout(legend = list(orientation = "h")) }) } shinyApp(ui, server) }
Walks the typed AST of a single formula and returns one row per Shiny
id ever rendered by ptr_ui() / ptr_server() for that formula:
placeholder sleeves, inner widgets, source companions, layer-level
controls (checkbox, subtab, content container), pipeline stage
toggles, plus the static infrastructure ids (ptr_plot, ptr_error,
ptr_code, ptr_code_mode, ptr_update_plot).
ptr_id_table(formula, id = NULL)ptr_id_table(formula, id = NULL)
formula |
A single ggpaintr formula string (the same input you
would pass to |
id |
Optional outer namespace — the same string you pass to
|
Advanced (L3) users call this to build their own UI by hand and still
get the server's bindings to match. The default-layout L2 path does
not need it; ptr_ui() emits these ids internally.
A data.frame with one row per Shiny id and ten columns:
id — the Shiny id (prefixed when id= is given and
scope == "instance").
kind — "input_widget" or "output_slot".
role — semantic role: "placeholder", "source_companion",
"layer_checkbox", "layer_subtab", "layer_content",
"stage_enabled", "ptr_plot", "ptr_error", "ptr_code",
"ptr_code_mode", "ptr_update_plot".
scope — "instance" (namespaced via shiny::NS(id)) or
"global" (un-namespaced; only used by cross-formula shared-panel
keys in a ptr_shared() setup — see Single-formula section).
include_in_ui — TRUE when the row is something the user
places in their custom UI; FALSE when the server populates
it inside a sleeve (<id>_ui) and the user must not place it
manually. The two FALSE cases are the inner placeholder widget
and ppUpload's file-name companion.
layer — layer name ("ggplot", "geom_point", …) or NA.
keyword — placeholder keyword ("ppVar"/"ppText"/…) or NA.
param — argument or aesthetic name ("x", "color", "data",
…) or NA.
parent_call — immediate enclosing call ("aes", "head", or
the layer itself when the placeholder is a direct layer arg) or
NA.
shared — shared key ("xcol", …) or NA.
Adding a layer at the end keeps existing ids stable. Reordering
arguments inside aes() or a pipeline shifts positional paths and
therefore changes ids; renaming a placeholder keyword or its
shared= annotation also changes ids.
ptr_id_table() accepts a single formula. In multi-instance
(ptr_shared(formulas = list(…))) layouts the partition rule
decides whether shared_<key> lives in an instance's inline section
or the cross-formula panel. The scope column reflects the
single-formula interpretation ("instance"); when embedding into a
shared-panel context those rows are bare ("global") and you should
override accordingly.
ptr_id_table("ggplot(ppUpload, aes(x = ppVar, y = ppVar)) + geom_point(color = ppText)") ptr_id_table("ggplot(ppUpload, aes(x = ppVar))", id = "myplot")ptr_id_table("ggplot(ppUpload, aes(x = ppVar, y = ppVar)) + geom_point(color = ppText)") ptr_id_table("ggplot(ppUpload, aes(x = ppVar))", id = "myplot")
Builds the ptr_state object — the translated typed AST (as a
reactiveVal), the runtime result, the per-layer resolved-data caches,
the eval environment, the input-snapshot machinery, and the shared
bindings / draw trigger — used by ptr_server() and the advanced-embedder
accessors (ptr_extract_plot() / ptr_extract_error() /
ptr_extract_code(), ptr_gg_extra()).
ptr_init_state( formula, envir = parent.frame(), ui_text = NULL, expr_check = TRUE, safe_to_remove = character(), shared = list(), draw_trigger = NULL, producer_debounce_ms = NULL, ns = shiny::NS(NULL), server_ns = ns, auto_bind_shared = FALSE, shared_resolutions = list(), shared_stage_enabled = list(), panel_sources = list(), plots = NULL )ptr_init_state( formula, envir = parent.frame(), ui_text = NULL, expr_check = TRUE, safe_to_remove = character(), shared = list(), draw_trigger = NULL, producer_debounce_ms = NULL, ns = shiny::NS(NULL), server_ns = ns, auto_bind_shared = FALSE, shared_resolutions = list(), shared_stage_enabled = list(), panel_sources = list(), plots = NULL )
formula |
A single formula string with |
envir |
Environment used to resolve local data objects. |
ui_text |
Optional named list of copy overrides; see |
expr_check |
Controls formula-level |
safe_to_remove |
Character vector of additional function names whose zero-argument calls should be dropped after substitution. |
shared |
Named list of reactives (one per shared key) supplied by an
outer wrapper such as |
draw_trigger |
Optional reactive carrying a click counter — a numeric
scalar that is |
producer_debounce_ms |
Optional. Controls the debounce window applied
to producer-style placeholder inputs ( |
ns |
A namespace function used for rendered ids (UI side). |
server_ns |
A namespace function used for server-side input lookups.
Defaults to |
auto_bind_shared |
If |
shared_resolutions |
Named list (keyed by raw shared key) of
host-computed resolutions for shared data-consumer ( |
shared_stage_enabled |
Named list (keyed by raw shared key) of
reactives, each returning a logical, that toggle the orphan pipeline
stages owned by that shared key (as carried in a
|
panel_sources |
Named list (keyed by source-node id) of reactives,
each returning the host-loaded data for a panel-owned shared source
(ADR 0023). Populated by the host's |
plots |
Optional list of formula strings for grid contexts. When
supplied (typically by |
This is a state container, not a from-scratch reactive-app builder: it
allocates the reactives but does not attach the pipeline / runtime
observers (those live in internal ptr_setup_* helpers wired by
ptr_server()). Reach for ptr_init_state() directly when you want to
drive the typed tree programmatically or exercise ggpaintr under
shiny::testServer(); for a fully wired app use ptr_server().
A ptr_state list (S3 class c("ptr_state", "list")).
shiny::isolate({ state <- ptr_init_state( "ggplot(mtcars, aes(x = ppVar, y = ppVar)) + geom_point()" ) is.list(state) })shiny::isolate({ state <- ptr_init_state( "ggplot(mtcars, aes(x = ppVar, y = ppVar)) + geom_point()" ) is.list(state) })
Returns the text of inst/llm/primer.md as a single string. Intended
for use as the system_prompt = (or equivalent) argument when wiring
ggpaintr into an LLM client such as ellmer, so the model knows when
to reach for ggpaintr and at which of the three integration levels.
ptr_llm_primer()ptr_llm_primer()
The primer is short by design — it establishes the extensibility
model and points the model at ptr_llm_topic() for runnable
examples. A companion tool that exposes ptr_llm_topic() to the
model lets it pull only the example it needs, instead of loading
every topic into the system prompt.
A single character string.
ptr_llm_topic(), ptr_llm_topics()
primer <- ptr_llm_primer() cat(substr(primer, 1, 200))primer <- ptr_llm_primer() cat(substr(primer, 1, 200))
One-line registration of the ggpaintr docs tool on an existing
ellmer Chat object. The tool wraps
ptr_llm_topic() so the LLM can pull focused, runnable examples
on demand instead of loading every topic into the system prompt.
ptr_llm_register(chat, tool_name = "ggpaintr_docs")ptr_llm_register(chat, tool_name = "ggpaintr_docs")
chat |
An ellmer |
tool_name |
String. The name the LLM will call the tool under.
Defaults to |
The set of valid topic names is snapshotted at registration time
using ptr_llm_topics(), so the LLM cannot request a topic that
does not exist. If you upgrade ggpaintr in the same session and new
topics are added, call this again on a fresh chat.
This function does not set the chat's system prompt. Pass
ptr_llm_primer() to the system_prompt = argument of your
chat_*() constructor so the model knows when to reach for the tool.
The chat object (invisibly), for piping.
ptr_llm_primer(), ptr_llm_topic(), ptr_llm_topics()
if (interactive()) { library(ellmer) chat <- chat_anthropic(system_prompt = ptr_llm_primer()) ptr_llm_register(chat) chat$chat("Build a Shiny app where the user picks X and Y columns from mtcars.") }if (interactive()) { library(ellmer) chat <- chat_anthropic(system_prompt = ptr_llm_primer()) ptr_llm_register(chat) chat$chat("Build a Shiny app where the user picks X and Y columns from mtcars.") }
Returns the runnable example + commentary for one topic as a single
character string. Designed to back an LLM tool such as
ggpaintr_docs(topic): the model calls it when the user asks for
help with an interactive ggplot task, and receives exactly one
focused example instead of the entire manual.
ptr_llm_topic(topic)ptr_llm_topic(topic)
topic |
Topic name. Must be one of |
Each topic is derived from (and kept in sync with) either
README.Rmd or the tutorial vignette
(ggpaintr-tutorial).
A single character string.
ptr_llm_topics(), ptr_llm_primer()
cat(ptr_llm_topic("level1_ptr_app"))cat(ptr_llm_topic("level1_ptr_app"))
Returns the character vector of topic names accepted by
ptr_llm_topic(). Matches the files in inst/llm/topics/ (stripped
of the .md extension), sorted alphabetically.
ptr_llm_topics()ptr_llm_topics()
A character vector.
ptr_llm_topic(), ptr_llm_primer()
ptr_llm_topics()ptr_llm_topics()
ggpaintr
Normalize incoming column names so ppVar placeholders can require exact,
syntactic, unique column-name matches at runtime.
ptr_normalize_column_names(data)ptr_normalize_column_names(data)
data |
A data frame or an object coercible with |
Uploaded data is normalized automatically by the runtime — every
successful ppUpload placeholder passes through this function (or its
data.frame-coercing sibling for non-data.frame returns from
readRDS / readxl / jsonlite). Call ptr_normalize_column_names()
yourself only for in-session data frames you reference by name from
a formula. If a local data frame has spaces, reserved words, or
duplicates in its column names, pipe it through this function before
passing it to ptr_app().
A tabular object with ggpaintr-safe column names. Existing
data.frame subclasses keep their class. Names are made syntactic, unique,
and safe against reserved-word collisions. Non-data.frame inputs return
the data.frame created by as.data.frame().
messy <- data.frame( check.names = FALSE, "first column" = 1:3, "if" = 4:6 ) clean <- ptr_normalize_column_names(messy) names(clean)messy <- data.frame( check.names = FALSE, "first column" = 1:3, "if" = 4:6 ) clean <- ptr_normalize_column_names(messy) names(clean)
Combined getter/setter for ggpaintr's global settings, modeled after base
base::options(). Calling with no arguments returns all current settings
as a named list. Passing one or more named logical arguments sets those
settings and invisibly returns the previous values, suitable for the
do.call(ptr_options, old) round-trip pattern used by
withr::with_options() and on.exit().
ptr_options(...)ptr_options(...)
... |
Named logical arguments — one per setting to update. Setting names must match the registry above. |
When called with no arguments, a named list of all current setting values. When called with named arguments, the previous values of the updated settings, returned invisibly.
verboseLogical. When TRUE, ggpaintr emits informative messages such as
"Layer foo() removed (no arguments provided)." Default FALSE —
these messages are intended for debugging the formula pipeline and
are off by default. Underlying option: options(ggpaintr.verbose = ...).
gate_drawLogical. When TRUE (the default), the rendered plot updates only
when the user clicks the "Update plot" button — every placeholder
change is batched until the click. When FALSE, the button is
omitted from the UI and the plot re-renders reactively on every
placeholder change (live mode). Read once when the app is built, so
set it before calling ptr_app() / ptr_ui(). Underlying option:
options(ggpaintr.gate_draw = ...).
suppress_warningsLogical. When TRUE, R warnings emitted while the plot is drawn
(e.g. loess fit warnings such as "all data on boundary of
neighborhood" or "Failed to fit group N") are silenced rather than
printed to the console. Default FALSE — warnings surface as usual.
Only the plot-drawing step is wrapped; errors still propagate to the
inline error pane. Read once when the app is built, so set it before
calling ptr_app() / ptr_ui(). Underlying option:
options(ggpaintr.suppress_warnings = ...).
# Inspect current values ptr_options() # Silence the "Layer ... removed" notice for one block old <- ptr_options(verbose = FALSE) on.exit(do.call(ptr_options, old), add = TRUE)# Inspect current values ptr_options() # Silence the "Layer ... removed" notice for one block old <- ptr_options(verbose = FALSE) on.exit(do.call(ptr_options, old), add = TRUE)
ptr_ggplotly() Linked Selection As Rows or a Flag Columnptr_plotly_selection() is the companion reader for ptr_ggplotly(): it
returns a Shiny reactive carrying the current brush/lasso selection of the
plotly widget, projected one of two ways. Call it once in your server (not
inside a render) on the live instance returned by ptr_server() and feed
the returned reactive to a table, a second formula, or any downstream
consumer.
ptr_plotly_selection(state, mode = c("rows", "flag"), source = NULL)ptr_plotly_selection(state, mode = c("rows", "flag"), source = NULL)
state |
A |
mode |
|
source |
Character scalar for plotly's |
A selection names rows of the drawn data — state$runtime()$plot$data
of the draw that produced the widget — never "the original object". Keys are
minted per draw and are meaningless across draws, so the selection
resets to empty on every draw (a redraw after a filter()/mutate()
pipeline-head change or an upload swap starts the selection over). A
plotly_deselect event clears it likewise. Two projections of the one
selection:
mode = "rows" — the selected slice; a zero-row data frame with the
drawn data's columns when nothing is selected (so a shiny::renderTable()
consumer needs no req() dance).
mode = "flag" — the full drawn data plus a logical .ptr_selected
column, TRUE exactly at the selected rows (all FALSE when empty).
.ptr_selected is a reserved name: a pre-existing .ptr_selected on the
drawn data is silently overwritten (the overwrite keeps chained
selection-fed instances correct). The internal .ptr_row key never appears
in either projection.
Pre-draw window: before the first draw there is no snapshot yet, so the
returned reactive raises Shiny's silent pre-draw condition (via
shiny::req()); under live mode a selection-fed instance shows its inline
pre-draw state for one flush. Live-mode key reset: changing a placeholder
widget re-draws the source plot, which re-mints the keys, so the selection
resets to empty on that picker change.
plotly is an optional dependency (in Suggests); this reader is only
meaningful alongside ptr_ggplotly(), which guards on plotly being
installed.
A Shiny reactive. Its value is the rows projection (a data frame, the
selected slice; zero rows, same columns, when empty) or the flag projection
(the full drawn data plus a logical .ptr_selected), per mode.
ptr_ggplotly() for the widget side; ptr_server() for the L3
custom-render contract.
if (interactive()) { library(shiny) library(plotly) f <- rlang::expr( ggplot(mtcars, aes(x = ppVar(wt), y = ppVar(mpg))) + geom_point() ) ui <- ptr_ui_page( ptr_ui_controls(formula = f, id = "p"), plotly::plotlyOutput("plt"), tableOutput("sel") ) server <- function(input, output, session) { state <- ptr_server(f, "p") output$plt <- plotly::renderPlotly({ ptr_ggplotly(state, tooltip = "all") }) # Full loop: brush the plot -> the selected rows appear in the table. sel <- ptr_plotly_selection(state, mode = "rows") output$sel <- renderTable(sel()) } shinyApp(ui, server) }if (interactive()) { library(shiny) library(plotly) f <- rlang::expr( ggplot(mtcars, aes(x = ppVar(wt), y = ppVar(mpg))) + geom_point() ) ui <- ptr_ui_page( ptr_ui_controls(formula = f, id = "p"), plotly::plotlyOutput("plt"), tableOutput("sel") ) server <- function(input, output, session) { state <- ptr_server(f, "p") output$plt <- plotly::renderPlotly({ ptr_ggplotly(state, tooltip = "all") }) # Full loop: brush the plot -> the selected rows appear in the table. sel <- ptr_plotly_selection(state, mode = "rows") output$sel <- renderTable(sel()) } shinyApp(ui, server) }
Looks up the effective label/help/placeholder for a single UI element,
applying the defaults -> params -> layers specificity chain and
interpolating the {param} / {layer} tokens. Placeholder authors can
call this inside a custom build_ui hook so their control is labelled
through the same override chain as the built-in controls.
ptr_resolve_ui_text( component, keyword = NULL, param = NULL, layer_name = NULL, ui_text = NULL )ptr_resolve_ui_text( component, keyword = NULL, param = NULL, layer_name = NULL, ui_text = NULL )
component |
One of |
keyword |
Placeholder keyword (e.g. |
param |
Optional parameter / aesthetic name (e.g. |
layer_name |
Optional layer name (e.g. |
ui_text |
|
A named list with label, help, placeholder, and empty_text.
# Resolve copy for the title element ptr_resolve_ui_text("title") # Resolve copy for a var control on the x-axis ptr_resolve_ui_text("control", keyword = "ppVar", param = "x") # Inside a custom `build_ui` hook, label the control through the same # override chain ggpaintr uses for built-in controls: my_build_ui <- function(node, label, ...) { copy <- ptr_resolve_ui_text( "control", keyword = node$keyword, param = node$param, ui_text = list(...)$ui_text ) shiny::textInput(node$id, label = copy$label %||% label) }# Resolve copy for the title element ptr_resolve_ui_text("title") # Resolve copy for a var control on the x-axis ptr_resolve_ui_text("control", keyword = "ppVar", param = "x") # Inside a custom `build_ui` hook, label the control through the same # override chain ggpaintr uses for built-in controls: my_build_ui <- function(node, label, ...) { copy <- ptr_resolve_ui_text( "control", keyword = node$keyword, param = node$param, ui_text = list(...)$ui_text ) shiny::textInput(node$id, label = copy$label %||% label) }
ggpaintr FormulaThe single public server-side entry point for a ggpaintr formula,
used identically at L2 (default layout via ptr_ui()) and L3 (your own
hand-composed layout from the bare ptr_ui_* pieces). It namespaces and
wires the whole reactive engine, registers the built-in ptr_plot /
ptr_error / ptr_code outputs (a piece you never place in the UI is a
harmless no-op), and returns the ptr_state so you can drive a
custom renderer. Additional arguments are forwarded to ptr_init_state()
(e.g. shared, draw_trigger, expr_check, safe_to_remove,
ui_text).
ptr_server( formula, id = NULL, envir = parent.frame(), ..., shared_state = NULL, spec = NULL )ptr_server( formula, id = NULL, envir = parent.frame(), ..., shared_state = NULL, spec = NULL )
formula |
Either a single character scalar containing a ggplot
expression with |
id |
Optional module id; must match the id passed to |
envir |
Environment used to resolve local data objects. |
... |
Forwarded to |
shared_state |
Optional |
spec |
An optional named list of fully-qualified Shiny input id -> value, used to override widget defaults at session boot. |
The ptr_state list from ptr_init_state(). This is the
supported L3 custom-render handle: state$runtime()$plot /
$code / $error. (Its server_ns_fn / ui_ns_fn slots are
internal plumbing — not a public escape hatch.)
Custom rendering is UI-side: place your own output widget (e.g.
plotly::plotlyOutput()) at shiny::NS(id)("my_plot"), never place
ptr_ui_plot(), and read the live plot off the returned state — there
is no user-authored moduleServer wrapping any ggpaintr engine and
no lower-level server function to reach for:
# server:
state <- ptr_server(formula, "p")
output$my_plot <- plotly::renderPlotly(state$runtime()$plot)
# ui: plotly::plotlyOutput(shiny::NS("p")("my_plot"))
state$runtime() is reactive; $plot is the built ggplot/ggplot-like
object, $code the generated source string, $error any inline error.
See vignette("ggpaintr-tutorial").
For cross-formula coordination — multiple ggpaintr instances driven by
one widget — build the coordinator with ptr_shared_server() and pass
the returned ptr_shared_state as shared_state =. The state's
shared / draw_trigger / shared_resolutions slots are unpacked and
forwarded to ptr_init_state(); if an explicit shared = ... /
draw_trigger = ... / shared_resolutions = ... is also passed via
..., that explicit value wins. A single formula with shared = "..."
placeholders needs no shared_state — ptr_server() self-binds every
declared key under its own namespace, matching what ptr_app() does.
ptr_ui(), ptr_ui_plot(), ptr_shared(),
ptr_shared_panel(), ptr_shared_server().
if (interactive()) { f <- rlang::expr(ggplot(mtcars, aes(x = ppVar, y = ppVar)) + geom_point()) # L2: default layout shiny::shinyApp( ui = shiny::fluidPage(ptr_ui(!!f, "p")), server = function(input, output, session) { ptr_server(!!f, "p") } ) # L3: own the render path off the returned state shiny::shinyApp( ui = ptr_ui_page( ptr_ui_controls(formula = !!f, id = "p"), plotly::plotlyOutput(shiny::NS("p")("my_plot")) ), server = function(input, output, session) { state <- ptr_server(!!f, "p") output[[shiny::NS("p")("my_plot")]] <- plotly::renderPlotly(state$runtime()$plot) } ) }if (interactive()) { f <- rlang::expr(ggplot(mtcars, aes(x = ppVar, y = ppVar)) + geom_point()) # L2: default layout shiny::shinyApp( ui = shiny::fluidPage(ptr_ui(!!f, "p")), server = function(input, output, session) { ptr_server(!!f, "p") } ) # L3: own the render path off the returned state shiny::shinyApp( ui = ptr_ui_page( ptr_ui_controls(formula = !!f, id = "p"), plotly::plotlyOutput(shiny::NS("p")("my_plot")) ), server = function(input, output, session) { state <- ptr_server(!!f, "p") output[[shiny::NS("p")("my_plot")]] <- plotly::renderPlotly(state$runtime()$plot) } ) }
Placeholder resolve_expr (and validate_input) hooks should call
ptr_signal_partial() – instead of rlang::abort() / stop() – when the
current widget value is incomplete or transiently unparseable. Typical
example: a free-text expr placeholder whose body has not finished being
typed (mpg /). The condition carries class "ptr_partial_input".
ptr_signal_partial(message, ...)ptr_signal_partial(message, ...)
message |
Diagnostic text. Surfaced on the gated plot path. |
... |
Additional named fields attached to the condition (forwarded
to |
ggpaintr's live reactive boundaries (column-picker entry_reactives that
re-fire on every keystroke) catch this class and silently cancel the
current re-render, so the downstream picker keeps its previous state
instead of strobing blank and writing a Shiny warning to the R console.
The Update / Draw-gated plot path does not catch it, so a value that is
still partial when the user clicks "Update" still surfaces as a normal
inline error.
Use ordinary rlang::abort() for failures that are NOT user mid-typing
artifacts (security violations, real argument-shape errors, etc.).
Never returns – always signals.
# A resolve hook that treats an unfinished expression as a transient, # silently-cancelled partial input rather than a hard error: my_expr_resolve <- function(value, node, ...) { tryCatch( rlang::parse_expr(value), error = function(e) ptr_signal_partial(conditionMessage(e)) ) }# A resolve hook that treats an unfinished expression as a transient, # silently-cancelled partial input rather than a hard error: my_expr_resolve <- function(value, node, ...) { tryCatch( rlang::parse_expr(value), error = function(e) ptr_signal_partial(conditionMessage(e)) ) }
ggpaintr FormulaThe L2 default-layout UI bundle for a ggpaintr formula: owns its own
.ptr-app theme scope + asset bundle (nothing else to remember) and is
namespaced by id. Pair with the single public ptr_server(). For a
hand-composed L3 layout, use the bare ptr_ui_* pieces instead and pair
them with the same ptr_server().
ptr_ui( formula, id = NULL, envir = parent.frame(), ui_text = NULL, expr_check = TRUE, css = NULL, shared = NULL )ptr_ui( formula, id = NULL, envir = parent.frame(), ui_text = NULL, expr_check = TRUE, css = NULL, shared = NULL )
formula |
Either a single character scalar containing a ggplot
expression with |
id |
Optional module id; the namespace prefix for inputs and outputs.
Defaults to |
envir |
Environment used to resolve a |
ui_text |
Optional named list of copy overrides; see |
expr_check |
Controls formula-level |
css |
Optional character vector of paths to additional CSS files;
linked after |
shared |
Optional coordinator object from |
A shiny.tag — a fluidPage shell containing the controls panel,
plot output, and asset bundle.
ptr_server(), ptr_css() for the css = argument and
themable CSS custom properties.
# Expression form (primary): an unquoted ggplot call. ui <- ptr_ui(ggplot(mtcars, aes(x = ppVar, y = ppVar)) + geom_point(), "plot1") # Stored in a variable, spliced with `!!` (paired with ptr_server(!!f, "plot1")). f <- rlang::expr(ggplot(mtcars, aes(x = ppVar, y = ppVar)) + geom_point()) ui2 <- ptr_ui(!!f, "plot1") # String form (fallback): equivalent. ui3 <- ptr_ui("ggplot(mtcars, aes(x = ppVar, y = ppVar)) + geom_point()", "plot1")# Expression form (primary): an unquoted ggplot call. ui <- ptr_ui(ggplot(mtcars, aes(x = ppVar, y = ppVar)) + geom_point(), "plot1") # Stored in a variable, spliced with `!!` (paired with ptr_server(!!f, "plot1")). f <- rlang::expr(ggplot(mtcars, aes(x = ppVar, y = ppVar)) + geom_point()) ui2 <- ptr_ui(!!f, "plot1") # String form (fallback): equivalent. ui3 <- ptr_ui("ggplot(mtcars, aes(x = ppVar, y = ppVar)) + geom_point()", "plot1")
ggpaintr
The full ggpaintr asset bundle as a shiny::tagList(): the
structural-layer dependency (ptr_set_class handler + stage CSS), the
cosmetic ggpaintr.css theme dependency + code-window JavaScript, and
any user override stylesheets. The CSS/JS ship as
htmltools::htmlDependency() objects, so emitting this anywhere on a
page — even several times — yields exactly one <head> injection of
each. The single-piece UI builders (ptr_ui_plot(),
ptr_ui_controls(), ...) emit no assets; an L3 page that composes
pieces by hand normally gets them from the page shell, or includes
this directly for a non-fluidPage root. The bundled apps and the
ptr_*_ui() composites inject it for you.
ptr_ui_assets(css = NULL)ptr_ui_assets(css = NULL)
css |
Optional character vector of paths to additional CSS files;
linked after |
ptr_ui_plot(), ptr_ui_controls(), ptr_ui_toggle_code(), ptr_css()
ptr_ui_assets()ptr_ui_assets()
ggpaintr FormulaThe generated-code output on its own: a shiny::verbatimTextOutput()
bound to the ptr_code id the server writes to (see ptr_server()).
One of the single-piece UI builders for the L3 "own every UI piece"
workflow.
ptr_ui_code(id = NULL, style = c("panel", "window"))ptr_ui_code(id = NULL, style = c("panel", "window"))
id |
Optional module id; the namespace prefix for the output.
Defaults to |
style |
|
A shiny::tag.
ptr_ui_toggle_code(), ptr_ui_plot(), ptr_server()
ptr_ui_code("myplot") ptr_ui_code("myplot", style = "window")ptr_ui_code("myplot") ptr_ui_code("myplot", style = "window")
ggpaintr FormulaThe generated control widgets (layer picker, per-layer parameter
panels, the "Update plot" button) as a bare shiny::tagList() with
no .ptr-app wrapper and no bundled assets. One of the
single-piece UI builders for the L3 "own every UI piece" workflow:
compose it with ptr_ui_assets() and the output pieces, place each
wherever you like, and wire the server with ptr_server() /
ptr_server().
ptr_ui_controls( formula, id = NULL, envir = parent.frame(), ui_text = NULL, expr_check = TRUE, shared = NULL )ptr_ui_controls( formula, id = NULL, envir = parent.frame(), ui_text = NULL, expr_check = TRUE, shared = NULL )
formula |
Either a single character scalar containing a ggplot
expression with |
id |
Optional module id; the namespace prefix for inputs.
Defaults to |
envir |
Environment used to resolve a |
ui_text |
Optional named list of copy overrides; see
|
expr_check |
Controls formula-level |
shared |
Optional coordinator object from |
Because the panel includes a shinyWidgets::pickerInput() (the layer
selector) and the Bootstrap grid, it must be rendered inside a
Bootstrap page that also carries the .ptr-app theme scope and the
asset bundle. Don't assemble that scaffolding by hand: wrap your
composed pieces in ptr_ui_page(), which is the Bootstrap page and
owns the single .ptr-app scope + the (deduped) assets. For a
navbarPage or bslib root (which ptr_ui_page() does not cover) see
vignette("ggpaintr-tutorial").
For finer control still — placing individual placeholder widgets
independently rather than the whole panel — register a custom placeholder
type; see ptr_define_placeholder_value().
ptr_ui_page(), ptr_ui_assets(), ptr_ui_plot(),
ptr_ui_code(), ptr_shared(), ptr_server()
# Expression form (primary): an unquoted ggplot call. ptr_ui_controls( ggplot(mtcars, aes(x = ppVar, y = ppVar)) + geom_point(), id = "p" ) # String form (fallback): equivalent. ptr_ui_controls( "ggplot(mtcars, aes(x = ppVar, y = ppVar)) + geom_point()", id = "p" )# Expression form (primary): an unquoted ggplot call. ptr_ui_controls( ggplot(mtcars, aes(x = ppVar, y = ppVar)) + geom_point(), id = "p" ) # String form (fallback): equivalent. ptr_ui_controls( "ggplot(mtcars, aes(x = ppVar, y = ppVar)) + geom_point()", id = "p" )
ggpaintr FormulaThe inline error slot on its own: a shiny::uiOutput() bound to the
ptr_error id the server writes parse/runtime error alerts to (see
ptr_server()). One of the single-piece UI builders for the L3 "own
every UI piece" workflow.
ptr_ui_error(id = NULL)ptr_ui_error(id = NULL)
id |
Optional module id; the namespace prefix for the output.
Defaults to |
A shiny::tag.
ptr_ui_plot(), ptr_ui_code(), ptr_ui_controls(), ptr_server()
ptr_ui_error("myplot")ptr_ui_error("myplot")
ggpaintr
The slim branded header bar (logo + title) the polished default shell
uses in place of shiny::titlePanel(). One of the single-piece UI
builders for the L3 "own every UI piece" workflow.
ptr_ui_header(title = "ggpaintr")ptr_ui_header(title = "ggpaintr")
title |
Heading text. Defaults to |
A shiny::tag.
ptr_ui_controls(), ptr_ui_plot(), ptr_app()
ptr_ui_header() ptr_ui_header("My App")ptr_ui_header() ptr_ui_header("My App")
Output combinator: takes an already-built bare plot piece
(ptr_ui_plot()) and an already-built bare error piece
(ptr_ui_error()) and returns the plot card with the error slot
rendered inline in the card body — the layout the bundled apps use.
Pure DOM structure; no server coupling (the server registers
ptr_plot / ptr_error regardless). Nestable inside
ptr_ui_toggle_code().
ptr_ui_inline_error(plot, error)ptr_ui_inline_error(plot, error)
plot |
A plot piece, typically |
error |
An error piece, typically |
This combinator does not add the .ptr-output toggle scope (it has
no toggle, so it needs none); ptr_ui_toggle_code() owns that wrapper.
A shiny::tag — the plot card with error nested in its body.
ptr_ui_plot(), ptr_ui_error(), ptr_ui_toggle_code()
ptr_ui_inline_error(ptr_ui_plot("p"), ptr_ui_error("p"))ptr_ui_inline_error(ptr_ui_plot("p"), ptr_ui_error("p"))
Wraps composed L3 pieces in a Bootstrap page + the single .ptr-app
theme scope + the (deduped) asset bundle. The only thing an L3 user
must remember.
ptr_ui_page(..., page = shiny::fluidPage, css = NULL)ptr_ui_page(..., page = shiny::fluidPage, css = NULL)
... |
UI children (pieces, layout, your own widgets). |
page |
A Bootstrap-3 page builder whose |
css |
Optional character vector of extra stylesheet paths,
linked after |
A shiny.tag — the Bootstrap page node ready to pass to
shiny::shinyApp() as ui.
ptr_ui_plot(), ptr_ui_controls(), ptr_server(), ptr_css()
f <- rlang::expr(ggplot(mtcars, aes(x = ppVar, y = ppVar)) + geom_point()) ptr_ui_page( shiny::sidebarLayout( shiny::sidebarPanel(ptr_ui_controls(id = "p", formula = !!f)), shiny::mainPanel(ptr_ui_plot("p")) ) )f <- rlang::expr(ggplot(mtcars, aes(x = ppVar, y = ppVar)) + geom_point()) ptr_ui_page( shiny::sidebarLayout( shiny::sidebarPanel(ptr_ui_controls(id = "p", formula = !!f)), shiny::mainPanel(ptr_ui_plot("p")) ) )
ggpaintr FormulaThe plot card on its own: a shiny::plotOutput() bound to the
ptr_plot id the server writes to (see ptr_server()). One of the
single-piece UI builders for the L3 "own every UI piece" workflow;
place it anywhere in your own layout and wire the server with
ptr_server() / ptr_server().
ptr_ui_plot(id = NULL)ptr_ui_plot(id = NULL)
id |
Optional module id; the namespace prefix for the output.
Defaults to |
The piece is truly bare: just the plot card, with no error slot and
no show-code button. Behaviour is added compositionally by the
combinators ptr_ui_inline_error() (nests an error slot in the card
body) and ptr_ui_toggle_code() (adds the </> toggle + slide-out
code window) — not by flags on this function.
A shiny::tag.
ptr_ui_error(), ptr_ui_code(), ptr_ui_inline_error(),
ptr_ui_toggle_code(), ptr_ui_controls(), ptr_server()
ptr_ui_plot("myplot")ptr_ui_plot("myplot")
Returns the effective copy tree ggpaintr uses to label every generated
control. Call it with no arguments to see the current defaults, or pass
ui_text = a list of overrides to get back a validated, merged
ptr_ui_text object. That object can be reused across ptr_app() /
ptr_server() calls — those entry points short-circuit when handed an
already-merged ptr_ui_text, so the overrides are validated once.
ptr_ui_text(ui_text = NULL)ptr_ui_text(ui_text = NULL)
ui_text |
|
Use it to (1) discover the override schema (names(ptr_ui_text()) and
the section below), and (2) fail fast on a malformed override list before
launching an app — ptr_ui_text() raises on unknown sections, keywords,
or leaf fields.
A ptr_ui_text object containing the merged copy rules.
Override lists mirror the structure of ptr_ui_text(). Recognised paths
(every leaf is a named list of the fields below):
shell$title$<leaf>
shell$draw_button$<leaf>
shell$draw_all_button$<leaf>
shell$layer_picker$<leaf>
shell$data_subtab$<leaf>
shell$controls_subtab$<leaf>
upload$file$<leaf>
upload$name$<leaf>
layer_checkbox$<leaf>
defaults$<keyword>$<leaf> — per placeholder keyword (ppVar, ppText,
ppNum, ppExpr, ppUpload, ...)
params$<param>$<keyword>$<leaf> — per aesthetic/argument name
(x, y, color, ...); aliases (colour, size) are normalized
layers$<layer_name>$<keyword>$<param>$<leaf> — per layer override
(use __unnamed__ as <param> for positional arguments)
Leaf fields are label, help, placeholder, and empty_text. Leaf
strings may use the {param} and {layer} tokens, which are interpolated
at resolve time.
# Default rules rules <- ptr_ui_text() rules$shell$title$label # Override the draw button label rules <- ptr_ui_text( ui_text = list(shell = list(draw_button = list(label = "Render"))) ) rules$shell$draw_button$label# Default rules rules <- ptr_ui_text() rules$shell$title$label # Override the draw button label rules <- ptr_ui_text( ui_text = list(shell = list(draw_button = list(label = "Render"))) ) rules$shell$draw_button$label
</> ToggleOutput combinator: wraps a plot-ish tag (a bare ptr_ui_plot() or the
output of ptr_ui_inline_error()) and a bare code piece
(ptr_ui_code()) in the single .ptr-output scope the bundled
JavaScript needs, injecting the </> show-code button into the plot
card head and presenting the code inside the draggable slide-out
.ptr-code-window (with Copy / Close). The button hides/shows that
window purely DOM-locally — no Shiny input/output is involved. This is
the toggle layout ptr_app() / ptr_ui() render internally.
ptr_ui_toggle_code(plotish, code)ptr_ui_toggle_code(plotish, code)
plotish |
A plot-ish tag. The designed input is a bare
|
code |
A code piece, typically |
Use this when you want the familiar toggle behaviour while still owning
the surrounding layout. For fully independent placement of the plot and
code panes (no toggle), keep the bare pieces uncombined — a standalone
ptr_ui_code() is always visible and needs no wiring.
A shiny::tag — one .ptr-output containing plotish (with
the toggle button) and the .ptr-code-window-wrapped code.
ptr_ui_plot(), ptr_ui_code(), ptr_ui_inline_error(),
ptr_ui_controls(), ptr_server()
ptr_ui_toggle_code( ptr_ui_inline_error(ptr_ui_plot("p"), ptr_ui_error("p")), ptr_ui_code("p", style = "window") )ptr_ui_toggle_code( ptr_ui_inline_error(ptr_ui_plot("p"), ptr_ui_error("p")), ptr_ui_code("p", style = "window") )
Interactive RStudio addin. Highlight a token in your ggplot expression
(e.g. mpg in aes(x = mpg)), run the addin, and pick a placeholder from
a command-palette gadget; the selection is rewritten to ppVar(mpg). With
nothing highlighted the same palette opens and inserts ppVar() with the
caret between the parens. The placeholder list is read live from the
registry, so custom placeholders registered this session
(via ptr_define_placeholder_value() and friends) appear automatically.
ptr_wrap_placeholder_addin()ptr_wrap_placeholder_addin()
The gadget's Wrap in app button (left of Insert) takes a different
action: instead of inserting a placeholder it wraps the whole selection in a
braced block piped into ptr_app() –
{ / <selection> / } |> / ptr_app() – turning a ggplot
expression into a runnable ggpaintr app skeleton.
Invisibly NULL. Called for its side effect of editing the active
RStudio document.
By default the palette follows your RStudio editor theme (dark theme -> dark
palette, light -> light), via rstudioapi::getThemeInfo(). Force one with
options(ggpaintr.addin_theme = "dark"), "light", or "auto" (the
default).
For a highlight-then-keystroke flow, bind the addin once (RStudio reads
shortcuts only from your own keybindings, so packages cannot ship one). The
addin must be installed (not merely load_all()-ed) to appear in the
shortcut dialog:
Tools > Addins > Browse Addins..., then the Keyboard Shortcuts... button. (Or Tools > Modify Keyboard Shortcuts... and type "ggpaintr" in the search box.)
Find the ggpaintr placeholder row and click its Shortcut cell.
Press the recommended combination: Cmd+Shift+G on macOS, Ctrl+Shift+G on Windows/Linux. (Any free combination works; pick another if that one is already bound.)
Apply.
Requires rstudioapi and miniUI; it errors with a clear message when run outside RStudio.