Adjusting jitter width in Altair charts

altair
Author

ChuckPR

Published

February 21, 2025

In a previous post I made a strip plot with jitter. I realized as I was making that plot that I did not know how to adjust the “jitter width” (i.e how far points are spread apart). Below I describe one method for modifying jitter width.

import altair as alt
from vega_datasets import data

source = data.movies.url

I’ll set up the chart similar to last time. But, instead of calculating jitter using a vega-lite expression string, I will just write the expression in Python and I am also going to add a jitter width parameter to the calculation whose value is bound to a slider.

1slider = alt.binding_range(min=0, max=1, name="Jitter width:  ")
2jitter_width = alt.param(bind=slider, value=0.35)

base = (
    alt.Chart(source, height=alt.Step(25), width=500)
    .transform_calculate(
3        jitter=(alt.expr.random() - 0.5) * jitter_width,
    )
4    .add_params(
        jitter_width
    )
)
1
Here we create a slider.
2
The jitter width parameter is bound to the slider value with a default value of 0.35.
3
This is where the jitter is calculated. This transform adds a jitter column that we can reference later.
4
We need to add the jitter width parameter in order to reference it. This will also make the slider show up in the chart.

The rest of the chart is almost identical to the original. The only difference is the scale setting for the yOffset encoding. You want to fix the scale domain so that it does not change with your jitter width effectively canceling out your jitter spread adjustment.

x_field = "IMDB_Rating"
type_ = "Q"
sort = alt.EncodingSortField(field=x_field, op="median", order="descending")
axis_kwargs = {"titleFontSize": 14, "titlePadding": 15, "labelFontSize": 12}

points = base.mark_circle(
    size=20, stroke="steelblue", strokeOpacity=0.5, fill="steelblue", fillOpacity=0.15
).encode(
    y=alt.Y("Major_Genre:N").sort(sort).axis(title=None, **axis_kwargs),
    x=alt.X(f"{x_field}:{type_}"),
1    yOffset=alt.YOffset("jitter:Q").scale(domain=[-0.5, 0.5]),
)

ticks = base.mark_tick(stroke="firebrick", strokeOpacity=0.85, thickness=1.5).encode(
    y=alt.Y("Major_Genre:N").sort(sort),
    x=alt.X(f"{x_field}:{type_}", aggregate="median").axis(
        title="IMDB Rating", **axis_kwargs
    ),
)

points + ticks
1
Note how the scale domain is fixed.