Marks & Geometries
While Encodings and Scales define the mathematical relationship between data and space, the Mark is the physical manifestation of that relationship. A Mark is the geometric primitive used to represent a data point or a set of data points.
For the rules that classify marks into tiers and define statistical atoms and composite marks, see Design Rules: Marks, Tiers & Statistical Atoms.
The Role of a Mark
In Charton, a Mark is not just a drawing instruction; it is a Template that knows how to interpret resolved aesthetic values (pixels, hex codes, shapes) into final geometry.
As defined in the Mark trait, every mark type in Charton:
- Identifies itself: Each mark has a unique string identifier (e.g.,
"point","bar"). - Provides Defaults: Marks define fallback values for properties like
stroke,opacity, andshapeif they are not explicitly mapped to data. - Determines Rendering Logic: Different marks require different drawing strategies—a
Pointis a single coordinate, while anAreais a complex polygon.
Common Mark Types
Charton provides a rich set of marks to cover various visualization needs:
Point Mark (mark_point)
The simplest mark, representing each data row as an individual geometric shape.
- Dimensions: Primarily uses
XandY. - Aesthetics: Heavily utilizes
Shape,Size, andColor. - Use Case: Scatter plots and bubble charts.
- Layouts: When one axis is categorical,
.with_layout(...)decides how the points share the space of their category."jitter"scatters them at random,"beeswarm"packs them tightly so no two points overlap (best for small groups), and"quasirandom"spreads them with a density aware sequence so the cloud traces a violin outline (best for large groups). The quasirandom pairing can be changed with.with_quasirandom_method("pseudorandom").
Line Mark (mark_line)
Connects data points in a specific order (usually by the X-axis) to show trends.
- Connectivity: Unlike points, the Line mark treats a sequence of rows as a single continuous path. A missing
xorybreaks the path, so a hole in the data shows as a gap rather than a line drawn across it. See Missing Values & Gaps. - Visuals: Focuses on
stroke_widthandcolor.
Path Mark (mark_path)
Connects rows in row order without sorting and without closing the shape, and
groups them by path_group. This is the "follow the data" curve: trajectories,
contour lines, network edges, parallel coordinates. See
Connecting the dots for how it
differs from mark_line and mark_polygon.
.with_dash([dash, gap]) draws the outline as a dashed line (an empty pattern,
the default, is solid). Dashes are measured along the path, so a dashed contour
reads as a broken line on every backend, CPU and GPU alike.
Polygon Mark (mark_polygon)
The closed, filled form of the same geometry: rows sharing a path_group are
connected in order, the loop is closed and the interior is painted. Violin
outlines, map regions and hand-built filled ribbons use it (mark_geoshape is
the geographic name). It fills the polygon you give it; it does not extract the
region between two contour levels. .with_dash([dash, gap]) dashes the outline
(the fill stays solid).
Bar Mark (mark_bar)
Represents data as rectangles with a width (the category dimension) and a span (the value dimension).
- Baseline bars read
XandY: the rectangle grows from zero toY. - Floating bars add
Y2: the rectangle spansYtoY2instead. This is the geometry behind a candlestick body and a waterfall step. WithY2present,coloris an attribute rather than a lane (no dodging layout), and the value axis is not forced to include zero — the interval keeps the axis to itself. - Width and lanes come from the shared layout (
CoordLayout+ [Position]), so grouped bars sit side by side without manual offsets. Fill/interval marks tile (no gap); outline/point marks keep a small gap. See The position rules.
Area Mark (mark_area)
Similar to a line but filled between a baseline (Y2) and the data value (Y).
- Topology: Highlighting the volume between two series or between a series and the zero-axis.
- Gaps: A missing
xoryopens the area at that point instead of bridging it. See Missing Values & Gaps.
Specialized Marks
- Rule & Tick: Used for annotations or error margins.
- Rect: Drawing arbitrary rectangles based on coordinate pairs.
- Text: Placing strings directly into the coordinate space.
From Mark to Geometry: The Renderer
Behind every Mark lies a corresponding Renderer. When Charton enters the "Realization" phase (Chapter 3.4), it translates the mark's configuration into physical geometry:
- PointElement: A simple struct containing
x, y, shape, size. - PathConfig: A collection of points and stroke properties used for Lines and Areas.
- RectConfig: Defined by
x, y, width, heightfor Bars and Histograms.
Marks and Categorical Stacking
One of Charton's advanced features is how Marks handle Stacking and Grouping.
As seen in the MarkBar implementation, when multiple series exist on the same X-coordinate:
- Stacked: The
Yvalue of the second mark starts at theYend-point of the first. - Grouped (Side-by-Side): Marks are offset by a fraction of the category slot so they sit next to each other without manual coordinate calculation. The arithmetic lives in Position, shared by bars, boxes, error bars, points and violins.
Connecting the dots: line, path and polygon
Three geometries connect a sequence of points. They answer two questions differently:
- In what order are the points joined?
- Is the shape closed and filled?
| Geometry | Order | Grouped by | Closed? | Filled? | ggplot2 | Typical use |
|---|---|---|---|---|---|---|
mark_line | sorted by x | colour | no | no | geom_line | time series, trend lines |
mark_path | row order | path_group | no | no | geom_path | trajectory, contour line, edges |
mark_polygon | row order | path_group | yes | yes | geom_polygon | violin outline, map, filled ribbon |
mark_geoshape is mark_polygon under a geographic name; they are the same
renderer.
Open vs closed is the whole difference
Draw the same three points with each geometry and look at the produced path:
mark_path M p0 L p1 L p2 (stops at p2; stroked)
mark_polygon M p0 L p1 L p2 Z (Z joins p2 back to p0; filled)
So a "trajectory" and an "outline" are not the same idea, even though both are drawn from a sequence of points:
- a trajectory is an open curve — it has two loose ends and nothing to fill
⇒
mark_path; - an outline is a closed loop that encloses an area to fill ⇒
mark_polygon.
A contour line is the classic trap. A single contour can itself be a closed loop
(around a peak), but you still draw it with mark_path: you want the line,
not a filled disc. Only a filled contour band is a mark_polygon.
Two mistakes follow from ignoring the flag: giving an open curve to
mark_polygon adds a straight edge from the last point back to the first; and
giving a line loop to mark_polygon fills it, hiding everything inside.
#![allow(unused)] fn main() { // A trajectory: open, order preserved, stroked. chart!(x, y, id)? .mark_path()? .encode((alt::x("x"), alt::y("y"), alt::path_group("id")))?; // The same points as a filled region: closed and filled. chart!(x, y, id)? .mark_polygon()? .encode((alt::x("x"), alt::y("y"), alt::path_group("id")))?; }
Marks are primitives; charts are compositions
A mark draws the data it is given. It does not know about statistics or grouping — those belong to earlier stages. Complex chart types are therefore compositions, not new marks. A violin plot is a density statistic, the general band geometry and the shared polygon mark:
#![allow(unused)] fn main() { chart!(iris)? .transform_density( DensityTransform::new("sepal_length") .with_as("sepal_length", "density") .with_trim(true), )? .transform_band(BandTransform::new("sepal_length", "density"))? .mark_polygon()? .encode((alt::x("x"), alt::y("y"), alt::path_group("path_group")))? }
Rainclouds and split violins stack more layers (.and(…)) on the same parts.
See The Layer Pipeline and
Violin.
Visual Consistency (The Mark Trait)
In mark.rs, the Mark trait ensures that all geometric primitives share a common interface. This allows the LayeredChart to treat a PointChart and a LineChart identically during the high-level orchestration phase, even though their low-level draw calls are completely different.
Key Takeaways
- Marks are the "ink" on the page.
- Mark choice changes the narrative of the data (e.g., a Line implies a trend, while a Bar implies a comparison).
- Geometric resolution is the final step where abstract scales are converted into physical shapes (Circles, Rects, Paths).