---
title: "Introduction to tinyplot"
format: html
engine: knitr
vignette: |
  %\VignetteIndexEntry{Tutorial}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r include=FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  # fig.path = "man/figures/README-",
  out.width = "70%",
  fig.width = 8,
  dpi = 300,
  asp = 0.625
)
```

**tinyplot** is a lightweight extension of the base R graphics system, designed
to simplify and enhance data visualization. This tutorial provides a gentle
introduction to the package's core features and syntax. We won’t cover
everything, but you should come away with a solid understanding of how
**tinyplot** works and how it can integrate with your own projects.

We start this tutorial by loading the package and a slightly modified version
of the `airquality` dataset that comes bundled with base R.

```{r aq}
library(tinyplot)

aq = transform(
  airquality,
  Month = factor(month.abb[Month], levels = month.abb[5:9]),
  hot   = ifelse(Temp>=75, "hot", "cold"),
  windy = ifelse(Wind>=15, "windy", "calm")
)
```

## Equivalence with `plot()`

As far as possible, `tinyplot` tries to be a drop-in replacement for regular `plot`
calls.

```{r base_1}
par(mfrow = c(1, 2))

plot(0:10, main = "plot")
tinyplot(0:10, main = "tinyplot")
par(mfrow = c(1, 1)) # reset layout
```

Similarly, we can plot elements from a data frame using either the atomic or
formula methods. Here is a simple example using the `aq` dataset that we created
earlier.

```{r tinyplot_simple}
# with(aq,  tinyplot(Day, Temp)) # atomic method (same as below)
tinyplot(Temp ~ Day, data = aq)  # formula method
```

::: {.callout-tip}
## Tip: `plt()` shorthand alias

Use the `plt()` alias instead of `tinyplot()` to save yourself a few keystrokes.
For example:

```{r plt_simple}
#| eval: false
plt(Temp ~ Day, data = aq)
```

Feel free to try this shorthand with any of the plots that follow; `tinyplot(<args>)`
and `plt(<args>)` should produce identical results.
:::

## Grouped data

Where `tinyplot` starts to diverge from its base counterpart is
with respect to grouped data. In particular, `tinyplot` allows you to characterize
groups using the `by` argument.^[At this point, experienced base plot users 
might protest that you _can_ colour by groups using the `col` argument, e.g.
`with(aq, plot(Day, Temp, col = Month))`. This is true,
but there are several limitations. First, you don't get an automatic legend.
Second, the base `plot.formula` method doesn't specify the grouping within the
formula itself (not a deal-breaker, but not particularly consistent either).
Third, and perhaps most importantly, this grouping doesn't carry over to line
plots (i.e., type="l"). Instead, you have to transpose your data and use
`matplot`. See
[this](https://stackoverflow.com/questions/10519873/how-to-create-a-line-plot-with-groups-in-base-r-without-loops)
old StackOverflow thread for a longer discussion.]

```{r by}
# tinyplot(aq$Day, aq$Temp, by = aq$Month) # same as below
with(aq, tinyplot(Day, Temp, by = Month))
```

An arguably more convenient approach is to use the equivalent formula syntax.
Just place the "by" grouping variable after a vertical bar (i.e., `|`).

```{r formula}
tinyplot(Temp ~ Day | Month, data = aq)
```

You can use standard base plotting arguments to adjust features of your plot.
For example, change `pch` (plot character) to get filled points and `cex`
(character expansion) to change their size.

```{r pch_16}
tinyplot(
  Temp ~ Day | Month, data = aq,
  pch = 16,
  cex = 2
)
```

Similarly, converting to a grouped line plot is a simple matter of adjusting the
`type` argument.

```{r type_l}
tinyplot(
  Temp ~ Day | Month, data = aq,
  type = "l"
)
```

The default behaviour of `tinyplot` is to represent groups through colour. However,
note that we can automatically adjust `pch` and `lty` by groups too by passing
the `"by"` convenience keyword. This can be used in conjunction with the default
group colouring. Or, as a replacement for group colouring---an option that may
be particularly useful for contexts where colour is expensive or prohibited
(e.g., certain academic journals).

```{r by_lty}
tinyplot(
  Temp ~ Day | Month, data = aq,
  type = "l",
  col = "black", # override automatic group colours
  lty = "by"     # change line type by group instead
)
```

The `"by"` convenience argument is also available for mapping group colours to
background fill `bg` (alias `fill`). One use case is to override the grouped
border colours for filled plot characters and instead pass them through the
background fill.

```{r}
tinyplot(
  Temp ~ Day | Month, data = aq,
  pch = 21,      # use filled circles
  col = "black", # override automatic group (border) colours of points
  fill = "by"    # use background fill by group instead
)
```

## Aesthetic and themes

The previous examples illustrate a core pillar of the **tinyplot** philosophy:
customizing your plot aesthetic should be easy. But we can go further still.
Take colours, for instance. The default set of discrete colours for grouped
plots are inherited from the user's current global palette; most likely the "R4"
palette (see `?palette`). But users can switch to another any of R's (many)
other built-in colour palettes simply by passing the name as a convenience
string to the `palette` argument.^[See `palette.pals()` and `hcl.pals()` for the
full set of available palettes. Or, read Zeileis & Murrell (2023, The R Journal,
[doi:10.32614/RJ-2023-071](https://doi.org/10.32614/RJ-2023-071)). Beyond these
convenience strings, users can also supply a valid palette-generating function
for finer control and additional options. If you have installed the **ggsci**
package ([link](https://nanx.me/ggsci/index.html)), for example, then you could
use `palette = pal_npg()` to generate a palette consistent with those used by
the Nature Publishing Group.] For example:

```{r palette_tableau}
tinyplot(
  Temp ~ Day | Month, data = aq,
  pch = 19,
  palette = "tableau" # or "ggplot2", "okabe", "set2", "harmonic", etc.
)
```

You can also use the `alpha` argument to adjust the (alpha) transparency of your colours.

```{r}
tinyplot(
  Temp ~ Day | Month, data = aq,
  pch = 19,
  palette = "tableau",
  alpha = 0.3
)
```

The `tinyplot` function accepts many such arguments for tweaking your plot
aesthetic. However, rather than overwhelm readers with all of the options, we
would rather highlight a simpler way to customize your plots: _themes_. For
example:

```{r theme_dark}
tinyplot(
  Temp ~ Day | Month, data = aq,
  pch = 19,
  theme = "dark"
)
```

Themes do a number of things at once and are the subject of a dedicated
[Themes](themes.qmd) vignette that goes into details. For the moment, just
accept them as a convenient way to adjust your plot aesthetic without having to
finagle all of various parameters that base R graphics often requires. Beyond
convenience, themes also have the added benefit of enabling dynamic adjustment
of the plot regions, so that excess whitespace is reduced.^[You can already see
in the "dark" theme example above: Notice that the plot region takes up more
vertical space, since it isn't trying to reserve space for the missing title
and subtitle.]

In the previous plot, we used `theme = "dark"` to invoke an ephemeral theme. If
we drew a new plot, then it would revert back to the default aesthetic. But we
can also set a persistent theme via the `tinytheme()` function. Let's do that
now, under the knowledge that all of the remaining plots in this vignette will
adopt the same aesthetic (until we reset at the end).

```{r global_theme}
# Set persistent theme for the rest of this vignette
tinytheme("clean2")
```

Try it out:

```{r theme_clean2}
tinyplot(
  Temp ~ Day | Month, data = aq,
  type = "l",
  main = "Our new persistent theme",
  sub = "Notice that the subtitle is above the plot",
  cap = "Source: A helpful caption"
)
```

## Legend

In all of the preceding plots, you will have noticed that we get an automatic
legend. The legend position and look can be customized with the `legend`
argument. At a minimum, you can pass the familiar legend position keywords as a
convenience string (`"topright"`, `"bottom"`, etc.). Moreover, a key feature
of `tinyplot` is that we can easily and elegantly place the legend _outside_ the
plot area by adding a trailing "!" to these keywords. (As you may have realised,
the default legend position is `"right!"`.) Let's demonstrate by moving the
legend to the left of the plot.

```{r legend_bottom}
tinyplot(
  Temp ~ Day | Month, data = aq,
  type = "l",
  legend = "left!"
)
```

Another option is `legend = "direct"`, which places text labels at the end of
each group's data instead of a separate legend box. This works best for
line-based plots with x-sorted data, and pairs well with dynamic themes (like
the `"clean2"` theme that we already enable abovesd) for tighter margins.

```{r legend_direct}
tinyplot(
  Temp ~ Day | Month, data = aq,
  type = "l",
  legend = "direct"
)
```

Beyond the convenience of positional keywords, the `legend` argument also
permits additional customization in the form of a list of arguments, which will
be passed on to the standard `legend()` function internally. So you can change
or turn off the legend title, remove the bounding box, switch the direction of
the legend text to horizontal, etc. Here is a grouped density plot example,
where we also add some shading by specifying that the background colour should
vary by groups too.

```{r density_topright}
tinyplot(
  ~ Temp | Month,
  data = aq,
  type = "density",
  fill = "by",                                    # add fill by groups
  legend = list("top!", title = FALSE, bty = "o") # change legend features
)
```

All of the legend examples that we have seen thus far are representations of
discrete groups. However, please note that **tinyplot** also supports grouping
by continuous variables, which automatically yield gradient legends.

```{r legend_gradient}
tinyplot(Ozone ~ Temp | Wind, data = aq)
```

Gradient legends (and plots) can be customized in an identical manner to
discrete legends by adjusting the keyword positioning, palette choice, alpha
transparency, etc. Here is a quick adaptation of the previous plot to
demonstrate.

```{r legend_gradient2}
tinyplot(
  Ozone ~ Temp | Wind, data = aq,
  pch  = 21,      # use filled plot character
  cex  = 2,       # bigger points
  col  = "black", # override automatic (grouped) border colour of points
  fill = 0.5,     # use background fill instead with added alpha transparency 
)
```

As an aside, note that we passed a numeric convenience argument to `fill`
(alias `bg`) above. Specifically, when `fill` is given as a numeric in the range
of `[0,1]` then it automatically inherits the grouped colour mappings, but with
corresponding alpha transparency.

## More plot types

We’ve already seen several plot types, such as `"p"` (points), `"l"` (lines),
and `"density"`. In general, **tinyplot** supports all of the standard plot
types and elements available in base R, along with a number of additional types
that can be tedious to code manually. You can find the full list of available
types in the dedicated [Plot Types](types.qmd) vignette. For the moment, we’ll
content ourselves with only one or two illustrative examples.

Earlier, we saw a grouped density plot. But these can be hard to reason about
if there is a high degree of overlap between densities. A good alternative in
such cases is a ridge(line) plot, which **tinyplot** supports out of the box.
Note that ridge plots are best viewed with their own dedicated theme.

```{r ridge}
tinyplot(
  Month ~ Temp, aq,
  type = "ridge",
  theme = "ridge2", # or "ridge" (adds a plot frame)
  # optional customizations
  gradient = TRUE, col = "white"
)
```

```{r}
#| include: false
tinytheme("clean2")
```

Similarly, **tinyplot** also supports special types to fit models and display
their predictions, along with confidence intervals. Here is a somewhat silly
example where we fit a linear model to predict temperature by day of month.^[The
grouped setting here makes this visualization equivalent to
`predict(lm(Temp ~ 0 + Month / Day, data = aq), interval = "confidence")`.]

```{r ribbon_pred}
tinyplot(
  Temp ~ Day | Month, aq,
  type = "lm",
  legend = list("direct", repel = TRUE),
  main = "Linear model"
)
```

The default behaviour of these model types can be adjusted by passing
appropriate arguments to the equivalent _functional_ version of the type in
question. These functional types all follow a `type_<typename>` syntax, so that
`"lm"` is paired with `type_lm()`, `"ridge"` with `type_ridge()`, etc. Below we
illustrate with an adapted generalised linear model, where we explicitly pass
a [family](https://search.r-project.org/R/refmans/stats/html/family.html)
argument to fit a logistic regression.

```{r ribbon_pred_glm}
tinyplot(
  I(hot == "hot") ~ Wind, aq,
  # type = "glm", ## default is gaussian
  type = type_glm(family = binomial),
  main = "Logit model: Temps above 75 °F",
  ylab = "Probability", yaxl = "%"
)
```

Again, these are just a few examples of the _many_ different plot types that
**tinyplot** supports. Please also take a look at the dedicated 
[Plot Types](types.qmd) vignette for  the full list, as well a guide for
creating your own custom types. You can also request support for additional plot
types, or see what's on our roadmap, by heading over to this
[pinned issue](https://github.com/grantmcdermott/tinyplot/issues/97)
on our GitHub repo.

## Facets

Alongside the standard "by" grouping approach that we have seen thus far,
**tinyplot** also supports faceted plots. Mirroring the main `tinyplot`
function, the `facet` argument accepts both atomic and formula methods. In
general, however, we recommend the formula version as being safer since it does
a better job of handling missing values.

```{r facet_simple}
tinyplot(
  Temp ~ Day, aq,
  facet = ~Month, ## <= facet, not by
  type = "lm",
  main = "Predicted air temperatures"
)
```

Facets are easily combined with grouping. This can either be done separately
(i.e., distinct arguments for `by` and `facet`), or along the same dimension.
For the latter case, we provide a special `facet = "by"` convenience shorthand.

```{r facet_by}
tinyplot(
  Temp ~ Day | Month, aq,
  facet = "by", # facet along same dimension as groups
  type = "lm",
  main = "Predicted air temperatures"
)
```

To customize facets, simply pass a list of named arguments through the companion
`facet.args` argument. Customization options include: override the default
"square" facet window arrangement; allow free-scaled axes so that the limits of
each individual facet are drawn independently; adjust the padding (margin)
between individual facets; change the facet title text and background; etc.
Here is a simple example where we (1) arrange the facets in a single row and (2)
change the facet strip text and background fill.

```{r facet_nrow}
tinyplot(
  Temp ~ Day, aq,
  facet = ~Month,
  facet.args = list(nrow = 1, col = "white", bg = "black"),
  type = "lm",
  main = "Predicted air temperatures"
)
```

The `facet.args` customizations can also be set globally via the `tpar()`
function, or as part of a your `tinytheme()` configuration.

Finally, the `facet` argument also accepts a _two-sided_ formula for arranging
facets in a fixed grid layout. Here is a simple, if contrived, example.

```{r facet_grid}
tinyplot(
  Temp ~ Day, data = aq,
  facet = windy ~ hot
)
```

## Layers

In many contexts, it is convenient to build plots step-by-step, adding layers
with different elements on top of a base. The **tinyplot** package offers a few
ways to achieve this layering effect.

Similar to many base plotting functions, users can invoke the
`tinyplot(..., add=TRUE)` argument to draw a plot on top of an existing one,
rather than opening a new window. However, while this argument is useful, it can
be verbose since it requires users to make very similar successive calls, with
many shared arguments.

For this reason, **tinyplot** provides a special `tinyplot_add()` convenience
function for adding layers to an existing tinyplot. The idea is that users need
simply pass the _specific_ arguments that they want to add or modify relative to
the base layer; all others arguments will be inherited from the original call.

An example may help to demonstrate. Here we first draw some faceted points with
group colouring and various other aesthetic tweaks. Next, we add regression
fits with a simple `tinyplot_add(type = "lm")` call. Notice that the original
grouping, data and aesthetic options are all carried over correctly, without
having to repeat ourselves.

```{r tinyplot_add}
tinyplot(
  Temp ~ Day | Month, aq,
  facet = "by",
  legend = FALSE,
  main = "Actual and predicted air temperatures",
  cap = "Note: Shaded regions give OLS fit"
)
# Add regression fits
tinyplot_add(type = "lm")
```

::: {.callout-tip}
## Tip: `plt_add()` shorthand alias

Much like `plt()` is an alias for `tinyplot()`, you can save yourself a few
keystrokes by typing `plt_add()` instead of `tinyplot_add()`:

```{r plt_add}
#| eval: false
plt_add(type = "lm")
```

:::

A related---but distinct---concept to adding plot layers is _drawing_ on a plot.
The canonical use case is annotating your plot with text or some other
function-based (rather than data-based) logic. For example, you may want to
demarcate some threshold values with horizontal or vertical lines using `abline()`, or simply
annotate your plot using `text()`. This works as usual with a regular plots, but not with 
facetted plots because `text()`, `abline()`, `rect()`, etc. are not facet aware. 

The **tinyplot** way of adding common elements to facetted plots is by passing the
`tinyplot(..., draw = <draw_function>)` argument. Here we demonstrate with a
simplified version of our facet grid example from earlier.

```{r draw_simple}
tinyplot(
 Temp ~ Day, data = aq,
 facet = windy ~ hot,
 # draw a horizontal (threshold) line in each facet using the abline function
 draw = abline(h = 75, lty = 2)
)
```

This works because the `draw` argument is facet-aware and will ensure that each
individual facet is correctly drawn upon. Moreover, with `draw` you can leverage
the special `ii` internal counter to draw programmatically across facets (see
[here](https://github.com/grantmcdermott/tinyplot/pull/245#issue-2642589951)).
Third, the `draw` argument is fully generic and accepts _any_
drawing/annotating function. You can combine multiple drawing functions by
wrapping them with curly brackets`{}`, and even pass `tinyplot()` back towards
itself.

Here is a slightly more complicated "spaghetti" plot example, where we pass
multiple functions through `draw = {...}`, including drawing all of the
lines in the background (of each facet) via a secondary `tinyplot()` call.
(We'll also override with a different theme, to avoid overplotting with the
grid lines, etc.)

```{r draw_spaghetti}
tinyplot(
  Temp ~ Day | Month, aq, facet = "by", lwd = 3, type = "l",
  legend = FALSE,
  theme = "float", ylim = c(50, 100), frame = FALSE,
  draw = {
    tinyplot_add(col = "grey", lwd = 1, facet = NULL)
    abline(h = 75, lty = 2)
    text(5.5, 75, "Cold", pos = 1, offset = 0.4)
    text(5.5, 75, "Hot", pos = 3, offset = 0.4)
  }
)
```

## Saving plots

A final point to note is that **tinyplot** offers convenience features for
exporting plots to disk. Simply invoke the `file` argument to specify the
relevant file path (including the extension type). You can customize the output
dimensions (in inches) via the accompanying `width` and `height`
arguments.^[The default dimensions are 7x7, with a resolution of 300 DPI.
However, these too can be customized via the `file.width`, `file.height`, and
`file.res` parameters in
[`tpar()`](https://grantmcdermott.com/tinyplot/man/tpar.html).]

```{r save_plot}
#| eval: false
tinyplot(
  Temp ~ Day | Month, data = aq,
  file = "aq.png", width = 8, height = 5
)

# optional: delete the saved plot
unlink("aq.png")
```

Alongside convenience, the benefit of this native **tinyplot** approach (versus
the traditional approach of manually opening an external graphics device, e.g.
`png()`) is that all of your current graphic settings are automatically carried
over to the exported file. Feel free to try yourself by setting some global
graphics parameters via `tpar()` and then using `file` to save a plot.

## Reset theme

Don't forget to reset the plot theme.

```{r}
# reset theme to default
tinytheme()
```

## Conclusion

The goal of this tutorial has been to give you a clear sense of how **tinyplot**
works and what it offers. The sales pitch is simple: you get to use the familiar
syntax from base R `plot()`, but with _many_ additional plot types and
user-friendly features.

Believe it or not, there's loads more to **tinyplot** that we didn't cover here. 
We recommend continuing with the [Plot types](types.qmd) and
[Themes](themes.qmd) vignettes if you'd like to keep exploring. Or, take a look
at our [Gallery](gallery.qmd) page if you're looking for quick inspiration with
code examples.

We'll close by emphasizing that **tinyplot** has no dependencies other than base
R itself. We hope that this makes it an attractive and lightweight option for
package developers---and regular R users!--- who want to create convenient,
sophisticated plots with minimal overhead.

Happy tinyplotting!
