charton 0.4.1

A high-level, layered charting system for Rust, designed for Polars-first data workflows and multi-backend rendering.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
# Quick Start

Welcome to **Charton Quick Start**! 

This chapter will guide you through creating charts in Rust using Charton from scratch. By the end of this chapter, you'll know how to:

- Initialize a Rust project and add Charton dependencies
- Load and preprocess data using Polars
- Build charts using Chart, Mark, and Encoding
- Render charts in multiple formats and environments
- Avoid common pitfalls and errors

The goal is to make you productive **within minutes**.

## Project Setup

First, create a new Rust project:

```bash
cargo new demo
cd demo
```
Edit your `Cargo.toml` to add Charton and Polars dependencies:
```toml
[dependencies]
charton = "0.4"
polars = { version = "0.49", features = ["lazy", "csv", "parquet"] }
```
Run `cargo build` to ensure everything compiles.

## Creating Your First Chart

Charton adopts a **declarative visualization** philosophy, drawing heavily from the design principles of Altair and Vega-Lite. Every Charton chart is composed of **three core elements** which allow you to specify *what* you want to see, rather than *how* to draw it:

1. **Chart** – The base container that holds your data (`Chart::build(&df)`).
2. **Mark** – The visual primitive you choose (point, bar, line, etc., defined by `.mark_point()`).
3. **Encoding** – The mapping that links data fields to visual properties (x, y, color, size, etc., defined by `.encode(...)`).

**Example: Analyzing Car Weight vs. MPG (Scatter Plot)**

This minimal Charton example uses the built-in `mtcars` dataset to create a scatter plot of car weight (`wt`) versus miles per gallon (`mpg`).
```rust
use charton::prelude::*;
use polars::prelude::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 1. Data Preparation (Polars)
    let df = load_dataset("mtcars")?
        .lazy()
        .with_columns([col("gear").cast(DataType::String)]) // Cast 'gear' for categorical coloring
        .collect()?;

    // 2. Chart Declaration (Chart, Mark, Encoding)
    Chart::build(&df)?          // Chart: Binds the data source
        .mark_point()?          // Mark: Specifies the visual primitive (dots)
        .encode((
            x("wt"),            // Encoding: Maps 'wt' (weight) to the X-axis
            y("mpg"),           // Encoding: Maps 'mpg' (fuel efficiency) to the Y-axis
        ))?
        // 3. Converted to Layered Chart
        .into_layered()
        // 4. Saving the Layered Chart to SVG
        .save("./scatter_chart.svg")?;

    println!("Chart saved to scatter_chart.svg");
    Ok(())
}
```
You can also display the result directly in your evcxr jupyter notebook using the `show()` method for quick iteration:
```rust
// ... (using the same 'df' DataFrame)
Chart::build(&df)?
    .mark_point()?
    .encode((x("wt"), y("mpg")))?
    .into_layered()
    .show()?;
```

You can even save the chart object to a variable and use it later. For example:
```rust
// ... (using the same 'df' DataFrame)
let chart = Chart::build(&df)?
    .mark_point()?
    .encode((x("wt"), y("mpg")))?
    .into_layered();

chart.save("./scatter_chart.svg")?; // or chart.show()?
```
This mirrors the **declarative style of Altair**, now in Rust.

**Explicit form**

The code above is equivalent to the following explicit construction using LayeredChart (see chapter 5).
```rust
use charton::prelude::*;
use polars::prelude::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 1. Data Preparation (Polars)
    let df = load_dataset("mtcars")?
        .lazy()
        .with_columns([col("gear").cast(DataType::String)]) // Cast 'gear' for categorical coloring
        .collect()?;

    // 2. Chart Declaration (Chart, Mark, Encoding)
    let scatter = Chart::build(&df)?    // Chart: Binds the data source
        .mark_point()?                  // Mark: Specifies the visual primitive (dots)
        .encode((
            x("wt"),                    // Encoding: Maps 'wt' (weight) to the X-axis
            y("mpg"),                   // Encoding: Maps 'mpg' (fuel efficiency) to the Y-axis
        ))?;
    
    // 3. Create a layered chart
    LayeredChart::new() 
        .add_layer(scatter)             // Add the chart as a layer of the layered chart
        .save("./scatter_chart.svg")?;  // Save the layered chart

    println!("Chart saved to scatter_chart.svg");
    Ok(())
}
```

