Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions barphase.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.pyplot as plt
import numpy as np
from phase_breaks import draw_phase_breaks

# Data for Escape-Maintained Off-Task Behavior (Frequency per 10-minute interval)
# Lower frequency is the desired outcome here.
Expand Down Expand Up @@ -68,9 +69,9 @@
ax.set_ylabel("Frequency of Escape-Maintained Off-Task Behavior")
ax.set_title("Impact of Negative Reinforcement on Off-Task Behavior", fontsize=14, pad=20)

# Add vertical dotted lines for phase changes
for boundary in phase_boundaries:
ax.axvline(x=boundary + 0.5, color='black', linestyle=':', linewidth=1.5)
# Add APA/JABA-style zig-zag phase breaks.
# Change styles to "solid" if the publication target requires solid separators.
draw_phase_breaks(ax, phase_boundaries, styles="dotted", zigzag=True)

# Add phase labels above the graph
current_x_pos = 0.5 # Start position for the first label (center of first bar)
Expand Down
13 changes: 7 additions & 6 deletions linephase.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.pyplot as plt
import numpy as np
from phase_breaks import draw_phase_breaks

# Data for Escape-Maintained Off-Task Behavior (Frequency per 10-minute interval)
# Lower frequency is the desired outcome here.
Expand Down Expand Up @@ -72,10 +73,10 @@
ax.set_ylabel("Frequency of Escape-Maintained Off-Task Behavior")
ax.set_title("Impact of Negative Reinforcement on Off-Task Behavior", fontsize=14, pad=20)

# Add vertical dotted lines for phase changes
# These lines should be placed at the boundary *between* the last point of one phase and the first point of the next
for boundary in phase_boundaries:
ax.axvline(x=boundary + 0.5, color='black', linestyle=':', linewidth=1.5)
# Add APA/JABA-style zig-zag phase breaks.
# Boundaries are placed between the last point of one phase and the first point
# of the next. Use styles="solid" when a manuscript requires solid breaks.
draw_phase_breaks(ax, phase_boundaries, styles="dotted", zigzag=True)

# Add phase labels above the graph
# Calculate x positions for labels within each phase
Expand Down
11 changes: 6 additions & 5 deletions loglin.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.pyplot as plt
import numpy as np
from phase_breaks import draw_phase_breaks

# Data for Escape-Maintained Off-Task Behavior (Frequency per 10-minute interval)
# Lower frequency is the desired outcome here.
Expand Down Expand Up @@ -77,9 +78,9 @@
ax.set_ylabel("Frequency of Escape-Maintained Off-Task Behavior (Log Scale)")
ax.set_title("Impact of Negative Reinforcement on Off-Task Behavior Across Phases (Log-Linear)", fontsize=14, pad=20)

# Add vertical dotted lines for phase changes
for boundary in phase_boundaries:
ax.axvline(x=boundary + 0.5, color='black', linestyle=':', linewidth=1.5)
# Add APA/JABA-style zig-zag phase breaks.
# Change styles to "solid" if the publication target requires solid separators.
draw_phase_breaks(ax, phase_boundaries, styles="dotted", zigzag=True)

# Add phase labels above the graph
current_x_pos_start = 1 # Starting x for label calculation based on 1-indexed sessions
Expand Down
107 changes: 107 additions & 0 deletions phase_breaks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
"""Helpers for APA/JABA-style phase-change separators.

The plotting scripts in this repo already draw phase boundaries with
``ax.axvline``. This module keeps that simple path, but adds a reusable
zig-zag separator for papers that require phase breaks to be shown as
asymptote-like vertical breaks instead of plain straight lines.
"""

from __future__ import annotations

from collections.abc import Iterable, Sequence

import numpy as np
from matplotlib.axes import Axes
from matplotlib.transforms import blended_transform_factory


def draw_phase_breaks(
ax: Axes,
phase_boundaries: Iterable[float],
*,
styles: Sequence[str] | str = "dotted",
zigzag: bool = True,
color: str = "black",
linewidth: float = 1.5,
zigzag_width: float = 0.18,
zigzag_steps: int = 18,
) -> None:
"""Draw publication-friendly phase-change separators.

Parameters
----------
ax:
Matplotlib axes to draw on.
phase_boundaries:
Session indices after which a new phase starts. A boundary of ``24``
is drawn at ``24.5``, between sessions 24 and 25.
styles:
Either one style for every boundary or one style per boundary.
Accepted values are ``"dotted"``, ``"solid"``, ``"dashed"``, and
Matplotlib linestyles such as ``":"`` or ``"--"``.
zigzag:
When true, draw a vertical zig-zag phase separator. When false, draw
a straight line with the requested style.
color:
Separator color.
linewidth:
Separator stroke width.
zigzag_width:
Horizontal zig-zag amplitude in x-axis data units.
zigzag_steps:
Number of line segments used from the bottom to the top of the plot.
"""

boundaries = list(phase_boundaries)
if isinstance(styles, str):
resolved_styles = [styles] * len(boundaries)
else:
resolved_styles = list(styles)
if len(resolved_styles) != len(boundaries):
raise ValueError("styles must be one value or match phase_boundaries length")

transform = blended_transform_factory(ax.transData, ax.transAxes)

for boundary, style in zip(boundaries, resolved_styles):
x_position = boundary + 0.5
linestyle = _resolve_linestyle(style)

if not zigzag:
ax.axvline(
x=x_position,
color=color,
linestyle=linestyle,
linewidth=linewidth,
)
continue

y_values = np.linspace(0, 1, zigzag_steps + 1)
x_values = np.array(
[
x_position + (zigzag_width if index % 2 else -zigzag_width)
for index in range(len(y_values))
]
)

ax.plot(
x_values,
y_values,
color=color,
linestyle=linestyle,
linewidth=linewidth,
transform=transform,
clip_on=False,
solid_capstyle="butt",
)


def _resolve_linestyle(style: str) -> str:
normalized = style.strip().lower()
aliases = {
"dotted": ":",
"dot": ":",
"solid": "-",
"dashed": "--",
"dash": "--",
}
return aliases.get(normalized, style)