BLAND — Elixir Technical Drawing.
A pure-Elixir library for producing monochrome, paper-ready plots in the visual tradition of 1960s–1980s engineering reports: thin black rules, serif type, crisp frames, hatched fills, and optional drafting title blocks.
BLAND emits SVG. SVG is the right format for paper output — resolution- independent, prints clean on any printer, and embeds into Livebook, PDF pipelines, and LaTeX figures without conversion.
See the gallery for every plot type at a glance.
Design philosophy
BLAND deliberately avoids color. The library leans on the legibility vocabulary of technical drafting — stroke weight, dash patterns, hatch density, and marker shape — so plots stay readable in photocopies, grayscale prints, and for readers with color vision deficiency.
Quick start
xs = Enum.map(0..100, &(&1 / 10.0))
fig =
Bland.figure(size: :a5_landscape, title: "Damped oscillation")
|> Bland.axes(xlabel: "t [s]", ylabel: "x(t)")
|> Bland.line(xs, Enum.map(xs, &(:math.exp(-&1 / 4) * :math.cos(&1))),
label: "response", stroke: :solid)
|> Bland.line(xs, Enum.map(xs, &(:math.exp(-&1 / 4))),
label: "envelope", stroke: :dashed)
|> Bland.legend(position: :top_right)
Bland.to_svg(fig) |> File.write!("plot.svg")API overview
figure/1— begin a new plot with canvas + themeaxes/2— labels, limits, scale type, gridline/4— connected polyline (time series, curves)scatter/4— discrete marked pointsbar/4— categorical bars with hatched fillshistogram/3— binned observations on a numeric axisheatmap/3— 2D grid shaded with a hatch ramparea/4— filled region with hatched fillcolorbar/2— ramp legend for a heatmapgraticule/2— lat/lon grid overlay (for geo figures)basemap/3— add a built-in geographic base layer (coastlines, borders, lunar maria)hline/3,vline/3— reference linesannotate/2— text / arrow overlayslegend/2— add a legendtitle_block/2— drafting title block in the cornerto_svg/1— render
All builder functions return the updated %Bland.Figure{} so you can pipe
freely.
Guides
Summary
Functions
Adds an annotation overlay.
Adds a filled area series.
Sets axis options on a figure.
Adds a categorical bar series. categories and values must line up.
Adds a horizontal bar series — categories run down the y axis and values along x. Use it when category labels are long enough that vertical bars would force them to slant.
Adds a built-in geographic base layer to the figure.
Adds a Bland-Altman agreement plot — the difference between two measurement methods against their mean, with the bias and limits of agreement drawn as reference rules.
Renders a Bode plot — magnitude (dB, log-linear) on top, phase (degrees, log-linear) on the bottom — as a two-panel SVG.
Adds a box-and-whisker summary. categories_and_samples pairs each
category label with a list of raw observations; BLAND computes the
quartiles, Tukey-fence whiskers, and outliers for you via
Bland.Stats.boxplot_stats/2.
Attaches a colorbar (ramp legend) to the figure.
Adds contour (iso-level) curves over a 2D scalar grid.
Adds an error-bar series — data points with X and/or Y uncertainty whiskers.
Creates a new figure.
Fills the region between two curves sharing an x axis — a tolerance envelope, a min/max sweep, the gap between a model and a measurement.
Adds a latitude/longitude graticule as a series of dotted reference
lines. Only meaningful on figures with projection: :mercator or
:equirect.
Composes a list of figures into a single SVG with a grid layout.
Like grid/2, but returns a Kino.Image for Livebook inline
display.
Adds a heatmap series.
Adds a histogram series.
Adds a horizontal reference line at y-value y.
Adds a horizontal shaded band spanning y ∈ [y1, y2]. Same options
as vspan/4. Useful for acceptance bands and tolerance windows.
Nests a second figure inside this one — a detail view of a crowded region, or a zoom on the feature the main plot is about.
Attaches (or replaces) a legend on the figure.
Adds a line series.
Adds a north arrow.
Adds a Nyquist plot — the imaginary against the real part of a frequency response, traced as a parametric curve.
Creates a polar figure.
Adds a polar reference grid to a figure built via polar_figure/1.
Adds a closed polygon series. xs and ys give the vertices; the
renderer connects the last point back to the first.
Adds a Q-Q (quantile-quantile) plot — sample quantiles vs theoretical
quantiles of a named distribution, with a y = x reference line.
Adds a vector-field (quiver) series. Each (xs[i], ys[i]) gets an
arrow with components (us[i], vs[i]).
Adds a map scale bar — an alternating black/white ruler with a distance label, sized from the figure's own x scale.
Adds a scatter series.
Creates a Smith chart figure — a unit-disk canvas for plotting
reflection coefficients Γ in RF / microwave work.
Adds the classical Smith chart grid to a figure: constant-resistance
circles (r = 0.2, 0.5, 1, 2, 5 by default), constant-reactance arcs
(at ±0.2, ±0.5, ±1, ±2, ±5), plus the unit circle boundary and the
real axis.
Adds a stem-plot series — the discrete-signal staple from DSP.
Adds a step series — a staircase rather than a straight interpolation, for a quantity that holds between samples: a sampled control signal, a tariff schedule, a survival curve.
Attaches (or replaces) a drafting title block. See Bland.TitleBlock for
the full field list.
Shortcut for rendering + Livebook inline display.
Renders a figure to an SVG string.
Adds a vertical reference line at x-value x.
Adds a vertical shaded band spanning x ∈ [x1, x2].
Renders a figure and writes it to path.
Functions
@spec annotate( Bland.Figure.t(), keyword() ) :: Bland.Figure.t()
Adds an annotation overlay.
Supported shapes
text: "…", at: {x, y}— text at data-space coordinates. Accepts:font_sizeand:anchor("start"/"middle"/"end").arrow: {from_xy, to_xy}— straight arrow between two data points.callout: "…", at: {x, y}— a leader line from the data point to a text box, placed where there is room rather than at a fixed offset. Accepts:font_sizeand:offset(leader length in px). Setmanual_layout: trueon the figure to pin it to the offset instead.dimension: {from_xy, to_xy}— a drafting dimension line: witness lines at each end, arrows facing inward, and the measurement written on the run. Accepts:offset(perpendicular clearance in px),:label(defaults to the measured distance) and:font_size.
Example
fig
|> Bland.annotate(text: "peak", at: {3.7, 0.92})
|> Bland.annotate(arrow: {{3.5, 0.85}, {3.7, 0.92}})
|> Bland.annotate(callout: "3 dB point", at: {1.0, 0.707})
|> Bland.annotate(dimension: {{1.0, 0.0}, {4.0, 0.0}}, label: "3.00 s")
@spec area(Bland.Figure.t(), [number() | Date.t()], [number() | Date.t()], keyword()) :: Bland.Figure.t()
Adds a filled area series.
Options
:label:hatch— fill pattern (default cycle):baseline— baseline y-value (default0):stroke— outline dash preset (default:solid):stroke_width
@spec axes( Bland.Figure.t(), keyword() ) :: Bland.Figure.t()
Sets axis options on a figure.
Options
:xlabel,:ylabel— axis titles:xlim,:ylim— explicit{min, max}or:auto:xscale,:yscale—:linear(default) or:log:grid—:none,:major(default),:both
@spec bar(Bland.Figure.t(), [String.t()], [number()], keyword()) :: Bland.Figure.t()
Adds a categorical bar series. categories and values must line up.
Options
:label— legend text:hatch— pattern preset (seeBland.Patterns). Defaults cycle.:group— any term used to bucket bars for side-by-side grouping. Multiple bar series with distinct:groupvalues render as a grouped bar chart; series sharing a group stack in the same slot.:stack—truestacks this series on top of the stacked bar series added before it, per category, instead of taking a side-by-side slot of its own:stroke_width
Uncertainty
:yerr and :xerr accept any Bland.Uncertainty spec — a constant
half-width, a per-point list, {:bounds, los, his}, {:relative, frac}
or :poisson. :error_style picks :band or :bars, :error_hatch
overrides the band fill and :cap_width the I-bar cap.
Example
cats = ["A", "B", "C"]
Bland.bar(fig, cats, [3, 7, 2], label: "trial 1", hatch: :diagonal, group: 1)
|> Bland.bar(cats, [5, 4, 6], label: "trial 2", hatch: :crosshatch, group: 2)
@spec barh(Bland.Figure.t(), [String.t()], [number()], keyword()) :: Bland.Figure.t()
Adds a horizontal bar series — categories run down the y axis and values along x. Use it when category labels are long enough that vertical bars would force them to slant.
Takes every bar/4 option, including :stack and :yerr; error is
measured along x with the bars.
Bland.barh(fig, ["Ku-band downlink", "S-band TT&C"], [42.0, 7.5])
@spec basemap(Bland.Figure.t(), Bland.Basemaps.layer(), keyword()) :: Bland.Figure.t()
Adds a built-in geographic base layer to the figure.
See Bland.Basemaps for the available layers. Typical usage:
Bland.figure(size: :a4_landscape, projection: :mercator,
xlim: {-180, 180}, ylim: {-70, 75})
|> Bland.basemap(:earth_coastlines)
|> Bland.basemap(:earth_borders, stroke: :dashed)
|> Bland.basemap(:earth_tropics, stroke: :dotted)
# Lunar plate
Bland.figure(size: :square, projection: :equirect,
xlim: {-90, 90}, ylim: {-60, 60})
|> Bland.basemap(:moon_maria, hatch: :dots_sparse)Options
:resolution— for:earth_coastlinesand:earth_borders, selects which vendored Natural Earth dataset to load::low(1:110m, default),:high(1:50m), or:schematic(the hand-drawn outlines shipped with BLAND 0.1).:stroke— line-dash preset for open features / outlines (default:solid):stroke_width— override stroke weight:hatch— for closed features, fill with this pattern instead of leaving the interior transparent. Ignored on open features like:earth_tropics.:only— list of feature names to include (filters the layer's built-in feature set):except— list of feature names to exclude
Examples
# High-res countries, filled with a light hatch
Bland.basemap(fig, :earth_borders, resolution: :high,
stroke: :solid, stroke_width: 0.4)
# Only draw a few named countries
Bland.basemap(fig, :earth_borders,
only: ["United States of America", "Canada", "Mexico"])
# Fill lunar maria with a hatched pattern
Bland.basemap(fig, :moon_maria, hatch: :diagonal)
@spec bland_altman(Bland.Figure.t(), [number()], [number()], keyword()) :: Bland.Figure.t()
Adds a Bland-Altman agreement plot — the difference between two measurement methods against their mean, with the bias and limits of agreement drawn as reference rules.
The conventional way to ask "do these two instruments agree?", as opposed to a correlation plot, which only asks whether they move together.
Options
:label— legend text for the points:loa— limits of agreement in standard deviations (default1.96, the 95% interval):band—trueshades the region between the limits:marker— marker preset (default:circle_open)
Example
Bland.figure() |> Bland.bland_altman(method_a, method_b)
Renders a Bode plot — magnitude (dB, log-linear) on top, phase (degrees, log-linear) on the bottom — as a two-panel SVG.
omegas is a list of angular frequencies (or just frequencies — they
just become x-coordinates on a log axis). Pass either:
- Pre-computed
{mag_db, phase_deg}lists, OR - A transfer-function callback
fn ω -> {real, imag} endreturning the complex value ofH(jω)at each frequency.
Returns an SVG binary ready to write or embed.
Options
:cell_width,:cell_height— per-panel size:title— outer title:xlabel— frequency axis label (default"ω"):mag_label— magnitude y-axis label (default"|H| [dB]"):phase_label— phase y-axis label (default"∠H [°]"):theme— passed through to both panels
Examples
# From precomputed magnitude and phase
Bland.bode(omegas, mag_db, phase_deg)
# From a transfer-function callback: H(s) = 1 / (1 + s/10) evaluated
# at s = jω
Bland.bode(omegas, fn omega ->
{1 / (1 + omega * omega / 100), -omega / (10 + omega * omega / 10)}
end)
@spec boxplot(Bland.Figure.t(), [{String.t(), [number()]}], keyword()) :: Bland.Figure.t()
Adds a box-and-whisker summary. categories_and_samples pairs each
category label with a list of raw observations; BLAND computes the
quartiles, Tukey-fence whiskers, and outliers for you via
Bland.Stats.boxplot_stats/2.
Options
:label— legend text:hatch— IQR box fill (default cycles):box_width— width fraction of the category slot (default0.6):stroke_width
Example
Bland.boxplot(fig, [
{"control", control_samples},
{"treated", treated_samples}
], label: "distribution")
@spec colorbar( Bland.Figure.t(), keyword() ) :: Bland.Figure.t()
Attaches a colorbar (ramp legend) to the figure.
By default the colorbar describes the last heatmap added. You may
also pass an explicit series: index or a ramp: and range:
directly for standalone ramps.
Options
:position—:right(default),:left,:bottom, or a{px, py}tuple.:label— axis label for the ramp; defaults to the heatmap's:label.:ramp— override the ramp (otherwise inherited from the heatmap series).:range— override the{lo, hi}bounds shown on the ramp.:levels— number of tick marks on the ramp (default 5).
@spec contour(Bland.Figure.t(), [[number()]], keyword()) :: Bland.Figure.t()
Adds contour (iso-level) curves over a 2D scalar grid.
Options
:levels— list of scalar values at which to draw contours (default: 7 evenly-spaced levels across the data range):x_edges,:y_edges— cell boundaries (default0..cols,0..rows):origin—:bottom_left(default) or:top_left:stroke— dash preset (default:solid). Negative levels render dashed automatically to convey sign.:stroke_width,:label
Example
grid =
for j <- -20..20, do: (for i <- -20..20, do: :math.sin(i * 0.2) * :math.cos(j * 0.2))
Bland.contour(fig, grid,
x_edges: Enum.map(-20..21, &(&1 * 0.1)),
y_edges: Enum.map(-20..21, &(&1 * 0.1)),
levels: [-0.8, -0.4, 0, 0.4, 0.8])
@spec errorbar(Bland.Figure.t(), [number()], [number()], keyword()) :: Bland.Figure.t()
Adds an error-bar series — data points with X and/or Y uncertainty whiskers.
yerr/xerr accept either a list of symmetric half-widths or a list
of {lower, upper} tuples for asymmetric error.
Options
:yerr— symmetric or asymmetric y-error per point:xerr— same for x:marker— marker at each point (default:circle_filled; setnilto suppress):marker_size,:cap_width,:stroke_width,:label
Examples
Bland.errorbar(fig, xs, ys, yerr: sigmas, label: "±1σ")
Bland.errorbar(fig, xs, ys, yerr: Enum.zip(lower, upper))
Bland.errorbar(fig, xs, ys, yerr: yerr, xerr: xerr)
@spec figure(keyword()) :: Bland.Figure.t()
Creates a new figure.
Options
:size— paper preset atom or{width, height}tuple. Default:letter_landscape. SeeBland.Figurefor the full list.:theme— theme preset atom or theme map. SeeBland.Theme.:title,:subtitle— figure-level text:margins—{top, right, bottom, left}in px- All other
Bland.Figurestruct fields are also accepted.
Examples
Bland.figure(size: :a4, title: "Figure 3.2")
Bland.figure(size: {800, 600}, theme: :blueprint)
@spec fill_between(Bland.Figure.t(), [number()], [number()], [number()], keyword()) :: Bland.Figure.t()
Fills the region between two curves sharing an x axis — a tolerance envelope, a min/max sweep, the gap between a model and a measurement.
Options
:label— legend text:hatch— region fill (seeBland.Patterns). Defaults cycle.:edges—falseto fill without stroking the bounding curves (defaulttrue):stroke_width
Example
Bland.fill_between(fig, xs, lower, upper, label: "±3σ", hatch: :dots_sparse)
@spec graticule( Bland.Figure.t(), keyword() ) :: Bland.Figure.t()
Adds a latitude/longitude graticule as a series of dotted reference
lines. Only meaningful on figures with projection: :mercator or
:equirect.
Options
:lon_step(default30) — meridian spacing in degrees:lat_step(default30) — parallel spacing in degrees:lon_range(default{-180, 180}):lat_range(default{-80, 80}):stroke(default:dotted):labels(defaulttrue) — annotate each line with its lat/lon value:label_position—:lon_edge | :plot_edge | :none(default:lon_edge: meridians label at the equator, parallels label at the western lon bound)
Example
Bland.figure(size: :a4_landscape, projection: :mercator)
|> Bland.graticule(lon_step: 30, lat_step: 20)
|> Bland.line(coast_lons, coast_lats, stroke: :solid)
|> Bland.to_svg()
@spec grid( [Bland.Figure.t()], keyword() ) :: String.t()
Composes a list of figures into a single SVG with a grid layout.
This is how you build multi-panel figures — Bode plots, dashboards, before/after comparisons — while getting a single printable SVG at the end. Each panel renders independently: its own ticks, labels, legend, ornaments.
See Bland.Grid for the full option list. Common options:
:columns,:rows— grid shape:cell_width,:cell_height— pixel size of each cell:gap,:padding— spacing:title— outer title across all panels
Example
a = Bland.figure(title: "Before") |> Bland.line(xs, ys1)
b = Bland.figure(title: "After") |> Bland.line(xs, ys2)
svg = Bland.grid([a, b], columns: 2, title: "Comparison")
@spec grid_to_kino( [Bland.Figure.t()], keyword() ) :: any()
Like grid/2, but returns a Kino.Image for Livebook inline
display.
@spec heatmap(Bland.Figure.t(), [[number()]], keyword()) :: Bland.Figure.t()
Adds a heatmap series.
grid is a 2D list (rows x cols) of numeric values. Each cell is
quantized to one of N levels and filled with the corresponding
pattern from the ramp.
Options
:x_edges— list of lengthcols + 1giving column boundaries in data space. Defaults to0..cols.:y_edges— list of lengthrows + 1giving row boundaries. Defaults to0..rows.:ramp— list of pattern preset atoms, light → dark. Defaults toBland.Heatmap.default_ramp/0(7 levels).:range—{lo, hi}for quantization. Defaults to:auto(min/max of the data).:origin—:bottom_left(default, Cartesian) or:top_left(matrix-style; row 0 renders at the top).:label— label for the colorbar entry.
Examples
# 20 × 20 grid of a 2D Gaussian
grid =
for j <- -10..9, into: [] do
for i <- -10..9, into: [] do
:math.exp(-(i * i + j * j) / 40)
end
end
Bland.figure(size: :square, title: "2D Gaussian")
|> Bland.axes(xlabel: "x", ylabel: "y")
|> Bland.heatmap(grid,
x_edges: Enum.map(-10..10, &(&1 * 1.0)),
y_edges: Enum.map(-10..10, &(&1 * 1.0)),
label: "density")
|> Bland.colorbar()
|> Bland.to_svg()See Bland.Heatmap for the underlying ramp/quantize helpers.
@spec histogram(Bland.Figure.t(), [number()], keyword()) :: Bland.Figure.t()
Adds a histogram series.
observations is a raw list of numeric samples — BLAND bins them for
you. Unlike bar/4, histograms render on a numeric x-axis with bars
flush against each other at bin boundaries.
Options
:bins— bin-count strategy. IntegerNfor exactlyNequal-width bins, or one of:sturges(default),:sqrt,:scott,:freedman_diaconis.:bin_edges— explicit edge list; overrides:bins.:normalize— how values are normalized::count(default) — raw counts per bin:pmf— probability mass:count / total,Σ = 1:density— density:count / (total · width), ∫ = 1:cmf— cumulative mass: rendered as a staircase step line from0at the leftmost edge to1at the rightmost
:density— shorthand fornormalize: :density. Kept for backwards compatibility.:label:hatch— fill pattern preset (default cycles). Ignored for:cmf, which renders as a line.:stroke— for:cmfonly; dash preset for the step line (default:solid):stroke_width
Examples
# 20 equal-width bins, count on the y-axis
Bland.histogram(fig, samples, bins: 20, label: "trial 1")
# PMF — probability mass per bin
Bland.histogram(fig, samples, bins: 30, normalize: :pmf,
label: "Pr{X ∈ bin}")
# Density with Scott's rule
Bland.histogram(fig, samples, bins: :scott, normalize: :density)
# Empirical CDF — renders as a staircase line, not bars
Bland.histogram(fig, samples, bins: 50, normalize: :cmf,
label: "F(x)")
# Explicit edges — useful for apples-to-apples comparison across
# two datasets
Bland.histogram(fig, samples_a,
bin_edges: Enum.map(0..10, &(&1 * 1.0)), label: "A")See Bland.Histogram for the underlying binning helpers.
@spec hline(Bland.Figure.t(), number(), keyword()) :: Bland.Figure.t()
Adds a horizontal reference line at y-value y.
Options: :label, :stroke (default :dashed), :stroke_width.
@spec hspan(Bland.Figure.t(), number() | Date.t(), number() | Date.t(), keyword()) :: Bland.Figure.t()
Adds a horizontal shaded band spanning y ∈ [y1, y2]. Same options
as vspan/4. Useful for acceptance bands and tolerance windows.
@spec inset(Bland.Figure.t(), Bland.Figure.t(), keyword()) :: Bland.Figure.t()
Nests a second figure inside this one — a detail view of a crowded region, or a zoom on the feature the main plot is about.
The inset renders independently and keeps its own ticks, labels and
ornaments; set its :xlim / :ylim to the region you want magnified.
Options
:anchor—:best(default) scores the corners against the ink already drawn and takes the emptiest; or name one of:top_right,:top_left,:bottom_right,:bottom_left, or give a{fx, fy}tuple of fractions of the plot rectangle:size—{fw, fh}fractions of the plot rectangle (default{0.34, 0.34}):frame—falseto omit the border around the inset
Example
detail =
Bland.figure(size: {300, 220}, grid: :none)
|> Bland.line(xs, ys)
|> Bland.axes(xlim: {2.0, 2.5}, ylim: {0.9, 1.1})
Bland.figure() |> Bland.line(xs, ys) |> Bland.inset(detail)
@spec legend( Bland.Figure.t(), keyword() ) :: Bland.Figure.t()
Attaches (or replaces) a legend on the figure.
Options
:position—:top_right(default),:top_left,:bottom_right,:bottom_left, or a{px, py}tuple for manual placement.:title— optional bold heading above the entries.
@spec line(Bland.Figure.t(), [number() | Date.t()], [number() | Date.t()], keyword()) :: Bland.Figure.t()
Adds a line series.
Options
:label— legend text. When omitted, the series is unlabeled.:stroke— dash preset::solid,:dashed,:dotted,:dash_dot,:long_dash,:fine. Defaults cycle viaBland.Strokes.:stroke_width— px override:markers—trueto draw markers at each data point:marker— marker preset (seeBland.Markers). Defaults cycle.:marker_size— px override
Uncertainty
:yerr and :xerr accept any Bland.Uncertainty spec — a constant
half-width, a per-point list, {:bounds, los, his}, {:relative, frac}
or :poisson. :error_style picks :band or :bars, :error_hatch
overrides the band fill and :cap_width the I-bar cap.
Example
Bland.line(fig, xs, ys, label: "velocity", stroke: :dashed, markers: true)
# ±1 SD as a hatched band behind the curve (the default for lines)
Bland.line(fig, xs, ys, yerr: sd)
@spec north_arrow( Bland.Figure.t(), keyword() ) :: Bland.Figure.t()
Adds a north arrow.
Options
:position—:top_right(default),:top_left,:bottom_right,:bottom_left:size— arrow height in px (default28):label— letter beside the point (default"N")
@spec nyquist(Bland.Figure.t(), [number()], [number()], keyword()) :: Bland.Figure.t()
Adds a Nyquist plot — the imaginary against the real part of a frequency response, traced as a parametric curve.
Equal aspect is set on the figure, since a Nyquist plot is read by its distance from the critical point and stretching either axis destroys that. The critical point itself is marked.
Options
:label— legend text:critical—falseto omit the-1 + j0marker:markers—trueto mark each sampled frequency
Example
Bland.figure() |> Bland.nyquist(re, im, label: "open loop")
@spec polar_figure(keyword()) :: Bland.Figure.t()
Creates a polar figure.
Series data on the returned figure is interpreted as {θ, r} pairs
(θ in radians). The renderer projects every point through
(θ, r) → (r·cos θ, r·sin θ), clips to the disk of radius rmax,
and suppresses the default x/y axes.
Pair with polar_grid/2 to add the concentric / radial reference
grid:
Bland.polar_figure(rmax: 1.0, title: "Antenna gain")
|> Bland.polar_grid(r_ticks: [0.25, 0.5, 0.75, 1.0])
|> Bland.line(thetas, gains)Options
:rmax— radius of the plotting disk in data units (default1.0):size— canvas size; defaults to:squarefor equal aspect- Any other figure option (
:title,:theme, etc.) is forwarded.
@spec polar_grid( Bland.Figure.t(), keyword() ) :: Bland.Figure.t()
Adds a polar reference grid to a figure built via polar_figure/1.
Produces concentric circles at the requested radii and radial lines at the requested angles, plus perimeter labels for the angles.
Options
:r_ticks— list of radii (default: four evenly-spaced steps ending atrmax):theta_step— angle between radial lines, in degrees (default30):stroke— dash preset for the grid lines (default:dotted):stroke_width— grid line weight (default0.4):labels—true(default) to annotate each angular direction at the perimeter:r_labels—trueto annotate each radius on the 0° ray (defaultfalse):samples— points per circle (default120)
@spec polygon(Bland.Figure.t(), [number()], [number()], keyword()) :: Bland.Figure.t()
Adds a closed polygon series. xs and ys give the vertices; the
renderer connects the last point back to the first.
Unlike area/4, which fills down to a baseline, polygon/4 fills
whatever arbitrary shape the vertices describe — suitable for
country outlines, mare boundaries, inset markers.
Options
:label:hatch— fill pattern;nil(default) leaves the polygon stroke-only (transparent interior):stroke— outline dash preset (default:solid):stroke_width
@spec qq_plot(Bland.Figure.t(), [number()], keyword()) :: Bland.Figure.t()
Adds a Q-Q (quantile-quantile) plot — sample quantiles vs theoretical
quantiles of a named distribution, with a y = x reference line.
Options
:distribution—:normal(default). Other distributions can be added later.:reference—true(default) to draw the y=x reference line:marker— default:circle_open:marker_size,:label
Example
Bland.qq_plot(fig, samples, label: "residuals")
@spec quiver( Bland.Figure.t(), [number()], [number()], [number()], [number()], keyword() ) :: Bland.Figure.t()
Adds a vector-field (quiver) series. Each (xs[i], ys[i]) gets an
arrow with components (us[i], vs[i]).
Options
:scale— multiply each vector before drawing (default1.0):head_size— arrow-head pixel length (default6):stroke,:stroke_width,:label
@spec scale_bar( Bland.Figure.t(), keyword() ) :: Bland.Figure.t()
Adds a map scale bar — an alternating black/white ruler with a distance label, sized from the figure's own x scale.
Options
:length_km— bar length in kilometres. When omitted, a nice-rounded length near a fifth of the plot width is chosen.:position—:bottom_left(default),:bottom_right,:top_left,:top_right:segments— alternating blocks (default4):label— override the distance text
Distances assume degrees of longitude at the plot's centre latitude, so the bar is accurate where it sits rather than across the whole map.
@spec scatter( Bland.Figure.t(), [number() | Date.t()], [number() | Date.t()], keyword() ) :: Bland.Figure.t()
Adds a scatter series.
Accepts :label, :marker, :marker_size, :stroke_width, and
:sizes — a per-point marker radius, for encoding a third variable in
marker area.
Uncertainty
:yerr and :xerr accept any Bland.Uncertainty spec — a constant
half-width, a per-point list, {:bounds, los, his}, {:relative, frac}
or :poisson. :error_style picks :band or :bars, :error_hatch
overrides the band fill and :cap_width the I-bar cap.
Scatter defaults to capped I-bars rather than a band.
@spec smith_figure(keyword()) :: Bland.Figure.t()
Creates a Smith chart figure — a unit-disk canvas for plotting
reflection coefficients Γ in RF / microwave work.
The returned figure has no standard axes, a circular clip to |Γ| ≤ 1, and a square canvas. Pair with smith_grid/2 for the
classical grid of constant-resistance circles and constant-reactance
arcs.
Bland.smith_figure(title: "S₁₁")
|> Bland.smith_grid()
|> Bland.line(gamma_real, gamma_imag, label: "sweep")Convert impedance values to Γ via Bland.Smith.gamma_from_z/1.
Options
:size— canvas size; defaults to:square- All other figure options are forwarded.
@spec smith_grid( Bland.Figure.t(), keyword() ) :: Bland.Figure.t()
Adds the classical Smith chart grid to a figure: constant-resistance
circles (r = 0.2, 0.5, 1, 2, 5 by default), constant-reactance arcs
(at ±0.2, ±0.5, ±1, ±2, ±5), plus the unit circle boundary and the
real axis.
Options
:r_values— list of normalized resistances for the R-circles (defaultBland.Smith.default_r_values/0):x_values— list of normalized reactance magnitudes; each also draws its negative counterpart (defaultBland.Smith.default_x_values/0):stroke— grid dash preset (default:dotted):stroke_width— (default0.4):boundary_stroke_width— weight of the unit circle and real axis (default0.8):labels—trueto annotate each R circle and X arc (defaulttrue):samples— points per circle (default120)
@spec stem(Bland.Figure.t(), [number()], [number()], keyword()) :: Bland.Figure.t()
Adds a stem-plot series — the discrete-signal staple from DSP.
Each point renders as a vertical line from :baseline (default 0)
up to (x, y), with a marker at the tip.
Options
:baseline,:marker(default:circle_filled),:marker_size,:stroke,:stroke_width,:label
@spec step( Bland.Figure.t(), [number() | Date.t()], [number() | Date.t()], atom(), keyword() ) :: Bland.Figure.t()
Adds a step series — a staircase rather than a straight interpolation, for a quantity that holds between samples: a sampled control signal, a tariff schedule, a survival curve.
kind names where the riser sits:
:post(default) — hold each value until the next sample:pre— step to the new value at the previous sample's x:mid— riser halfway between samples
Takes every line/4 option, including :yerr; an error band follows
the staircase rather than cutting the corners.
Bland.step(fig, ts, levels, :post, label: "setpoint")
@spec title_block( Bland.Figure.t(), keyword() ) :: Bland.Figure.t()
Attaches (or replaces) a drafting title block. See Bland.TitleBlock for
the full field list.
@spec to_kino(Bland.Figure.t()) :: any()
Shortcut for rendering + Livebook inline display.
In Livebook, Kino.Image.new/2 expects a binary and a MIME type. If
:kino is not installed, call to_svg/1 and wrap the result yourself.
@spec to_svg(Bland.Figure.t()) :: String.t()
Renders a figure to an SVG string.
@spec vline(Bland.Figure.t(), number() | Date.t(), keyword()) :: Bland.Figure.t()
Adds a vertical reference line at x-value x.
Options: :label, :stroke (default :dashed), :stroke_width.
@spec vspan(Bland.Figure.t(), number() | Date.t(), number() | Date.t(), keyword()) :: Bland.Figure.t()
Adds a vertical shaded band spanning x ∈ [x1, x2].
Drawn behind every data series so it never obscures curves. Both
bounds accept numeric values OR Date.t() (auto-converted to epoch
days when the figure's xscale is :date).
Options
:hatch— fill pattern (default:dots_sparse):alpha— fill opacity 0–1 (default0.35):stroke— optional border dash preset;nil(default) gives a borderless band:stroke_width,:label
Example — event window on a price chart
Bland.figure() |> Bland.axes(xscale: :date)
|> Bland.vspan(~D[2024-03-10], ~D[2024-03-25],
hatch: :diagonal, alpha: 0.25, label: "embargo")
|> Bland.line(dates, prices)
@spec write!(Bland.Figure.t(), Path.t()) :: :ok
Renders a figure and writes it to path.