Skip to main content
Annotations help highlight important features in your plots, such as thresholds, regions of interest, or specific data points. Kuva provides three annotation types:
  • Text Annotations - Labels with optional arrows pointing to data
  • Reference Lines - Horizontal or vertical lines marking specific values
  • Shaded Regions - Colored rectangles highlighting ranges

Text Annotations

Add text labels to your plots with optional arrows pointing to specific coordinates.

Basic Text Annotation

use kuva::prelude::*;

let data = vec![(1.0, 2.0), (2.0, 4.0), (3.0, 3.0), (4.0, 7.0)];
let scatter = ScatterPlot::new()
    .with_data(data)
    .with_color("steelblue");

let plots = vec![scatter.into()];

let layout = Layout::auto_from_plots(&plots)
    .with_title("Text Annotation")
    .with_annotation(
        TextAnnotation::new("Important point", 3.5, 6.0)
    );

let scene = render_multiple(plots, layout);

Text Annotation with Arrow

Point to specific data with an arrow:
use kuva::prelude::*;

let layout = Layout::auto_from_plots(&plots)
    .with_annotation(
        TextAnnotation::new("Outlier!", 5.0, 7.5)
            .with_arrow(6.0, 9.0)  // Arrow points to (6.0, 9.0)
            .with_color("red")
    );
See tests/annotations_svg.rs:9 for the complete example.

Customizing Text Annotations

use kuva::prelude::*;

let annotation = TextAnnotation::new("Peak", 5.0, 8.5)
    .with_arrow(6.0, 10.0)       // Arrow pointing to (6.0, 10.0)
    .with_color("darkred")       // Text and arrow color
    .with_font_size(14)          // Font size (default: 12)
    .with_arrow_padding(8.0);    // Padding from arrow tip to target (default: 6.0)

let layout = Layout::auto_from_plots(&plots)
    .with_annotation(annotation);

Multiple Annotations

use kuva::prelude::*;

let layout = Layout::auto_from_plots(&plots)
    .with_annotation(
        TextAnnotation::new("Start", 0.5, 1.0)
            .with_color("green")
    )
    .with_annotation(
        TextAnnotation::new("Peak", 5.0, 8.5)
            .with_arrow(6.0, 10.0)
            .with_color("red")
    )
    .with_annotation(
        TextAnnotation::new("End", 9.5, 2.0)
            .with_color("blue")
    );

Reference Lines

Add horizontal or vertical lines to mark thresholds, baselines, or other important values.

Horizontal Reference Line

use kuva::prelude::*;

let layout = Layout::auto_from_plots(&plots)
    .with_reference_line(
        ReferenceLine::horizontal(5.0)
            .with_color("red")
            .with_label("y = 5")
    );

Vertical Reference Line

use kuva::prelude::*;

let layout = Layout::auto_from_plots(&plots)
    .with_reference_line(
        ReferenceLine::vertical(3.0)
            .with_color("green")
            .with_label("x = 3")
    );
See tests/annotations_svg.rs:46 for the reference lines example.

Customizing Reference Lines

use kuva::prelude::*;

let line = ReferenceLine::horizontal(5.0)
    .with_color("red")              // Line color (default: "red")
    .with_label("threshold")        // Optional label
    .with_stroke_width(2.0)         // Line width (default: 1.0)
    .with_dasharray("8 4");         // SVG dash pattern (default: "6 4")

let layout = Layout::auto_from_plots(&plots)
    .with_reference_line(line);

Multiple Reference Lines

use kuva::prelude::*;

let layout = Layout::auto_from_plots(&plots)
    .with_reference_line(
        ReferenceLine::horizontal(5.0)
            .with_color("red")
            .with_label("y = 5")
    )
    .with_reference_line(
        ReferenceLine::vertical(3.0)
            .with_color("green")
            .with_label("x = 3")
    );

Solid Reference Lines

use kuva::prelude::*;

let line = ReferenceLine::horizontal(0.0)
    .with_color("black")
    .with_dasharray("");  // Empty string = solid line

let layout = Layout::auto_from_plots(&plots)
    .with_reference_line(line);

Shaded Regions

Highlight ranges or regions of interest with semi-transparent colored rectangles.

Horizontal Shaded Region

Shade between two Y values (spans the full X range):
use kuva::prelude::*;

let layout = Layout::auto_from_plots(&plots)
    .with_shaded_region(
        ShadedRegion::horizontal(3.0, 5.0)
            .with_color("orange")
            .with_opacity(0.2)
    );

Vertical Shaded Region

Shade between two X values (spans the full Y range):
use kuva::prelude::*;

let layout = Layout::auto_from_plots(&plots)
    .with_shaded_region(
        ShadedRegion::vertical(2.0, 4.0)
            .with_color("blue")
            .with_opacity(0.1)
    );
See tests/annotations_svg.rs:88 for the shaded regions example.

Customizing Shaded Regions

use kuva::prelude::*;

let region = ShadedRegion::horizontal(0.0, 1.0)
    .with_color("steelblue")   // Fill color (default: "blue")
    .with_opacity(0.15);       // Opacity 0.0-1.0 (default: 0.15)

