Skip to content

Gallery

Every figure on this page is drawn from the bundled demo while the docs are built, by the code shown under it, so what you see is what the current release renders. Hover a fixation or a word for its values. The app draws the same figures from its plot controls, and its Share subtab prints the code that reproduces whichever one is on screen.

All of them start from the demo and one of its trials:

import scanpath_studio as sps

words, fixations = sps.load_sample_data()
pid, tid = "l37_1129", "l37_1129_2_1_1_Ele_r0"

A reading

The app's Scanpath design, which plot_scanpath draws by default: each fixation where it landed, sized by its duration, and the saccades between them, over the text at its recorded position. The orange words are the answer to the trial's question.

fig = sps.plot_scanpath(words, fixations, pid, tid)

Where the participant dwelt

The heatmap on its own, here as a smooth duration-weighted density rather than one tint per word; the app's Heatmap controls offer both.

fig = sps.plot_scanpath(
    words,
    fixations,
    pid,
    tid,
    show_heatmap=True,
    heatmap_style="Interpolated",
    show_fixations=False,
    show_saccades=False,
)

Fixations by text line

Each fixation colored by the line of text it was assigned to: a quick check for vertical drift, which shows up as one line's color creeping onto the next.

fig = sps.plot_scanpath(words, fixations, pid, tid, color_by_line=True)

Saccades by reading class

Each saccade colored by its role in reading: forward, skip, refixation, return sweep, or regression.

ForwardSkipRefixationReturn sweepRegressionOther

fig = sps.plot_scanpath(
    words,
    fixations,
    pid,
    tid,
    saccade_color_mode="By type",
    saccade_type_legend=False,
)

A linear-reading schematic

One sentence's fixations snapped above the words they landed on, with the saccades arced over the text. It no longer shows exact positions, so the figure labels itself an Illustration.

fig = sps.plot_scanpath(
    words,
    fixations,
    pid,
    tid,
    fixation_snap_to_word=True,
    saccade_render_mode="Arc",
    fix_index_range=(124, 139),
)

Two participants, one text

The first fifty fixations of two trials of the same paragraph, on one canvas in two colors. The trials can also sit side by side, or come from two different datasets.

fig = sps.compare_scanpaths(
    words,
    fixations,
    (pid, tid),
    ("l7_1090", "l7_1090_2_1_1_Ele_r0"),
    fix_index_range=(1, 50),
    show_legend=True,
)

Raw gaze under the fixations

Gaze samples, colored by time, under the fixations, which are drawn hollow so the samples show through. The demo ships no recorded samples, so this trial's are synthesized from its fixations: the figure shows the layer, not real data.

raw_gaze = sps.load_sample_raw_gaze()
fig = sps.plot_scanpath(
    words,
    fixations,
    "l37_1129",
    "l37_1129_2_2_2_Adv_r0",
    raw_gaze=raw_gaze,
    hollow_fixations=True,
    show_saccades=False,
)

The replay

The reading unfolding fixation by fixation, here its first forty. Press ▶; the app replays in real time or faster, and exports the replay as HTML, GIF or MP4.

fig = sps.animate_scanpath(words, fixations, pid, tid, fix_index_range=(1, 40))

A text, word by word

Beyond single trials: the total fixation duration on every word of one text, averaged over the demo's participants, with ± one standard deviation as a band. The app's Corpus Analysis view draws this and more.

# The demo's word table carries EyeLink's own measures (IA_DWELL_TIME, …).
one_text = words[words["unique_paragraph_id"] == "2_1_1_Ele"]
profile = (
    one_text.groupby("IA_ID")
    .agg(
        value=("IA_DWELL_TIME", "mean"),
        sd=("IA_DWELL_TIME", "std"),
        word_text=("IA_LABEL", "first"),
    )
    .rename_axis("word_id")  # the column plot_corpus_figure reads
    .reset_index()
    .assign(lo=lambda d: d.value - d.sd, hi=lambda d: d.value + d.sd)
)
fig = sps.plot_corpus_figure(
    profile, kind="profile", measure_label="Total fixation duration (ms)"
)