u/Deep-Network1590

Charton: A columnar-native plotting library for Rust (Polars-friendly)
▲ 24 r/rust

Charton: A columnar-native plotting library for Rust (Polars-friendly)

Charton is a columnar-native plotting library designed to keep memory layouts as close to Apache Arrow as possible, allowing for near-zero-copy integration.

Here’s the core of how it handles data:

pub enum ColumnVector {
    Boolean { data: Vec<bool>, validity: Option<Vec<u8>> },
    Int8 { data: Vec<i8>, validity: Option<Vec<u8>> },
    // ... handles all int/float types
    String { data: Vec<String>, validity: Option<Vec<u8>> },
    Categorical { keys: Vec<u32>, values: Vec<String>, validity: Option<Vec<u8>> },
    Datetime { data: Vec<i64>, validity: Option<Vec<u8>>, timezone: Option<String> },
}
    // ... other types

And the Dataset structure ensures alignment:

pub struct Dataset {
    schema: AHashMap<String, usize>,
    columns: Vec<Arc<ColumnVector>>,
    row_count: usize,
}

The load_polars_df! macro is ready to use, and the Altair-style declarative API makes building layered charts intuitively.

The roadmap is to go full native Arrow and eventually integrate DataFusion for a "SQL-to-chart" workflow. It’s still early days, so if you’re doing data analysis in Rust, I’d love to get your thoughts or feedback on the API.

u/Deep-Network1590 — 14 hours ago
▲ 25 r/dataanalysis+1 crossposts

Designing a plotting Dataset for Rust: Balancing Polars support with zero-dependency weight

When building a visualization library in Rust, a classic architectural dilemma emerges: hard-coding Polars as the backend instantly makes the library heavy, slow to compile, and riddled with large dependencies—making it a no-go for lightweight applications. However, sticking purely to native Rust vectors alienates the data science community who live in Polars DataFrames.

For Charton (a rust visualization crate), the goal was to bridge this gap: keep the core plotting Dataset dependency-free, but provide a seamless, opt-in bridge for Polars users.

Instead of embedding Polars into the core, Charton works natively with clean Rust types but offers a load_polars_df!() macro. This allows Polars users to instantly ingest their data frames with zero friction, while keeping the core library dead-lightweight.

Here is how the API handles data ingestion in practice:

use charton::prelude::*;
use polars::prelude::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 1. Create a Polars DataFrame with diverse, high-performance types
    let df = df!(
        "id" => &[1, 2, 3, 4, 5],
        "status" => &["High", "Low", "High", "Medium", "Low"],
        "value" => &[Some(1.2), None, Some(5.6), Some(7.8), None],
        "date" => Series::new("date".into(), &[19858i32, 19859, 19860, 19861, 19862]).cast(&DataType::Date)?, 
        "datetime" => Series::new("datetime".into(), &[1715760000000i64, 1715763600000, 1715767200000, 1715770800000, 1715774400000])
            .cast(&DataType::Datetime(TimeUnit::Milliseconds, None))?,
        "duration" => Series::new("duration".into(), &[3_600_000i64, 7_200_000, 1_800_000, 10_800_000, 5_400_000])
            .cast(&DataType::Duration(TimeUnit::Milliseconds))?,
    )?;

    // 2. Convert to Charton dataset seamlessly via macro (Polars remains optional at compile time)
    let ds = load_polars_df!(df)?;
    
    // 3. Dataset is now ready for encoding-axis binding and layout transformations
    println!("{:?}", ds);

    Ok(())
}

Charton ensures strict metadata alignment during conversion. The following table illustrates how Polars logical types map to Charton physical storage:

Polars Logical Type Charton Physical Type Notes
Int8, Int16, Int32, Int64 i8, i16, i32, i64 Direct physical mapping.
UInt32, UInt64 u32, u64 Direct physical mapping.
Float32, Float64 f32, f64 NaN values are treated as Nulls.
Boolean bool Mapped to nullable boolean vector.
Utf8 / String String Stored as nullable string vectors.
Categorical(_, _), Enum(_, _) Categorical Preserves dictionary encoding + validity.
Date Date Stored as i32 days since Unix epoch.
Time Time Stored as i64 nanoseconds since midnight.
Datetime(unit, _) Datetime Normalized to i64 nanoseconds since Unix epoch.
Duration(unit) Duration Normalized to i64 nanoseconds.

Curious to hear how other library authors tackle the "heavy data frame dependency vs. lightweight core" problem in Rust and hope it helps for everyone who are facing this dilemma.

u/Deep-Network1590 — 3 days ago
▲ 357 r/rust

Hi,

I’ve been working on a Rust plotting library called Charton. I wanted to bring a true "Grammar of Graphics" experience to Rust, similar to what Altair does for Python or ggplot2 for R.

I’ve always felt that many Rust plotting tools are either too low-level—where you have to write dozens of lines just for a simple chart—or they’re just too rigid. To fix this, I built Charton with a custom Dataset engine that feels a lot like Polars. You can feed it native arrays, vectors, or Polars DataFrames directly, and it handles the data very efficiently.

The API is minimal by design. In most cases, you can get a plot done in a single line. It uses a layered approach, so you can stack different marks to build complex visualizations. Right now, it supports 9 core types: points, bars, lines, area, rect, boxplots, rules, ticks, and text.

Here’s a quick look at how you’d use it:

let height = vec![160.0, 170.0, 180.0];
let weight = vec![60.0, 75.0, 85.0];

chart!(height, weight)?
    .mark_point()?
    .encode((alt::x("height"), alt::y("weight")))?
    .save("out.svg")?;

And for layered chart:

// Create individual layers
let line = chart!(height, weight)?.mark_line()?.encode((alt::x("height"), alt::y("weight")))?;
let point = chart!(height, weight)?.mark_point()?.encode((alt::x("height"), alt::y("weight")))?;

// Combine into a composite chart
line.and(point).save("layered.svg")?;

Next on my list is adding a WGPU backend for hardware acceleration and integrating with Nushell for terminal-based plotting.

However, I’m curious to hear from the community: What are you looking for in a Rust plotting library? In what direction should Charton evolve to best fit the current trends in the Rust ecosystem?

u/Deep-Network1590 — 19 days ago