Skip to content

Commit edbb90b

Browse files
authored
Merge branch 'main' into lazy-imports-perf
2 parents c7ad117 + facda2c commit edbb90b

21 files changed

Lines changed: 1935 additions & 289 deletions

.github/workflows/build-ultraplot.yml

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ jobs:
4343
4444
- name: Test Ultraplot
4545
run: |
46-
pytest --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml ultraplot
46+
pytest -n auto --cov=ultraplot --cov-branch --cov-report term-missing --cov-report=xml ultraplot
4747
4848
- name: Upload coverage reports to Codecov
4949
uses: codecov/codecov-action@v5
@@ -71,29 +71,55 @@ jobs:
7171
cache-environment: true
7272
cache-downloads: false
7373

74+
# Cache Baseline Figures (Restore step)
75+
- name: Cache Baseline Figures
76+
id: cache-baseline
77+
uses: actions/cache@v4
78+
with:
79+
path: ./ultraplot/tests/baseline # The directory to cache
80+
# Key is based on OS, Python/Matplotlib versions, and the PR number
81+
key: ${{ runner.os }}-baseline-pr-${{ github.event.pull_request.number }}-${{ inputs.python-version }}-${{ inputs.matplotlib-version }}
82+
restore-keys: |
83+
${{ runner.os }}-baseline-pr-${{ github.event.pull_request.number }}-${{ inputs.python-version }}-${{ inputs.matplotlib-version }}-
84+
85+
# Conditional Baseline Generation (Only runs on cache miss)
7486
- name: Generate baseline from main
87+
# Skip this step if the cache was found (cache-hit is true)
88+
if: steps.cache-baseline.outputs.cache-hit != 'true'
7589
run: |
76-
mkdir -p baseline
90+
mkdir -p ultraplot/tests/baseline
91+
# Checkout the base branch (e.g., 'main') to generate the official baseline
7792
git fetch origin ${{ github.event.pull_request.base.sha }}
7893
git checkout ${{ github.event.pull_request.base.sha }}
94+
95+
# Install the Ultraplot version from the base branch's code
96+
pip install --no-build-isolation --no-deps .
97+
98+
# Generate the baseline images and hash library
7999
python -c "import ultraplot as plt; plt.config.Configurator()._save_yaml('ultraplot.yml')"
80-
pytest -W ignore \
81-
--mpl-generate-path=./baseline/ \
100+
pytest -x -W ignore \
101+
--mpl-generate-path=./ultraplot/tests/baseline/ \
82102
--mpl-default-style="./ultraplot.yml"\
83103
ultraplot/tests
84-
git checkout ${{ github.sha }} # Return to PR branch
85104
105+
# Return to the PR branch for the rest of the job
106+
git checkout ${{ github.sha }}
107+
108+
# Image Comparison (Uses cached or newly generated baseline)
86109
- name: Image Comparison Ultraplot
87110
run: |
111+
# Re-install the Ultraplot version from the current PR branch
112+
pip install --no-build-isolation --no-deps .
113+
88114
mkdir -p results
89115
python -c "import ultraplot as plt; plt.config.Configurator()._save_yaml('ultraplot.yml')"
90-
pytest -W ignore \
91-
--mpl \
92-
--mpl-baseline-path=./baseline/ \
93-
--mpl-results-path=./results/ \
94-
--mpl-generate-summary=html \
95-
--mpl-default-style="./ultraplot.yml" \
96-
ultraplot/tests
116+
pytest -x -W ignore \
117+
--mpl \
118+
--mpl-baseline-path=./ultraplot/tests/baseline \
119+
--mpl-results-path=./results/ \
120+
--mpl-generate-summary=html \
121+
--mpl-default-style="./ultraplot.yml" \
122+
ultraplot/tests
97123
98124
# Return the html output of the comparison even if failed
99125
- name: Upload comparison failures

