library(tinyplot)
aq = transform(
airquality,
Month = factor(Month, labels = month.abb[unique(Month)]),
Hot = Temp > median(Temp)
)Plot types
A key feature of tinyplot is the type argument, which allows you to specify different kinds of plots. This tutorial will guide you through the various plot types available in tinyplot, demonstrating how the type argument can be used to to create a wide range of visualizations.
We will consider three categories of plot types:
- Base types
- tinyplot types
- Custom types
We will use the built-in airquality dataset for our examples:
Base types
The type argument in tinyplot supports all the standard plot types from base R’s plot function. These are specified using single-character strings.
"p"(Points): Produces a scatter plot of points. This is (usually) the default plot type."l"(Lines): Produces a line plot."b"(Points and Lines): Combines points and lines in same the plot."c"(Empty Points Joined by Lines): Plots empty points connected by lines."o"(Overplotted Points and Lines): Overlaps points and lines."s"and"S"(Stair Steps): Creates a step plot."h"(Histogram-like Vertical Lines): Plots vertical lines resembling a histogram."n"(Empty Plot): Creates an empty plot frame without data.
For example, we can use "b" to create a plot with combined points and lines.
tinyplot(Temp ~ Day | Month, data = aq, type = "b", main = "Points and lines")
tinyplot types
Beyond the base types, tinyplot introduces additional plot types for more advanced visualizations. Each of these additional types are available either as a convenience string (with default behaviour) or a companion type_*() function (with options for customized behaviour).
Shapes
| string | function | description | docs |
|---|---|---|---|
"area" |
type_area() |
Plots the area under the curve from y = 0 to y = f(x). |
link |
"errorbar" |
type_errorbar() |
Adds error bars to points; requires ymin and ymax. |
link |
"l" / "b" / etc. |
type_lines() |
Draws lines and line-alike (same as base "l", "b", etc.) |
link |
"pointrange" |
type_pointrange() |
Combines points with error bars. | link |
"p" |
type_points() |
Draws points (same as base "p"). |
link |
"polygon" |
type_polygon() |
Draws polygons. | link |
"polypath" |
type_polypath() |
Draws a path whose vertices are given in x and y. |
link |
"rect" |
type_rect() |
Draws rectangles; requires xmin, xmax, ymin, and ymax. |
link |
"ribbon" |
type_ribbon() |
Creates a filled area between ymin and ymax. |
link |
"segments" |
type_segments() |
Draws line segments between pairs of points. | link |
"text" |
type_text() |
Adds text annotations to a plot. | link |
Visualizations
| string | function | description | docs |
|---|---|---|---|
"barplot" / "bar" |
type_barplot() |
Creates a bar plot. | link |
"boxplot" / "box" |
type_boxplot() |
Creates a box-and-whisker plot. | link |
"chull" |
type_chull() |
Draws convex hull(s) around grouped points. | link |
"density" |
type_density() |
Plots the density estimate of a variable. | link |
"ellipse" |
type_ellipse() |
Draws confidence ellipse(s) around grouped points. | link |
"histogram" / "hist" |
type_histogram() |
Creates a histogram of a single variable. | link |
"jitter" / "j" |
type_jitter() |
Jittered points. | link |
"qq" |
type_qq() |
Creates a quantile-quantile plot. | link |
"ridge" |
type_ridge() |
Creates a ridgeline (aka joy) plot. | link |
"rug" |
type_rug() |
Adds a rug to an existing plot. | link |
"spineplot" / "spine" |
type_spineplot() |
Creates a spine plot or spinogram. | link |
"violin" |
type_violin() |
Creates a violin plot. | link |
Models
| string | function | description | docs |
|---|---|---|---|
"loess" |
type_loess() |
Local regression curve. | link |
"lm" |
type_lm() |
Linear regression line. | link |
"glm" |
type_glm() |
Generalized linear model fit. | link |
"spline" |
type_spline() |
Cubic (or Hermite) spline interpolation. | link |
Functions
| string | function | description | docs |
|---|---|---|---|
"abline" |
type_abline() |
Line(s) with intercept and slope. | link |
"hline" |
type_hline() |
Horizontal line(s). | link |
"vline" |
type_vline() |
Vertical line(s). | link |
"function" |
type_function() |
Arbitrary function. | link |
"summary" |
type_summary() |
Summarizes y by unique values of x. |
link |
To see the difference between the convenience strings and their respective type_*() functional equivalents, let’s quickly walk through two examples.
Example 1: jittering. We can add noise to data points using jittering. This allows us to avoid overplotting and can be useful to visualize discrete variables. On the left, we use the "jitter string shortcut with default settings. On the right, we use the type_jitter() function to reduce the amount of jittering.
tinyplot(
Temp ~ Month, data = aq, main = "Default jittering",
type = "jitter"
)
tinyplot(
Temp ~ Month, data = aq, main = "Modest jittering",
type = type_jitter(amount = 0.05)
)

