Skip to main content
The Figure struct enables creation of sophisticated multi-panel layouts (also known as subplots or faceted plots). It supports grid-based arrangements, merged cells, shared axes, automatic panel labeling, and unified legends.

Constructor

new

pub fn new(rows: usize, cols: usize) -> Self
Create a new figure with the specified grid dimensions.
rows
usize
required
Number of rows in the grid.
cols
usize
required
Number of columns in the grid.
Example:
use kuva::render::figure::Figure;

// Create a 2x2 grid
let figure = Figure::new(2, 2);

Basic Configuration Methods

with_title

pub fn with_title<S: Into<String>>(mut self, title: S) -> Self
Set the overall figure title (appears at the top).
title
impl Into<String>
required
The figure title text.

with_title_size

pub fn with_title_size(mut self, size: u32) -> Self
Set the font size for the figure title.
size
u32
required
Font size in pixels. Default is 20.
Example:
let figure = Figure::new(2, 2)
    .with_title("Multi-Panel Analysis")
    .with_title_size(24);

Plot and Layout Configuration

with_plots

pub fn with_plots(mut self, plots: Vec<Vec<Plot>>) -> Self
Provide the plots for each panel. The outer vector corresponds to panels (in row-major order), and the inner vector contains plots to overlay in that panel.
plots
Vec<Vec<Plot>>
required
A vector of plot vectors, one per panel.

with_layouts

pub fn with_layouts(mut self, layouts: Vec<Layout>) -> Self
Provide custom layouts for each panel. If not provided, layouts are auto-generated from plots.
layouts
Vec<Layout>
required
A vector of Layout instances, one per panel.
Example:
let scatter1 = ScatterPlot::new().with_data(vec![(1.0, 2.0)]);
let scatter2 = ScatterPlot::new().with_data(vec![(2.0, 3.0)]);
let line1 = LinePlot::new().with_data(vec![(0.0, 1.0), (1.0, 2.0)]);
let line2 = LinePlot::new().with_data(vec![(0.0, 0.5), (1.0, 1.5)]);

let plots = vec![
    vec![scatter1.into()],  // Panel 0
    vec![scatter2.into()],  // Panel 1
    vec![line1.into()],     // Panel 2
    vec![line2.into()],     // Panel 3
];

let figure = Figure::new(2, 2)
    .with_plots(plots);

Panel Structure and Merging

with_structure

pub fn with_structure(mut self, structure: Vec<Vec<usize>>) -> Self
Define a custom panel structure to merge cells. Each inner vector contains cell indices (in row-major order) that should be merged into a single panel.
structure
Vec<Vec<usize>>
required
A vector where each element is a vector of cell indices to merge. Merged cells must form a rectangle.
Example:
// 2x2 grid where top row is merged:
// [  Panel 0 (merged)  ]
// [ Panel 1 ][ Panel 2 ]
let structure = vec![
    vec![0, 1],  // Merge cells 0 and 1 (top row)
    vec![2],     // Cell 2 (bottom-left)
    vec![3],     // Cell 3 (bottom-right)
];

let figure = Figure::new(2, 2)
    .with_structure(structure);
Merged cells must form filled rectangles. The validation logic will panic if cells form non-rectangular shapes or have gaps.

Panel Labeling

with_labels

pub fn with_labels(mut self) -> Self
Add bold uppercase panel labels (A, B, C, …) to each panel.

with_labels_numeric

pub fn with_labels_numeric(mut self) -> Self
Add numeric panel labels (1, 2, 3, …).

with_labels_lowercase

pub fn with_labels_lowercase(mut self) -> Self
Add lowercase panel labels (a, b, c, …).

with_labels_custom

pub fn with_labels_custom(mut self, labels: Vec<&str>, config: LabelConfig) -> Self
Add custom panel labels with specific styling.
labels
Vec<&str>
required
Custom label text for each panel.
config
LabelConfig
required
Label configuration (size, boldness).
Example:
use kuva::render::figure::{Figure, LabelConfig, LabelStyle};

// Uppercase labels (A, B, C, ...)
let figure = Figure::new(2, 2)
    .with_labels();

