All Projects → coolbutuseless → ggsvg

coolbutuseless / ggsvg

Licence: MIT license
Use SVG images as ggplot points

Programming Languages

r
7636 projects

ggsvg - Use SVG as points in ggplot

With the novel capability of aesthetic mappings to any SVG feature

R-CMD-check

ggsvg is an extension to ggplot to use SVG image for plotted points.

The SVG may be styled by either:

  • Mapping aesthetic values to CSS.
  • Parameterising the SVG by converting it to a {glue}-compatible string.

Note: {ggsvg} is currently undergoing rapid development.

Keep an eye on this README and the vignettes to see examples of current usage.


What’s in the box

  • geom_point_svg() is equivalent to geom_point() but the glyphs are defined by SVG rather than points.
  • scale_svg_default() will assign reasonable default scales to arbitrary aesthetics which adhere to the preferred aesthetic naming scheme
  • scale_svg_* a complete set of compatible scale functions for controlling the mapping of values to arbitrary named aesthetics. Having a good knowledge of scales work in ggplot2 will be a big advantage in customing SVG appearing in plots.

Installation

Install from GitHub.

The {rsvg} package is used to convert SVG into an R raster object. This requires at least rsvg(>= 2.3.0).

# install.package('remotes')
remotes::install_github('coolbutuseless/ggsvg')

Preferred naming for custom aesthetics

Custom aesthetics should be named with a _[type] suffix in order to keep {ggsvg} working well with {ggplot2}. E.g:

  • Use shade_fill rather than shade or fill_shade
  • Use rect_size rather than size_rect or rect_bigness
  • Use path_alpha rather than transparency

type can be any one of the standard ggplot aesthetics (fill, colour, size etc).

Click here for more details

{ggplot2} doesn’t know how to deal with a new aesthetic is hasn’t seen before. The ggplot2 packages knows about a certain set of aesthetics: alpha, colour, fill, linetype, shape, size etc. And it knows how to find appropriate default scales for each of these aesthetics in order to convert, say, numeric values in a data.frame into colours for points.

In ggsvg novel aesthetics are being created during plot creation in order to colour/style the SVG. Since ggplot2 won’t know how to find an appropriate default scale to go with these aesthetics, the user may name things carefully in order to trigger some automatic detection within the ggsvg package.

  1. Name aesthetics to be of the form [blah]_[type] with an underscore
    • e.g. “circle_radius”, “rect_colour”, “cat_fill”
  2. Apply a general default scale for these specially named aesthetics by adding scale_svg_default() to the plot.
  3. To get a good plot, you’ll probably still need to add specific scales to carefully control the value mapping e.g. scale_svg_fill_brewer(aesthetics = "rect_fill")

Note that any CSS properties that are specified as text (e.g. font-family) do not currently have any built-in scale. Instead you will have to specify the possible values for such a scale using e.g. scale_svg_discrete_manual().

Simple plot

car_url <- 'https://www.svgrepo.com/download/114837/car.svg'
car_svg <- paste(readLines(car_url), collapse = "\n")
grid::grid.draw( svg_to_rasterGrob(car_svg) )

ggplot(mtcars) + 
  geom_point_svg(aes(mpg, wt), svg = car_svg) + 
  theme_bw()

Simple plot with mapped size aesthetic

ggplot(mtcars) + 
  geom_point_svg(aes(mpg, wt, size = mpg), svg = car_svg) + 
  theme_bw() 

Styling SVG with CSS Aesthetics (Example 1)

The 3rd path element in the SVG is the body of the car - which corresponds to the CSS selector path:nth-child(3).

We use the css() helper function define how to map fill to a CSS element.

The general form of the css() function call is: css(selector, property = value)

In this case we want to select the element at path:nth-child(3), and set the CSS property fill to be mapped from as.factor(cyl). I.e.

css("path:nth-child(3)", fill = as.factor(cyl))

ggplot(mtcars) + 
  geom_point_svg(
    aes(mpg, wt, css("path:nth-child(3)", fill = as.factor(cyl))),
    svg = car_svg
  ) +
  theme_bw() + 
  scale_svg_default()

Styling SVG with CSS Aesthetics (Example 2)

In this example, the stroke-width and the stroke of the rectangle are to be mapped to some values in the data.

Since ggplot2 has never heard of these custom aesthetics, use scale_svg_default() to get good initial defaults.

svg_text <- '
  <svg viewBox="0 0 100 100 ">
    <rect width="100" height="100" fill="#555555" 
        style="stroke: black; stroke-width: 20"/>
    <circle class="pale" cx="50" cy="50" r="20" fill="white" />
  </svg>
  '

grid::grid.draw( svg_to_rasterGrob(svg_text) )

