Skip to content

wangjiawen2013/charton

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

92 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Charton - A versatile plotting library for Rust

Altair-style declarative plotting for Rust. High-performance, Polars-native, and Wasm-ready.

"Really nice project. ... This works perfectly as an ecosystem." β€” Ritchie Vink, Creator of Polars

Crates.io Documentation Build Status License

Charton is a powerful plotting library for Rust that provides first-class, native support for Polars, and offers an API similar to Python's Altair, making it easy for users familiar with declarative, instruction-based plotting to migrate. It also allows you to leverage existing mature visualization ecosystems, such as Altair and Matplotlib. By seamlessly integrating with evcxr_jupyter, Charton facilitates the creation of informative and aesthetically pleasing visualizations interactively, making it especially well-suited for exploratory data analysis.

🌟 Who Charton is For?

  • Data Scientists: Generate visualizations directly in Rust after data processing-no more context-switching to Python.
  • Performance-sensitive scenarios: Pure Rust rendering with no external dependencies, perfect for server-side batch generation.
  • Interactive Explorers: Rapidly iterate on visualization logic via evcxr in Jupyter Notebooks.
  • WebAssembly Developers: Bring Polars-powered data processing to the browser with a compact, web-optimized binary.

Installation

Charton includes a pure-Rust SVG renderer, which allows users to create visualizations entirely in Rust without any external dependencies. To use charton, simply add it into Cargo.toml:

[dependencies]
charton = "0.2.0"
polars = "0.49.1"

Quick Start (Rust-Native)

Charton employs a multi-layer plotting architecture, in which multiple layers are rendered within a shared coordinate system.

For most use cases involving single-layer charts, Charton provides a streamlined interface. The .into_layered() method allows you to convert a single chart layer directly into a complete, renderable chart.

use charton::prelude::*;
use polars::prelude::*;
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    // Create a polars dataframe
    let df = df![
        "length" => [5.1, 4.9, 4.7, 4.6, 5.0, 5.4, 4.6, 5.0, 4.4, 4.9],
        "width" => [3.5, 3.0, 3.2, 3.1, 3.6, 3.9, 3.4, 3.4, 2.9, 3.1]
    ]?;

    // Create a layer
    Chart::build(&df)?
        .mark_point()               // Scatter plot
        .encode((
            x("length"),            // Map length column to X-axis
            y("width"),             // Map width column to Y-axis
        ))?
        .into_layered()             // Create a layered chart
        .save("./scatter.svg")?;

    Ok(())
}

scatterplot

πŸ’‘ Tip: Chart::build(...)...into_layered() is the most concise way to create and save single-layer visualizations.

Explicit form: The code above is equivalent to the following explicit construction using LayeredChart. Use this form when you need to combine multiple layers or want more control over the chart structure.

use charton::prelude::*;
use polars::prelude::*;
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    // Create a polars dataframe
    let df = df![
        "length" => [5.1, 4.9, 4.7, 4.6, 5.0, 5.4, 4.6, 5.0, 4.4, 4.9],
        "width" => [3.5, 3.0, 3.2, 3.1, 3.6, 3.9, 3.4, 3.4, 2.9, 3.1]
    ]?;

    // Create a layer
    let scatter = Chart::build(&df)?
        .mark_point()               // Scatter plot
        .encode((
            x("length"),            // Map length column to X-axis
            y("width"),             // Map width column to Y-axis
        ))?;

    LayeredChart::new()             // Create a layered chart
        .add_layer(scatter)         // Add the chart as a layer of the layered chart
        .save("./scatter.svg")?;    // Save the layered chart

    Ok(())
}

For more complex plots, you can use the layered chart to combine multiple layers.

use charton::prelude::*;
use polars::prelude::*;
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    // Create a polars dataframe
    let df = df![
        "length" => [4.4, 4.6, 4.7, 4.9, 5.0, 5.1, 5.4],
        "width" => [2.9, 3.1, 3.2, 3.0, 3.6, 3.5, 3.9]
    ]?;

    // Create a line chart layer
    let line = Chart::build(&df)?
        .mark_line()                        // Line chart
        .encode((
            x("length"),                    // Map length column to X-axis
            y("width"),                     // Map width column to Y-axis
        ))?;

    // Create a scatter point layer
    let scatter = Chart::build(&df)?
        .mark_point()                       // Scatter plot
        .encode((
            x("length"),                    // Map length column to X-axis
            y("width"),                     // Map width column to Y-axis
        ))?;

    LayeredChart::new()       
        .add_layer(line)                    // Add the line layer
        .add_layer(scatter)                 // Add the scatter point layer
        .save("./layeredchart.svg")?;

    Ok(())
}