// Custom labels
let config = LabelConfig {
    style: LabelStyle::Custom(vec![]),
    size: 14,
    bold: true,
};
let figure = Figure::new(2, 2)
    .with_labels_custom(vec!["Fig 1", "Fig 2", "Fig 3", "Fig 4"], config);

Shared Axes

with_shared_x_all

pub fn with_shared_x_all(mut self) -> Self
Share the x-axis across all columns. The range is unified, and only the bottom row shows tick labels.

with_shared_y_all

pub fn with_shared_y_all(mut self) -> Self
Share the y-axis across all rows. The range is unified, and only the leftmost column shows tick labels.

with_shared_x

pub fn with_shared_x(mut self, col: usize) -> Self
Share the x-axis within a single column.
col
usize
required
The column index (0-based).

with_shared_y

pub fn with_shared_y(mut self, row: usize) -> Self
Share the y-axis within a single row.
row
usize
required
The row index (0-based).

with_shared_x_slice

pub fn with_shared_x_slice(mut self, col: usize, row_start: usize, row_end: usize) -> Self
Share the x-axis within a column for a slice of rows.
col
usize
required
The column index.
row_start
usize
required
Starting row index (inclusive).
row_end
usize
required
Ending row index (inclusive).

with_shared_y_slice

pub fn with_shared_y_slice(mut self, row: usize, col_start: usize, col_end: usize) -> Self
Share the y-axis within a row for a slice of columns.
row
usize
required
The row index.
col_start
usize
required
Starting column index (inclusive).
col_end
usize
required
Ending column index (inclusive).
Example:
let figure = Figure::new(3, 2)
    .with_shared_x_all()  // All panels share x-axis
    .with_shared_y(0);    // Top row shares y-axis

Dimension and Spacing

with_cell_size

pub fn with_cell_size(mut self, w: f64, h: f64) -> Self
Set the width and height of individual cells in pixels.
w
f64
required
Cell width. Default is 500.0.
h
f64
required
Cell height. Default is 380.0.

with_figure_size

pub fn with_figure_size(mut self, w: f64, h: f64) -> Self
Set the total figure size in pixels. Cells auto-compute to fit. Takes precedence over with_cell_size.
w
f64
required
Total figure width.
h
f64
required
Total figure height.

with_spacing

pub fn with_spacing(mut self, px: f64) -> Self
Set the spacing between panels in pixels.
px
f64
required
Spacing in pixels. Default is 15.0.

with_padding

pub fn with_padding(mut self, px: f64) -> Self
Set the padding around the entire figure in pixels.
px
f64
required
Padding in pixels. Default is 10.0.
Example:
let figure = Figure::new(2, 3)
    .with_figure_size(1200.0, 800.0)
    .with_spacing(20.0)
    .with_padding(15.0);

Shared Legend

with_shared_legend

pub fn with_shared_legend(mut self) -> Self
Add a shared legend to the right of the figure, auto-collected from all plots.

with_shared_legend_bottom

pub fn with_shared_legend_bottom(mut self) -> Self
Add a shared legend below the figure.

with_shared_legend_position

pub fn with_shared_legend_position(mut self, pos: FigureLegendPosition) -> Self
Override the shared legend position.
pos
FigureLegendPosition
required
Either FigureLegendPosition::Right or FigureLegendPosition::Bottom.

with_shared_legend_entries

pub fn with_shared_legend_entries(mut self, entries: Vec<LegendEntry>) -> Self
Provide manual legend entries instead of auto-collecting from plots.
entries
Vec<LegendEntry>
required
Custom legend entries.

with_keep_panel_legends

pub fn with_keep_panel_legends(mut self) -> Self
Keep per-panel legends visible alongside the shared legend. By default, panel legends are hidden when a shared legend is active. Example:
use kuva::render::figure::{Figure, FigureLegendPosition};

let figure = Figure::new(2, 2)
    .with_plots(plots)
    .with_shared_legend()  // Auto-collect legend on right
    .with_keep_panel_legends();  // Also show per-panel legends

Rendering

render

pub fn render(self) -> Scene
Consume the figure and render it to a Scene (internal representation). Use backend::svg::SvgBackend or other backends to convert the scene to the final output format. Example:
use kuva::render::figure::Figure;
use kuva::backend::svg::SvgBackend;

let figure = Figure::new(2, 2)
    .with_plots(plots)
    .with_labels()
    .with_shared_x_all();