docs/_scripts/fetch_releases.py

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
Dynamically build what's new page based on github releases
33
"""
44

5-
import requests, re
5+
import re
66
from pathlib import Path
77

8+
import requests
9+
from m2r2 import convert
10+
811
GITHUB_REPO = "ultraplot/ultraplot"
912
OUTPUT_RST = Path("whats_new.rst")
1013

@@ -14,21 +17,10 @@
1417

1518
def format_release_body(text):
1619
"""Formats GitHub release notes for better RST readability."""
17-
lines = text.strip().split("\n")
18-
formatted = []
19-
20-
for line in lines:
21-
line = line.strip()
22-
23-
# Convert Markdown ## Headers to RST H2
24-
if line.startswith("## "):
25-
title = line[3:].strip() # Remove "## " from start
26-
formatted.append(f"{title}\n{'~' * len(title)}\n") # RST H2 Format
27-
else:
28-
formatted.append(line)
20+
# Convert Markdown to RST using m2r2
21+
formatted_text = convert(text)
2922

3023
# Convert PR references (remove "by @user in ..." but keep the link)
31-
formatted_text = "\n".join(formatted)
3224
formatted_text = re.sub(
3325
r" by @\w+ in (https://github.com/[^\s]+)", r" (\1)", formatted_text
3426
)

docs/colorbars_legends.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,3 +469,44 @@
469469
ax = axs[1]
470470
ax.legend(hs2, loc="b", ncols=3, center=True, title="centered rows")
471471
axs.format(xlabel="xlabel", ylabel="ylabel", suptitle="Legend formatting demo")
472+
# %% [raw] raw_mimetype="text/restructuredtext"
473+
# .. _ug_guides_decouple:
474+
#
475+
# Decoupling legend content and location
476+
# --------------------------------------
477+
#
478+
# Sometimes you may want to generate a legend using handles from specific axes
479+
# but place it relative to other axes. In UltraPlot, you can achieve this by passing
480+
# both the `ax` and `ref` keywords to :func:`~ultraplot.figure.Figure.legend`
481+
# (or :func:`~ultraplot.figure.Figure.colorbar`). The `ax` keyword specifies the
482+
# axes used to generate the legend handles, while the `ref` keyword specifies the
483+
# reference axes used to determine the legend location.
484+
#
485+
# For example, to draw a legend based on the handles in the second row of subplots
486+
# but place it below the first row of subplots, you can use
487+
# ``fig.legend(ax=axs[1, :], ref=axs[0, :], loc='bottom')``. If ``ref`` is a list
488+
# of axes, UltraPlot intelligently infers the span (width or height) and anchors
489+
# the legend to the appropriate outer edge (e.g., the bottom-most axis for ``loc='bottom'``
490+
# or the right-most axis for ``loc='right'``).
491+
492+
# %%
493+
import numpy as np
494+
495+
import ultraplot as uplt
496+
497+
fig, axs = uplt.subplots(nrows=2, ncols=2, refwidth=2, share=False)
498+
axs.format(abc="A.", suptitle="Decoupled legend location demo")
499+
500+
# Plot data on all axes
501+
state = np.random.RandomState(51423)
502+
data = (state.rand(20, 4) - 0.5).cumsum(axis=0)
503+
for ax in axs:
504+
ax.plot(data, cycle="mplotcolors", labels=list("abcd"))
505+
506+
# Legend 1: Content from Row 2 (ax=axs[1, :]), Location below Row 1 (ref=axs[0, :])
507+
# This places a legend describing the bottom row data underneath the top row.
508+
fig.legend(ax=axs[1, :], ref=axs[0, :], loc="bottom", title="Data from Row 2")
509+
510+
# Legend 2: Content from Row 1 (ax=axs[0, :]), Location below Row 2 (ref=axs[1, :])
511+
# This places a legend describing the top row data underneath the bottom row.
512+
fig.legend(ax=axs[0, :], ref=axs[1, :], loc="bottom", title="Data from Row 1")

docs/conf.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@
1212
# -- Imports and paths --------------------------------------------------------------
1313

1414
# Import statements
15-
import os
16-
import sys
1715
import datetime
16+
import os
1817
import subprocess
19-
from pathlib import Path
18+
import sys
2019

2120
# Surpress warnings from cartopy when downloading data inside docs env
2221
import warnings
22+
from pathlib import Path
2323

2424
try:
2525
from cartopy.io import DownloadWarning
@@ -38,6 +38,7 @@
3838
if not hasattr(sphinx.util, "console"):
3939
# Create a compatibility layer
4040
import sys
41+
4142
import sphinx.util
4243
from sphinx.util import logging
4344

@@ -54,7 +55,7 @@ def __getattr__(self, name):
5455
# Build what's news page from github releases
5556
from subprocess import run
5657

57-
run("python _scripts/fetch_releases.py".split(), check=False)
58+
run([sys.executable, "_scripts/fetch_releases.py"], check=False)
5859

5960
# Update path for sphinx-automodapi and sphinxext extension
6061
sys.path.append(os.path.abspath("."))
@@ -63,7 +64,6 @@ def __getattr__(self, name):
6364
# Print available system fonts
6465
from matplotlib.font_manager import fontManager
6566

66-
6767
# -- Project information -------------------------------------------------------
6868
# The basic info
6969
project = "UltraPlot"
@@ -297,6 +297,11 @@ def __getattr__(self, name):
297297

298298
# -- Options for HTML output -------------------------------------------------
299299

300+
# Meta
301+
html_meta = {
302+
"google-site-verification": "jrFbkSQGBUPSYP5LERld7DDSm1UtbMY9O5o3CdzHJzU",
303+
}
304+
300305
# Logo
301306
html_logo = str(Path("_static") / "logo_square.png")
302307

0 commit comments

Comments
 (0)