Continuous position scales: scale_continuous
Mix.install([
{:giggly, path: Path.join(__DIR__, "..")},
{:kino, "~> 0.19"}
])
alias Giggly.Plot
mpg = Giggly.Datasets.mpg()
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
scale_x_continuous(), which
covers scale_y_continuous(), scale_x_log10(), scale_y_log10(), scale_x_reverse(),
scale_y_reverse(), scale_x_sqrt() and scale_y_sqrt() 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.
scale_x_continuous() and scale_y_continuous() are the default scales for a continuous x and
y. Setting one by hand changes its title, limits, breaks or labels, or transforms it; the log10,
square root and reverse scales are shortcuts for common transformations. Giggly has each of these
functions, Giggly.Plot.scale_x_continuous/2 and the rest, with their titles, limits, breaks, minor
breaks, labels, expansion and transformations, and formatters for labels in Giggly.Format; it has
no sec.axis option (sec_axis()), and no transformation of your own (scales::new_transform()).
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. The cells draw
Giggly.Datasets.mpg/0, ggplot2's mpg: fuel economy for 234 cars of 38 popular models from 1999
to 2008, which the setup cell binds to mpg, ggplot2's name for it.
The default scales
Both columns are numbers, so both axes get continuous scales, each titled by its column.
p1 <- ggplot(mpg, aes(displ, hwy)) +
geom_point()
p1
p1 =
mpg
|> Plot.new(%{x: :displ, y: :hwy})
|> Plot.geom_point()
render.(p1)
Axis titles
# Manipulating the default position scales lets you:
# * change the axis labels
p1 +
scale_x_continuous("Engine displacement (L)") +
scale_y_continuous("Highway MPG")
R's first argument is the scale's name, which titles its axis; Giggly's is name:.
p1
|> Plot.scale_x_continuous(name: "Engine displacement (L)")
|> Plot.scale_y_continuous(name: "Highway MPG")
|> render.()
Removing axis titles
# You can also use the short-cut labs().
# Use NULL to suppress axis labels
p1 + labs(x = NULL, y = NULL)
Giggly writes R's NULL as :none, which removes each title and the room it took.
p1
|> Plot.labs(x: :none, y: :none)
|> render.()
Axis limits
# * modify the axis limits
p1 + scale_x_continuous(limits = c(2, 6))
p1 + scale_x_continuous(limits = c(0, 10))
Limits narrower than the data drop the cars outside them, which ggplot2 warns of and Giggly does not; wider ones leave room beyond the data.
p1
|> Plot.scale_x_continuous(limits: {2, 6})
|> render.()
p1
|> Plot.scale_x_continuous(limits: {0, 10})
|> render.()
The xlim() shortcut
# you can also use the short hand functions `xlim()` and `ylim()`
p1 + xlim(2, 6)
p1
|> Plot.xlim(2, 6)
|> render.()
Where the ticks go
# * choose where the ticks appear
p1 + scale_x_continuous(breaks = c(2, 4, 6))
p1
|> Plot.scale_x_continuous(breaks: [2, 4, 6])
|> render.()
Labels of your own
# * choose your own labels
p1 + scale_x_continuous(
breaks = c(2, 4, 6),
label = c("two", "four", "six")
)
R matches label to the argument it begins, labels, which Giggly spells out.
p1
|> Plot.scale_x_continuous(breaks: [2, 4, 6], labels: ["two", "four", "six"])
|> render.()
Formatting labels
The example makes a small table of random numbers with R's set.seed(1), so that its labels have
large numbers and fractions to format.
# Typically you'll pass a function to the `labels` argument.
# Some common formats are built into the scales package:
set.seed(1)
df <- data.frame(
x = rnorm(10) * 100000,
y = seq(0, 1, length.out = 10)
)
p2 <- ggplot(df, aes(x, y)) + geom_point()
p2 + scale_y_continuous(labels = scales::label_percent())
p2 + scale_y_continuous(labels = scales::label_dollar())
p2 + scale_x_continuous(labels = scales::label_comma())
The cell writes out the x R drew, each number to the last digit R holds, and works out y as
R's seq() does, each step a ninth. Giggly's formatters are Giggly.Format's, each handed every
break so that the labels share their decimals, as the scales package's are: percent/2,
currency/2, which writes what label_dollar() writes at its defaults, and comma/2. The first
two plots' x axis, left to its default labels, reads 0 and 100000 where R writes 0e+00 and
1e+05, since Giggly writes numbers as Elixir does.
df = %{
"x" => [
-62645.38107423324, 18364.332422208223, -83562.86124100472, 159528.08021377915,
32950.777181536054, -82046.83841180152, 48742.90524284853, 73832.47051292173,
57578.13516534923, -30538.838715635604
],
"y" => Enum.map(0..9, fn step -> step * (1 / 9) end)
}
p2 =
df
|> Plot.new(%{x: :x, y: :y})
|> Plot.geom_point()
p2
|> Plot.scale_y_continuous(labels: &Giggly.Format.percent/1)
|> render.()
p2
|> Plot.scale_y_continuous(labels: &Giggly.Format.currency/1)
|> render.()
p2
|> Plot.scale_x_continuous(labels: &Giggly.Format.comma/1)
|> render.()
Transformations
# You can also override the default linear mapping by using a
# transformation. There are three shortcuts:
p1 + scale_y_log10()
p1 + scale_y_sqrt()
p1 + scale_y_reverse()
p1
|> Plot.scale_y_log10()
|> render.()
p1
|> Plot.scale_y_sqrt()
|> render.()
p1
|> Plot.scale_y_reverse()
|> render.()
Other transformations
# Or you can supply a transformation in the `trans` argument:
p1 + scale_y_continuous(transform = scales::transform_reciprocal())
# You can also create your own. See ?scales::new_transform
Giggly names a transformation by an atom, :identity, :log10, :sqrt, :reverse or
:reciprocal, where R takes the scales package's objects; it has none of the package's others,
and makes none of your own.
p1
|> Plot.scale_y_continuous(transform: :reciprocal)
|> render.()