## Loading and Preparing Data

Before creating visualizations, Charton requires your data to be stored in a Polars `DataFrame`. Charton itself does not impose restrictions on how data is loaded, so you can rely on Polars’ powerful I/O ecosystem.

### Built-in Datasets

Charton provides a few built-in datasets for quick experimentation, demos, and tutorials.
```rust
let df = load_dataset("mtcars")?;
```
This returns a Polars DataFrame ready for visualization.

### Loading CSV Files

CSV is the most common format for tabular data. Using Polars:
```rust
use polars::prelude::*;

let df = CsvReadOptions::default()
    .with_has_header(true)
    .try_into_reader_with_file_path(Some("./datasets/iris.csv".into()))?
    .finish()?;
```

### Loading Parquet Files

Parquet is a high-performance, columnar storage format widely used in data engineering.
```rust
let file = std::fs::File::open("./datasets/foods.parquet")?;
let df = ParquetReader::new(file).finish()?;
```
Parquet is recommended for large datasets due to compression and fast loading.

### Loading Data from Parquet Bytes (`Vec<u8>`) — Cross-Version Interoperability

One of the challenges when working with the Polars ecosystem is that **different crates may depend on different Polars versions**, which prevents passing `DataFrame` values directly between libraries. Charton solves this problem by offering a **version-agnostic data exchange format** based on **Parquet-serialized bytes**.

Charton provides an implementation of:
```rust
impl TryFrom<&Vec<u8>> for DataFrameSource
```
This allows you to:

- Serialize a Polars `DataFrame` into Parquet bytes (`Vec<u8>`)
- Pass those bytes to Charton
- Let Charton deserialize them internally using its Polars version
- Avoid Polars version conflicts entirely

This is especially useful when your application depends on a uncompatible Polars version with Charton. By using Parquet bytes as the intermediate format, **data can be exchanged safely across Polars versions**.

**Example: Passing a DataFrame to Charton Using Parquet Bytes**

Below is a full example demonstrating:

1. Creating a Polars `DataFrame`
2. Serializing it into Parquet bytes using your Polars version
3. Passing those bytes to Charton
4. Rendering a scatter plot

**Cargo.toml**
```toml
[dependencies]
polars = { version = "0.51", features = ["parquet"] }
charton = { version = "0.3" }
```
**Source Code Example**
```rust
use charton::prelude::*;
use polars::prelude::*;
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    // Create a Polars DataFrame using Polars 0.51
    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]
    ]?;

    // Serialize DataFrame into Parquet bytes
    let mut buf: Vec<u8> = Vec::new();
    ParquetWriter::new(&mut buf).finish(&mut df.clone())?;

    // Build a Chart using the serialized Parquet bytes
    Chart::build(&buf)?
        .mark_point()?
        .encode((
            x("length"),
            y("width"),
        ))?
        .into_layered()
        .save("./scatter.svg")?;

    Ok(())
}
```

## Simple Plotting Examples

This section introduces the most common chart types in Charton.

### Line Chart

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

// Create a line chart layer
Chart::build(&df)?
    .mark_line()?              // Line chart
    .encode((
        x("length"),           // Map length column to X-axis
        y("width"),            // Map width column to Y-axis
    ))?
    .into_layered()
    .save("line.svg")?;
```
Useful for trends or ordered sequences.

### Bar Chart

```rust
let df = df! [
    "type" => ["a", "b", "c", "d"],
    "value" => [4.9, 5.3, 5.5, 6.5],
]?;

Chart::build(&df)?
    .mark_bar()?
    .encode((
        x("type"),
        y("value"),
    ))?
    .into_layered()
    .save("bar.svg")?;
```

### Histogram

```rust
let df = load_dataset("iris")?;

Chart::build(&df)?
    .mark_hist()?
    .encode((
        x("sepal_length"),
        // The number of data points (or Frequency) falls into the corresponding bin are named "count".
        // You can use any arbitray name for the y-axis, here we use "count".
        y("count")
    ))?
    .into_layered()
    .save("hist.svg")?;
```
Charton automatically computes bin counts when `y("count")` is specified.

### Boxplot

```rust
let df = load_dataset("iris")?;

Chart::build(&df)?
    .mark_boxplot()?
    .encode((x("species"), y("sepal_length")))?
    .into_layered()
    .save("boxplot.svg")?;
