What a mapping maps each aesthetic to: a column, an expression computed from columns in each row, or a constant. A mapping is a map from aesthetic to value:
%{x: :displ, y: {&Kernel.//2, [:hwy, :cty]}, colour: {:constant, "smooth"}}Values
- A column is named by an atom or a string, compared with the table's column names as a
string, so
:hwyand"hwy"name the same column.true,falseandnilname none. - An expression,
{function, [value, ...]}, is the function applied in each row to what its arguments are in that row, so{&Kernel.//2, [:hwy, :cty]}divides each row'shwyby itscty. Each argument is a value, so it may be a column, a constant or another expression, and the function takes as many arguments as it is given. - A function by name,
{:log10, [value]}, is a common function Elixir lacks, computed by the engine holding the table:log10is the base-10 logarithm, minus infinity for 0, NaN for a negative number and missing for a missing value. Its value is a column, a constant or another function by name, never a capture or a closure, which the engine cannot run, so the logarithm of what a capture computes is a capture too,{&:math.log10/1, [value]}. Any other function is written as a capture. - A constant,
{:constant, term}, is the term in every row. A number, a boolean, a date or a time is a constant written alone; a string or an atom would name a column, so a constant one is written{:constant, "smooth"}. - A factor,
{:factor, [value]}, is the value read as discrete, so a column of numbers maps as levels: a discrete scale whatever the values, its levels in order of value (dates by date) with a missing one last, each written as text (a whole number without a decimal point,4; a boolean asTRUEorFALSE), and the rows grouped by them.{:factor, [value], levels: [level, ...]}gives the levels and their order, each named as the value holds it ([8, 4, 6]for numbers), a value not among them missing, so the axis, the legend, the groups and stacking follow that order;ordered: truemarks the factor's levels as ordered. A factor reads a whole column, so it is the whole of a mapping value, a factor's own or an after_stat's, never an expression's argument. - A reordering,
{:reorder, [value, by]}, isvalueread as discrete, as a factor reads it, its levels ordered by the mean ofbyover each one's rows, the lowest first: levels whose means tie keep the order the factor ofvaluegives them (its own levels where the table's column is a factor), and a level whose mean is missing, since its rows hold a missing or NaNbyor there are none, goes last. The mean is R'smean(), worked out as R works it out, digit for digit. Each ofvalueandbyis a column or a function by name, which the engine holding the table computes, as it computes the means, andbyis numbers; the levels are ordered, asordered: truemarks a factor's, wherevalueis an ordered factor. A reordering reads whole columns, as a factor does, so it goes where a factor goes. - An after_stat,
{:after_stat, [value]}, is the value computed from what the layer's stat computes: its names are the stat's computed variables (counting's arecountandprop), and it is evaluated once the stat has run, so it takes no part in grouping or in what the stat computes. Its value is put through its aesthetic's scale. It is the whole of a mapping value, never inside another, so the share as a percentage is written{:after_stat, [{&Kernel.*/2, [:prop, 100]}]}.
A constant is a value a column can hold: nil, a list, and a value with no column type of its
own (a map, a tuple, a struct other than a date or a time) are refused, and so are a function
alone, a tuple that is neither an expression nor a constant, and a function given a number of
arguments it does not take. A function by name is refused where the table's engine cannot run
it, and log10 where its value is a column or a constant that is not numbers, a factor's
column among them. A factor's levels are refused unless they are a list of one or more
values a column can hold, of one type, none of them nil and none given twice (4 and 4.0
are one); levels of another type than the value's are refused when the plot is run. A
reordering is refused where either of its values is a constant, a capture or a closure, a factor
or another reordering, or where by is a column that is not numbers. A layer
whose stat computes nothing, and so documents no variables, refuses every after_stat.
iex> plot =
...> %{"hwy" => [29, 24], "cty" => [18, 16]}
...> |> Giggly.Plot.new(%{x: :cty, y: {&Kernel.//2, [:hwy, :cty]}})
...> |> Giggly.Plot.geom_point(%{colour: {:constant, "smooth"}})
iex> frame = Giggly.layer_data(plot)
iex> Giggly.DataFrame.pull(frame, :y)
[1.6111111111111112, 1.5]
iex> Giggly.DataFrame.pull(frame, :color)
["#F8766D", "#F8766D"]Each class's share of all the rows, counted as one group:
iex> plot =
...> %{"class" => ["suv", "compact", "suv", "midsize"]}
...> |> Giggly.Plot.new(%{x: :class, y: {:after_stat, [:prop]}, group: 1})
...> |> Giggly.Plot.geom_bar()
iex> frame = Giggly.layer_data(plot)
iex> Giggly.DataFrame.pull(frame, :y)
[0.25, 0.25, 0.5]The cylinders in the order given, so the axis reads 8, 4, 6:
iex> plot =
...> %{"cyl" => [4, 6, 8, 4], "mpg" => [26.0, 19.7, 15.0, 30.4]}
...> |> Giggly.Plot.new(%{x: {:factor, [:cyl], levels: [8, 4, 6]}, y: :mpg})
...> |> Giggly.Plot.geom_point()
iex> Giggly.panel_scales(plot, 1).x.levels
[8, 4, 6]The classes in order of their mean highway mileage, 19 for the SUVs, 27 and 28:
iex> plot =
...> %{"class" => ["suv", "compact", "suv", "midsize"], "hwy" => [18, 28, 20, 27]}
...> |> Giggly.Plot.new(%{x: {:reorder, [:class, :hwy]}, y: :hwy})
...> |> Giggly.Plot.geom_point()
iex> Giggly.panel_scales(plot, 1).x.levels
["suv", "midsize", "compact"]Titles
A guide is titled by what its aesthetic is mapped to:
- a column by its name,
cty; - an expression by its text, as Elixir writes the call: a capture of one of
Kernel's operators as the operator,hwy / cty, and any other capture as a call to the function it names,:math.log10(carat), asinspect/1names it; - a function by name as a call to it,
log10(carat); - an expression whose function is a closure, such as
fn hwy -> hwy * 2 end, has no text, so its guide has no title untilGiggly.Plot.labs/2gives it one; - a factor by its value's text inside
factor(),factor(cyl), and its options as R writes them,factor(cyl, levels = c(8, 4, 6), ordered = TRUE); - a reordering as a call to R's
reorder(),reorder(class, hwy); - an after_stat by its value's text,
prop; - a constant by its aesthetic's name,
color, unless another layer maps the aesthetic to a column or an expression, whose title wins.
So a capture is the form to write where the title should say what was computed, and a closure
the form to write inline, with a title from labs/2.
iex> svg =
...> %{"carat" => [0.23, 1.5], "price" => [326, 18823]}
...> |> Giggly.Plot.new(%{x: {:log10, [:carat]}, y: {:log10, [:price]}})
...> |> Giggly.Plot.geom_point()
...> |> Giggly.to_svg()
...> |> IO.iodata_to_binary()
iex> svg =~ ">log10(carat)</text>" and svg =~ ">log10(price)</text>"
true
iex> svg =
...> %{"hwy" => [29, 24], "cty" => [18, 16]}
...> |> Giggly.Plot.new(%{x: :cty, y: {&Kernel.//2, [:hwy, :cty]}})
...> |> Giggly.Plot.geom_point()
...> |> Giggly.to_svg()
...> |> IO.iodata_to_binary()
iex> svg =~ ">hwy / cty</text>"
true
Summary
Types
An after_stat: a value computed from the stat's computed variables.
A column, named by an atom or a string.
A constant: {:constant, term}, or a number, a boolean, a date or a time written alone.
An expression: a function and the values it is applied to in each row, or a function by name and the value it is computed from.
A factor: a value read as discrete, with the levels it holds and their order.
A reordering: a value read as discrete, its levels ordered by another's means.
A value as a plot holds it: a column by its name as a string, a constant as {:constant, term},
and an expression as its function and its arguments, each held so.
A mapping as a plot holds it: aesthetic to resolved value.
A value as written: a column, an expression, a constant, a factor, a reordering or an after_stat.
Types
@type after_stat() :: {:after_stat, [value()]}
An after_stat: a value computed from the stat's computed variables.
A column, named by an atom or a string.
@type constant() :: {:constant, term()} | number() | boolean() | Date.t() | Time.t() | NaiveDateTime.t() | DateTime.t()
A constant: {:constant, term}, or a number, a boolean, a date or a time written alone.
An expression: a function and the values it is applied to in each row, or a function by name and the value it is computed from.
A factor: a value read as discrete, with the levels it holds and their order.
@type reordering() :: {:reorder, [column() | expression()]}
A reordering: a value read as discrete, its levels ordered by another's means.
@type resolved() :: String.t() | {:constant, term()} | {:factor, [resolved()]} | {:factor, [resolved()], levels: [term()], ordered: boolean()} | {:reorder, [resolved()]} | {:after_stat, [resolved()]} | {function(), [resolved()]} | {:log10, [resolved()]}
A value as a plot holds it: a column by its name as a string, a constant as {:constant, term},
and an expression as its function and its arguments, each held so.
A mapping as a plot holds it: aesthetic to resolved value.
@type value() :: column() | expression() | constant() | factor() | reordering() | after_stat()
A value as written: a column, an expression, a constant, a factor, a reordering or an after_stat.