mpl_wrap
Matplotlib helper functions for plotting wrapped, angular, or periodic data: angles, phases, times of day, longitudes, and anything else that repeats or rotates.
Manually plotting this data can be tricky. Using a modulus such as y % 360 is simple,
but introduces a few problems:
- Line jumps at the crossing points, and lines that stop short of the wrap boundaries at the crossing points
- Aliasing when data spans multiple crossings, obscuring the real underlying behavior
- Completely broken rendering for fill_between
mpl_wrap solves these issues, and provides simple functions to make plotting wrapped
data easy.
Installation
pip install mpl_wrap
Or install from source:
git clone https://github.com/scottshambaugh/mpl_wrap.git
cd mpl_wrap
uv sync --group dev
Basic Usage
import numpy as np
import matplotlib.pyplot as plt
from mpl_wrap import set_wrap, plot_wrapped, fill_between_wrapped, errorbar_wrapped
t = np.linspace(0, 10, 500)
angle = 80.0 * t # degrees
width = 5.0 + 4.0 * t # degrees
fig, ax = plt.subplots()
set_wrap(ax, wrapy=(0, 360)) # helpers on ax now wrap y into (0, 360)
fill_between_wrapped(ax, t, angle - width, angle + width, alpha=0.3, label='uncertainty')
plot_wrapped(ax, t, angle, label='angle')
ax.set(xlabel="time (s)", ylabel="angle (deg)")
ax.legend()
The helpers mirror their matplotlib counterparts, taking the target Axes as
the first argument plus optional wrapx / wrapy (min, max) windows:
| mpl_wrap | mirrors |
|---|---|
plot_wrapped(ax, x, y, ...) |
ax.plot |
scatter_wrapped(ax, x, y, ...) |
ax.scatter |
hlines_wrapped(ax, y, xmin, xmax) |
ax.hlines |
vlines_wrapped(ax, x, ymin, ymax) |
ax.vlines |
axhspan_wrapped(ax, ymin, ymax) |
ax.axhspan |
axvspan_wrapped(ax, xmin, xmax) |
ax.axvspan |
fill_between_wrapped(ax, x, y1, y2) |
ax.fill_between |
fill_betweenx_wrapped(ax, y, x1, x2) |
ax.fill_betweenx |
step_wrapped(ax, x, y, where=...) |
ax.step |
stairs_wrapped(ax, values, edges) |
ax.stairs |
errorbar_wrapped(ax, x, y, yerr, xerr) |
ax.errorbar |
Plus fill_around(ax, x, y, width), a corridor of constant width around a track.
It needs shapely (pip install mpl_wrap[geo]).
Each returns the same artist type as the method it mirrors, in the same Axes
container. The two span helpers return a list of Rectangle, since a band
across the seam is two rectangles.
Passing wrapx=False / wrapy=False disables wrapping for a single call (or
clears the stored window when passed to set_wrap), and wrapx=True /
wrapy=True requires the stored window.
set_wrap also sets the axis limits to the window by default, and ticks the
window evenly so that ticks land exactly on the window edges, at about the
automatic tick density. Opt out with set_lims=False / edge_ticks=False,
or pass seam_lines=True to mark the window edges with lines.
You must pass the original unwrapped data for these to work.
If your data is already wrapped, np.unwrap may be able to recover that if it's sampled at a high enough rate.
API
The helpers are free functions, and are also available as methods on an
AxesWrap axes. These three are equivalent:
from mpl_wrap import set_wrap, plot_wrapped, wrap_axes
# 1. Free functions, on any existing axes
fig, ax = plt.subplots()
set_wrap(ax, wrapy=(0, 360))
plot_wrapped(ax, t, angle)
# 2. Methods on an AxesWrap axes, created with the "wrap" projection
fig, ax = plt.subplots(subplot_kw={"projection": "wrap"})
ax.set_wrap(wrapy=(0, 360))
ax.plot_wrapped(t, angle)
# 3. Methods on an existing axes, upgraded in place from Axes to AxesWrap
fig, ax = plt.subplots()
wrap_axes(ax, wrapy=(0, 360))
ax.plot_wrapped(t, angle)
The data processing is also exposed on its own: wrap_line and wrap_points
take data plus windows and return the wrapped arrays without plotting anything
(also available as AxesWrap methods).
Wrapping x, y, or both
Both axes can be wrapped independently or together:
Datetime data
Datetime data and windows work on either axis. Here a five-day series is wrapped to show a time-of-day view:
set_wrap(ax, wrapx=(t0, t0 + np.timedelta64(1, "D")))
plot_wrapped(ax, times, signal)
Geographic data (longitude and latitude)
When wrapping around a globe, latitude is not periodic. A track that runs past the north pole comes back down the opposite side of the Earth, 180 deg away in longitude.
With geographic=True, mpl_wrap folds latitude at the poles and wraps longitude
at the antimeridian. On a cartopy GeoAxes this is on by default, but it works with
normal axes as well.
import cartopy.crs as ccrs
from mpl_wrap import fill_around, plot_wrapped, scatter_wrapped
x = np.linspace(0, 720, 400)
lon = -160 + 0.3 * x
lat = x
fig, ax = plt.subplots(subplot_kw={"projection": ccrs.Robinson()})
ax.set_global()
ax.coastlines()
fill_around(ax, lon, lat, 5, alpha=0.3) # 5 deg either side
plot_wrapped(ax, lon, lat)
scatter_wrapped(ax, lon[::40], lat[::40])
On a geographic axes fill_around's width is in degrees of arc on the
sphere, and the corridor crosses the poles intact. The filled helpers need
shapely here, which cartopy already installs.
Radians
Windows that are multiples of π/2 are automatically detected and labeled with ticks that are fractions of π.
set_wrap(ax, wrapy=(-np.pi, np.pi))
plot_wrapped(ax, t, angle)
Metadata
Release files for mpl-wrap 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| mpl_wrap-0.3.0.tar.gz | 37.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| mpl_wrap-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 78.1 kB
Release files / mpl_wrap-0.3.0.tar.gz
| Download URL | mpl_wrap-0.3.0.tar.gz |
|---|---|
| Size | 37.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
02aad748a9e068c0ac136a50be2543fcabe917637309833e028b785290839d8c
|
|
BLAKE2b-256 checksum How to use checksums |
a2e498fc238c0753daf9ac09807a36ffb7355997c8a81d3ff9c97c0584178ee7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 11, 2026.
Transparency logRelease files / mpl_wrap-0.3.0-py3-none-any.whl
| Download URL | mpl_wrap-0.3.0-py3-none-any.whl |
|---|---|
| Size | 41.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9f5d8386fdb75b4c9456e9dd7a403ca491b4b9fca4a01d978dd499cdef26f353
|
|
BLAKE2b-256 checksum How to use checksums |
eb2e46d48f94b7ca137a650fbc5dee7ed0ca8204091068167672a1f915d31f64
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 11, 2026.
Transparency log