An open API service indexing awesome lists of open source software.

https://github.com/coolbutuseless/devout

Write R graphics output devices in plain R
https://github.com/coolbutuseless/devout

Last synced: about 1 month ago
JSON representation

Write R graphics output devices in plain R

Awesome Lists containing this project

README

        

---
output: github_document
---

```{r, include = FALSE}
suppressPackageStartupMessages({
library(devout)
library(ggplot2)
library(dplyr)
library(grid)
})
knitr::opts_chunk$set(
collapse = FALSE,
comment = "",
fig.path = "man/figures/README-",
out.width = "100%"
)

set.seed(1)
mtcars <- mtcars %>%
mutate(cyl = as.factor(cyl))
```

```{r echo = FALSE, eval = FALSE}
# Quick logo generation. Borrowed heavily from Nick Tierney's Syn logo process
library(magick)
library(showtext)
font_add_google("Abril Fatface", "gf")

# pkgdown::build_site(override = list(destination = "../coolbutuseless.github.io/package/devout"))
```

```{r echo = FALSE, eval = FALSE}
img <- image_read("man/figures/white.png")

hexSticker::sticker(subplot = img,
s_x = 0.92,
s_y = 1.2,
s_width = 1.5,
s_height = 0.95,
package = "/dev/out/",
p_x = 1,
p_y = 1,
p_color = "#223344",
p_family = "gf",
p_size = 9,
h_size = 1.2,
h_fill = "#ffffff",
h_color = "#223344",
filename = "man/figures/logo.png")

image_read("man/figures/logo.png")
```

# devout

