Composition and layout
Figures combine into figures. This page covers drawing figures over one another, arranging them in grids and facets, deciding which parts share a scale, and the titles, sizes and themes of the result.
open Hugin let t = Nx.linspace Nx.float64 0. 10. 200 let signal k = Nx.mul (Nx.exp (Nx.mul_s t (-0.1 *. k))) (Nx.sin t)
Layers
layer draws its figures over one another, the first at the bottom. The
children of a layer share every scale, so they share their axes and
legends:
let () = layer [ area ~x:(num t) ~y:(num (signal 1.)) ~opacity:(const 0.2) (); line ~x:(num t) ~y:(num (signal 1.)) (); rule ~y:(floats [| 0. |]) (); ] |> save "layers.png"
Grids
grid arranges figures in rows of cells, from the top and from the left.
The data areas of a column share their left and right edges, and those of a
row their top and bottom edges, whatever the width of each cell's tick
labels:
let decay = line ~x:(num t) ~y:(num (signal 0.5)) () let fast = line ~x:(num t) ~y:(num (Nx.mul_s (signal 2.) 1000.)) () let hist = rect ~x:(strings [| "a"; "b" |]) ~y:(floats [| 3.; 5. |]) () let () = grid [ [ decay; fast ]; [ hist; layer [] ] ] |> save ~size:(Size.figure 420. 300.) "grid.png"
layer [] is an empty cell. ~widths and ~heights weigh the columns and
rows, and span ~cols:2 f makes f a cell two columns wide:
let () = grid ~heights:[ 2.; 1. ] [ [ span ~cols:2 decay ]; [ fast; hist ] ] |> save ~size:(Size.figure 420. 300.) "spans.png"
Which scales are shared
The cells of a grid keep their own x and y scales, and every other scale is
shared by the whole figure, so a figure has one colour legend however it is
arranged. share changes that for one name at one node:
let () = grid [ [ decay ]; [ line ~x:(num t) ~y:(num (signal 1.)) () ] ] |> share [ ("x", `Shared) ] |> save "shared-x.png"
With one x scale, the two cells line up on one domain and the axis is
labelled once, below the bottom cell. share [ ("color", `Independent) ]
gives each cell of a grid its own colour scale and legend.
Facets
The facet channels ~fx and ~fy split one mark into panels: each row is
drawn in the column of its fx category and the row of its fy category.
Facet panels share every scale:
let damping = Nx.create Nx.float64 [| 4; 1 |] [| 0.1; 0.2; 0.4; 0.8 |] let signals = Nx.mul (Nx.exp (Nx.neg (Nx.mul damping t))) (Nx.sin t) let () = line ~x:(num t) ~y:(num signals) ~fx:(dim ~title:"damping" ~labels:[| "0.1"; "0.2"; "0.4"; "0.8" |] 0) () |> save ~size:(Size.figure 480. 200.) "facets.png"
A band scale with ~wrap wraps the panels into rows of that many:
~fx:(dim ~scale:(Scale.band ~wrap:2 ()) 0) puts the four panels in two rows.
A mark without facet channels, layered with a faceted one, is drawn in every
panel, so a reference line appears in each.
Titles and coordinates
title sets a bold title above a figure, aligned with the left edge of its
data areas by default. Titles nest, and a grid of titled cells labels each
panel:
let () = grid [ [ title "(a)" decay; title "(b)" fast ] ] |> title ~align:`Center "Two decays" |> save ~size:(Size.figure 480. 220.) "titled.png"
coord sets the coordinate system of a figure's panels.
Coord.cartesian ~aspect:1. () makes one unit of y as long as one unit of x,
which keeps a heatmap's cells square or a trajectory's shape true:
let () = let angle = Nx.linspace Nx.float64 0. 6.28 100 in line ~x:(num (Nx.cos angle)) ~y:(num (Nx.sin angle)) () |> coord (Coord.cartesian ~aspect:1. ()) |> save "circle.png"
Sizes
A size fixes one length and lets the layout derive the rest.
Size.figure w h fixes the page, and the panels share what the axes,
titles and legends leave. Size.panels w h fixes each data area, and the
page grows to hold the rest. In a grid, Size.panels gives each column of
weight k a data area k *. w points wide. Lengths are in points;
Size.mm converts millimetres.
If the page is too small for what it must hold, layout raises
Invalid_argument and names a size at which the figure fits.
Themes
A theme holds what a figure does not state: colours of ink, paper and
marks, the faces text is set in, the base size, the default colour schemes
and the locale numbers are written in. Every length derives from the base
size, so ~size:8. sets a figure for a printed column:
let print = Theme.v ~size:8. ~palette:Scheme.okabe_ito () let () = grid [ [ title "(a)" decay; title "(b)" fast ] ] |> save ~theme:print ~size:(Size.figure (Size.mm 85.) (Size.mm 40.)) "column.pdf"
Theme.dark sets light ink on dark paper, and a scheme for continuous
colour scales that starts at viridis's middle, since its darkest colours
would fade into the paper. Theme.talk and Theme.poster enlarge text and
every length for slides and posters. Presets are themes like any other,
combined through their accessors:
let slides = Theme.(v ~ink:(ink dark) ~paper:(paper dark) ~scheme:(scheme dark) ~size:(size talk) ()) let () = decay |> save ~theme:Theme.dark "dark.png" let () = decay |> save ~theme:slides ~size:(Size.figure 576. 384.) "slide.png"
The same figure renders in any theme, since the theme is given at rendering.
this page is tested with mdxedit this page ↗