Example 2: Logit fit. In this example, we use type_glm() to fit a logistic regression model to the data, but with different confidence intervals.1
tinyplot(
Hot ~ Wind, data = aq, main = "Logit with 95% interval",
type = type_glm(family = "binomial")
)
tinyplot(
Hot ~ Wind, data = aq, main = "Logit with 50% interval",
type = type_glm(family = "binomial", level = 0.5)
)

To see what arguments are available for each type, simply consult the type-specific documentation.
?type_jitter
?type_glm
# etcFinally, please note that all tinyplot types support grouping and faceting.
tinyplot(Temp ~ Wind | Month, data = aq, facet = "by", type = "lm")
Custom types
We want tinyplot to be as extensible as possible. Users should be able to create their own custom tinyplot types and, where appropriate, make them available to others.2 To create a custom type, you need only define a new type_<type>() function that adheres to a few basic principles.
Let’s walk through a minimal example to see how this works in practice.
A minimal example
Consider a custom type_log() that logs both x and y before plotting points.
type_log = function(base = exp(1)) {
# data transformation function
data_log = function() {
fun = function(settings, ...) {
# extract raw datapoints from settings environment
datapoints = settings$datapoints
# transform (log) the data
datapoints$x = log(datapoints$x, base = base)
datapoints$y = log(datapoints$y, base = base)
datapoints = datapoints[order(datapoints$x), ]
# re-assign modified datapoints back to settings
settings$datapoints = datapoints
}
return(fun)
}
# drawing function
draw_log = function() {
fun = function(ix, iy, icol, ...) {
points(
x = ix,
y = iy,
col = icol
)
}
return(fun)
}
# return object (list)
out = list(
data = data_log(),
draw = draw_log(),
name = "p" # fallback behaviour same as "p" type (e.g., legend defaults)
)
class(out) = "tinyplot_type"
return(out)
}To use, simply pass to type = type_log(<args>) as per as normal.
tinyplot(mpg ~ wt | factor(am), data = mtcars,
type = type_log(), main = "Ln (natural logarithm)")
tinyplot(mpg ~ wt | factor(am), data = mtcars,
type = type_log(base = 10), main = "Log (base 10)")

