charcoal 0.1.1

Declarative, DataFrame-native chart library for Polars. No browser required.
Documentation

charcoal

Crates.io docs.rs

A declarative, DataFrame-native chart library for Polars. No browser. No Python. No C FFI.

[dependencies]
charcoal = "0.1.1"

Quickstart

use charcoal::{Chart, Theme};
use polars::prelude::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let df = DataFrame::new(vec![
        Series::new("x",     &[1.0f64, 2.0, 3.0, 4.0, 5.0]),
        Series::new("y",     &[2.3f64, 3.1, 2.8, 4.5, 3.9]),
        Series::new("group", &["a", "a", "b", "b", "b"]),
    ])?;

    let chart = Chart::scatter(&df)
        .x("x")
        .y("y")
        .color_by("group")
        .title("My First Chart")
        .theme(Theme::Default)
        .build()?;

    chart.save_svg("output.svg")?;
    chart.save_html("output.html")?;
    Ok(())
}

Chart Types

Chart Method Required Columns
Scatter Chart::scatter(&df) .x(), .y()
Line Chart::line(&df) .x(), .y()
Bar Chart::bar(&df) .x(), .y()
Histogram Chart::histogram(&df) .x()
Heatmap Chart::heatmap(&df) .x(), .y(), .z()
Box Plot Chart::box_plot(&df) .x(), .y()
Area Chart::area(&df) .x(), .y()

Output Formats

Format Method Feature Flag
SVG string chart.svg() none
SVG file chart.save_svg("out.svg") none
Standalone HTML chart.save_html("out.html") none
PNG / JPEG / WEBP chart.save_png("out.png") static
evcxr notebook chart.display() notebook

Feature Flags

Feature Enables Extra Dependencies
(default) SVG and HTML output
static PNG/JPEG/WEBP raster export resvg (pure Rust)
notebook Inline display in evcxr evcxr_runtime
ndarray Array2<f64> input for heatmaps ndarray
interactive Plotly.js interactive HTML export none

Themes

Theme::Default     // clean light theme
Theme::Dark        // dark background
Theme::Minimal     // no gridlines, minimal chrome
Theme::Colorblind  // Wong 8-color palette

Error Quality

charcoal errors tell you what went wrong, where, and what to do next:

CharcoalError::ColumnNotFound
  column "sepal_lenght" not found
  Did you mean: sepal_length
  Available: sepal_length, sepal_width, petal_length, petal_width, species

Null Handling

Every column role has a documented null policy. Nulls are never silently dropped without a warning. Access warnings after rendering:

let chart = Chart::scatter(&df)
    .x("sepal_length")
    .y("sepal_width")
    .build()?;

for warning in chart.warnings() {
    eprintln!("warning: {warning}");
}

Row Limits

  • Above 500k rows: warning emitted, scatter points subsampled
  • Above 1M rows: .build() returns Err(CharcoalError::DataTooLarge)

Configure the limit via the builder:

Chart::scatter(&df)
    .x("x")
    .y("y")
    .row_limit(2_000_000)
    .build()?;

Alternatives

Plotters is a low-level drawing library that gives you full control over rendering primitives, but axis scaling, layout, and data mapping are all your responsibility. charcoal trades that flexibility for a DataFrame-in, chart-out API: if your data is already in Polars, charcoal needs one builder chain where Plotters needs a full rendering pipeline.

plotly wraps Plotly.js and produces rich interactive charts, but output requires a browser and a JavaScript runtime at view time. charcoal targets static, self-contained SVG and HTML — no JavaScript engine, no network dependency, embeddable anywhere.

charts-rs produces SVG output with a similar philosophy but uses a JSON/config-driven API and has no Polars integration. charcoal is built around Polars DataFrames as the primary input, so column selection, null handling, and type inference come for free.

License

Licensed under either of MIT or Apache 2.0 at your option.