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 ```