This script will draw axis break symbols on matplotlib plots. This is good if the plot's axes don't start at 0.
```
import matplotlib.pyplot as plt
import matplotlib.transforms as transforms
import matplotlib.patches as mpatches
from matplotlib.path import Path
import numpy as np
def add_axis_break(ax, axis='x', position=0.05, size=10, angle=75,
linewidth=0.9, color='black', zorder=10, gap=3,
fill_color='white', fill_zorder=9):
"""
Add break symbols to indicate discontinuous axes.
Parameters
----------
ax : matplotlib axes
The axes to add the break symbol to
axis : str, default='x'
Which axis to add the break to ('x' or 'y')
position : float, default=0.5
Position along the axis (0-1) where to place the break
size : float, default=10
Size of the break symbol in points (fixed size)
angle : float, default=30
Angle of the diagonal lines in degrees
linewidth : float, default=1.5
Width of the break lines
color : str, default='black'
Color of the break lines
zorder : int, default=10
Drawing order (higher = on top)
gap : float, default=3
Gap between the two diagonal lines in points
fill_color : str or None, default='white'
Color to fill between the lines. Use None for transparent.
fill_zorder : int, default=9
Drawing order for the fill (should be less than zorder)
"""
if axis.lower() == 'x':
# Get axis limits and position in data coordinates
xlim = ax.get_xlim()
x_pos = xlim[0] + position * (xlim[1] - xlim[0])
# Create a blended transform:
# x in data coordinates, y in axes coordinates
trans = transforms.blended_transform_factory(ax.transData, ax.transAxes)
# Convert size from points to data coordinates for x
# and to axes coordinates for y
fig = ax.get_figure()
dpi = fig.dpi
points_to_pixels = size / 72.0 * dpi
# Get the axis dimensions in pixels
bbox = ax.get_window_extent()
axis_height_pixels = bbox.height
axis_width_pixels = bbox.width
# Convert pixel size to data coordinates
x_scale = (xlim[1] - xlim[0]) / axis_width_pixels
dx = points_to_pixels * x_scale
# Convert pixel size to axes coordinates for y
dy = points_to_pixels / axis_height_pixels
# Center point on the axis spine
y_center = 0 # bottom of axes
# Convert angle to radians
angle_rad = np.radians(angle)
# Calculate diagonal line endpoints
x_offset = dx * np.cos(angle_rad)
y_offset = dy * np.sin(angle_rad)
# Gap in data coordinates
gap_data = gap / 72.0 * dpi * x_scale
# Create fill patch if requested
if fill_color is not None:
# Create polygon vertices for the fill area
vertices = [
(x_pos - x_offset/2, y_center - y_offset/2),
(x_pos + x_offset/2, y_center + y_offset/2),
(x_pos + x_offset/2 + gap_data, y_center + y_offset/2),
(x_pos - x_offset/2 + gap_data, y_center - y_offset/2),
(x_pos - x_offset/2, y_center - y_offset/2)
]
# Create the polygon patch
poly = mpatches.Polygon(vertices, closed=True,
transform=trans,
facecolor=fill_color,
edgecolor='none',
zorder=fill_zorder,
clip_on=False)
ax.add_patch(poly)
# First diagonal line
line1 = plt.Line2D([x_pos - x_offset/2, x_pos + x_offset/2],
[y_center - y_offset/2, y_center + y_offset/2],
transform=trans, color=color, linewidth=linewidth,
zorder=zorder, clip_on=False)
# Second diagonal line (offset by gap)
line2 = plt.Line2D([x_pos - x_offset/2 + gap_data, x_pos + x_offset/2 + gap_data],
[y_center - y_offset/2, y_center + y_offset/2],
transform=trans, color=color, linewidth=linewidth,
zorder=zorder, clip_on=False)
# Add lines to axes
ax.add_line(line1)
ax.add_line(line2)
elif axis.lower() == 'y':
# Get axis limits and position in data coordinates
ylim = ax.get_ylim()
y_pos = ylim[0] + position * (ylim[1] - ylim[0])
# Create a blended transform:
# x in axes coordinates, y in data coordinates
trans = transforms.blended_transform_factory(ax.transAxes, ax.transData)
# Convert size from points to coordinates
fig = ax.get_figure()
dpi = fig.dpi
points_to_pixels = size / 72.0 * dpi
# Get the axis dimensions in pixels
bbox = ax.get_window_extent()
axis_height_pixels = bbox.height
axis_width_pixels = bbox.width
# Convert pixel size to axes coordinates for x
dx = points_to_pixels / axis_width_pixels
# Convert pixel size to data coordinates for y
y_scale = (ylim[1] - ylim[0]) / axis_height_pixels
dy = points_to_pixels * y_scale
# Center point on the axis spine
x_center = 0 # left of axes
# Convert angle to radians
angle_rad = np.radians(angle)
# Calculate diagonal line endpoints
x_offset = dx * np.sin(angle_rad)
y_offset = dy * np.cos(angle_rad)
# Gap in data coordinates
gap_data = gap / 72.0 * dpi * y_scale
# Create fill patch if requested
if fill_color is not None:
# Create polygon vertices for the fill area
vertices = [
(x_center - x_offset/2, y_pos - y_offset/2),
(x_center + x_offset/2, y_pos + y_offset/2),
(x_center + x_offset/2, y_pos + y_offset/2 + gap_data),
(x_center - x_offset/2, y_pos - y_offset/2 + gap_data),
(x_center - x_offset/2, y_pos - y_offset/2)
]
# Create the polygon patch
poly = mpatches.Polygon(vertices, closed=True,
transform=trans,
facecolor=fill_color,
edgecolor='none',
zorder=fill_zorder,
clip_on=False)
ax.add_patch(poly)
# First diagonal line
line1 = plt.Line2D([x_center - x_offset/2, x_center + x_offset/2],
[y_pos - y_offset/2, y_pos + y_offset/2],
transform=trans, color=color, linewidth=linewidth,
zorder=zorder, clip_on=False)
# Second diagonal line (offset by gap)
line2 = plt.Line2D([x_center - x_offset/2, x_center + x_offset/2],
[y_pos - y_offset/2 + gap_data, y_pos + y_offset/2 + gap_data],
transform=trans, color=color, linewidth=linewidth,
zorder=zorder, clip_on=False)
# Add lines to axes
ax.add_line(line1)
ax.add_line(line2)
else:
raise ValueError("axis must be 'x' or 'y'")
return ax
```