layeredchart

Quick Start (External Backends)

Charton is primarily a Rust-native plotting library, and native rendering is recommended whenever possible. When certain features are not yet available, Charton can also delegate rendering to external visualization libraries.

Here, we use the Python Altair library to demonstrate how Charton works with external visualization tools. For other libraries (e.g., Matplotlib), see the tutorial.

For the Python backend, make sure the required packages are available in your Python environment:

pip install polars
pip install pyarrow

Charton runs external plotting code in a separate process. Rust data is passed to Python via standard input, and the rendered output is returned via standard output. No temporary files are created.

use charton::prelude::*;
use polars::prelude::*;
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    // Set the path to your Python executable on windows/linux/macOS
    let exe_path = r"where-is-my/python";
    let df1 = df![
        "Model" => ["S1", "M1", "R2", "P8", "M4", "T5", "V1"],
        "Price" => [2430, 3550, 5700, 8750, 2315, 3560, 980],
        "Discount" => [Some(0.65), Some(0.73), Some(0.82), None, Some(0.51), None, Some(0.26)],
    ]?;

    // Code for plotting with Altair
    let raw_plotting_code = r#"
import altair as alt

chart = alt.Chart(df1).mark_point().encode(
    x='Price',
    y='Discount',
    color='Model',
).properties(width=200, height=200)
"#;
    Plot::<Altair>::build(data!(&df1)?)?
        .with_exe_path(exe_path)?
        .with_plotting_code(raw_plotting_code)
        .save("scatter.svg")?;

    Ok(())
}

ℹ️ Note: The Altair chart object must be assigned to a variable named chart, because chart.to_json() is used implicitly to convert the chart to JSON format. Besides, the dataframe name must be the same as the dataframe passed to Plot::build().

Interactive Plots in Jupyter

Charton integrates with evcxr, allowing you to display plots interactively in a Jupyter Notebook. To use this feature, evcxr must be installed beforehand. See the Jupyter/evcxr article for detailed setup instructions.

The following code shows a minimal example of this.

:dep charton = { version="0.2.0" }
:dep polars = { version="0.49.1" }

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

// Create a polars dataframe
let df = df![
    "length" => [5.1, 4.9, 4.7, 4.6, 5.0, 5.4, 4.6, 5.0, 4.4, 4.9],
    "width" => [3.5, 3.0, 3.2, 3.1, 3.6, 3.9, 3.4, 3.4, 2.9, 3.1]
]?;

// Create a layer
Chart::build(&df)?
    .mark_point()
    .encode((
        x("length"),
        y("width"),
    ))?
    // Convert the layer to a layered chart
    .into_layered()
    .show()?;

Run it in a Jupyter Notebook cell, and the chart will be displayed inline.

The same workflow applies when using external visualization libraries: place the corresponding Rust code snippet into a Jupyter cell, and Charton will render the visualization interactively.

:dep charton = { version="0.2.0" }
:dep polars = { version="0.49.1" }

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

// Set the path to your Python executable on windows/linux/macOS
let exe_path = r"where-is-my/python";
let df1 = df![
    "Model" => ["S1", "M1", "R2", "P8", "M4", "T5", "V1"],
    "Price" => [2430, 3550, 5700, 8750, 2315, 3560, 980],
    "Discount" => [Some(0.65), Some(0.73), Some(0.82), None, Some(0.51), None, Some(0.26)],
].unwrap();

let raw_plotting_code = r#"
import altair as alt

chart = alt.Chart(df1).mark_point().encode(
    x='Price',
    y='Discount',
    color='Model',
).properties(width=200, height=200)
"#;

Plot::<Altair>::build(data!(&df1)?)?
    .with_exe_path(exe_path)?
    .with_plotting_code(raw_plotting_code)
    .show()?;

scatterplot

🌐 Front-end Integration

Charton excels at decoupling the data processing (in Rust) from the visualization rendering (in the browser). By leveraging the Vega-Lite specification as an intermediary format, Charton ensures seamless integration with modern web applications.

The core of Charton's frontend support is its ability to generate a complete Vega-Lite JSON object. Currently, Charton achieves this by bridging the functionality of the Altair library (which is built upon Vega-Lite). The to_json() method generates the final Vega-Lite JSON object that can be consumed by any compatible JavaScript library.

This allows you to utilize powerful frontend visualization tools like:

  • Vega-Embed: The standard library for embedding Vega and Vega-Lite charts.
  • React-Vega: For easy integration within React applications.
  • Any library supporting the Vega-Lite or Vega standard.

The example below shows how Charton generates the specification from your Rust data and Altair code, ready for web consumption:

