--- title: "ggfoundry" description: | Add a layer of custom fillable shapes to a ggplot. output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{ggfoundry} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} bibliography: references.bib --- ```{r options, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", out.width = "100%" ) ``` ## Motivation ggfoundry was inspired a little by Stack Overflow posts seeking specific shapes. But, in truth, mostly by a personal interest in getting acquainted with grid graphics (the underpinnings of ggplot2). ## Shape landscape Yes, there is already a seemingly near-infinite number of shapes out there: - Those familiar to ggplot users (some fillable) as described in the [ggplot2 documentation](https://ggplot2.tidyverse.org/articles/ggplot2-specs.html#point); - Colourable [unicodes](https://www.compart.com/en/unicode/category/So) and [icons](https://fontawesome.com/icons/) like fontawesome; - [ggimage](https://github.com/GuangchuangYu/ggimage) enables the use of whole pictures; - And then there is the DIY (Do-It-Yourself) approach: Conjuring up grobs (grid graphical objects); perhaps with a sprinkle of trigonometry. But sometimes you just can't find what you want. Nor manipulate it in the way you would like. ggfoundry offers arbitrary hand-crafted colourable and fillable shapes for ggplot2 and is reviewed side-by-side with other options in [contrast with alternatives](https://cgoo4.github.io/ggfoundry/articles/contrast). ## Foundry process These artisanal symbols begin life as hand-drawn vector images with two layers: an outline and a fill. Each SVG pair is converted to Cairo graphics format, forged at extreme temperatures into objects of class "Picture", and finally delicately cast as a `gTree` representation of the original shape. But not quite back to where we started, because they are now editable. When cooled and finely burnished, the `gTree` and all its grob children may then be manipulated by `geom_casting()` to render the desired ggplot with those special high-end adornments. ## Available shapes ggfoundry may well be the destination of "last resort"! After travelling the mountains, seas and forests of the world in search of that elusive shape (or small set), a hand-made grob may be the fillable "Holy Grail" sought via a [Github issue](https://github.com/cgoo4/ggfoundry/issues). These sets are included with the latest version of the package. You can "mix and match" shapes from different sets; the "set" is for grouping shapes in the documentation and for use in `shapes_cast()` to filter for the desired shapes. ```{r sets, message=FALSE, warning=FALSE, fig.height=7} library(ggfoundry) library(dplyr) library(forcats) library(stringr) df <- shapes_cast() |> filter(!str_ends(shape, "3|4|5|6")) |> mutate(x = row_number(), shape = fct_inorder(shape), .by = set) df |> ggplot(aes(x, set)) + geom_text(aes(label = shape), nudge_y = -0.5, colour = "grey70", size = 3) + geom_casting(aes(shape = shape), size = 0.19, fill = "skyblue") + scale_shape_manual(values = as.character(df$shape)) + scale_x_continuous(expand = expansion(add = 0.5)) + scale_y_discrete(expand = expansion(add = 0.7)) + labs(x = NULL, y = NULL, caption = "sunflowers 1-8 available") + theme_minimal() + theme( text = element_text(colour = "grey70"), axis.text.y = element_text(angle = 90, hjust = 0.5), axis.text.x = element_blank(), axis.ticks.x = element_blank(), legend.position = "none" ) ``` ## Simple example Using some made-up data simulating a "random walk", `geom_casting()` adds a layer of custom shapes to the plot. When the shape is mapped to a variable, then `scale_shape_manual()` is required to explicitly name the desired shapes as a character vector. This is because standard shapes (as used for example in `geom_point()`) are associated with a number, e.g. a circle is 19, whereas `geom_casting()` shapes are associated only with character strings. One grob is created for each of the 2 groups. And each grob stores the `x` and `y` coordinates for that group to enable each shape to be rendered at several locations. Using `scale_colour_manual()` and `scale_fill_manual()`, we can also select a custom palette for the shape colours and fills. ```{r} # Toy Data set.seed(123) random_walk <- \(x, y, z) cumsum(rnorm(x, mean = y, sd = sqrt(z))) df <- data.frame( x = rep(1:10, 2), y = c( random_walk(10, 1, 1), random_walk(10, 3, 1.3) ), group = factor(c(rep(1, 10), rep(2, 10))) ) # Plot with geom_casting() df |> ggplot(aes(x, y, shape = group, colour = group, fill = group)) + geom_line(show.legend = FALSE) + geom_casting() + scale_colour_manual(values = c("darkred", "darkgreen")) + scale_fill_manual(values = c("pink", "lightgreen")) + scale_shape_manual(values = c("cross1", "cross2")) + labs(title = "ggfoundry") + theme_bw() + theme(plot.subtitle = element_text(size = 10)) ``` See the [showcase](https://cgoo4.github.io/ggfoundry/articles/example_uses) article to explore other use cases and [contrast with alternatives](https://cgoo4.github.io/ggfoundry/articles/contrast) to review against other options. ## Acknowledgements Without the pivotal grConvert [@grConvert] and grImport2 [@grImport2] packages, the foundry process would not have been viable. And only thanks to the work behind ggplot2 [@ggplot2], may the shapes cast be so beautifully visualised.