```
Boxplots summarize distributions using quartiles, medians, whiskers, and outliers.

### Layered Charts

In Charton, complex visualizations are built by **layering multiple charts** on the same axes. Each layer defines a single mark type, and layers are composed to form a unified view with shared scales and coordinates.
```rust
use charton::prelude::*;
use polars::prelude::*;

fn main() -> Result<(), Box<dyn std::error::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(())
}
```

## Exporting Charts

Charton supports exporting charts to different file formats depending on the selected rendering backend. All backends share the same API:

```rust
chart.save("output.png")?;
```
The file format is inferred from the extension.

This section describes the supported formats and saving behavior for each backend.

### Rust Native Backend


The Rust native backend is the default renderer and supports:

- **SVG** — vector graphics output
- **PNG** — rasterized SVG (using resvg with automatic system font loading)

**Saving SVG**
```rust
chart.save("chart.svg")?;
```
**Saving PNG**

PNG is generated by rasterizing the internal SVG at 2× resolution:
```rust
chart.save("chart.png")?;
```
This produces high-quality PNG output suitable for publication.

### Altair Backend (Vega-Lite)


The Altair backend uses Vega-Lite as the rendering engine and supports:

- **SVG** — via Vega → SVG conversion
- **PNG** — SVG rasterized via resvg
- **JSON** — raw Vega-Lite specification

**Saving SVG**
```rust
chart.save("chart.svg")?;
```
**Saving PNG**
```rust
chart.save("chart.png")?;
```
**Saving Vega-Lite JSON**
```rust
chart.save("chart.json")?;
```
The JSON file can be opened directly in the online Vega-Lite editor.

### Matplotlib Backend


The Matplotlib backend supports:

- **PNG** — returned as base64 from Python, decoded and saved

**Saving PNG**
```rust
chart.save("chart.png")?;
```
Other formats (SVG, JSON, PDF, etc.) are not currently supported by this backend.

### Unsupported Formats & Errors


Charton will return an error if:

- The file extension is missing
- The extension is not supported by the selected backend
- SVG → PNG rasterization fails
- File write errors occur

Example:
```rust
if let Err(e) = chart.save("output.bmp") {
    eprintln!("Save error: {}", e);
}
```
### Summary of Supported Formats

| Backend     | SVG | PNG | JSON |
| ----------- | :-: | :-: | :--: |
| Rust Native |  ✔️ |  ✔️ ||
| Altair      |  ✔️ |  ✔️ |  ✔️  |
| Matplotlib  ||  ✔️ ||

### Exporting Charts as Strings (SVG / JSON)

In addition to saving charts to files, Charton also supports exporting charts **directly as strings**.
This is useful in environments where writing to disk is undesirable or impossible, such as:

- Web servers returning chart data in API responses
- Browser/WASM applications
- Embedding charts into HTML templates
- Passing Vega-Lite specifications to front-end visualizers
- Testing and snapshot generation

Charton provides two kinds of in-memory exports depending on the backend.

**SVG Output (Rust Native Backend)**
The Rust-native renderer can generate the complete SVG markup of a chart and return it as a `String`:
```rust
let svg_string = chart.to_svg()?;
```
This returns the full `<svg>...</svg>` element including:
- Layout
- Axes
- Marks
- Legends
- Background

The string can be:
- Embedded directly into HTML
- Returned from a web API
- Rendered inside a WASM application
- Passed to a templating engine such as Askama or Tera

*Example*
```rust
let svg = chart.to_svg()?;
```
**Vega-Lite JSON (Altair Backend)**
When using the Altair backend, charts can be exported as raw *Vega-Lite JSON*:
```rust
let json = chart.to_json()?;
```
This produces the complete Vega-Lite specification generated by Altair. Typical usage scenarios include:
- Front-end rendering using Vega/Vega-Lite
- Sending the chart spec from a Rust API to a browser client
- Storing chart specifications in a database
- Generating reproducible visualization specs

*Example*
```rust
let json_spec = chart.to_json()?;
println!("{}", json_spec);
```
This JSON is fully compatible with the *official online Vega-Lite editor*.

**Summary: In-Memory Export Methods**

| Backend     | `to_svg()`    | `to_json()`              |
| ----------- | ------------- | ------------------------ |
| Rust Native | ✔️ SVG string | ❌ unsupported            |
| Altair      | ❌ (file-only) | ✔️ Vega-Lite JSON string |
| Matplotlib  |||

String-based export complements file export by enabling fully in-memory rendering and programmatic integration.

## Viewing Charts

Charton charts can be viewed directly inside *Evcxr Jupyter notebooks* using the `.show()` method.  

When running inside Evcxr Jupyter, Charton automatically prints the correct MIME content so that the chart appears inline.

Outside Jupyter (e.g., running a binary), `.show()` does nothing and simply returns `Ok(())`.

The rendering behavior differs depending on the selected backend.

## Rust Native Backend

The Rust-native backend renders charts to *inline SVG*.  

When `.show()` is called inside Evcxr Jupyter, the SVG is printed using `text/html` MIME type.

*Example*

```rust
use charton::prelude::*;
use polars::prelude::*;

