The goal of gggeom is to provide a compact way to represent geometric objects and useful tools to wrok with them. This package is (or soon will be) used to power ggvis: if you want to create a new layer function, you’ll need to be somewhat familiar with this package. But gggeom is low-level and is not tightly tied to ggvis so you could use it to implement a new graphics system if you wanted.
Compared to ggplot2, gggeom is somewhat similar to the geoms, but gggeom has a much purer take on geometric primitives. For example, ggplot2 has the geom_histogram() which is really a combination of a statistical transformation (stat_bin()) and a bar (geom_bar()). gggeom avoids this muddle, sticking purely to geometric objects. It also provides many more tools for manipulating geometries, indepedent of the particular plot they will eventually generate.
All gggeom manipulations should be able to proces s ~100,000 geometries in less than 0.1. More geometries than that is unlikely to produce a useful plot. If you do have very large datasets, they should be summarised (using e.g. the tools ggstat) before being visualised.
There are only three fundamental geometric primitives needed to draw any graphic:
However, because these primitives are so general, it is hard to define useful operations on them. So gggeoms provide a number of additional geometric objects that restrict the properties of points, paths and polygons in useful ways:
Geometries are described in turns of their position. When rendered a geometric object will need other properties (like stroke, fill, stroke width, …) but gggeom concerns itself only computations that involve position.
A geometry is represented as a data frame, where each row corresponds to a single object. You turn a data frame into a geometry using the appropriate render function:
scatter <- iris %>% render_point(~Sepal.Length, ~Sepal.Width) %>% head()
scatter
#> Sepal.Length Sepal.Width Petal.Length Petal.Width Species x_ y_
#> 1 5.1 3.5 1.4 0.2 setosa 5.1 3.5
#> 2 4.9 3.0 1.4 0.2 setosa 4.9 3.0
#> 3 4.7 3.2 1.3 0.2 setosa 4.7 3.2
#> 4 4.6 3.1 1.5 0.2 setosa 4.6 3.1
#> 5 5.0 3.6 1.4 0.2 setosa 5.0 3.6
#> 6 5.4 3.9 1.7 0.4 setosa 5.4 3.9
#> Geometry: geom_point
The default behaviour of the render function preserve all existing columms so that they can be later mapped to other properties of the geometry. However, this is mostly incidental to gggeom - it only works with the position columns (which all end in _ to avoid clashes with other vars).
All geometries inherit from “geom” and “data.frame”. Additional inheritance structure is based no on the appearance of the geom but on the data need to display the geom. This means that:
All geometries have a base graphics plot() method. These are useful for examples, explanation and debugging, but not serious data visualisation. ... is passed on to the underlying base graphic method, so if you’re familiar with the graphic parameters, you can tweak the appearance.
plot(scatter)
If each row represents a single object, how are paths, polygons, lines and steps represented? We take advantage of a relatively esoteric R feature - data frame columns can be lists. For example, take a look at the built-in nz data set:
head(nz)
#> x_ y_ island
#> 1 <dbl[714]> <dbl[714]> North
#> 2 <dbl[642]> <dbl[642]> South
#> 3 <dbl[54]> <dbl[54]> Stewart
#> 4 <dbl[18]> <dbl[18]> Great.Barrier
#> 5 <dbl[16]> <dbl[16]> Resolution
#> 6 <dbl[5]> <dbl[5]> Little.Barrier
#> Geometry: geom_polygon
plot(nz)
The x_ and y_ variables are lists of numeric vectors:
nz$x_[[5]]
#> [1] 166 167 167 167 167 167 167 167 167 167 167 166 166 166 166 166
nz$y_[[5]]
#> [1] -45.9 -45.8 -45.9 -45.9 -45.9 -45.9 -45.9 -45.8 -45.8 -45.8 -45.8
#> [12] -45.8 -45.9 -45.9 -45.9 -45.9
As well as a plot() method, paths also have a points() method which makes it easier to see exactly where the data lie:
class(nz)
#> [1] "geom_polygon" "geom_path" "geom" "data.frame"
nz %>% subset(island == "Stewart") %>% plot() %>% points()
(Not that plot() invisibly returns the input data to make this sort of chaining easy.)
You can convert any geometry to its equivalent primitive path by using geom_pointificate(). For example, imagine we have some rects:
df <- data.frame(x = c(1:3, 3), y = c(1:3, 2))
rects <- render_tile(df, ~x, ~y, width = 0.95, height = 0.95)
rects
#> x y x1_ x2_ y1_ y2_
#> 1 1 1 0.525 1.48 0.525 1.48
#> 2 2 2 1.525 2.48 1.525 2.48
#> 3 3 3 2.525 3.48 2.525 3.48
#> 4 3 2 2.525 3.48 1.525 2.48
#> Geometry: geom_rect
plot(rects)
We can convert these to four point polygons with geometry_pointificate():
rects %>% geometry_pointificate()
#> x y x_ y_
#> 1 1 1 <dbl[4]> <dbl[4]>
#> 2 2 2 <dbl[4]> <dbl[4]>
#> 3 3 3 <dbl[4]> <dbl[4]>
#> 4 3 2 <dbl[4]> <dbl[4]>
#> Geometry: geom_polygon
rects %>% geometry_pointificate() %>% plot() %>% points()
The rendering looks similar at first glance, but by using the points() command we can see that each rectangle is composed of four points.
The main advantage to converting to polygons is that there are a number of transformations that make sense for polygons, but not for rects, because the resulting transformation would not still be a rect:
polys <- rects %>% geometry_pointificate(close = TRUE)
# Rotate each polygon 5 degrees clockwise
polys %>% geometry_rotate(5) %>% plot() %>% points()
# Transform into polar coordinates
polys %>% geometry_warp("polar", tolerance = 0.0001) %>% plot() %>% points()
# Need to figure out why this looks so bad - must be warp bug :(
(In other words the set of rects is not closed under many useful transformations.)
There are also operations that make sense for rects, but not for general polygons. For example, it makes sense to stack rects so that their lower edge falls on the x-axis, and the stack up from there. There’s no useful way to stack arbitrary polygons:
rects %>% geometry_stack() %>% plot()
The following table lists all transformations implemented in gggeom in the rows, and the geometries to which they apply in the columns:
#>
#> arc line path point polygon rect ribbon segment text
#> dodge *
#> flip * * * * * * * * *
#> jitter * * * * * * * * *
#> pointificate * * * * * * *
#> reflect * * * * * * * * *
#> scale * * *
#> simplify *
#> stack * * * * * * * * *
#> transform * * * * * * *
#> warp *