![](https://img.shields.io/badge/cool-useless-green.svg)
![](http://img.shields.io/badge/dev-out-blue.svg)
[![R-CMD-check](https://github.com/coolbutuseless/devout/workflows/R-CMD-check/badge.svg)](https://github.com/coolbutuseless/devout/actions)

`devout` is a package that enables R graphics devices to be written in plain R.

`devout` uses a pseudo-graphics-device which translates graphics
calls into a call to an R function of your design.

This means we can create alternative output devices (like `pdf()` or `png()`) using
only plain R.

## How normal (C/C++) graphics devices work

```{r echo=FALSE, eval=FALSE}
# /Users/mike/projects/devout/man/figures/graffle/graphics-device/Canvas 4.png
file.copy("man/figures/graffle/graphics-device/Canvas 4.png", "man/figures/graffle/graphics-device/Canvas 4a.png", overwrite = TRUE)
system("convert -delay 300 man/figures/graffle/graphics-device/*.png man/figures/graffle/graphics-device/anim.gif")
system("convert man/figures/graffle/graphics-device/anim.gif \\( +clone -set delay 500 \\) +swap +delete man/figures/graffle/graphics-device/animpause.gif")
system("gifsicle man/figures/graffle/graphics-device/animpause.gif --colors 8 > man/figures/graffle/graphics-device/animo.gif")

unlink("man/figures/graffle/graphics-device/anim.gif")
unlink('man/figures/graffle/graphics-device/animpause.gif')
```

animation (click to close)

## How the devout device enables plain R graphics devices

```{r echo=FALSE, eval=FALSE}
system("convert -delay 300 man/figures/graffle/devout-device/*.png man/figures/graffle/devout-device/anim.gif")
system("convert man/figures/graffle/devout-device/anim.gif \\( +clone -set delay 500 \\) +swap +delete man/figures/graffle/devout-device/animpause.gif")
system("gifsicle man/figures/graffle/devout-device/animpause.gif --colors 8 > man/figures/graffle/devout-device/animo.gif")

unlink("man/figures/graffle/devout-device/anim.gif")
unlink('man/figures/graffle/devout-device/animpause.gif')
```

animation (click to close)

## What's in the box

* `rdevice()` - a generic device wrapper which will call the given R function to
handle the graphics drawing.
* Two example devices written in plain R (but using the underlying `rdevice()`)
* `descriptive()` - an output device which dumps information about the device calls.
* `ascii()` - a graphics device which outputs an ascii representation of the plot
to a file, or to the console/terminal

## How do I write my own graphics device?

If you want to write your own graphics device in plain R using `devout` you can:

1. Read the `R/ascii-callback.R` included in the package
2. Read the vignettes.

A series of 4 vignettes are included in this package. They walk through the process
of writing a naive SVG graphics device.

* [Creating an SVG device - part 1 - The Callback Function](https://coolbutuseless.github.io/package/devout/articles/creating-an-svg-device-01.html)
* [Creating an SVG device - part 2 - The Drawing Canvas](https://coolbutuseless.github.io/package/devout/articles/creating-an-svg-device-02.html)
* [Creating an SVG device - part 3 - Rendering Graphics](https://coolbutuseless.github.io/package/devout/articles/creating-an-svg-device-03.html)
* [Creating an SVG device - part 4 - %#$^ you I won't do what you tell me](https://coolbutuseless.github.io/package/devout/articles/creating-an-svg-device-04.html)

## Installation

You can install from [GitHub](https://github.com/coolbutuseless/devout) with:

``` r
# install.packages("remotes")
remotes::install_github("coolbutuseless/devout")
```

# Simple device written in plain R: `debug` device

The following 5 lines of code are about the simplest device you can write with
`devout` in plain R.

This devices prints each of the device calls that were generated when the
plot was "drawn".

```{r}
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Write a function which takes 3 arguments
# @param device_call name of device function call
# @param args the arguments to that device call
# @param state current state of the graphics device
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
debug_function <- function(device_call, args, state) {
if (device_call %in% c('mode', 'strWidthUTF8', 'metricInfo')) return()
cat("[", device_call, "]: ")
cat(paste(names(args), args, sep = " = ", collapse = ", "), "\n", sep = "")
}
```

```{r eval=FALSE}
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Call the 'rdevice' and tell it that all calls should be passed to
# the above 'debug_function'
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
rdevice(debug_function)
plot(1:10)
invisible(dev.off())
```

```{r echo=FALSE}
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Open the 'rdevice' and tell it that all graphics calls should
# be passed to the above 'debug_function()'
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
rdevice(debug_function)
plot(1:10)
invisible(dev.off())
```

# `ascii()` device

This is an example of a more complete graphics device. It is written in plain R and
relies on `devout` for all the interfacing with C++ and the internals of R.

The `ascii()` device draws an approximation of the graphics using ASCII characters
on the console (by default) or saved to a text file (if specified).

Limitations

* No support for: filled polygons, plotmath, alpha blending, angled text, rasters
* You should probably always add `theme(legend.position = 'none')` because legends look awful in ascii.

### `ggplot2`: Basic scatterplot

```{r}
library(ggplot2)
library(devout)

p <- ggplot(mtcars) +
geom_point(aes(mpg, wt)) +
labs(
y = "Car Weight",
title = "Basic scatter plot",
subtitle = "Rendered with devout::ascii()"
) +
theme_bw()

ascii(width = 100)
p
invisible(dev.off())

```

### `pie` plot in base R

```{r}
ascii(width = 100)
pie(c(cool = 4, but = 2, use = 1, less = 8))
invisible(dev.off())
```

### `geom_sf()` map of the Gulf of Mexico

geom_sf() example (click to open)

* Example taken from the [r-spatial website](https://www.r-spatial.org/r/2018/10/25/ggplot2-sf.html)
* This would probably look better with filled polygons, but they are not supported yet.

```{r warning = FALSE, eval = FALSE}
library(ggplot2)
library(sf)
library(rnaturalearth)
world <- ne_countries(scale = "medium", returnclass = "sf")

world_points <- st_centroid(world)
world_points <- cbind(world, st_coordinates(st_centroid(world$geometry)))

ascii(width = 200)
ggplot(data = world) +
geom_sf() +
geom_text(data= world_points,aes(x=X, y=Y, label=name),
color = "darkblue", fontface = "bold", check_overlap = FALSE) +
annotate(geom = "text", x = -90, y = 26, label = "Gulf of Mexico", fontface = "italic", color = "grey22", size = 6) +
coord_sf(xlim = c(-102.15, -74.12), ylim = c(7.65, 33.97), expand = FALSE) +
theme_bw()
invisible(dev.off())
```


## Ideas for other Output Devices

* Colour ASCII/ANSI output
* Audio output
* HPGL plotting output
* [CNC](https://en.wikipedia.org/wiki/Numerical_control) machine tooling instructions
* Directly drive motors to power an etch-a-sketch

## News:

* v0.2.0 - Major refactor. `devout` is now a way of writing graphics devices in
plain R with the `ascii()` device as an example.
* v0.1.2 - Added support for multiple page output
* v0.1.1 - Added support for path objects, so more map plots now work.
* v0.1.0 - initial release