3D Surface Plot

Renders a 2D grid of Z values as a depth-sorted quadrilateral mesh with orthographic projection. Each grid cell becomes a filled quad, optionally colored by its average Z value through a colormap. Uses the same open-box wireframe, back-pane fills, and rotated tick labels as the 3D scatter plot.

Import path: kuva::plot::surface3d::Surface3DPlot


Basic usage

Pass a 2D grid of Z values (z_data[row][col]):

#![allow(unused)]
fn main() {
use kuva::plot::surface3d::Surface3DPlot;
use kuva::plot::heatmap::ColorMap;
use kuva::backend::svg::SvgBackend;
use kuva::render::render::render_multiple;
use kuva::render::layout::Layout;
use kuva::render::plots::Plot;

let z_data: Vec<Vec<f64>> = (0..20).map(|i| {
    (0..20).map(|j| {
        let x = (i as f64 - 10.0) / 5.0;
        let y = (j as f64 - 10.0) / 5.0;
        x * x + y * y
    }).collect()
}).collect();

let surface = Surface3DPlot::new(z_data)
    .with_z_colormap(ColorMap::Viridis)
    .with_x_label("X")
    .with_y_label("Y")
    .with_z_label("Z");

let plots = vec![Plot::Surface3D(surface)];
let layout = Layout::auto_from_plots(&plots).with_title("Paraboloid");

let scene = render_multiple(plots, layout);
let svg = SvgBackend.render_scene(&scene);
std::fs::write("surface3d.svg", svg).unwrap();
}

When with_z_colormap() is set, a colorbar is rendered automatically. with_z_label() also labels the colorbar.

Paraboloid surface

From a function

Generate a surface from a math function over a coordinate range:

#![allow(unused)]
fn main() {
use kuva::plot::surface3d::Surface3DPlot;
use kuva::plot::heatmap::ColorMap;
let surface = Surface3DPlot::new(vec![])
    .with_data_fn(
        |x, y| (x * x + y * y).sqrt().sin(),
        -3.0..=3.0, -3.0..=3.0, 50, 50,
    )
    .with_z_colormap(ColorMap::Viridis);
}

The last two arguments are grid resolution (x_res, y_res). Higher values produce smoother surfaces at the cost of more SVG paths.

Wave surface

Explicit coordinates

By default axes are labeled 0..nrows / 0..ncols. Supply real coordinates with with_x_coords and with_y_coords:

#![allow(unused)]
fn main() {
use kuva::plot::surface3d::Surface3DPlot;
use kuva::plot::heatmap::ColorMap;
let xs: Vec<f64> = (-5..=5).map(|i| i as f64 * 0.5).collect();
let ys: Vec<f64> = (-5..=5).map(|i| i as f64 * 0.5).collect();
let z_data: Vec<Vec<f64>> = ys.iter()
    .map(|&y| xs.iter().map(|&x| (x * x + y * y).sqrt().sin()).collect())
    .collect();

let surface = Surface3DPlot::new(z_data)
    .with_x_coords(xs)
    .with_y_coords(ys)
    .with_z_colormap(ColorMap::Viridis);
}

Wireframe and transparency

The wireframe is on by default. Disable it with with_no_wireframe(), or combine it with transparency:

#![allow(unused)]
fn main() {
use kuva::plot::surface3d::Surface3DPlot;
use kuva::plot::heatmap::ColorMap;
// Semi-transparent with fine wireframe
let surface = Surface3DPlot::new(vec![])
    .with_data_fn(|x, y| x * y, -2.0..=2.0, -2.0..=2.0, 20, 20)
    .with_z_colormap(ColorMap::Viridis)
    .with_alpha(0.8)
    .with_wireframe_color("#222222")
    .with_wireframe_width(0.3);

// No wireframe — clean filled surface
let surface2 = Surface3DPlot::new(vec![])
    .with_data_fn(|x, y| x * y, -2.0..=2.0, -2.0..=2.0, 20, 20)
    .with_z_colormap(ColorMap::Inferno)
    .with_no_wireframe();
}

Builder reference

MethodDefaultDescription
.with_z_data(grid)Set Z value grid directly
.with_x_coords(vec)0..ncolsExplicit X coordinate per column
.with_y_coords(vec)0..nrowsExplicit Y coordinate per row
.with_data_fn(f, xr, yr, xn, yn)Generate grid from f(x, y) -> z
.with_color(css)"steelblue"Uniform surface color (when no colormap)
.with_z_colormap(map)Color faces by average Z; renders a colorbar automatically
.with_no_wireframe()wireframe onHide wireframe edges
.with_wireframe_color(css)"#333333"Wireframe edge color
.with_wireframe_width(px)0.5Wireframe stroke width
.with_alpha(f)1.0Surface opacity (0.0–1.0)
.with_azimuth(deg)-60.0Horizontal viewing angle
.with_elevation(deg)30.0Vertical viewing angle
.with_view(View3D)Set azimuth and elevation together
.with_x_label(s)X-axis label
.with_y_label(s)Y-axis label
.with_z_label(s)Z-axis label (also labels the colorbar)
.with_no_grid()grid onHide grid lines on back walls
.with_no_box()box onHide the wireframe bounding box
.with_grid_lines(n)5Number of grid/tick divisions per axis
.with_z_axis_right(bool)autoForce Z axis to right (true) or left (false)
.with_z_axis_auto()Reset to automatic placement (default)
.with_legend(s)Legend entry label

CLI

The CLI accepts long format (x, y, z columns) or matrix format (--matrix, one row of Z values per line).

# Long format with colormap
kuva surface3d data.tsv --x x --y y --z z --z-color viridis \
    --title "3D Surface" --x-label "X" --y-label "Y" --z-label "Z"

# Upsample a coarse grid to 50×50 with bilinear interpolation
kuva surface3d data.tsv --x x --y y --z z \
    --z-color inferno --resolution 50

# Matrix format (no header, each row = one row of Z values)
kuva surface3d matrix.tsv --matrix --z-color viridis --no-header

# Semi-transparent surface, no wireframe
kuva surface3d data.tsv --x x --y y --z z \
    --z-color viridis --alpha 0.8 --no-wireframe

# Custom view
kuva surface3d data.tsv --x x --y y --z z \
    --z-color viridis --azimuth 45 --elevation 45

CLI flags

FlagDefaultDescription
--x <COL>0X value column (long format)
--y <COL>1Y value column (long format)
--z <COL>2Z value column (long format)
--matrixoffRead input as a matrix of Z values
--z-color <MAP>Colormap: viridis, inferno, grayscale
--color <CSS>steelblueUniform surface color (when no colormap)
--alpha <F>1.0Surface opacity (0.0–1.0)
--resolution <N>Upsample grid to N×N via bilinear interpolation
--no-wireframewireframe onHide wireframe edges
--azimuth <DEG>-60Horizontal viewing angle
--elevation <DEG>30Vertical viewing angle
--x-label <S>X-axis label
--y-label <S>Y-axis label
--z-label <S>Z-axis label
--no-gridgrid onHide back-wall grid lines
--no-boxbox onHide wireframe bounding box
--grid-lines <N>5Grid/tick divisions per axis
--z-axis-leftautoForce Z axis to the left side