let chart_json = Plot::<Altair>::build(data!(&df1)?)?
    .with_exe_path(exe_path)?
    .with_plotting_code(raw_plotting_code)
    .to_json()?;

// The chart_json string can now be sent via an API endpoint
// or embedded directly in an HTML file for client-side rendering.
println!("{}", chart_json);

This is the output (excerpt):

{
  "$schema": "https://vega.github.io/schema/vega-lite/v5.20.1.json",
  "data": {
    "name": "data-8572dbb2f2fe2e54e92fc99f68a5f076"
  },
  "datasets": {
    "data-8572dbb2f2fe2e54e92fc99f68a5f076": [
      {
        "Discount": 0.65,
        "Model": "S1",
        "Price": 2430
      },
      // ... more data rows ...
    ]
  },
  "encoding": {
    "color": {
      "field": "Model",
      "type": "nominal"
    },
    "x": {
      "field": "Price",
      "type": "quantitative"
    },
    // ... other encoding and properties ...
  },
  "mark": {
    "type": "point"
  }
}

⚑ High-Performance WebAssembly

Charton is engineered for the modern web. By stripping away unnecessary bloat, it delivers heavy-duty data power in a lightweight package:

  • Compact Footprint: Optimized to ~4MB raw binary (~900KB after Gzip), making it one of the lightest Polars-integrated Wasm modules available.
  • Zero-Lag Rendering: Designed to process and visualize data-intensive sets directly in the browser (optimized for real-time responsiveness). wasm2
  • Modern Toolchain Support: Optimized for wasm-pack; compatible with modern bundlers like Vite and Webpack.

πŸ“– Wasm Getting Started: Check out Chapter 9 of our Tutorial for a step-by-step example of rendering a scatter plot in the browser using Wasm.

Examples

This gallery contains a selection of examples of the plots charton can create. Go to the examples folder to view the source code.

Altair

Altair

Matplotlib

Matplotlib

Grouped Bar Chart

Grouped Bar Chart

Distribution

Distribution

Histogram

Histogram

Rule

Rule

Stacked Bar Chart

Stacked Bar Chart

Text

Text

2d Density

2d Density Chart

Grouped Boxplot

Grouped Boxplot

Density

Density Chart

Cumulative Frequency

Cumulative Frequency

Heatmap

Heatmap

Scatter Chart

Scatter Chart

Line Errorbar

Line Errorbar

Bar Errorbar

Bar Errorbar

Log Scale

Log Scale

Swapped Axes

Swapped Axes

Line

Line

Donut

Donut

Features

While the Rust ecosystem has seen efforts in native visualization and integration with external tools (like PyO3), existing solutions often suffer from poor maintenance, inefficient temporary file usage, or complex notebook environment limitations (e.g., restricted Conda support in evcxr).

Charton is engineered to solve these challenges, providing a modern, efficient, and user-friendly alternative with several key advantages:

Key Feature Charton πŸ’‘ Advantage / Description
πŸ¦€ Pure Rust Execution βœ… Fully native rendering without reliance on external runtimes.
πŸ“Š Polars Support βœ… Seamless integration with Polars, allowing direct plotting from Rust DataFrames.
πŸ““ Jupyter/evcxr βœ… Native support for evcxr Jupyter notebooks enables interactive visualization.
⚑ IPC (Inter-Process Communication) βœ… Enables efficient use of visualization systems without writing temporary files.
🧩 API Friendliness βœ… Altair-like API ensures a minimal learning curve for users familiar with Python tools.
🌐 Frontend JSON Output βœ… Generates Vega-Lite JSON for easy integration and dynamic client-side web rendering.
πŸ’» Wasm (WebAssembly) βœ… Aims to bring high-performance visualization to the web.

🀝 Contributions Welcome

Contributions are welcome! Whether it's improving documentation, adding new chart types, or optimizing performance, your help is appreciated.

πŸ“„ License

Charton is licensed under the Apache License 2.0.

πŸ™ Acknowledgements

  • AI Collaboration: This project was developed based on a human-led architectural design, with AI tools (including Qwen, Kimi, ChatGPT, Gemini, and others) serving as intelligent pair-programmers. While AI assisted in accelerating the implementation through boilerplate generation and API exploration, all core logic, architectural decisions, and final implementations were strictly audited, refined, and verified by the author.
  • Personal Support: I would like to thank my family for their support, providing the time and environment needed to bring this project to life.

Note: This project is developed independently and is not affiliated with or endorsed by any organization.

About

A high-level, layered charting system for Rust, designed for Polars-first data workflows and multi-backend rendering.

Resources

License

Stars

Watchers

Forks

Packages

No packages published