Rectangles: geom_tile
Mix.install([
{:giggly, path: Path.join(__DIR__, "..")},
{:kino, "~> 0.19"}
])
alias Giggly.Plot
mtcars = Giggly.Datasets.mtcars()
render = fn plot ->
plot
|> Giggly.to_svg()
|> IO.iodata_to_binary()
|> Kino.Image.new(:svg)
end
About this notebook
This notebook follows the examples on ggplot2's reference page for
geom_tile(), which covers
geom_raster() and geom_rect() too, in the order the page gives them. Each example shows
ggplot2's code first and then Giggly's. An example Giggly cannot draw yet keeps its ggplot2 code and
names what it is waiting for, so the page is also a list of what remains.
geom_rect() and geom_tile() both draw a rectangle for each row. geom_tile() places it by its
centre and size (x, y, width, height); geom_rect() can do the same or place it by its
corners (xmin, xmax, ymin, ymax). geom_raster() is a fast special case for tiles that are
all the same size. Giggly has Giggly.Plot.geom_rect/3, which places each rectangle by its corners
only; it has no geom_tile() or geom_raster(), and maps no width or height. A plot is
described with Giggly.Plot and drawn with Giggly.to_svg/2, which returns an SVG document. The
setup cell aliases Giggly.Plot, so the cells write Plot.new/2, and defines render, which draws
a plot and shows it below the cell with Kino.Image. It binds Giggly.Datasets.mtcars/0, R's
mtcars, to mtcars, R's name for it.
A surface
ggplot2's faithfuld is a two-dimensional density estimate of the eruptions of the Old Faithful
geyser: the density at each point of a grid of waiting times and eruption lengths.
# The most common use for rectangles is to draw a surface. You always want
# to use geom_raster here because it's so much faster, and produces
# smaller output when saving to PDF
ggplot(faithfuld, aes(waiting, eruptions)) +
geom_raster(aes(fill = density))
Not in Giggly yet: geom_raster() and the faithfuld dataset.
Interpolation
# Interpolation smooths the surface & is most helpful when rendering images.
ggplot(faithfuld, aes(waiting, eruptions)) +
geom_raster(aes(fill = density), interpolate = TRUE)
Not in Giggly yet: geom_raster() and its interpolate option, and the faithfuld dataset.
Tiles
# If you want to draw arbitrary rectangles, use geom_tile() or geom_rect()
df <- data.frame(
x = rep(c(2, 5, 7, 9, 12), 2),
y = rep(c(1, 2), each = 5),
z = factor(rep(1:5, each = 2)),
w = rep(diff(c(0, 4, 6, 8, 10, 14)), 2)
)
ggplot(df, aes(x, y)) +
geom_tile(aes(fill = z), colour = "grey50")
Not in Giggly yet: geom_tile().
Tiles of different widths
ggplot(df, aes(x, y, width = w)) +
geom_tile(aes(fill = z), colour = "grey50")
The widths, w, are the gaps between 0, 4, 6, 8, 10 and 14, so the tiles centred on the x
values meet edge to edge.
Not in Giggly yet: geom_tile() and mapping width.
Rectangles by their corners
ggplot(df, aes(xmin = x - w / 2, xmax = x + w / 2, ymin = y, ymax = y + 1)) +
geom_rect(aes(fill = z), colour = "grey50")
The cell writes out the tiles' table, df. R's z is a factor of the numbers 1 to 5, so the cell
writes its levels as strings, which fill reads as levels, as R's does. Each corner is an
expression of the columns, x - w / 2 written {&Kernel.-/2, [:x, {&Kernel.//2, [:w, 2]}]}. R's
"grey50" is "#7F7F7F".
df = %{
"x" => [2, 5, 7, 9, 12, 2, 5, 7, 9, 12],
"y" => [1, 1, 1, 1, 1, 2, 2, 2, 2, 2],
"z" => ["1", "1", "2", "2", "3", "3", "4", "4", "5", "5"],
"w" => [4, 2, 2, 2, 4, 4, 2, 2, 2, 4]
}
half_width = {&Kernel.//2, [:w, 2]}
df
|> Plot.new(%{
xmin: {&Kernel.-/2, [:x, half_width]},
xmax: {&Kernel.+/2, [:x, half_width]},
ymin: :y,
ymax: {&Kernel.+/2, [:y, 1]}
})
|> Plot.geom_rect(%{fill: :z}, colour: "#7F7F7F")
|> render.()
Anchoring the cells
R's expand.grid() makes a 6 by 6 grid, and runif() gives each cell a random value.
# Justification controls where the cells are anchored
df <- expand.grid(x = 0:5, y = 0:5)
set.seed(1)
df$z <- runif(nrow(df))
# default is compatible with geom_tile()
ggplot(df, aes(x, y, fill = z)) +
geom_raster()
# zero padding
ggplot(df, aes(x, y, fill = z)) +
geom_raster(hjust = 0, vjust = 0)
By default each cell is centred on its x and y, as a tile is; hjust and vjust move the
cells off centre.
Not in Giggly yet: geom_raster() and its hjust and vjust options.
Points along a discrete axis
# Inspired by the image-density plots of Ken Knoblauch
cars <- ggplot(mtcars, aes(mpg, factor(cyl)))
cars + geom_point()
factor(cyl) makes the number of cylinders discrete, so the y axis has a level for each. Giggly
writes it {:factor, [:cyl]}.
cars = Plot.new(mtcars, %{x: :mpg, y: {:factor, [:cyl]}})
cars
|> Plot.geom_point()
|> render.()
Counting in two dimensions
cars + stat_bin_2d(aes(fill = after_stat(count)), binwidth = c(3,1))
cars + stat_bin_2d(aes(fill = after_stat(density)), binwidth = c(3,1))
Not in Giggly yet: stat_bin_2d() and its binwidth option.
Densities as a raster
cars +
stat_density(
aes(fill = after_stat(density)),
geom = "raster",
position = "identity"
)
cars +
stat_density(
aes(fill = after_stat(count)),
geom = "raster",
position = "identity"
)
factor(cyl) gives each density a y of its own, which ggplot2 keeps beside the curve it
computes along x, so each row of cells is one number of cylinders.
Not in Giggly yet: stat_density() given a discrete y beside x, which each curve keeps,
giving a stat a geom by name, here geom = "raster", and geom_raster().