Encodings
Encodings are the core of Charton’s declarative visualization system. They determine how data fields map to visual properties such as:
- Position (
x,y,y2,theta) - Color
- Shape
- Size
- Text labels
Every chart in Charton combines:
- A mark (point, line, bar, arc, rect, etc.)
- Encodings that map data fields to visual channels
This chapter explains all encoding channels, how they work, and provides complete code examples using mtcars.
What Are Encodings?
An encoding assigns a data field to a visual channel.
#![allow(unused)] fn main() { Chart::build(&df)? .mark_point() .encode(( x("hp"), y("mpg"), color("cyl"), ))?; }
This produces a scatter plot:
- X axis → horsepower
- Y axis → miles per gallon
- Color → number of cylinders
Encoding System Architecture
Every encoding implements the following trait:
#![allow(unused)] fn main() { pub trait IntoEncoding { fn apply(self, enc: &mut Encoding); } }
Users never interact with Encoding directly.
They simply write:
#![allow(unused)] fn main() { .encode((x("A"), y("B"), color("C"))) }
The API supports tuple-based composition of up to 9 encodings.
Position Encodings
X – Horizontal Position
The X channel places data along the horizontal axis.
✔ When to use X
- Continuous values (e.g.,
hp,mpg,disp) - Categorical values (
cyl,gear,carb) - Histogram binning
- Log scales
API
#![allow(unused)] fn main() { x("column_name") }
Optional settings
#![allow(unused)] fn main() { x("hp") .with_bins(30) .with_scale(Scale::Log) .with_zero(true) }
Example: mtcars horsepower vs mpg
#![allow(unused)] fn main() { let df = load_dataset("mtcars"); Chart::build(&df) .mark_point() .encode(( x("hp"), y("mpg"), )); }
Expected: Scatter plot showing hp vs mpg.
Y – Vertical Position
The Y channel has identical behavior to X.
Example
#![allow(unused)] fn main() { Chart::build(&df) .mark_point() .encode(( x("wt"), y("mpg"), )); }
Expected: Heavier cars generally have lower mpg.
Y2 – Second Vertical Coordinate
Used when a mark needs two vertical positions:
- Interval bands
- Confidence intervals
- Error bars
- Range rules
Example: Upper & Lower MPG Bounds
#![allow(unused)] fn main() { Chart::build(&df) .mark_area() .encode(( x("hp"), y("mpg_low"), y2("mpg_high"), )); }
Angular Position: θ (Theta)
Used in:
- Pie charts
- Donut charts
- Radial bar charts
Example: Pie chart of cylinders
#![allow(unused)] fn main() { Chart::build(&df) .mark_arc() .encode(( theta("count"), color("cyl"), )); }
Color Encoding
Color maps a field to the fill color of a mark.
✔ When to use
- Categorical grouping
- Continuous magnitude
- Heatmaps
- Parallel categories
Example: Color by gears
#![allow(unused)] fn main() { Chart::build(&df) .mark_point() .encode(( x("hp"), y("mpg"), color("gear"), )); }
Shape Encoding
Shape – Point Symbol Mapping
Only applies to point marks.
Available shapes include:
- Circle
- Square
- Triangle
- Cross
- Diamond
- Star
Example
#![allow(unused)] fn main() { Chart::build(&df) .mark_point() .encode(( x("hp"), y("mpg"), shape("cyl"), )); }
Size Encoding
Size – Radius / Area Encoding
Used for:
- Bubble plots
- Weighted scatter plots
- Emphasizing magnitude
Example: Bubble plot with weight
#![allow(unused)] fn main() { Chart::build(&df) .mark_point() .encode(( x("hp"), y("mpg"), size("wt"), )); }
Opacity Encoding
Opacity – Transparency
Used for:
- Reducing overplotting
- Encoding density
- Showing relative uncertainty
Example: Opacity mapped to horsepower
#![allow(unused)] fn main() { Chart::build(&df) .mark_point() .encode(( x("wt"), y("mpg"), opacity("hp"), )); }
Text Encoding
Text – Label Encoding
Works with:
- Point labels
- Bar labels
- Annotation marks
Example: Label each point with car model
#![allow(unused)] fn main() { Chart::build(&df) .mark_text() .encode(( x("hp"), y("mpg"), text("model"), )); }
Stroke Encoding
Stroke – Outline Color
Useful when:
- Fill color is already used
- Emphasizing boundaries
- Donut chart outlines
Example
#![allow(unused)] fn main() { Chart::build(&df) .mark_point() .encode(( x("hp"), y("mpg"), stroke("gear"), )); }
Stroke Width Encoding
Stroke Width – Border Thickness
Used for:
- Highlighting
- Encoding magnitude
- Interval charts
Example
#![allow(unused)] fn main() { Chart::build(&df) .mark_point() .encode(( x("hp"), y("mpg"), stroke_width("wt"), )); }
Combined Example: All Encodings
This chart uses eight encodings simultaneously:
#![allow(unused)] fn main() { Chart::build(&df) .mark_point() .encode(( x("hp"), y("mpg"), color("cyl"), shape("gear"), size("wt"), opacity("qsec"), stroke("carb"), stroke_width("drat"), )); }
Expected:
A rich multi-dimensional visualization of mtcars.
Configuring Encodings (The Intent Pattern)
Charton uses a unified interface where you define your Intent (e.g., "I want this column to be on the X-axis using a Log scale"), and the engine handles the Resolution (calculating the actual pixel coordinates).
Each encoding (like X, Color, or Size) follows a Fluent Builder Pattern. You can refine how the mapping behaves by chaining methods on the encoding object before passing it to the chart.
Position Encoding (x, y)
Position encodings control the spatial layout.
#![allow(unused)] fn main() { chart.encode(( x("gdp") .with_scale(Scale::Log) // Use logarithmic transformation .with_domain(ScaleDomain::Continuous(0.0, 100.0)) // Force limits .with_zero(false) // Don't force 0.0 into view .with_bins(10), // Aggregate data into 10 bins y("population") ))? }
Aesthetic Encoding (shape, size, opacity)
These control the "look" of the marks based on data values.
#![allow(unused)] fn main() { chart.encode(( color("species") .with_scale(Scale::Ordinal), // Explicitly treat as categorical size("magnitude") .with_domain(ScaleDomain::Continuous(1.0, 10.0)) ))? }
The "Intent vs. Resulution" Architecture
One of the most powerful features of Charton's encoding system is the separation of User Intent and System Resolution.
- Intent (Inputs): When you call
x("price").with_scale(Scale::Log), you are defining a specification. - Resolution (Outputs): During the
build()phase, Charton's engine scans the data, finds the min/max values, applies your overrides, and "back-fills" aResolvedScale.
Why this matters:
Because the resolved_scale is stored inside the encoding (often wrapped in an Arc), multiple layers can share the same scale. If you have a scatter plot and a regression line in the same chart, they will automatically synchronize their axes because they refer to the same resolved intent.
Avoiding "Mega-Methods"
Charton avoids polluting the main Chart API. Notice that methods like with_bins belong to the X or Y structs, not the Chart itself.
- Incorrect:
chart.set_x_bins(10)(Bloats the main API) - Correct:
chart.encode(x("col").with_bins(10))(Keeps logic namespaced)
This ensures that as Charton adds more complex encoding features (like time-unit formatting or custom color palettes), the top-level API remains clean and easy to navigate.
Tips & Best Practices
✔ Use color for major categories
Examples: cyl, gear, carb.
✔ Use size sparingly Only when magnitude matters.
✔ Avoid using both color & shape unless required Choose one main grouping.
✔ Use opacity to reduce overplotting mtcars has many overlapping data points.
✔ Avoid encoding more than 5 dimensions Human perception becomes overloaded.
Summary Table
| Channel | Purpose | Works With | Example |
|---|---|---|---|
x | horizontal position | all marks | x("hp") |
y | vertical position | all marks | y("mpg") |
y2 | interval upper bound | area, rule | y2("high") |
theta | angle (pie/donut) | arc | theta("count") |
color | fill color | all | color("cyl") |
shape | symbol | point | shape("gear") |
size | area/size | point | size("wt") |
opacity | transparency | point/area | opacity("hp") |
text | labels | text mark | text("model") |
stroke | outline color | point/rect/arc | stroke("carb") |
stroke_width | outline thickness | all | stroke_width("drat") |