Skip to content
Draft
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
14 changes: 12 additions & 2 deletions ultraplot/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
from . import internals, externals, tests # noqa: F401
from .internals.benchmarks import _benchmark

with _benchmark("pyplot"):
from matplotlib import pyplot # noqa: F401
# Defer pyplot import - it's the biggest import time bottleneck
# It will be imported lazily in ui.py when needed
with _benchmark("cartopy"):
try:
import cartopy # noqa: F401
Expand Down Expand Up @@ -115,3 +115,13 @@

if rc["ultraplot.check_for_latest_version"]:
check_for_update("ultraplot")


# Lazy pyplot access for backward compatibility
def __getattr__(name):
"""Lazy load pyplot when accessed as ultraplot.pyplot."""
if name == "pyplot":
from .ui import _get_pyplot

return _get_pyplot()
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
20 changes: 7 additions & 13 deletions ultraplot/axes/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
import matplotlib.lines as mlines
import matplotlib.patches as mpatches
import matplotlib.ticker as mticker
import matplotlib.pyplot as mplt
import matplotlib as mpl
from packaging import version
import numpy as np
Expand Down Expand Up @@ -387,25 +386,20 @@
docstring._snippet_manager["plot.cycle"] = _cycle_docstring
docstring._snippet_manager["plot.cmap_norm"] = _cmap_norm_docstring


# Log plot docstrings - built without importing pyplot
_log_doc = """
Plot {kind}

UltraPlot is optimized for visualizing logarithmic scales by default. For cases with large differences in magnitude,
we recommend setting `rc["formatter.log"] = True` to enhance axis label formatting.
{matplotlib_doc}
"""

docstring._snippet_manager["plot.loglog"] = _log_doc.format(
kind="loglog", matplotlib_doc=mplt.loglog.__doc__
)

docstring._snippet_manager["plot.semilogy"] = _log_doc.format(
kind="semilogy", matplotlib_doc=mplt.semilogy.__doc__
)
See matplotlib.pyplot.{kind} for more details.
"""

docstring._snippet_manager["plot.semilogx"] = _log_doc.format(
kind="semilogx", matplotlib_doc=mplt.semilogx.__doc__
)
docstring._snippet_manager["plot.loglog"] = _log_doc.format(kind="loglog")
docstring._snippet_manager["plot.semilogy"] = _log_doc.format(kind="semilogy")
docstring._snippet_manager["plot.semilogx"] = _log_doc.format(kind="semilogx")

# Levels docstrings
# NOTE: In some functions we only need some components
Expand Down
32 changes: 30 additions & 2 deletions ultraplot/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
"""
The starting point for creating ultraplot figures.
"""
import matplotlib.pyplot as plt

from . import axes as paxes
from . import figure as pfigure
from . import gridspec as pgridspec
Expand All @@ -20,8 +18,31 @@
"ion",
"ioff",
"isinteractive",
# Note: pyplot is NOT in __all__ to prevent eager loading during star import
# It's accessible via __getattr__ for lazy loading: import ultraplot as uplt; uplt.pyplot
]

# Lazy pyplot import
_plt = None


def _get_pyplot():
"""Get matplotlib.pyplot, importing it lazily on first use."""
global _plt
if _plt is None:
import matplotlib.pyplot as plt

_plt = plt
return _plt


# Make pyplot accessible at module level
def __getattr__(name):
"""Lazy load pyplot when accessed as module attribute."""
if name == "pyplot":
return _get_pyplot()
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")


# Docstrings
_pyplot_docstring = """
Expand Down Expand Up @@ -58,6 +79,7 @@ def show(*args, **kwargs):
*args, **kwargs
Passed to `matplotlib.pyplot.show`.
"""
plt = _get_pyplot()
return plt.show(*args, **kwargs)


Expand All @@ -72,6 +94,7 @@ def close(*args, **kwargs):
*args, **kwargs
Passed to `matplotlib.pyplot.close`.
"""
plt = _get_pyplot()
return plt.close(*args, **kwargs)


Expand All @@ -86,6 +109,7 @@ def switch_backend(*args, **kwargs):
*args, **kwargs
Passed to `matplotlib.pyplot.switch_backend`.
"""
plt = _get_pyplot()
return plt.switch_backend(*args, **kwargs)


Expand All @@ -95,6 +119,7 @@ def ion():
Call `matplotlib.pyplot.ion`.
%(ui.pyplot)s
"""
plt = _get_pyplot()
return plt.ion()


Expand All @@ -104,6 +129,7 @@ def ioff():
Call `matplotlib.pyplot.ioff`.
%(ui.pyplot)s
"""
plt = _get_pyplot()
return plt.ioff()


Expand All @@ -113,6 +139,7 @@ def isinteractive():
Call `matplotlib.pyplot.isinteractive`.
%(ui.pyplot)s
"""
plt = _get_pyplot()
return plt.isinteractive()


Expand Down Expand Up @@ -141,6 +168,7 @@ def figure(**kwargs):
matplotlib.figure.Figure
"""
_parse_figsize(kwargs)
plt = _get_pyplot()
return plt.figure(FigureClass=pfigure.Figure, **kwargs)


Expand Down