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
- Host: GitHub
- URL: https://github.com/coolbutuseless/devout
- Owner: coolbutuseless
- License: mit
- Created: 2019-05-01T09:41:18.000Z (almost 6 years ago)
- Default Branch: master
- Last Pushed: 2022-01-23T06:30:36.000Z (about 3 years ago)
- Last Synced: 2025-02-27T13:21:03.738Z (about 2 months ago)
- Language: C++
- Homepage: https://coolbutuseless.github.io/package/devout/
- Size: 828 KB
- Stars: 98
- Watchers: 5
- Forks: 3
- Open Issues: 1
-
Metadata Files:
- Readme: README.Rmd
- License: LICENSE
Awesome Lists containing this project
- jimsghstars - coolbutuseless/devout - Write R graphics output devices in plain R (C++)
- awesome-r-dataviz - devout - Write R graphics output devices in plain R. (ggplot / Devices)
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://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