`
- to download the full example code.{2}
+ to download the full example code{2}
.. rst-class:: sphx-glr-example-title
diff --git a/lib/matplotlib/tests/test_figure.py b/lib/matplotlib/tests/test_figure.py
index 5f0e68648966..e666a3b99f7f 100644
--- a/lib/matplotlib/tests/test_figure.py
+++ b/lib/matplotlib/tests/test_figure.py
@@ -1688,6 +1688,9 @@ def test_unpickle_with_device_pixel_ratio():
assert fig.dpi == 42*7
fig2 = pickle.loads(pickle.dumps(fig))
assert fig2.dpi == 42
+ assert all(
+ [orig / 7 == restore for orig, restore in zip(fig.bbox.max, fig2.bbox.max)]
+ )
def test_gridspec_no_mutate_input():
diff --git a/lib/matplotlib/tests/test_getattr.py b/lib/matplotlib/tests/test_getattr.py
index f0f5823600ca..fe302220067a 100644
--- a/lib/matplotlib/tests/test_getattr.py
+++ b/lib/matplotlib/tests/test_getattr.py
@@ -1,25 +1,29 @@
from importlib import import_module
from pkgutil import walk_packages
+import sys
+import warnings
-import matplotlib
import pytest
+import matplotlib
+from matplotlib.testing import is_ci_environment, subprocess_run_helper
+
# Get the names of all matplotlib submodules,
# except for the unit tests and private modules.
-module_names = [
- m.name
- for m in walk_packages(
- path=matplotlib.__path__, prefix=f'{matplotlib.__name__}.'
- )
- if not m.name.startswith(__package__)
- and not any(x.startswith('_') for x in m.name.split('.'))
-]
+module_names = []
+backend_module_names = []
+for m in walk_packages(path=matplotlib.__path__, prefix=f'{matplotlib.__name__}.'):
+ if m.name.startswith(__package__):
+ continue
+ if any(x.startswith('_') for x in m.name.split('.')):
+ continue
+ if 'backends.backend_' in m.name:
+ backend_module_names.append(m.name)
+ else:
+ module_names.append(m.name)
-@pytest.mark.parametrize('module_name', module_names)
-@pytest.mark.filterwarnings('ignore::DeprecationWarning')
-@pytest.mark.filterwarnings('ignore::ImportWarning')
-def test_getattr(module_name):
+def _test_getattr(module_name, use_pytest=True):
"""
Test that __getattr__ methods raise AttributeError for unknown keys.
See #20822, #20855.
@@ -28,8 +32,35 @@ def test_getattr(module_name):
module = import_module(module_name)
except (ImportError, RuntimeError, OSError) as e:
# Skip modules that cannot be imported due to missing dependencies
- pytest.skip(f'Cannot import {module_name} due to {e}')
+ if use_pytest:
+ pytest.skip(f'Cannot import {module_name} due to {e}')
+ else:
+ print(f'SKIP: Cannot import {module_name} due to {e}')
+ return
key = 'THIS_SYMBOL_SHOULD_NOT_EXIST'
if hasattr(module, key):
delattr(module, key)
+
+
+@pytest.mark.parametrize('module_name', module_names)
+@pytest.mark.filterwarnings('ignore::DeprecationWarning')
+@pytest.mark.filterwarnings('ignore::ImportWarning')
+def test_getattr(module_name):
+ _test_getattr(module_name)
+
+
+def _test_module_getattr():
+ warnings.filterwarnings('ignore', category=DeprecationWarning)
+ warnings.filterwarnings('ignore', category=ImportWarning)
+ module_name = sys.argv[1]
+ _test_getattr(module_name, use_pytest=False)
+
+
+@pytest.mark.parametrize('module_name', backend_module_names)
+def test_backend_getattr(module_name):
+ proc = subprocess_run_helper(_test_module_getattr, module_name,
+ timeout=120 if is_ci_environment() else 20)
+ if 'SKIP: ' in proc.stdout:
+ pytest.skip(proc.stdout.removeprefix('SKIP: '))
+ print(proc.stdout)
diff --git a/lib/matplotlib/tests/test_legend.py b/lib/matplotlib/tests/test_legend.py
index 61fae63a298e..5f83b25b90a5 100644
--- a/lib/matplotlib/tests/test_legend.py
+++ b/lib/matplotlib/tests/test_legend.py
@@ -1667,3 +1667,86 @@ def test_boxplot_legend_labels():
bp4 = axs[3].boxplot(data, label='box A')
assert bp4['medians'][0].get_label() == 'box A'
assert all(x.get_label().startswith("_") for x in bp4['medians'][1:])
+
+
+def test_legend_linewidth():
+ """Test legend.linewidth parameter and rcParam."""
+ fig, ax = plt.subplots()
+ ax.plot([1, 2, 3], label='data')
+
+ # Test direct parameter
+ leg = ax.legend(linewidth=2.5)
+ assert leg.legendPatch.get_linewidth() == 2.5
+
+ # Test rcParam
+ with mpl.rc_context({'legend.linewidth': 3.0}):
+ fig, ax = plt.subplots()
+ ax.plot([1, 2, 3], label='data')
+ leg = ax.legend()
+ assert leg.legendPatch.get_linewidth() == 3.0
+
+ # Test None default (should inherit from patch.linewidth)
+ with mpl.rc_context({'legend.linewidth': None, 'patch.linewidth': 1.5}):
+ fig, ax = plt.subplots()
+ ax.plot([1, 2, 3], label='data')
+ leg = ax.legend()
+ assert leg.legendPatch.get_linewidth() == 1.5
+
+ # Test that direct parameter overrides rcParam
+ with mpl.rc_context({'legend.linewidth': 1.0}):
+ fig, ax = plt.subplots()
+ ax.plot([1, 2, 3], label='data')
+ leg = ax.legend(linewidth=4.0)
+ assert leg.legendPatch.get_linewidth() == 4.0
+
+
+def test_patchcollection_legend():
+ # Test that PatchCollection labels show up in legend and preserve visual
+ # properties (issue #23998)
+ fig, ax = plt.subplots()
+
+ pc = mcollections.PatchCollection(
+ [mpatches.Circle((0, 0), 1), mpatches.Circle((2, 0), 1)],
+ label="patch collection",
+ facecolor='red',
+ edgecolor='blue',
+ linewidths=3,
+ linestyle='--',
+ )
+ ax.add_collection(pc)
+ ax.autoscale_view()
+
+ leg = ax.legend()
+
+ # Check that the legend contains our label
+ assert len(leg.get_texts()) == 1
+ assert leg.get_texts()[0].get_text() == "patch collection"
+
+ # Check that the legend handle exists and has correct visual properties
+ assert len(leg.legend_handles) == 1
+ legend_patch = leg.legend_handles[0]
+ assert mpl.colors.same_color(legend_patch.get_facecolor(),
+ pc.get_facecolor()[0])
+ assert mpl.colors.same_color(legend_patch.get_edgecolor(),
+ pc.get_edgecolor()[0])
+ assert legend_patch.get_linewidth() == pc.get_linewidths()[0]
+ assert legend_patch.get_linestyle() == pc.get_linestyles()[0]
+
+
+def test_patchcollection_legend_empty():
+ # Test that empty PatchCollection doesn't crash
+ fig, ax = plt.subplots()
+
+ # Create an empty PatchCollection
+ pc = mcollections.PatchCollection([], label="empty collection")
+ ax.add_collection(pc)
+
+ # This should not crash
+ leg = ax.legend()
+
+ # Check that the label still appears
+ assert len(leg.get_texts()) == 1
+ assert leg.get_texts()[0].get_text() == "empty collection"
+
+ # The legend handle should exist
+ assert len(leg.legend_handles) == 1
diff --git a/lib/matplotlib/tests/test_sphinxext.py b/lib/matplotlib/tests/test_sphinxext.py
index ede3166a2e1b..c6f4e13c74c2 100644
--- a/lib/matplotlib/tests/test_sphinxext.py
+++ b/lib/matplotlib/tests/test_sphinxext.py
@@ -205,6 +205,30 @@ def test_plot_html_show_source_link_custom_basename(tmp_path):
assert 'custom-name.py' in html_content
+def test_plot_html_code_caption(tmp_path):
+ # Test that :code-caption: option adds caption to code block
+ shutil.copyfile(tinypages / 'conf.py', tmp_path / 'conf.py')
+ shutil.copytree(tinypages / '_static', tmp_path / '_static')
+ doctree_dir = tmp_path / 'doctrees'
+ (tmp_path / 'index.rst').write_text("""
+.. plot::
+ :include-source:
+ :code-caption: Example plotting code
+
+ import matplotlib.pyplot as plt
+ plt.plot([1, 2, 3], [1, 4, 9])
+""")
+ html_dir = tmp_path / '_build' / 'html'
+ build_sphinx_html(tmp_path, doctree_dir, html_dir)
+
+ # Check that the HTML contains the code caption
+ html_content = (html_dir / 'index.html').read_text(encoding='utf-8')
+ assert 'Example plotting code' in html_content
+ # Verify the caption is associated with the code block
+ # (appears in a caption element)
+ assert '= offset_bbox.x1 - 1e-6, \
+ f"bbox.x1 ({bbox.x1}) should be >= offset_bbox.x1 ({offset_bbox.x1})"
+ assert bbox.y1 >= offset_bbox.y1 - 1e-6, \
+ f"bbox.y1 ({bbox.y1}) should be >= offset_bbox.y1 ({offset_bbox.y1})"
diff --git a/src/_image_resample.h b/src/_image_resample.h
index 7e6c32c6bf64..6b325c8aa14b 100644
--- a/src/_image_resample.h
+++ b/src/_image_resample.h
@@ -496,7 +496,7 @@ typedef enum {
} interpolation_e;
-// T is rgba if and only if it has an T::r field.
+// T is rgba if and only if it has a T::r field.
template struct is_grayscale : std::true_type {};
template struct is_grayscale> : std::false_type {};
template constexpr bool is_grayscale_v = is_grayscale::value;
diff --git a/src/tri/_tri.h b/src/tri/_tri.h
index 2319650b367b..994b1f43c556 100644
--- a/src/tri/_tri.h
+++ b/src/tri/_tri.h
@@ -75,7 +75,7 @@
namespace py = pybind11;
-/* An edge of a triangle consisting of an triangle index in the range 0 to
+/* An edge of a triangle consisting of a triangle index in the range 0 to
* ntri-1 and an edge index in the range 0 to 2. Edge i goes from the
* triangle's point i to point (i+1)%3. */
struct TriEdge final
diff --git a/tools/boilerplate.py b/tools/boilerplate.py
index a617d12c7072..0a1a26c7cb76 100644
--- a/tools/boilerplate.py
+++ b/tools/boilerplate.py
@@ -263,6 +263,7 @@ def boilerplate_gen():
'pcolormesh',
'phase_spectrum',
'pie',
+ 'pie_label',
'plot',
'psd',
'quiver',