kuva

repository·main·Indexed 21 days ago

https://github.com/psy-fer/kuva

A scientific plotting library in Rust (v0.4.0) supporting over 60 plot types, including box, violin, scatter, contour, volcano, and Manhattan plots. It provides a high-level Rust API with SVG, PNG, and PDF backends, as well as a CLI tool for rendering from TSV, CSV, and Parquet files, including terminal-based visualization.

Tokens
255K
Snippets
678
Records
854
Agent score
70%

What's inside kuva

  1. Explore Kuva Plot Types

    main

    Kuva supports a wide variety of statistical and scientific plot types. You can use these via the CLI or as a library to visualize different data distributions and relationships. Supported plots include:

    • Statistical Plots: Scatter Plot, Line Plot, Bar Chart, Histogram, Density Plot, Ridgeline Plot, ECDF Plot, Q-Q Plot, Box Plot, Violin Plot, Raincloud Plot, Strip Plot, Dot Plot, Dice Plot.
    • Composition & Hierarchy: Treemap, Sunburst Chart, Pie Chart, Waffle Chart, Venn Diagram, Mosaic Plot.
    • Relational & Network Plots: Chord Diagram, Network Plot, Sankey Diagram, Phylogenetic Tree, Synteny Plot, Parallel Coordinates.
    • Scientific & Specialized Plots: Volcano Plot, Manhattan Plot, Forest Plot, ROC Curve, Precision-Recall Curve, Kaplan-Meier Survival Curve, Contour Plot, Quiver Plot, Candlestick Plot, Polar Plot, Ternary Plot, Radar / Spider Chart, 3D Scatter Plot, 3D Surface Plot.
    • Time & Flow: Bump Chart, Series Plot, Band Plot, Brick Plot, Waterfall Chart, Stacked Area Plot, Streamgraph, Gantt Chart, Horizon Chart, Calendar Heatmap.
  2. Configure Strip Plot layout modes (Jitter, Beeswarm, Center)

    main

    You can control how points are spread horizontally within each group slot using three different modes:

    1. Jittered strip: Uses .with_jitter(j). j is the half-width as a fraction of the slot (e.g., 0.3 spreads points ±30% of the slot width). This is the default (j = 0.3). Use .with_seed() to ensure reproducible jitter positions.
    2. Beeswarm: Uses .with_swarm(). Employs a deterministic algorithm to place points as close to the center as possible without overlapping. Best for N < ~200 per group.
    3. Center stack: Uses .with_center(). Places all points at the group center with no horizontal spread, creating a vertical column to show density via packing.
    // Jittered
    let strip = StripPlot::new().with_group("A", data).with_jitter(0.35);
    
    // Beeswarm
    let strip = StripPlot::new().with_group("B", data).with_swarm();
    
    // Center stack
    let strip = StripPlot::new().with_group("C", data).with_center();
  3. Choose a Network Layout algorithm

    main

    You can set the layout algorithm using .with_layout(alg). Supported algorithms via NetworkLayout include:

    • NetworkLayout::ForceDirected (Default): Uses the Fruchterman-Reingold algorithm where connected nodes attract and all nodes repel. Uses Barnes-Hut approximation for $n > 256$. Best for most graphs.
    • NetworkLayout::KamadaKawai: A stress-based layout where Euclidean distances reflect graph-theoretic distances. Better for small-to-medium graphs.
    • NetworkLayout::Circle: Nodes are evenly spaced on a circle. Deterministic and clean for small-to-medium graphs.

    You can also pin specific nodes to fixed coordinates in normalized [0, 1] space using .with_node_position(label, x, y).

  4. Configure Pie Chart label positioning

    main

    Control where slice labels appear using .with_label_position(PieLabelPosition).

    VariantBehaviour
    AutoInside large slices; outside (with leader line) for small ones. Default.
    InsideAll labels placed at mid-radius, regardless of slice size.
    OutsideAll labels outside with leader lines. Labels are spaced to avoid overlap.
    NoneNo slice labels. Often used in conjunction with a legend.

    Outside positioning is recommended when slices vary widely in size or when there are many slices to prevent overlap via leader lines.

    use kuva::plot::{PiePlot, PieLabelPosition};
    # use kuva::render::plots::Plot;
    
    let pie = PiePlot::new()
        .with_slice("Apples",  30.0, "seagreen")
        .with_slice("Oranges", 25.0, "darkorange")
        .with_slice("Bananas", 20.0, "gold")
        .with_slice("Grapes",  12.0, "mediumpurple")
        .with_slice("Mango",    8.0, "coral")
        .with_slice("Kiwi",     5.0, "olivedrab")
        .with_label_position(PieLabelPosition::Outside);
  5. How JointPlot works and how to render it

    main

    A JointPlot is a composite renderer that combines a central scatter plot with marginal distribution panels (histograms or KDE curves) on the top and right edges.

    Important: JointPlot is a standalone composite renderer and is not a variant of the Plot enum. To render it, you must use the render_jointplot(jp, layout) function instead of render_multiple.

    Key components include:

    • Central Plot: A scatter plot showing the bivariate relationship.
    • Marginal Panels: Univariate distributions for the X and Y axes.
    • Layout: Defines the canvas size and coordinate system.
    use kuva::prelude::*;
    
    let jp = JointPlot::new()
        .with_xy(x, y)
        .with_x_label("Feature A")
        .with_y_label("Feature B");
    
    let layout = Layout::new((0.0, 6.0), (1.5, 6.5))
        .with_title("Joint Plot");
    
    // Use render_jointplot instead of render_multiple
    let svg = SvgBackend.render_scene(&render_jointplot(jp, layout));
  6. How to structure hierarchical data in Sunburst plots

    main

    Sunburst charts represent hierarchy through nested TreemapNode objects:

    1. Inner Nodes: Use TreemapNode::new(label, children) to create a node with children. If the value is set to 0.0, the value is automatically calculated as the sum of its children's values.
    2. Leaf Nodes: Use TreemapNode::leaf(label, value) for terminal nodes.
    3. Multiple Roots (Forest): You can add multiple top-level nodes using .with_children(label, children). These roots will share the innermost ring, each receiving a distinct category color.

    Example of a multi-level hierarchy:

    let plot = SunburstPlot::new()
        .with_node(TreemapNode::new("Animals", vec![
            TreemapNode::new("Mammals", vec![
                TreemapNode::leaf("Dog",  40.0),
                TreemapNode::leaf("Cat",  35.0),
                TreemapNode::leaf("Bear", 25.0),
            ]),
            TreemapNode::new("Birds", vec![
                TreemapNode::leaf("Eagle",  60.0),
                TreemapNode::leaf("Parrot", 40.0),
            ]),
        ]));
    # use kuva::plot::sunburst::SunburstPlot;
    # use kuva::plot::treemap::TreemapNode;
    let plot = SunburstPlot::new()
        .with_node(TreemapNode::new("Animals", vec![
            TreemapNode::new("Mammals", vec![
                TreemapNode::leaf("Dog",  40.0),
                TreemapNode::leaf("Cat",  35.0),
                TreemapNode::leaf("Bear", 25.0),
            ]),
            TreemapNode::new("Birds", vec![
                TreemapNode::leaf("Eagle",  60.0),
                TreemapNode::leaf("Parrot", 40.0),
            ]),
        ]));
  7. Manually scale TextAnnotation and ReferenceLine fonts/widths

    main

    When using .with_scale(f) on a Layout, certain properties defined explicitly in constructors are not automatically scaled. You must scale them manually:

    1. TextAnnotation::font_size: The text size remains at its default (12) unless you multiply it by your scale factor in the constructor.
    2. ReferenceLine::stroke_width: The line width remains at its default (1.0) unless you multiply it by your scale factor.

    For Raster (PNG) output: Instead of using Layout::with_scale, use RasterBackend::with_scale(f) to increase pixel density without changing the SVG layout.

    // Scaling TextAnnotation font manually
    let scale = 2.0_f64;
    let layout = Layout::auto_from_plots(&plots)
        .with_annotation(
            TextAnnotation::new("Peak", 9.0, 16.0)
                .with_arrow(9.0, 16.0)
                .with_font_size((11.0 * scale).round() as u32),
        )
        .with_scale(scale);
    
    // Scaling ReferenceLine stroke manually
    let layout = Layout::auto_from_plots(&plots)
        .with_reference_line(
            ReferenceLine::horizontal(10.0)
                .with_stroke_width(1.0 * scale),
        )
        .with_scale(scale);
  8. Create Venn Diagrams with VennPlot

    main

    A Venn diagram in kuva displays set membership and overlap between 2, 3, or 4 groups using translucent circles or ellipses. You can use the VennPlot struct to define your sets and overlaps.

    There are two primary ways to provide data to a VennPlot:

    1. Raw elements: Provide actual lists of elements (e.g., gene names). kuva will automatically compute intersections using set operations.
    2. Pre-computed sizes: Provide the total size of each set and the specific sizes of intersections directly. This is useful when you already have the counts and don't want to provide the full lists.

    Import path: kuva::plot::venn::{VennPlot, VennSet, VennOverlap}

    use kuva::plot::venn::VennPlot;
    
    // Mode 1: Raw elements
    let venn = VennPlot::new()
        .with_set("DESeq2", vec!["BRCA1", "TP53", "MYC", "EGFR"])
        .with_set("edgeR",  vec!["TP53", "MYC", "KRAS", "PIK3CA"]);
    
    // Mode 2: Pre-computed sizes
    let venn = VennPlot::new()
        .with_set_size("Set A", 500)
        .with_set_size("Set B", 400)
        .with_overlap(["Set A", "Set B"], 120);
  9. Set coordinate conventions for Polar Plots

    main

    You can switch between the default compass convention and the mathematical convention using .with_theta_start() and .with_clockwise().

    • Compass convention (default): $\theta=0$ at North (top), increasing clockwise.
    • Math convention: $\theta=0$ at East, increasing counter-clockwise (CCW). Use .with_theta_start(90.0) and .with_clockwise(false).
    // Compass convention (default): 0° = north, clockwise
    let compass = PolarPlot::new()
        .with_theta_start(0.0)
        .with_clockwise(true);
    
    // Math convention: 0° = east, CCW
    let math = PolarPlot::new()
        .with_theta_start(90.0)
        .with_clockwise(false);
  10. Configure gene labels in Volcano Plots

    main

    You can label the n most significant points (those with the lowest p-values) using .with_label_top(n). The visual style of these labels is controlled via .with_label_style(s) using the LabelStyle enum.

    Available styles:

    • LabelStyle::Nudge (Default): Labels are sorted by x position and nudged vertically to reduce stacking. Best for most datasets.
    • LabelStyle::Arrow { offset_x, offset_y }: Moves labels by a specific pixel offset and draws a short gray leader line to the point. Useful for crowded high-significance regions.
    • LabelStyle::Exact: Places labels at the precise point position. Best for sparse plots or post-processing.
    // Nudge (Default)
    let vp = VolcanoPlot::new()
        .with_points(results)
        .with_label_top(12);
    
    // Arrow style
    use kuva::plot::{VolcanoPlot, LabelStyle};
    let vp = VolcanoPlot::new()
        .with_points(results)
        .with_label_top(10)
        .with_label_style(LabelStyle::Arrow { offset_x: 14.0, offset_y: 16.0 });
    
    // Exact style
    use kuva::plot::{VolcanoPlot, LabelStyle};
    let vp = VolcanoPlot::new()
        .with_points(results)
        .with_label_style(LabelStyle::Exact);
  11. Configure Scatter Plot layout and axis ranges

    main

    Layouts control how plots are arranged and how axes are scaled.

    • Use Layout::auto_from_plots(&plots) to automatically compute axis ranges based on the provided data.
    • Use Layout::new((x_min, x_max), (y_min, y_max)) to set axis ranges manually.
  12. How auto-collected legends work

    main

    Legends in kuva can be assembled automatically from plot data. When you call .with_legend("label") on a Plot, the entry is recorded. You then use Layout::auto_from_plots(&plots) to collect all entries from the provided plots and render them alongside the canvas. By default, the legend is placed in the right margin (OutsideRightTop) and the canvas expands automatically to accommodate it.

    Import paths:

    • kuva::render::layout::Layout — position and appearance builders
    • kuva::plot::legend::{LegendEntry, LegendShape, LegendPosition, LegendGroup} — entry types
    use kuva::prelude::*;
    
    let plots: Vec<Plot> = vec![
        ScatterPlot::new()
            .with_data([(1.1, 2.3), (1.9, 3.1), (2.4, 2.7), (3.0, 3.8), (3.6, 3.2)])
            .with_color("steelblue").with_legend("Cluster A").with_size(6.0)
            .into(),
        ScatterPlot::new()
            .with_data([(4.0, 1.2), (4.8, 1.8), (5.3, 1.4), (6.0, 2.0), (6.5, 1.6)])
            .with_color("orange").with_legend("Cluster B").with_size(6.0)
            .into(),
    ];
    
    let layout = Layout::auto_from_plots(&plots)
        .with_title("Auto-Collected Legend")
        .with_x_label("X")
        .with_y_label("Y");
    
    let svg = SvgBackend.render_scene(&render_multiple(plots, layout));