Nothing
#' Manage a list of properties.
#'
#' \code{props()} provides a tool for concise creation of \code{prop} objects
#' using a set of conventions designed to capture the most common use cases.
#' If you need something less common, you'll need to use \code{\link{prop}} to
#' access all possible options.
#'
#' @section Heuristics:
#'
#' If the values are not already objects of class \code{prop}, \code{props}
#' uses the following heuristics to when creating the prop:
#'
#' \itemize{
#' \item atomic vectors, e.g. \code{x = 1}: scaled = FALSE
#' \item an interative input, e.g. \code{x = input_slider}:
#' scaled = FALSE
#' \item a formula containing a single value, e.g. \code{x ~ 1}:
#' scaled = TRUE
#' \item a formula containing a name or expression, \code{x ~ mpg}:
#' scaled = TRUE
#' }
#'
#' @section Non-standard evaluation:
#'
#' \code{props} uses non-standard evaluation in a slightly unusual way:
#' if you provide a formula input, the LHS of the formula will provide the
#' name of the component. In otherwise, \code{props(x = y ~ 1)} is the
#' same as \code{props(y ~ 1)}.
#'
#' You can combine variables from the dataset and variables defined in the
#' local environment: expressions will be evaluated in the environment which
#' the formula was defined.
#'
#' If you have the name of a variable in a string, see the
#' props vignette for how to create the needed property mapping.
#'
#' @section Enter, exit, hover, and update events:
#'
#' There are four different property events that the marks
#' can use. These can, for example, be used to change the appearance of a mark
#' when the mouse cursor is hovering over it: when the mark is hovered over, it
#' uses the hover event, and when the mark isn't hovered over, it uses the
#' update event
#'
#' \itemize{
#' \item enter: This event is used by marks when they are added to a plot.
#' \item update: This event is used by marks after they have entered, and
#' also after they have been hovered over.
#' \item exit: This event is used by marks as they are removed from a plot.
#' \item hover: This event is used when the mouse cursor is over the mark.
#' }
#'
#' You can specify the event for a property, by putting a period and the
#' event after the property name. For example,
#' \code{props(fill.update := "black", fill.hover := "red")} will make a mark
#' have a black fill normally, and red fill when it is hovered over.
#'
#' The default event is update, so if you run \code{props(fill := "red")},
#' this is equivalent to \code{props(fill.update := "red")}.
#'
#' In practice, the enter and exit events are useful only when the update has
#' a duration (and is therefore not instantaneous). The update event can be
#' thought of as the "default" state.
#'
#' @section Key property:
#'
#' In addition to the standard properties, there is a special optional property
#' called \code{key}. This is useful for plots with dynamic data and smooth
#' transitions: as the data changes, the key is used to tell the plot how the
#' new data rows should be matched to the old data rows. Note that the key must
#' be an unscaled value. Additionally, the key property doesn't have a event,
#' since it is independent of enter, update, exit, and hover events.
#'
#' @template properties
#' @param ... A set of name-value pairs. The name should be a valid vega
#' property.
#'
#' The first two unnamed components are taken to be \code{x} and \code{y}.
#' Any additional unnamed components will raise an error.
#' @param .props When calling \code{props} from other functions, you'll
#' often have a list of quoted function functions. You can pass that function
#' to the \code{.props} argument instead of messing around with
#' substitute. In other words, \code{.props} lets you opt out of the
#' non-standard evaluation that \code{props} does.
#' @param inherit If \code{TRUE}, the defaults, will inherit from properties
#' from the parent layer If \code{FALSE}, it will start from nothing.
#' @param env The environment in which to evaluate variable properties.
#' @export
#' @examples
#' # Set to constant values
#' props(x := 1, y := 2)
#' # Map to variables in the dataset
#' props(x = ~mpg, y = ~cyl)
#' # Set to a constant value in the data space
#' props(x = 1, y = 1)
#' # Use an interactive slider
#' props(opacity := input_slider(0, 1))
#'
#' # To control other settings (like custom scales, mult and offset)
#' # use a prop object
#' props(prop("x", "old", scale = "x", offset = -1))
#'
#' # Red when hovered over, black otherwise (these are equivalent)
#' props(fill := "black", fill.hover := "red")
#' props(fill.update := "black", fill.hover := "red")
#'
#' # Use a column called id as the key (for dynamic data)
#' props(key := ~id)
#'
#' # Explicitly create prop objects. The following are equivalent:
#' props(fill = ~cyl)
#' props(fill.update = ~cyl)
#' props(prop("fill", ~cyl))
#' props(prop("fill", ~cyl, scale = "fill", event = "update"))
#'
#' # Prop objects can be programmatically created and added:
#' property <- "fill"
#' expr <- parse(text = "wt/mpg")[[1]]
#' p <- prop(property, expr)
#' props(p)
#'
#' # Using .props
#' props(.props = list(x = 1, y = 2))
#' props(.props = list(x = ~mpg, y = ~cyl))
#' props(.props = list(quote(x := ~mpg)))
props <- function(..., .props = NULL, inherit = TRUE, env = parent.frame()) {
check_empty_args()
args <- dots(...)
all <- args_to_props(c(args, .props), env)
structure(
all,
inherit = inherit,
class = "ggvis_props"
)
}
dots <- function(...) {
as.list(substitute(...()))
}
uses_colon_equals <- function(x) {
is.call(x) && identical(x[[1]], quote(`:=`))
}
# Given a list of unevaluated expressions, return a list of prop objects
args_to_props <- function(args, env) {
# Given a name and expression, create a prop
expr_to_prop <- function(name, expr, scale = NULL) {
name <- strsplit(name, ".", fixed = TRUE)[[1]]
property <- name[1]
event <- if (length(name) > 1) name[2] else NULL
val <- eval(expr, env)
prop(property, val, scale = scale, event = event, label = as.character(val))
}
# Given a prop, get the full name, like x.update
prop_full_name <- function(p) {
# Use this form so that if event is NULL, there won't be trailing .
paste(c(p$property, p$event), collapse = ".")
}
arg_names <- names2(args)
named_args <- args[arg_names != ""]
unnamed_args <- args[arg_names == ""]
# First pass: Convert named arguments to props
named_args <- Map(named_args, names(named_args),
f = function(x, name) expr_to_prop(name, x, scale = TRUE)
)
# Second pass: Convert unnamed arguments to prop objects, or raw value
unnamed_args <- lapply(unnamed_args, function(x) {
if (uses_colon_equals(x)) {
expr_to_prop(deparse(x[[2]]), x[[3]], scale = FALSE)
} else {
# It's either prop() call, or an unnamed value
eval(x, env)
}
})
# Some of the unnamed items are now props, others are raw values.
is_prop <- vapply(unnamed_args, is.prop, logical(1))
unnamed_props <- unnamed_args[is_prop]
unnamed_values <- unnamed_args[!is_prop]
# Assign full name, like x.update
names(named_args) <- vapply(named_args, prop_full_name, character(1))
names(unnamed_props) <- vapply(unnamed_props, prop_full_name, character(1))
# Assign missing names to unnamed values
missing_names <- setdiff(c("x.update", "y.update"),
c(names(named_args), names(unnamed_props)))
if (length(unnamed_values) > length(missing_names)) {
stop("Too many unnamed properties (can only have x and y)", call. = FALSE)
}
names(unnamed_values) <- missing_names[seq_along(unnamed_values)]
# Final pass: Convert the now-named values to props
unnamed_values <- Map(unnamed_values, names(unnamed_values),
f = function(x, name) expr_to_prop(name, x, scale = TRUE)
)
c(named_args, unnamed_props, unnamed_values)
}
#' @export
format.ggvis_props <- function(x, ...) {
labels <- lapply(x, format, ...)
if (length(labels) > 0) {
inherit <- if (!attr(x, "inherit", TRUE)) "\ninherit: FALSE" else ""
paste0(
paste0("* ", names(x), ": ", labels, collapse = "\n"),
inherit,
sep = "\n")
} else {
"props()"
}
}
#' @export
print.ggvis_props <- function(x, ...) cat(format(x, ...))
#' @rdname props
#' @export
#' @param x an object to test for props-ness.
is.ggvis_props <- function(x) inherits(x, "ggvis_props")
#' @export
`[.ggvis_props` <- function(x, idx) {
structure(NextMethod(), inherit = attr(x, "inherit", TRUE), class = "ggvis_props")
}
# Merge two ggvis_props objects
#
# merge_props(props(x = ~x))
# merge_props(props(x = ~x), props(x = ~y))
# merge_props(props(x = ~x, y = 1), props(x = ~y))
# merge_props(props(x = ~x, y = 1), props(x = ~y, inherit = FALSE))
merge_props <- function(parent = NULL, child = NULL,
inherit = attr(child, "inherit", TRUE)) {
if (is.null(parent)) return(child)
if (is.null(child)) return(parent)
stopifnot(is.ggvis_props(parent), is.ggvis_props(child))
if (identical(inherit, FALSE)) return(child)
structure(merge_vectors(parent, child),
inherit = attr(parent, "inherit", TRUE),
class = "ggvis_props")
}
is.formula <- function(x) inherits(x, "formula")
find_prop_var <- function(props, name) {
prop <- props[[name]]
if (is.null(prop)) {
stop("Can't find prop ", name, call. = FALSE)
}
if (!is.prop_variable(prop)) {
stop("Visual property ", name, " is not a variable", call. = FALSE)
}
stats::formula(prop)
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.