let df = df![
    "x" => [1, 2, 3],
    "y" => [10, 20, 30]
]?;

let chart = Chart::build(&df)?
    .mark_point()?
    .encode((x("x"), y("y")))?
    .into_layered();

chart.show()?;   // displays inline SVG in Jupyter
```
*Internal Behavior*
```text
EVCXR_BEGIN_CONTENT text/html
<svg>...</svg>
EVCXR_END_CONTENT
```
This enables rich inline SVG display in notebooks.

### Altair Backend (Vega-Lite)

When using the Altair backend, `.show()` emits *Vega-Lite JSON* with the correct MIME type:
```bash
application/vnd.vegalite.v5+json
```
Jupyter then renders the chart using the built-in Vega-Lite renderer.

*Example*
```rust
chart.show()?;   // displays interactive Vega-Lite chart inside Jupyter
```
*Internal Behavior*
```text
EVCXR_BEGIN_CONTENT application/vnd.vegalite.v5+json
{ ... Vega-Lite JSON ... }
EVCXR_END_CONTENT
```
This produces interactive charts (tooltips, zooming, etc.) if supported by the notebook environment.

### Matplotlib Backend

The Matplotlib backend produces *base64-encoded PNG* images and sends them to the notebook using `image/png` MIME type.

*Example*

```rust
chart.show()?;   // displays inline PNG rendered by Matplotlib
```

*Internal Behavior*

```text
EVCXR_BEGIN_CONTENT image/png
<base64 image>
EVCXR_END_CONTENT
```
### Summary: What `.show()` displays in Jupyter

| *Backend* | *Output Type* | *MIME Type*     |
| ----------- | ----------- | ---------------------------------- |
| Rust Native | SVG         | `text/html`                        |
| Altair      | Vega-Lite   | `application/vnd.vegalite.v5+json` |
| Matplotlib  | PNG         | `image/png`                        |

`.show()` is designed to behave naturally depending on the backend, giving the best viewing experience for each renderer.

## Static interactive-style display in Jupyter (via `evcxr`)

Charton integrates with `evcxr` to display static charts *inline* inside Jupyter notebooks. This mode is “static” because the output is a fixed SVG, but it behaves “interactive-style” because:
- Each execution immediately re-renders the chart inside the notebook  
- Any changes to code/data result in instant visual updates  
- Ideal for exploration, education, and iterative refinement

This is similar to how Plotters or PlotPy integrate with `evcxr`.

### Example: Displaying a Charton chart inline in Jupyter

```rust
:dep charton = { version="0.4" }
:dep polars = { version="0.49" }

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

// Create sample data
let df = df![
    "length" => [5.1, 4.9, 4.7, 4.6, 5.0],
    "width"  => [3.5, 3.0, 3.2, 3.1, 3.6]
]?;

// Build a simple scatter plot
Chart::build(&df)?
    .mark_point()?
    .encode((x("length"), y("width")))?
    .show()?;   // <-- Displays directly inside the Jupyter cell
```
Even though the chart itself is static, the *workflow* feels interactive due to the rapid feedback loop.

## Summary

In this chapter, you learned how to:
- Load datasets from CSV, Parquet, and built-in sources
- Create essential chart types: scatter, bar, line, histogram, boxplot, layered plots
- Export your charts to SVG, PNG, and Vega JSON
- Preview visualizations in the notebook

With these foundations, you now have everything you need to build *end-to-end data visualizations* quickly and reliably. The next chapters will introduce the building blocks of Charton, including marks and eocodings.