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.
Number of rows in the grid.
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.
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.
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.
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.
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.
Custom label text for each panel.
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.
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.
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.
Starting row index (inclusive).
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.
Starting column index (inclusive).
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.
Cell width. Default is 500.0.
Cell height. Default is 380.0.
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.
with_spacing
pub fn with_spacing(mut self, px: f64) -> Self
Set the spacing between panels in pixels.
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.
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.
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);
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:
The labeling style (uppercase, lowercase, numeric, or custom).
Font size in pixels. Default is 16.
Whether labels are bold. Default is true.