Stacking: position_stack
Mix.install([
{:giggly, path: Path.join(__DIR__, "..")},
{:kino, "~> 0.19"}
])
alias Giggly.Plot
mtcars = Giggly.Datasets.mtcars()
diamonds = Giggly.Datasets.diamonds()
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
position_stack(), which covers
position_fill() 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.
position_stack() stacks bars, areas and other objects that share an x on top of one another;
position_fill() stacks them too, then stretches every stack to the same height, so its parts
show proportions. A stack runs in the reverse order of the group aesthetic, so its colours line
up with the default legend, and positive values stack upward from zero while negative ones stack
downward. vjust places an object that has a position rather than a size, such as a label,
within its part of the stack, and reverse turns the order over. Giggly stacks bars and areas: it
is the default position: :stack of Giggly.Plot.geom_bar/3, Giggly.Plot.geom_col/3,
Giggly.Plot.geom_histogram/3 and Giggly.Plot.geom_area/3, and Giggly.Plot.geom_density/3
stacks its curves with position: :stack. It has none of the rest yet: no position_fill(),
neither of position_stack()'s options, vjust and reverse, and no position option for
lines, points or text. 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 R's mtcars and ggplot2's diamonds, Giggly.Datasets.mtcars/0 and
Giggly.Datasets.diamonds/0, to their names in R.
Stacking and filling bars
# Stacking and filling ------------------------------------------------------
# Stacking is the default behaviour for most area plots.
# Fill makes it easier to compare proportions
ggplot(mtcars, aes(factor(cyl), fill = factor(vs))) +
geom_bar()
ggplot(mtcars, aes(factor(cyl), fill = factor(vs))) +
geom_bar(position = "fill")
Giggly draws the first plot, writing factor(cyl) as {:factor, [:cyl]}.
mtcars
|> Plot.new(%{x: {:factor, [:cyl]}, fill: {:factor, [:vs]}})
|> Plot.geom_bar()
|> render.()
Not in Giggly yet: position_fill().
Stacking and filling histograms
ggplot(diamonds, aes(price, fill = cut)) +
geom_histogram(binwidth = 500)
ggplot(diamonds, aes(price, fill = cut)) +
geom_histogram(binwidth = 500, position = "fill")
Giggly draws the first plot: Giggly.Plot.geom_histogram/3 stacks its bars, and fills cut, an
ordered factor, with viridis in the order of its levels, as R does.
diamonds
|> Plot.new(%{x: :price, fill: :cut})
|> Plot.geom_histogram(binwidth: 500)
|> render.()
Not in Giggly yet: position_fill().
Stacked areas
# Stacking is also useful for time series
set.seed(1)
series <- data.frame(
time = c(rep(1, 4),rep(2, 4), rep(3, 4), rep(4, 4)),
type = rep(c('a', 'b', 'c', 'd'), 4),
value = rpois(16, 10)
)
ggplot(series, aes(time, value)) +
geom_area(aes(fill = type))
R's rpois() draws the values at random, the same ones every time after set.seed(1); the cell
writes out the 16 it draws.
series = %{
"time" => Enum.flat_map([1.0, 2.0, 3.0, 4.0], fn time -> List.duplicate(time, 4) end),
"type" => List.flatten(List.duplicate(["a", "b", "c", "d"], 4)),
"value" => [8, 10, 7, 11, 14, 12, 11, 9, 14, 11, 8, 2, 8, 10, 7, 12]
}
series
|> Plot.new(%{x: :time, y: :value})
|> Plot.geom_area(%{fill: :type})
|> render.()
Setting the stacking order
# Stacking order ------------------------------------------------------------
# The stacking order is carefully designed so that the plot matches
# the legend.
# You control the stacking order by setting the levels of the underlying
# factor. See the forcats package for convenient helpers.
series$type2 <- factor(series$type, levels = c('c', 'b', 'd', 'a'))
ggplot(series, aes(time, value)) +
geom_area(aes(fill = type2))
factor()'s levels give type2 its order, which Giggly.Factor.new/2 gives a column in the
table.
series =
Map.put(series, "type2", Giggly.Factor.new(series["type"], levels: ["c", "b", "d", "a"]))
series
|> Plot.new(%{x: :time, y: :value})
|> Plot.geom_area(%{fill: :type2})
|> render.()
Ordering the legend
# You can change the order of the levels in the legend using the scale
ggplot(series, aes(time, value)) +
geom_area(aes(fill = type)) +
scale_fill_discrete(breaks = c('a', 'b', 'c', 'd'))
series
|> Plot.new(%{x: :time, y: :value})
|> Plot.geom_area(%{fill: :type})
|> Plot.scale_fill_discrete(breaks: ["a", "b", "c", "d"])
|> render.()
Reversing the stack
# If you've flipped the plot, use reverse = TRUE so the levels
# continue to match
ggplot(series, aes(time, value)) +
geom_area(aes(fill = type2), position = position_stack(reverse = TRUE)) +
coord_flip() +
theme(legend.position = "top")
Not in Giggly yet: position_stack()'s reverse option and coord_flip().
Stacking lines and points
# Non-area plots ------------------------------------------------------------
# When stacking across multiple layers it's a good idea to always set
# the `group` aesthetic in the ggplot() call. This ensures that all layers
# are stacked in the same way.
ggplot(series, aes(time, value, group = type)) +
geom_line(aes(colour = type), position = "stack") +
geom_point(aes(colour = type), position = "stack")
Not in Giggly yet: geom_line()'s and geom_point()'s position option, here position = "stack".
A line on each area
ggplot(series, aes(time, value, group = type)) +
geom_area(aes(fill = type)) +
geom_line(aes(group = type), position = "stack")
Not in Giggly yet: geom_line()'s position option, here position = "stack".
Stacking labels
# You can also stack labels, but the default position is suboptimal.
ggplot(series, aes(time, value, group = type)) +
geom_area(aes(fill = type)) +
geom_text(aes(label = type), position = "stack")
Not in Giggly yet: geom_text()'s position option, here position = "stack".
Centring labels
# You can override this with the vjust parameter. A vjust of 0.5
# will center the labels inside the corresponding area
ggplot(series, aes(time, value, group = type)) +
geom_area(aes(fill = type)) +
geom_text(aes(label = type), position = position_stack(vjust = 0.5))
Not in Giggly yet: geom_text()'s position option, and position_stack()'s vjust option.
Negative values
# Negative values -----------------------------------------------------------
df <- data.frame(
x = rep(c("a", "b"), 2:3),
y = c(1, 2, 1, 3, -1),
grp = c("x", "y", "x", "y", "y")
)
ggplot(data = df, aes(x, y, group = grp)) +
geom_col(aes(fill = grp), position = position_stack(reverse = TRUE)) +
geom_hline(yintercept = 0)
R's rep() repeats "a" twice and "b" three times. The one negative value stacks downward
from zero, apart from the positive ones.
Not in Giggly yet: position_stack()'s reverse option and geom_hline().
Labels in the middle of each part
ggplot(data = df, aes(x, y, group = grp)) +
geom_col(aes(fill = grp)) +
geom_hline(yintercept = 0) +
geom_text(aes(label = grp), position = position_stack(vjust = 0.5))
Not in Giggly yet: geom_hline(), geom_text()'s position option, and position_stack()'s
vjust option.