charcoal 0.1.1

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

[![Crates.io](https://img.shields.io/crates/v/charcoal.svg)](https://crates.io/crates/charcoal)
[![docs.rs](https://img.shields.io/docsrs/charcoal)](https://docs.rs/charcoal/0.1.1/charcoal/)

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

```toml
[dependencies]
charcoal = "0.1.1"
```

## Quickstart

```rust
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

```rust
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:

```rust
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:

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

## Alternatives

**[Plotters](https://crates.io/crates/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](https://crates.io/crates/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](https://crates.io/crates/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](LICENSE-MIT) or [Apache 2.0](LICENSE-APACHE) at your option.