let layout = Layout::auto_from_plots(&plots)
    .with_shaded_region(region);

Multiple Shaded Regions

use kuva::prelude::*;

let layout = Layout::auto_from_plots(&plots)
    .with_shaded_region(
        ShadedRegion::horizontal(3.0, 5.0)
            .with_color("orange")
            .with_opacity(0.2)
    )
    .with_shaded_region(
        ShadedRegion::vertical(2.0, 4.0)
            .with_color("blue")
            .with_opacity(0.1)
    );

Combining Annotations

All three annotation types can be combined on the same plot:
use kuva::prelude::*;

let xs: Vec<f64> = (0..=60).map(|i| i as f64 * 0.1).collect();
let line = LinePlot::new()
    .with_data(xs.iter().map(|&x| (x, x.sin())))
    .with_color("steelblue")
    .with_stroke_width(2.0);

let plots = vec![line.into()];

let layout = Layout::auto_from_plots(&plots)
    .with_title("Annotations")
    .with_x_label("X")
    .with_y_label("Y")
    // Shade the region where y is positive
    .with_shaded_region(
        ShadedRegion::horizontal(0.0, 1.1)
            .with_color("steelblue")
            .with_opacity(0.08)
    )
    // Horizontal reference line at y = 0
    .with_reference_line(
        ReferenceLine::horizontal(0.0)
            .with_color("black")
            .with_label("y = 0")
    )
    // Vertical reference line at the first peak
    .with_reference_line(
        ReferenceLine::vertical(1.57)
            .with_color("crimson")
            .with_label("π/2")
    )
    // Text annotation pointing at the peak
    .with_annotation(
        TextAnnotation::new("peak", 1.0, 1.15)
            .with_arrow(1.57, 1.0)
            .with_color("crimson")
    );

let scene = render_multiple(plots, layout);
let svg = SvgBackend.render_scene(&scene);
See examples/layout.rs:107 for the complete combined annotations example.

Common Use Cases

Highlight statistical significance thresholds (e.g., p-value cutoffs):
use kuva::prelude::*;

let layout = Layout::auto_from_plots(&plots)
    .with_reference_line(
        ReferenceLine::horizontal(0.05)
            .with_color("red")
            .with_label("p < 0.05")
    )
    .with_shaded_region(
        ShadedRegion::horizontal(0.0, 0.05)
            .with_color("red")
            .with_opacity(0.1)
    );

Coordinate System

All annotation coordinates are in data space (not pixel coordinates):
use kuva::prelude::*;

// Data ranges from x: 0-10, y: 0-100
let data = vec![(0.0, 10.0), (5.0, 50.0), (10.0, 90.0)];

// Annotation at data coordinates (5.0, 75.0)
let annotation = TextAnnotation::new("Middle", 5.0, 75.0);

// Reference line at y = 50 in data space
let line = ReferenceLine::horizontal(50.0);

// Shaded region from x = 3 to x = 7 in data space
let region = ShadedRegion::vertical(3.0, 7.0);
This means annotations automatically adapt to different axis scales, including log scales.

Annotations with Log Scale

Annotations work seamlessly with log-scaled axes:
use kuva::prelude::*;

let data = vec![(1.0, 10.0), (10.0, 100.0), (100.0, 1000.0)];
let scatter = ScatterPlot::new()
    .with_data(data)
    .with_color("steelblue");

let plots = vec![scatter.into()];

let layout = Layout::auto_from_plots(&plots)
    .with_log_scale()  // Both axes log-scaled
    .with_reference_line(
        ReferenceLine::horizontal(100.0)  // Still uses data coordinates
            .with_color("red")
    )
    .with_annotation(
        TextAnnotation::new("10²", 20.0, 150.0)  // Data coordinates
            .with_arrow(10.0, 100.0)
    );

Best Practices

Too many overlapping shaded regions can make plots cluttered and hard to read. Prefer 1-3 regions per plot.
  • Use low opacity (0.1-0.2) for large regions
  • Use higher opacity (0.3-0.4) for narrow regions
  • Ensure shaded regions don’t obscure your data
  • Place text near but not on top of data
  • Use arrows to clearly point to the target
  • Adjust arrow_padding to avoid overlapping with markers
Match annotation colors to your data colors for clarity:
let layout = Layout::auto_from_plots(&plots)
    .with_reference_line(
        ReferenceLine::horizontal(threshold)
            .with_color("red")  // Matches the red points
    );
Always add labels to reference lines unless the value is obvious from the axis ticks:
ReferenceLine::horizontal(1.96)
    .with_label("95% CI")  // Clear what this threshold means

Implementation Details

Annotations are rendered in this order:
  1. Shaded regions (drawn first, behind everything)
  2. Grid and axes
  3. Data plots
  4. Reference lines (drawn over data)
  5. Text annotations (drawn last, on top)
This ensures that:
  • Shaded regions don’t obscure data
  • Reference lines are visible over data
  • Text is always readable
See src/render/annotations.rs:1 for the complete implementation.

Next Steps

Build docs developers (and LLMs) love