Bland.Figure (Elixir Technical Drawing v0.6.0)

Copy Markdown View Source

The plot document.

A %Figure{} holds the canvas dimensions, axis configuration, series list, and ornaments (legend, title block, annotations). Builder functions return updated figures so the whole pipeline stays immutable:

Bland.figure(size: :a5_landscape)
|> Bland.line([1, 2, 3], [1, 4, 9])
|> Bland.axes(xlabel: "n", ylabel: "n²")
|> Bland.render()

Paper sizes

size: accepts {width_px, height_px} tuples or one of the named paper presets (dimensions in pixels at 96 DPI):

  • :a4, :a4_landscape
  • :a5, :a5_landscape
  • :letter, :letter_landscape
  • :legal, :legal_landscape
  • :square — 600×600
  • :ieee_column — IEEE single column, 88 × 66 mm
  • :ieee_double — IEEE page-wide, 181 × 136 mm
  • :nature_single — Nature single column, 89 × 67 mm
  • :nature_double — Nature double column, 183 × 137 mm

The default is :letter_landscape.

Physical size

Named presets carry a physical size in millimetres alongside their pixel dimensions. The rendered SVG sets width/height in mm and leaves viewBox in user units, so the figure prints at true size and drops into a LaTeX or InDesign flow at the column width it was drawn for. size_mm: false opts out and emits pixels; an explicit {w, h} tuple size has no physical dimensions and always emits pixels.

Struct fields

  • :width, :height — canvas dimensions (px)
  • :margins{top, right, bottom, left} inside the border
  • :title, :subtitle — figure text
  • :xlim, :ylim — data-space limits, or :auto (default)
  • :xscale, :yscale:linear (default), :log, or :date
  • :xtick_format, :ytick_format — how tick labels are printed. A Calendar.strftime/2 format string for :date axes; one of :default | :si | :scientific | :degrees for numeric axes; or a 1-arity function taking the tick value and returning a string. nil (default) picks the sensible thing for the scale
  • :xticks, :yticks — explicit tick positions, overriding the nice-rounded ones
  • :xticklabels, :yticklabels — explicit labels, positionally matched to the ticks
  • :invert_x, :invert_y — run the axis high-to-low
  • :xaxis, :yaxis:full (default), :ticks (marks only, no labels or title), or :off
  • :aspect:auto (default) fills the margins; :equal gives one data-x unit the same page length as one data-y unit
  • :manual_layouttrue turns off collision-aware placement, so ornaments land at their nominal positions whatever is underneath

Twin y axis

A second y scale can be drawn on the right for a series measured in different units — temperature against pressure, gain against phase. Series opt in with axis: :right; the right margin widens to make room.

  • :ylabel2, :ylim2, :yscale2, :yticks2, :yticklabels2, :ytick_format2, :invert_y2 — the right axis's counterparts of the left-axis options
  • :xlabel, :ylabel
  • :grid:none | :major | :both

  • :series — list of %Bland.Series{} in draw order
  • :legend%{position: atom, title: string} | nil

  • :title_block%Bland.TitleBlock{} or nil
  • :annotations — list of %{type: atom, ...} overlays
  • :theme — map returned by Bland.Theme.get/1
  • :caption — explanatory text below the plot; the bottom margin grows to hold it, and long captions wrap
  • :figure_id — a figure number ("Fig. 3"), set to the left of the caption

Summary

Functions

Appends a series to the figure. Accepts a %Bland.Series.Line{} or similar struct, or any map tagged with :type.

Builds a new figure. opts accepts any of the struct fields, plus :size (alias for setting :width and :height from a preset) and :theme (atom or map, resolved via Bland.Theme.get/1).

Physical sizes of the built-in paper presets, in millimetres.

List of built-in paper size presets.

Physical {width_mm, height_mm} for a named paper preset, or nil for a {width, height} pixel tuple, which has no physical size.

Returns the inner plotting rectangle {x, y, w, h} in pixel coordinates, accounting for the figure's margins.

Resolves :size to {width, height}. Raises on unknown presets.

Updates the figure with a keyword list of overrides.

Types

size()

@type size() :: atom() | {pos_integer(), pos_integer()}

t()

@type t() :: %Bland.Figure{
  annotations: [map()],
  aspect: :auto | :equal,
  axes: term(),
  caption: String.t() | nil,
  clip: :rect | :circle,
  colorbar: map() | nil,
  figure_id: String.t() | nil,
  grid: :none | :major | :both,
  height: pos_integer(),
  insets: [map()],
  invert_x: boolean(),
  invert_y: boolean(),
  invert_y2: boolean(),
  legend: map() | nil,
  manual_layout: boolean(),
  margins: {number(), number(), number(), number()},
  projection: atom(),
  series: [map()],
  size_mm: {number(), number()} | nil | false,
  subtitle: String.t() | nil,
  theme: map(),
  title: String.t() | nil,
  title_block: map() | nil,
  width: pos_integer(),
  xaxis: :full | :ticks | :off,
  xlabel: String.t() | nil,
  xlim: {number(), number()} | :auto,
  xscale: :linear | :log | :date,
  xtick_format: term(),
  xticklabels: [String.t()] | nil,
  xticks: [number()] | nil,
  yaxis: :full | :ticks | :off,
  ylabel: String.t() | nil,
  ylabel2: String.t() | nil,
  ylim: {number(), number()} | :auto,
  ylim2: {number(), number()} | :auto,
  yscale: :linear | :log | :date,
  yscale2: :linear | :log,
  ytick_format: term(),
  ytick_format2: atom() | String.t() | (number() -> String.t()) | nil,
  yticklabels: [String.t()] | nil,
  yticklabels2: [String.t()] | nil,
  yticks: [number()] | nil,
  yticks2: [number()] | nil
}

Functions

add_series(fig, s)

@spec add_series(t(), map()) :: t()

Appends a series to the figure. Accepts a %Bland.Series.Line{} or similar struct, or any map tagged with :type.

new(opts \\ [])

@spec new(keyword()) :: t()

Builds a new figure. opts accepts any of the struct fields, plus :size (alias for setting :width and :height from a preset) and :theme (atom or map, resolved via Bland.Theme.get/1).

paper_mm()

@spec paper_mm() :: %{required(atom()) => {number(), number()}}

Physical sizes of the built-in paper presets, in millimetres.

paper_sizes()

@spec paper_sizes() :: %{required(atom()) => {pos_integer(), pos_integer()}}

List of built-in paper size presets.

physical_mm(name)

@spec physical_mm(size()) :: {number(), number()} | nil

Physical {width_mm, height_mm} for a named paper preset, or nil for a {width, height} pixel tuple, which has no physical size.

iex> Bland.Figure.physical_mm(:a4_landscape)
{297.0, 210.0}

iex> Bland.Figure.physical_mm({800, 600})
nil

plot_rect(figure)

@spec plot_rect(t()) :: {number(), number(), number(), number()}

Returns the inner plotting rectangle {x, y, w, h} in pixel coordinates, accounting for the figure's margins.

resolve_size(name)

@spec resolve_size(size()) :: {pos_integer(), pos_integer()}

Resolves :size to {width, height}. Raises on unknown presets.

update(fig, opts)

@spec update(
  t(),
  keyword()
) :: t()

Updates the figure with a keyword list of overrides.