let scene = figure.render();
let svg = SvgBackend.render_scene(&scene);
The render() method consumes the Figure. If you need to generate multiple outputs, clone your plots beforehand.

Complete Example

use kuva::prelude::*;
use kuva::render::figure::{Figure, FigureLegendPosition};
use kuva::render::layout::Layout;
use kuva::backend::svg::SvgBackend;

// Create plots for each panel
let scatter1 = ScatterPlot::new()
    .with_data(vec![(1.0, 2.0), (2.0, 3.0), (3.0, 2.5)])
    .with_color("steelblue")
    .with_legend_label("Series A");

let scatter2 = ScatterPlot::new()
    .with_data(vec![(1.0, 1.5), (2.0, 2.5), (3.0, 3.5)])
    .with_color("orange")
    .with_legend_label("Series B");

let line1 = LinePlot::new()
    .with_data(vec![(0.0, 1.0), (1.0, 2.0), (2.0, 3.0), (3.0, 4.0)])
    .with_color("red")
    .with_legend_label("Trend A");

let line2 = LinePlot::new()
    .with_data(vec![(0.0, 0.5), (1.0, 1.5), (2.0, 2.5), (3.0, 3.5)])
    .with_color("green")
    .with_legend_label("Trend B");

// Organize plots by panel
let plots = vec![
    vec![scatter1.into()],  // Top-left
    vec![scatter2.into()],  // Top-right
    vec![line1.into()],     // Bottom-left
    vec![line2.into()],     // Bottom-right
];

// Optional: custom layouts for each panel
let layouts = vec![
    Layout::auto_from_plots(&plots[0])
        .with_title("Scatter A")
        .with_x_label("Time"),
    Layout::auto_from_plots(&plots[1])
        .with_title("Scatter B")
        .with_x_label("Time"),
    Layout::auto_from_plots(&plots[2])
        .with_title("Line A")
        .with_y_label("Value"),
    Layout::auto_from_plots(&plots[3])
        .with_title("Line B")
        .with_y_label("Value"),
];

// Build the figure
let figure = Figure::new(2, 2)
    .with_title("Multi-Panel Analysis")
    .with_plots(plots)
    .with_layouts(layouts)
    .with_labels()  // Add A, B, C, D labels
    .with_shared_x_all()  // Share x-axis across columns
    .with_shared_legend()  // Unified legend on right
    .with_figure_size(1000.0, 800.0)
    .with_spacing(20.0);

// Render to SVG
let scene = figure.render();
let svg = SvgBackend.render_scene(&scene);
std::fs::write("figure.svg", svg).unwrap();

Advanced: Custom Panel Structure

// 3x3 grid with merged cells:
// [ Panel 0 (merged 2x2) ][ Panel 1 ]
// [                      ][ Panel 2 ]
// [ Panel 3 ][ Panel 4  ][ Panel 5 ]

let structure = vec![
    vec![0, 1, 3, 4],  // Merge cells 0,1,3,4 (top-left 2x2)
    vec![2],           // Cell 2
    vec![5],           // Cell 5
    vec![6],           // Cell 6
    vec![7],           // Cell 7
    vec![8],           // Cell 8
];

let figure = Figure::new(3, 3)
    .with_structure(structure)
    .with_plots(plots);

FigureLegendPosition

pub enum FigureLegendPosition {
    Right,
    Bottom,
}
Defines where the shared legend appears:
  • Right: Legend appears on the right side of the figure
  • Bottom: Legend appears below the figure

LabelStyle

pub enum LabelStyle {
    Uppercase,
    Lowercase,
    Numeric,
    Custom(Vec<String>),
}
Defines the style of panel labels:
  • Uppercase: A, B, C, D, …
  • Lowercase: a, b, c, d, …
  • Numeric: 1, 2, 3, 4, …
  • Custom(Vec<String>): User-provided labels

LabelConfig

pub struct LabelConfig {
    pub style: LabelStyle,
    pub size: u32,
    pub bold: bool,
}
Configuration for panel labels:
style
LabelStyle
The labeling style (uppercase, lowercase, numeric, or custom).
size
u32
Font size in pixels. Default is 16.
bold
bool
Whether labels are bold. Default is true.

Build docs developers (and LLMs) love