Dodging: position_dodge
Mix.install([
{:giggly, path: Path.join(__DIR__, "..")},
{:kino, "~> 0.19"}
])
alias Giggly.Plot
mtcars = Giggly.Datasets.mtcars()
mpg = Giggly.Datasets.mpg()
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_dodge(), which covers
position_dodge2() 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.
Dodging keeps each mark's vertical position and sets the marks that share an x side by side.
position_dodge() dodges by group, so it needs the groups; position_dodge2() needs none, dodging
each mark by its own extent and leaving a little padding between them, which suits box plots of
different widths. width gives the width to dodge within, for marks narrower than the bars they sit
beside, and preserve keeps either the total width at each position or the width of each mark.
Giggly dodges bars and histograms as position = "dodge" does, with position: :dodge, which has
no options, and bars, histograms and box plots as position_dodge2() does, with position: :dodge2
or Giggly.Plot.position_dodge2/1 and its preserve:, padding: and reverse:. Not in Giggly
yet: position_dodge()'s options and position_dodge2()'s width option. The setup cell binds R's
mtcars and ggplot2's mpg and diamonds, Giggly.Datasets.mtcars/0, Giggly.Datasets.mpg/0 and
Giggly.Datasets.diamonds/0, to their names in R.
Dodging without groups
ggplot(mtcars, aes(factor(cyl), fill = factor(vs))) +
geom_bar(position = "dodge2")
Giggly writes factor(cyl) as {:factor, [:cyl]} and position = "dodge2" as
position: :dodge2.
Plot.new(mtcars, %{x: {:factor, [:cyl]}, fill: {:factor, [:vs]}})
|> Plot.geom_bar(position: :dodge2)
|> render.()
The width of each bar
# By default, dodging with `position_dodge2()` preserves the total width of
# the elements. You can choose to preserve the width of each element with:
ggplot(mtcars, aes(factor(cyl), fill = factor(vs))) +
geom_bar(position = position_dodge2(preserve = "single"))
Giggly.Plot.position_dodge2/1 takes ggplot2's options, a word as an atom.
Plot.new(mtcars, %{x: {:factor, [:cyl]}, fill: {:factor, [:vs]}})
|> Plot.geom_bar(position: Plot.position_dodge2(preserve: :single))
|> render.()
Dodged histograms
ggplot(diamonds, aes(price, fill = cut)) +
geom_histogram(position="dodge2")
# see ?geom_bar for more examples
Giggly bins and dodges the diamonds with Giggly.Plot.geom_histogram/3 and position: :dodge2,
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(position: :dodge2)
|> render.()
Frequency polygons instead
# In this case a frequency polygon is probably a better choice
ggplot(diamonds, aes(price, colour = cut)) +
geom_freqpoly()
diamonds
|> Plot.new(%{x: :price, colour: :cut})
|> Plot.geom_freqpoly()
|> render.()
Columns dodged by group
# Dodging with various widths -------------------------------------
# To dodge items with different widths, you need to be explicit
df <- data.frame(
x = c("a","a","b","b"),
y = 2:5,
g = rep(1:2, 2)
)
p <- ggplot(df, aes(x, y, group = g)) +
geom_col(position = "dodge", fill = "grey50", colour = "black")
p
rep(1:2, 2) is 1, 2, 1, 2, so each x has a column for each group. position: :dodge sets
the columns side by side within the width one column takes, and fill: and colour: fill and
border them. R's "grey50" has no name in CSS, so the fill is written as its hex, "#7F7F7F".
df = %{"x" => ["a", "a", "b", "b"], "y" => [2, 3, 4, 5], "g" => [1, 2, 1, 2]}
p =
df
|> Plot.new(%{x: :x, y: :y, group: :g})
|> Plot.geom_col(position: :dodge, fill: "#7F7F7F", colour: "black")
render.(p)
A line range has no width
# A line range has no width:
p + geom_linerange(aes(ymin = y - 1, ymax = y + 1), position = "dodge")
With no width to dodge within, the line ranges are not moved apart, and do not sit on their columns.
Not in Giggly yet: line ranges and geom_linerange().
Giving the width
# So you must explicitly specify the width
p + geom_linerange(
aes(ymin = y - 1, ymax = y + 1),
position = position_dodge(width = 0.9)
)
Dodged within 0.9, the width the columns take, each line range sits on its column.
Not in Giggly yet: geom_linerange() and position_dodge()'s options.
Error bars
# The same principle applies to error bars, which are usually
# narrower than the bars
p + geom_errorbar(
aes(ymin = y - 1, ymax = y + 1),
width = 0.2,
position = "dodge"
)
p + geom_errorbar(
aes(ymin = y - 1, ymax = y + 1),
width = 0.2,
position = position_dodge(width = 0.9)
)
"dodge" sets the error bars side by side within their own width, 0.2, so they huddle at the
middle of each x; dodged within 0.9, each sits on its column.
Not in Giggly yet: error bars, geom_errorbar() and its width option, and position_dodge()'s
options.
Box plots
# Box plots use position_dodge2 by default, and bars can use it too
ggplot(mpg, aes(factor(year), displ)) +
geom_boxplot(aes(colour = hwy < 30))
Giggly writes hwy < 30 as {&Kernel.</2, [:hwy, 30]}, which titles the legend hwy < 30, as R
does.
Plot.new(mpg, %{x: {:factor, [:year]}, y: :displ})
|> Plot.geom_boxplot(%{colour: {&Kernel.</2, [:hwy, 30]}})
|> render.()
Box plots of different widths
ggplot(mpg, aes(factor(year), displ)) +
geom_boxplot(aes(colour = hwy < 30), varwidth = TRUE)
varwidth = TRUE makes each box's width grow with the square root of its number of rows, and
position_dodge2() dodges the boxes by their own widths.
Plot.new(mpg, %{x: {:factor, [:year]}, y: :displ})
|> Plot.geom_boxplot(%{colour: {&Kernel.</2, [:hwy, 30]}}, varwidth: true)
|> render.()
Bars dodged the same way
ggplot(mtcars, aes(factor(cyl), fill = factor(vs))) +
geom_bar(position = position_dodge2(preserve = "single"))
ggplot(mtcars, aes(factor(cyl), fill = factor(vs))) +
geom_bar(position = position_dodge2(preserve = "total"))
Every 8-cylinder car has vs 0, so that x has one bar: as wide as one bar of a pair with
preserve = "single", and as wide as the whole pair with preserve = "total".
Plot.new(mtcars, %{x: {:factor, [:cyl]}, fill: {:factor, [:vs]}})
|> Plot.geom_bar(position: Plot.position_dodge2(preserve: :single))
|> render.()
Plot.new(mtcars, %{x: {:factor, [:cyl]}, fill: {:factor, [:vs]}})
|> Plot.geom_bar(position: Plot.position_dodge2(preserve: :total))
|> render.()