Skip to content

ferrum.position

Position adjustment strategies for overlapping marks.

Position-adjustment value classes: Identity, Dodge, Jitter, Stack.

Identity

Explicit no-op position adjustment.

Distinct from position=None (which means "no adjustment declared"): Identity is part of the spec and round-trips through JSON. Use it when composing layered charts that need to opt out of an inherited stack or dodge on a per-layer basis.

Eligible marks: all.

Examples:

>>> import ferrum as fm
>>> fm.Chart(df).encode(x="grp", y="val").mark_bar(position=fm.Identity())

to_spec_dict

to_spec_dict() -> dict

Return the spec dict {"type": "identity"}.

Dodge

Side-by-side placement of marks grouped by a channel.

Eligible marks: bar, point, box, boxplot, boxen, swarm, violin, errorbar, errorband, ribbon, histogram, density.

Parameters:

Name Type Description Default
by str

Channel name to group by. Defaults to the color/fill channel when omitted.

None
padding float

Gap between dodged groups as a fraction of band width. Must be in [0, 1).

0.05

Raises:

Type Description
ValueError

If padding is outside [0, 1).

Examples:

>>> import ferrum as fm
>>> fm.Chart(df).encode(x="cat", y="val", color="grp").mark_bar(
...     position=fm.Dodge()
... )

to_spec_dict

to_spec_dict() -> dict

Return the serialized spec dict for this position adjustment.

Jitter

Random per-row noise applied to x, y, or both axes.

Uses a ChaCha8 RNG seeded from seed, making SVG output byte-deterministic for a given dataset and seed. When seed=None the Rust renderer derives a per-row seed from the row's data values via xxh3 — output remains deterministic across runs for fixed inputs.

Eligible marks: point, swarm, tick.

Parameters:

Name Type Description Default
axis ('x', 'y', 'both')

Which axis or axes to jitter.

"x"
width float

Maximum absolute displacement in scaled units. Must be > 0.

0.4
seed int or None

RNG seed. None means per-row data-derived seed (still deterministic).

None

Raises:

Type Description
ValueError

If axis is not one of "x", "y", "both".

ValueError

If width is <= 0.

Examples:

>>> import ferrum as fm
>>> fm.Chart(df).encode(x="grp", y="value").mark_point(
...     position=fm.Jitter(width=0.3, seed=42)
... )

to_spec_dict

to_spec_dict() -> dict

Return the serialized spec dict for this position adjustment.

Stack

Vertical accumulation of marks grouped by a channel.

Eligible marks: rect-style (bar, area, ribbon, histogram, density) and annotation-style (text, point, rule, tick). The latter sit on top of a stacked layer to label segments.

Parameters:

Name Type Description Default
by str

Channel name whose distinct values define the stack groups. Defaults to the color/fill channel when omitted.

None
offset ('zero', 'normalize', 'center')

Stack baseline strategy:

  • "zero" — standard cumulative stack from y = 0.
  • "normalize" — 100 % stack; each x-bin scales to a total of 1.
  • "center" — streamgraph; symmetric around y = 0.
"zero"
anchor ('top', 'mid')

Where each row's y output lands within its segment. "top" (default) returns the segment top so rect-style marks (bar, area, ribbon) draw from __stack_y_base__ to y. "mid" returns the segment midpoint so an annotation mark (mark_text, mark_point, …) sits at the visual centre of each stacked segment — used by composite marks like mark_class_prediction_error(show_counts=True) to land per-segment count labels without duplicating cumsum logic in Python.

"top"

Raises:

Type Description
ValueError

If offset is not one of "zero", "normalize", "center" or anchor is not one of "top", "mid".

Examples:

>>> import ferrum as fm
>>> fm.Chart(df).encode(x="year", y="count", color="category").mark_bar(
...     position=fm.Stack(offset="normalize")
... )

to_spec_dict

to_spec_dict() -> dict

Return the serialized spec dict for this position adjustment.

validate_position_eligibility

validate_position_eligibility(mark_name: str, position) -> None

Raise TypeError if mark_name does not accept position.

Called by Chart.mark_<name>(position=...) at construction time. Identity is accepted by every mark; other adjustments are constrained per the eligibility matrix in this module.