Plotting¶
Makie.jl (1.7)¶
The general flow is:
- make a
Figure
- make an
Axis
- plot into the
Axis
Once an Axis
is "assigned" a Figure
, it cannot be moved to another Figure
. Therefore, plotting functions should take a Figure
input argument.
Axes¶
Moving xlabel. The idea to simply place a Label
into a separate layout position:
Label(f[2, 1], "Test ", halign = :right, tellwidth = false)
Subplots¶
It helps to write plotting functions that take a Figure
as an input:
# Make a stand-alone figure. Could be omitted.
function myplot(x, y; kwargs...)
fig = Figure();
myplot!(f, x, y; kwargs...)
return fig
end
# Plot into existing Figure.
function myplot!(fig :: Figure, x, y; pos = (1,1), kwargs...)
ax = fig[pos...] = Axis(fig; xlabel = "x");
myplot!(ax, x, y; kwargs...);
return ax
end
# Plot into existing Axis
function myplot!(ax :: Axis, x, y; kwargs...)
lines!(ax, x, y; kwargs...);
end
# Now we can make a stand-alone figure with
fig = Figure();
myplot!(fig, x, y);
# Or we can make a subplot with
fig = Figure();
myplot!(fig, x, y; pos = (2,1));
Limitations: The kwargs
cannot be used to create the axis.
Looping over axes¶
One way of creating a figure with subplots is:
fig = Figure();
nr = 2; nc = 3;
for ir = 1:nr, ic = 1:nc
ax = fig[ir, ic] = Axis(fig);
end
This fixes the layout. Now we can iterate over the subplots with
ind2sub(j, sz) = Tuple(CartesianIndices(sz)[j]);
for j = 1 : (nr * nc)
fig[ind2sub(j, (nr,nc))...]
end
The roundabout way is necessary because Figure
does not support linear or cartesian indexing.
Grouped bar graphs¶
Building up a grouped bar graph by plotting each series one at a time does not work. The first series bar is too wide (it covers the width of the entire group of bars). One could set it by hand, but it is not clear how.
axislegend
does not work for grouped bar graphs (lacking labels).
Keyword arguments¶
As for 0.8, CairoMakie no longer ignores invalid keyword arguments. One approach is to provide all kwargs to the main function. It then splits the args into valid args for Figure
, Axis
, lines
, etc.
nothing
works with keyword arguments such as title
.
ylims
appears to be ignored. Need to use ylims!(ax, lims)
. Unspecified bounds are set to nothing
(not Inf
).
Legends¶
A legend outside of the plot area is created by providing a new figure position (but not an Axis
) to Legend
, as in Legend(fig[1,2], ...)
. The position often spans one dimension, as in fig[:, 2]
.
Missing values¶
NaN
values are simply not plotted.
Text boxes¶
One way of adding a text box to a figure:
gl = GridLayout(fig[1,1],
tellwidth = false, tellheight = false,
halign = :center, valign = :top)
box = Box(gl[1, 1], color = (:orange,0.5), strokecolor = :black)
lbl = Label(gl[1, 1], "Box text", padding = (10, 10, 20, 20))
Themes¶
update_theme!(thm, Lines = (linewidth = 4,))
creates a new "section" thm.Lines
that applies to lines.
update_theme!(thm, Lines = (linestyle = :dash,))
merges the new information into the existing Lines
section. It does not replace previous content.
Text sizes are determined by separate attributes for all elements; e.g., xticklabelsize
.
Setting line colors from the theme's colormap
:
thm = theme_dark();
update_theme!(thm; colormap = ColorSchemes.leonardo);
function make_figure()
fig = Figure();
ax = fig[1,1] = Axis(fig);
for j = 1 : 4;
lines!(ax, 1:10, (1:10) .^ (1/j); color = fill(j, 10), colorrange = (1,4));
end
return fig
end
fig = with_theme(make_figure, thm);
Errors in theme settings cause merge!
errors when plotting that bear no obvious relationship to themes.
AlgebraOfGraphics¶
Example of a weighted density plot that stacks multiple densities and groups data:
using AlgebraOfGraphics, CairoMakie, Random;
# y = weights, z = lines, grp = groups (subplots)
df = (x=randn(5000), y=1.0 .+ rand(5000), z=rand(["a", "b", "c"], 5000), grp=rand(["g1", "g2"], 5000));
specs = data(df) * mapping(:x, layout=:z, color = :grp) * AlgebraOfGraphics.density(datalimits = ((-2.5, 2.5),));
fig = draw(specs);
Results in 3 subplots (one for each z
value). Each subplot contains 2 density line graphs (one for each grp
value). Legends are automatic. Dense, but very powerful.
The key is to have the data in something like a DataFrame
with all the grouping variables.
Annotations¶
Use the Text
visual:
ptext = data((x = [1.5], y = [2.5], label = ["My text"])) * visual(Makie.Text) * mapping(:x, :y, text = :label => verbatim);
draw(plt + ptext)
BarPlots¶
By default, a plot with a single series results in grey bars, regardless of the theme. To match the theme color:
thm = AlgebraOfGraphics.aog_theme();
barColors = first(thm.palette);
# and in the plot
visual(BarPlot; color = first(barColors))
Ordering the bars is done in the color mapping (the bar mapping ignores it):
mapping(color = :x => sorter(["x1", "x2"]))
Crossbars¶
Crossbars are bars that are centered on a y value.
plt = data(df) * (mapping(:x, :y) * visual(AlgebraOfGraphics.BarPlot) + mapping(:x, :y, :yMin, :yMax) * visual(AlgebraOfGraphics.CrossBar, color = (:gray50, 0.5)))
creates a bar graph with crossbars around the y values.
Bar colors are set in the visual
.
A standard error band can be added to a bar graph with
mapping(:x, :y, (:y, :yError) => ((x,y) -> x - y), (:y, :yError) => ((x,y) -> x + y)) * visual(AlgebraOfGraphics.CrossBar, color = :gray50))
Errorbars¶
Errorbars are simply vertical lines, centered at y values.
plt = data(df) * mapping("x", "y", "yError") * visual(Errorbars)
draws errorbars at values of y +/- yError
. To combine with bar graph, add mapping("x", "y") * visual(BarPlot)
. This requires a separate mapping
(otherwise BarPlot
plots bars of height yError
, not y
).
Legends¶
For continuous variables, the default graph produces a colorbar instead of a legend. Avoid by declaring the variable governing color nonnumeric
:
mapping(:x, :y, color = :z => nonnumeric)
.
Hiding the legend title currently does not work (titlevisible
is ignored).
See Legend tweaking.
Layouts¶
Use a renamer
to assign better labels to subplots:
mapping(layout = :z => renamer(["Label $j" for j = 1 : n]...))
Note the splatting. The renamer
function takes pairs.
See also notes on multiple y variables.
Multiple y variables¶
Just create two plots and draw(plt1 + plt2)
.
Creating a subplot with different variables in each panel:
plt = data(df) * mapping(:x, [:y, :z], layout = dims(1) => renamer(fill(" ", 2))) * visual(BarPlot)
The renamer
for the dims
argument zeros out the subplot titles that would otherwise shows as CartesianIndex
.
By default, axes are linked when the y variables are the same. To link, use
fig = draw(plt; facet = (; linkyaxes = :all))
This suppresses the y labels for the interior axes. To make the visible again:
for g in fig.grid
g.axis.ylabelvisible = true;
end
Theming¶
There is no documented way of setting a theme (other than the AOG theme). However, one may plot with a theme:
with_theme(theme_ggplot2(), resolution = (600,400)) do
plt |> draw
end
Colormaps are ignored. But one can pass color palettes to draw:
draw(p, palettes=(; color=[:red, :green, :blue]))
or
draw(plt; palettes=(; color=ColorSchemes.Set1_3.colors))
Titles¶
One main title for multiple subplots: Not directly implemented (see this issue).
Label(fig.figure[0,:], "Title"; font = :bold)
works.
Tutorials¶
Plots.jl (1.5)¶
Visually, PlotlyJS produces the most appealing plots (for me). But it does not install on my system (1.5).
When a plotting related library is not found (as in “error compiling display”), try ]build Plots
.
Defaults are set with the defaults command.
Axis labels¶
bar
tends to have too small default margins for axis labels to show (at least in subplots). Try
using Plots.PlotMeasures
Colors¶
Saturation is given by a number between 0 and 1 as in color = (:blue, 0.2)
.
Legends¶
The label is set when each series is plotted. If labels are set when the plot is created (before the series are plotted), the entries are ignored.
Bar graphs¶
Switch off lines around bars with linealpha = 0
.
Default arguments¶
Make a Dict
with default arguments for each plot type, such as:
bar_defaults() = Dict([:leg => :bottom, :linecolor => :black]);
Make a function for each plot type, such as:
function bar_graph(groupLabelV, dataV; kwargs...)
args = merge(bar_defaults(), kwargs);
p = bar(groupLabelV, dataV; args...);
return p
end
Now any default arguments can be overridden and bar_graph
is called exactly as bar
would be.
Showing plots in the REPL¶
ElectronDisplay is easy. Generate a Figure
(fig
) with Makie
. Then issue electrondisplay(fig)
.
Saving data with plots¶
VegaLite does this natively.
with Plots.jl one can use hdf5plot_write to write an entire plot, including the data, to an hdf5 file.
This means that each plot has to be generated twice; once with whatever backend is used to generate PDF files; and then again with hdf5. In particular, one cannot first plot with another backend and then save the resulting plot object to hdf5.
The approach is then to first save the plot to hdf5, then load it and save it with another backend.
Note: In my current (v.1.3) installation, hdf5plot_write generates a bunch or warnings followed by a crash due to world age problems.