In ggvis, there are two types of layers. Simple layers correspond directly to vega marks and represent geometric primitives like points, lines and rectangles. Compound layers combine data transformations with one or more simple layers.


Marks are the fundamental building block. Each type of mark represents a different type of geometric object, and so in some sense they are the equivalent of geoms in ggplot2. However, there are a far fewer marks than geoms because marks tend to be more capable, and because they never perform any statistical transformation.

Most of the time you don’t use marks directly - you instead use their layer wrappers. That’s because most of the time you don’t want to think about the difference between simple and compound layers - you just want to make a plot. But if you want to understand more deeply how things work, or create your own layer functions, it’s important to understand the distinction.

ggvis exposes five vega marks that have very similar ggplot2 equivalents:

There are two marks that have no equivalent in ggplot2:

And two vega marks that are not currently implemented in ggvis:

All marks take two optional arguments which are only needed if you want to override the values inherited from parent:

Adding a layer to a plot

For example, the following two plots are equivalent. In the first one, the data and props are defined at the top level and inherited by the mark, and in the second, they are defined in the mark itself. Note that the order of the arguments to ggvis() and the mark function are different: this is because you’ll usually set the data in top-level plot object, and usually only override props in the mark.

mtcars %>% ggvis(x = ~wt, y = ~mpg, stroke := "red") %>% layer_points()

mtcars %>% ggvis() %>% layer_points(x = ~wt, y = ~mpg, stroke := "red")

Mark details

The following sections describe particular details related to individual marks.


Must set two of x, x2, and width, and two of y, y2 and height.

If you have an ordinal scale, set width and/or height to prop_band() to occupy the complete band corresponding to that categorical value.

hec <- ~ Hair + Eye, HairEyeColor))

hec %>% 
  ggvis(~Hair, ~Eye, fill = ~Freq) %>% 
  layer_rects(width = band(), height = band()) %>%
  scale_nominal("x", padding = 0, points = FALSE) %>%
  scale_nominal("y", padding = 0, points = FALSE)


layer_path produces lines if fill is empty, and polygons if it is set to a value.

df <- data.frame(x = c(1, 1, 2, 2), y = c(2, 1, 1, 2))
df %>% ggvis(~x, ~y, stroke := "red") %>% layer_paths()

# Add a fill colour to make it a polygon
df %>% ggvis(~x, ~y, fill := "red") %>% layer_paths()

You can not currently set the component of lines to different colours: track progress at

To create a line that spans the complete range of the plot, use prop_group():

ggvis() %>%
  layer_points(x = ~disp, y = ~wt, data = mtcars) %>%
  layer_paths(x := 0, y = ~mean(mtcars$wt, x2 := prop_group())) %>%
  layer_paths(x = ~mean(mtcars$disp), y := 0, y2 := prop_group())

Note that this won’t work because prop_group() hasn’t been written yet: track progress at


You need to set two of y, y2 and height:

df <- data.frame(x = 1:10, y = (1:10) ^ 2)
df %>% ggvis(~x, ~y, y2 := 0) %>% layer_ribbons()

# Set height in pixels
df %>% ggvis(~x, ~y, height := 20) %>% layer_ribbons()

Height can only be mapped to a constant, because it does not have an obvious scale associated with it. You could force height to use the y scale, but that doesn’t work - the area hangs below the y line, and increasing the value of height makes the area narrower! What’s going on is that the underlying graphics device has (0, 0) in the top-left corner, and so the y-scale is upside down. As you increase height, it’s mapped like a y variable so bigger values are further away.

df %>% ggvis(~x, ~y, prop("height", 80, scale = "y")) %>% layer_ribbons()

df <- data.frame(x = 1:10, y = (1:10) ^ 2)
df %>% ggvis(~x, ~y) %>%
  layer_ribbons(prop("height", input_slider(0, 100), scale = "y")) %>%
  layer_paths(stroke := "red", strokeWidth := 10)
#> Warning: Can't output dynamic/interactive ggvis plots in a knitr document.
#> Generating a static (non-dynamic, non-interactive) version of the plot.

If you want to set the height in data units, you need to compute the offsets yourself:

df %>% ggvis(~x, y = ~y - 2, y2 = ~y + 2) %>% layer_ribbons()

The distinction between dimension and location is important in other graphics system, and a grid vignette vignette("locndimn", package = "grid") has a little more information about how things work in grid.


In ggplot2, grouping was a special aesthetic; in ggvis, grouping is a data transformation: use group_by() to split your data up into pieces given a specified variable, or auto_split() to split up by any categorical variable included in the plot:

mtcars %>% ggvis(~wt, ~mpg) %>%
  layer_points() %>%
  group_by(cyl) %>%

mtcars %>% ggvis(~wt, ~mpg) %>%
  layer_points() %>%
  auto_group() %>%

Some layers, like layer_line(), include auto_split() so will split automatically:

mtcars %>% 
  dplyr::mutate(cyl2 = factor(cyl)) %>% 
  ggvis(~wt, ~mpg, stroke = ~cyl2) %>% 

Compound layers

Simple layers map directly to vega’s primitive drawing functions (marks). Compound layers combine data transformation and simple layers (potentially multiple of each) to more sophisticated graphics. For example:

All layers start with layer_. The source is usually relatively simple - the complexity generally comes when figuring out how to map properties supplied to the layer to individual marks:

#> function (vis, ..., span = 0.75, se = FALSE) 
#> {
#>     formula <- guess_formula(vis$cur_props, "loess", quiet = TRUE)
#>     layer_model_predictions(vis, ..., model = "loess", formula = formula, 
#>         model_args = list(span = span), se = se)
#> }
#> <environment: namespace:ggvis>
#> function (vis, ..., width = NULL, center = NULL, boundary = NULL, 
#>     closed = c("right", "left"), stack = TRUE, binwidth) 
#> {
#>     if (!missing(binwidth)) {
#>         width <- binwidth
#>         deprecated("binwidth", "width", version = "0.3.0")
#>     }
#>     closed <- match.arg(closed)
#>     new_props <- merge_props(cur_props(vis), props(...))
#>     check_unsupported_props(new_props, c("x", "y", "x2", "y2"), 
#>         c("enter", "exit", "hover"), "layer_histograms")
#>     x_var <- find_prop_var(new_props, "x.update")
#>     x_val <- eval_vector(cur_data(vis), x_var)
#>     vis <- set_scale_label(vis, "x", prop_label(new_props$x.update))
#>     vis <- scale_numeric(vis, "y", domain = c(0, NA), expand = c(0, 
#>         0.05), label = "count")
#>     layer_f(vis, function(x) {
#>         x <- compute_bin(x, x_var, width = width, center = center, 
#>             boundary = boundary, closed = closed)
#>         if (stack) {
#>             x <- compute_stack(x, stack_var = ~count_, group_var = ~x_)
#>             rect_props <- merge_props(new_props, props(x = ~xmin_, 
#>                 x2 = ~xmax_, y = ~stack_upr_, y2 = ~stack_lwr_))
#>             x <- emit_rects(x, rect_props)
#>         }
#>         else {
#>             rect_props <- merge_props(new_props, props(x = ~xmin_, 
#>                 x2 = ~xmax_, y = ~count_, y2 = 0))
#>             x <- emit_rects(x, rect_props)
#>         }
#>         x
#>     })
#> }
#> <environment: namespace:ggvis>

If you find yourself using a set of marks commonly, it might be worth writing your own layer function.

Conversion from ggplot2

The following list provides a conversion from ggplot2 geoms to vega marks. However, because ggvis currently provides few transformations, many translations don’t currently exists - but they are on the roadmap and will be added over time.