How it works
As illustrated by our minimal example, custom tinyplot types require three key functions:
type_<type>(): The main wrapper function that users will call. It should accept any type-specific arguments that users will need to adjust the final plot (e.g., here:base). More importantly, it must return a list of class"tinyplot_type"and contain three elements:data: A data transformation function (see below)draw: A drawing function (see below)name: The name of the underlying plot type. This can be anything, but re-using the name of a built-in type (e.g.,"p") is a convenient way to inherit its default/fallback behaviour (e.g. legend defaults).
data_<type>(): A function factory that returns a data transformation function:- Required arguments:
settingsand... settingsis an environment containing plot data and parameters, includingdatapoints(a data frame withx,y,by, etc.),legend_args(a list of legend customizations), and many other internal parameters (see Appendix: Available settings below for a complete list).- Should modify
settingsin-place (e.g.,settings$datapoints = ...) - Does not need to return anything
- Required arguments:
draw_<type>(): A function factory that returns a drawing function:- Required arguments:
... - Available arguments:
ix,iy,ixmin,ixmax,iymin,iymax,icol,ibg,ipch,ilty,ilwd,icex, and others - The
iprefix indicates data for a single group (when using|grouping or facets) - Should call base R graphics functions (e.g.,
points(),lines(),polygon()) to render the plot
- Required arguments:
The key insight is that data_*() transforms the data once, while draw_*() is called repeatedly for each group or facet.
Pro tip: Re-use existing plot types
Rather than code up all of the internals for a custom type from scratch, users are strongly encouraged to re-use the scaffolding for existing types. One of the easiest ways to do this—which we use repeatedly in the main tinyplot codebase—is by “delegating” to an existing type at the end of the data_<type> function. Again, this is perhaps easier seen than explained, so consider an alternate version of our previous type_log() function.
type_log2 = function(base = exp(1)) {
# data transformation function
data_log = function() {
fun = function(settings, ...) {
datapoints = settings$datapoints
datapoints$x = log(datapoints$x, base = base)
datapoints$y = log(datapoints$y, base = base)
datapoints = datapoints[order(datapoints$x), ]
settings$datapoints = datapoints
# re-assign/delegate to "p" type
settings$type = "p"
}
return(fun)
}
# return object (list)
out = list(
data = data_log(),
draw = NULL, # NULL => fall back to default type (now "p") drawing function
name = "log"
)
class(out) = "tinyplot_type"
return(out)
}The key differences from our original type_log function are:
settings$type = "p"(re-assign the type at the end ofdata_log())draw = NULL(drop the dedicateddraw_log()function entirely; will fall back todraw_points()internally now)
This new version is obviously shorter than our original version, since we can skip the custom draw_log() section. But re-using existing tinyplot scaffolding brings added benefits beyond code concision. In particular, it guarantees that all of the functionality from an existing type will be made available to your custom type. (Which, trust us, is safer.) For example, it guarantees that grouping behaviour by other aesthetics (e.g, pch) passes through “for free”.
tinyplot(mpg ~ wt | factor(am), data = mtcars,
pch = "by",
type = type_log2(), main = "Better to re-use an existing drawing type")
Moral of the story: be lazy and re-use existing tinyplot scaffolding wherever possible, especially for drawing functions.
More examples
The tinyplot source code contains many examples of type constructor functions that should provide a helpful starting point for custom plot types. Each built-in tinyplot type is itself implemented as a custom type, so you can see real-world examples of varying complexity. For example, take a look at the type_points.R or type_area.R code. (The latter provides an example where we delegate to the type_ribbon drawing code.)
Beyond that, the tinyplot team are always happy to help guide users on how to create their own types. Just let us know by raising an issue on our GitHub repo.
Advanced customization: declaring special type behaviour
In most cases, simply declaring the data_<type>() transformation and passing to an appropriate drawing function should be enough for your custom type. Additional features like faceting, theming, and layering should flow seamlessly through the rest of the tinyplot machinery.
However, some plot types require advanced customization features that go beyond the basics. For example, type_spineplot() ships its own special (dual) axes logic that the surrounding scaffolding—margins, axes limits—needs to account for. For these special cases, we provide several escape hatches that your type can plug into to override default behaviour. The one you’re most likely to need is settings$type_hints; a named list that you can set inside your data_<type>() function. If a specific hint is declared, then tinyplot knows to use it for custom behaviour rather than reverting to the default logic.
Again, an example may help to illustrate. Say we are defining a custom type whose axes should span a fixed [0, 1] range (i.e., we don’t want its axis limits determined by the empirical data range). We can pass the special has_proportional_lim flag to ensure this:
data_mytype = function() {
fun = function(settings, ...) {
settings$type_hints = list(has_proportional_lim = TRUE)
# ... other data transformations
}
return(fun)
}Currently, type_hints accepts several known hints; all of which are booleans. Any hint left undefined is treated internally as NULL and is ignored. So you need only define the list of hints that you actually want:
| Hint | Declared by | Effect when TRUE |
|---|---|---|
draws_own_axes |
type_ridge(), type_spineplot() |
Your draw_*() function draws its own axes (having set xaxt/yaxt to "n"). Reserves the tick-row margin that would otherwise be dropped, so self-drawn labels aren’t clipped. |
has_rhs_axis |
type_spineplot() |
Reserve right-hand margin for a secondary axis. |
has_proportional_lim |
type_spineplot() |
Treat the limits as proportional (e.g. [0, 1]); don’t expand them to axis breaks. |
framed |
type_spineplot() |
Report the user’s frame.plot choice when your type overrides the setting internally (e.g. because it draws its own box). Used to decide whether interior facets draw their own axes. |
legend_fills_from_col |
type_hexbin(), type_spineplot() |
Fill the legend key with the group colour (col) rather than bg. |
legend_fills_from_seq_palette |
type_ridge() |
Fill the legend key with a lighter step of the group colour’s sequential ramp. |
legend_border_fg |
type_spineplot() |
Draw the legend key border in the foreground colour rather than the group colour. |
To see type_hints in action, consult the source code of the types listed above.
Beyond type_hints, another customization escape hatch is provided by the sister type_info setting. Without going into too much detail, the key difference between these two is that type_hints specifies behaviour you want the plot machinery to respect, while type_info is used for passing bespoke rendering payload from data_<type>() through to draw_<type>() (e.g., coordinates, precomputed breaks, and so on). An example is the custom hex polygon offsets of type_hexbin() (see here).
We reiterate that type_hints and type_info are very much in the advanced corner of the tinyplot codebase. Most users can safely ignore these internal features when creating their own custom types. But feel free to reach out if you need help.
Appendix: Available settings
To see what objects and parameters are available in the settings environment, we can create a simple type_error() function that stops with the names of all available settings:
type_error = function() {
data_error = function() {
fun = function(settings, ...) {
stop(paste(sort(names(settings)), collapse = ", "))
}
return(fun)
}
out = list(
data = data_error()
)
class(out) = "tinyplot_type"
return(out)
}
tinyplot(mpg ~ wt, data = mtcars, type = type_error())Error in `settings$type_data()`:
! add, alpha, axes, bg, bubble, bubble_alpha, bubble_bg_alpha, bubble_pch, by, by_dep, call, cap, cex, cex_dep, col, datapoints, dodge, dots, draw, facet, facet_attr, facet_by, facet_dep, facet.args, file, fill, flip, frame.plot, group_offsets, height, labels, legend, legend_args, lighten, log, lty, lwd, null_by, null_facet, null_palette, null_xlim, null_ylim, offsets_axis, palette, pch, rev_x, rev_y, ribbon.alpha, sub, type, type_data, type_draw, type_hints, type_info, type_name, weights, weights_used, width, x, x_by, x_dep, xaxb, xaxl, xaxs, xaxt, xlab, xlabs, xlim, xmax, xmax_dep, xmin, xmin_dep, y, y_dep, yaxb, yaxl, yaxs, yaxt, ygroup, ylab, ylabs, ylim, ymax, ymax_dep, ymin, ymin_dep
As we can see, the settings environment contains many parameters that custom types can use as inputs or modify as needed.