---
title: "Plot types"
format:
  html:
    css: custom.css
tbl-colwidths: [20,20,50,10]
---

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:

1. Base types
2. **tinyplot** types
3. Custom types

We will use the built-in `airquality` dataset for our examples:

```{r}
library(tinyplot)

aq = transform(
  airquality,
  Month = factor(Month, labels = month.abb[unique(Month)]),
  Hot = Temp > median(Temp)
)
```

## 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.

```{r}
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](/man/type_ribbon.qmd) |
| `"errorbar"`          | `type_errorbar()`   | Adds error bars to points; requires `ymin` and `ymax`.         | [link](/man/type_errorbar.qmd) |
| `"l"` / `"b"` / etc.  | `type_lines()`      | Draws lines and line-alike (same as base `"l"`, `"b"`, etc.)   | [link](/man/type_lines.qmd) |
| `"pointrange"`        | `type_pointrange()` | Combines points with error bars.                               | [link](/man/type_errorbar.qmd) |
| `"p"`                 | `type_points()`     | Draws points (same as base `"p"`).                             | [link](/man/type_points.qmd) |
| `"polygon"`           | `type_polygon()`    | Draws polygons.                                                | [link](/man/type_polygon.qmd) |
| `"polypath"`          | `type_polypath()`   | Draws a path whose vertices are given in `x` and `y`.          | [link](/man/type_polypath.qmd) |
| `"rect"`              | `type_rect()`       | Draws rectangles; requires `xmin`, `xmax`, `ymin`, and `ymax`. | [link](/man/type_rect.qmd) |
| `"ribbon"`            | `type_ribbon()`     | Creates a filled area between `ymin` and `ymax`.               | [link](/man/type_ribbon.qmd) |
| `"segments"`          | `type_segments()`   | Draws line segments between pairs of points.                   | [link](/man/type_segments.qmd) |
| `"text"`              | `type_text()`       | Adds text annotations to a plot.                               | [link](/man/type_text.qmd) |

#### Visualizations

| string                     | function            | description                                    | docs |
|----------------------------|---------------------|------------------------------------------------|------|
| `"barplot"` / `"bar"`      | `type_barplot()`    | Creates a bar plot.                            | [link](/man/type_barplot.qmd) |
| `"boxplot"` / `"box"`      | `type_boxplot()`    | Creates a box-and-whisker plot.                | [link](/man/type_boxplot.qmd) |
| `"chull"`                  | `type_chull()`      | Draws convex hull(s) around grouped points.    | [link](/man/type_chull.qmd) |
| `"density"`                | `type_density()`    | Plots the density estimate of a variable.      | [link](/man/type_density.qmd) |
| `"ellipse"`                | `type_ellipse()`    | Draws confidence ellipse(s) around grouped points. | [link](/man/type_ellipse.qmd) |
| `"histogram"` / `"hist"`   | `type_histogram()`  | Creates a histogram of a single variable.      | [link](/man/type_histogram.qmd) |
| `"jitter"` / `"j"`         | `type_jitter()`     | Jittered points.                               | [link](/man/type_jitter.qmd) |
| `"qq"`                     | `type_qq()`         | Creates a quantile-quantile plot.              | [link](/man/type_qq.qmd) |
| `"ridge"`                  | `type_ridge()`      | Creates a ridgeline (aka joy) plot.            | [link](/man/type_ridge.qmd) |
| `"rug"`                    | `type_rug()`        | Adds a rug to an existing plot.                | [link](/man/type_rug.qmd) |
| `"spineplot"` / `"spine"`  | `type_spineplot()`  | Creates a spine plot or spinogram.             | [link](/man/type_spineplot.qmd) |
| `"violin"`                 | `type_violin()`     | Creates a violin plot.                         | [link](/man/type_violin.qmd) |
  
#### Models

| string                | function           | description                              | docs |
|-----------------------|--------------------|------------------------------------------|------|
| `"loess"`             | `type_loess()`     | Local regression curve.                  | [link](/man/type_loess.qmd) |
| `"lm"`                | `type_lm()`        | Linear regression line.                  | [link](/man/type_lm.qmd) |
| `"glm"`               | `type_glm()`       | Generalized linear model fit.            | [link](/man/type_glm.qmd) |
| `"spline"`            | `type_spline()`    | Cubic (or Hermite) spline interpolation. | [link](/man/type_spline.qmd) |

#### Functions

| string                | function           | description                              | docs |
|-----------------------|--------------------|-----------------------------------------|------|
| `"abline"`            | `type_abline()`    | Line(s) with intercept and slope.       | [link](/man/type_abline.qmd) |
| `"hline"`             | `type_hline()`     | Horizontal line(s).                     | [link](/man/type_abline.qmd) |
| `"vline"`             | `type_vline()`     | Vertical line(s).                       | [link](/man/type_abline.qmd) |
| `"function"`          | `type_function()`  | Arbitrary function.                     | [link](/man/type_function.qmd) |
| `"summary"`           | `type_summary()`   | Summarizes `y` by unique values of `x`. | [link](/man/type_summary.qmd) |


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.

```{r}
#| layout-ncol: 2
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.^[Note: If
we simply specified the `"glm"` convenience string on its own, we'd get a linear
fit since the default family is `gaussian`.]

```{r}
#| layout-ncol: 2
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.

```r
?type_jitter
?type_glm
# etc
```

Finally, please note that all **tinyplot** types support grouping and faceting.

```{r}
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.^[Examples of downstream R packages shipping bespoke
**tinyplot** types include **descstat** (see
[here](https://cran.r-project.org/web/packages/descstat/refman/descstat.html#tinyplot)
and
[here](https://cran.r-project.org/web/packages/descstat/vignettes/descstat.html))
and **trackframe** (see
[here](https://github.com/livingingroups/trackframe/blob/21c9be7924d871ef7a5a09c6d25285ec4865b2d9/R/plot_type_arrows.R)).]
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.

```{r}
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.

```{r}
#| layout-ncol: 2
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:

1. **`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).

2. **`data_<type>()`**: A function factory that returns a data transformation
   function:
    - **Required arguments**: `settings` and `...`
    - `settings` is an environment containing plot data and parameters,
       including `datapoints` (a data frame with `x`, `y`, `by`, etc.),
       `legend_args` (a list of legend customizations), and many other internal
       parameters (see [Appendix: Available settings](#appendix-available-settings)
       below for a complete list).
    - Should modify `settings` in-place (e.g., `settings$datapoints = ...`)
    - Does not need to return anything

3. **`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 `i` prefix 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

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.

```{r}
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 of `data_log()`)
- `draw = NULL` (drop the dedicated `draw_log()` function entirely; will fall back to `draw_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".

```{r}
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](https://github.com/grantmcdermott/tinyplot/tree/main/R)
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`](https://github.com/grantmcdermott/tinyplot/blob/main/R/type_points.R) or
[`type_area.R`](https://github.com/grantmcdermott/tinyplot/blob/main/R/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](https://github.com/grantmcdermott/tinyplot/issues)
on our GitHub repo.

### Advanced customization: declaring special type behaviour {#type-hints}

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: 

```r
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](https://github.com/grantmcdermott/tinyplot/tree/main/R) 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](https://github.com/grantmcdermott/tinyplot/blob/a1f1e2c80e1dd67508327ad2e05f229b6d537e17/R/type_hexbin.R#L317)).

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 {#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:

```{r}
#| error: true
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())
```

As we can see, the `settings` environment contains many parameters that custom
types can use as inputs or modify as needed.