ggplot(mtcars) + 
  geom_point_svg(
    aes(
      mpg, wt, 
      css("rect", "stroke-width" = mpg), 
      css("rect", stroke = as.factor(cyl))
    ), 
    svg = svg_text) +
  theme_bw() + 
  scale_svg_default() 

Be default, the size scaling for the stroke-width aesthetic isn’t readily apparent, so add a specific scale_svg_*() function to map the values onto thicker strokes

ggplot(mtcars) + 
  geom_point_svg(
    aes(
      mpg, wt, 
      css("rect", "stroke-width" = mpg), 
      css("rect", stroke = as.factor(cyl))
    ), 
    svg = svg_text) +
  theme_bw() + 
  scale_svg_default() +
  scale_svg_size_continuous(aesthetics = css("rect", "stroke-width" = mpg), range = c(5, 50))

Styling SVG with CSS Aesthetics (Example 3)

This world map SVG has CSS classes corresponding to the countries e.g. the CSS selector “.Canada” will select all elements having class = "Canada" in the SVG.

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# World map from: https://simplemaps.com/resources/svg-world
# Slightly modified to have a large <rect> background element
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
map_svg <- paste(readLines("man/figures/world-bg.svg"), collapse = "\n")

grid::grid.draw( svg_to_rasterGrob(map_svg) )

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Dummy data about Canada and Australia
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
N <- 10
value_df <-  data.frame(
  country = rep(c("Canada", 'Australia'), each = N),
  value   = c(rnorm(N, mean = 5), rnorm(N, mean= 7)),
  fill    = rep(c('brown', 'navy'), each = N)
)

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Boxplot comparison with map inset
# Set x_abs,y_abs, hjust, vjust to put the SVG in the top-right corner
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ggplot(value_df) +
  geom_boxplot(aes(x=country, y = value, colour=I(fill)))+
  geom_point_svg(
    mapping = NULL,
    x_abs = 0.99, y_abs = 0.99, hjust = 1, vjust = 1,
    css(".Canada"   , fill = 'brown'),  
    css(".Australia", fill = 'navy'),
    css("rect", fill='white'),       # Style the inset frame
    css("rect", stroke = '#555'),   # Style the inset frame
    css("rect", `stroke-width` = 5), # Style the inset frame
    svg = map_svg,
    size = 75
  ) +
  theme_bw() 

Styling SVG by parameterising as a {glue} string

SVG appearance may be styled by editing the SVG and inserting glue-compatible parameters which can then be mapped as aesthetics.

(1) Create base SVG Image

The following simple SVG constructed by hand is just a square and a circle.

library(ggplot2)
library(ggsvg)

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Define simple SVG
#   - Square with rounded corners and a circle inside it.
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
simple_text <- '
  <svg viewBox="0 0 100 100 ">
    <rect width="100" height="100" fill="#88ccaa" />
    <circle cx="50" cy="50" r="40" fill="white" />
  </svg>
  '

grid::grid.draw( svg_to_rasterGrob(simple_text) )

(2) Parameterise the SVG

Introduce parameters in the SVG using {glue} syntax with double curly braces i.e. {{}}

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Define simple SVG
#   - Square with rounded corners and a circle inside it.
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
parameterised_svg <- '
  <svg viewBox="0 0 100 100 ">
    <rect width="100" height="100" fill="{{rect_fill}}" />
    <circle cx="50" cy="50" r="{{circle_size}}" fill="white" />
  </svg>
  '

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Test the glue parameters
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
rect_fill   <- 'brown'
circle_size <- 10

final_text <- glue::glue(parameterised_svg, .open = "{{", .close = "}}")

grid::grid.draw( svg_to_rasterGrob(final_text) )

(3) Static aesthetics with parameterised SVG

ggplot(mtcars) +
  geom_point_svg(
    mapping     = aes(mpg, wt),
    svg         = parameterised_svg,
    rect_fill   = 'navy',
    circle_size = 20
  ) +
  theme_bw() + 
  labs(title = "{ggsvg}") 

(3) Map ggplot2 aesthetics to the parameterised SVG

ggplot(mtcars) +
  geom_point_svg(
    mapping  = aes(mpg, wt, circle_size = as.factor(cyl), rect_fill = disp),
    svg      = parameterised_svg
  ) +
  theme_bw() + 
  labs(title = "{ggsvg}") + 
  scale_svg_default() +
  scale_svg_size_discrete(
    aesthetics = 'circle_size',
    range = c(10, 45),
    guide = guide_legend(override.aes = list(size = 7))
  )
#> Warning: Using size for a discrete variable is not advised.

Acknowledgements

  • R Core for developing and maintaining the language.
  • CRAN maintainers, for patiently shepherding packages onto CRAN and maintaining the repository
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].