Skip to content

Commit c503592

Browse files
authored
Merge branch 'matplotlib:main' into enh-29681-hr
2 parents 44638f3 + 4af11e7 commit c503592

33 files changed

+1200
-89
lines changed

.github/workflows/codeql-analysis.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ jobs:
3131
persist-credentials: false
3232

3333
- name: Initialize CodeQL
34-
uses: github/codeql-action/init@fc7e4a0fa01c3cca5fd6a1fddec5c0740c977aa2 # v3.28.14
34+
uses: github/codeql-action/init@45775bd8235c68ba998cffa5171334d58593da47 # v3.28.15
3535
with:
3636
languages: ${{ matrix.language }}
3737

@@ -42,4 +42,4 @@ jobs:
4242
pip install --user -v .
4343
4444
- name: Perform CodeQL Analysis
45-
uses: github/codeql-action/analyze@fc7e4a0fa01c3cca5fd6a1fddec5c0740c977aa2 # v3.28.14
45+
uses: github/codeql-action/analyze@45775bd8235c68ba998cffa5171334d58593da47 # v3.28.15

.github/workflows/tests.yml

+4-4
Original file line numberDiff line numberDiff line change
@@ -196,10 +196,10 @@ jobs:
196196
~/.cache/matplotlib
197197
!~/.cache/matplotlib/tex.cache
198198
!~/.cache/matplotlib/test_cache
199-
key: 5-${{ matrix.os }}-py${{ matrix.python-version }}-mpl-${{ github.ref }}-${{ github.sha }}
199+
key: 6-${{ matrix.os }}-py${{ matrix.python-version }}-mpl-${{ github.ref }}-${{ github.sha }}
200200
restore-keys: |
201-
5-${{ matrix.os }}-py${{ matrix.python-version }}-mpl-${{ github.ref }}-
202-
5-${{ matrix.os }}-py${{ matrix.python-version }}-mpl-
201+
6-${{ matrix.os }}-py${{ matrix.python-version }}-mpl-${{ github.ref }}-
202+
6-${{ matrix.os }}-py${{ matrix.python-version }}-mpl-
203203
204204
- name: Install Python dependencies
205205
run: |
@@ -386,7 +386,7 @@ jobs:
386386
fi
387387
- name: Upload code coverage
388388
if: ${{ !cancelled() && github.event_name != 'schedule' }}
389-
uses: codecov/codecov-action@0565863a31f2c772f9f0395002a31e3f06189574 # v5.4.0
389+
uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # v5.4.2
390390
with:
391391
name: "${{ matrix.python-version }} ${{ matrix.os }} ${{ matrix.name-suffix }}"
392392
token: ${{ secrets.CODECOV_TOKEN }}

LICENSE/LICENSE_LAST_RESORT_FONT

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
Last Resort High-Efficiency Font License
2+
========================================
3+
4+
This Font Software is licensed under the SIL Open Font License,
5+
Version 1.1.
6+
7+
This license is copied below, and is also available with a FAQ at:
8+
http://scripts.sil.org/OFL
9+
10+
-----------------------------------------------------------
11+
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
12+
-----------------------------------------------------------
13+
14+
PREAMBLE
15+
The goals of the Open Font License (OFL) are to stimulate worldwide
16+
development of collaborative font projects, to support the font
17+
creation efforts of academic and linguistic communities, and to
18+
provide a free and open framework in which fonts may be shared and
19+
improved in partnership with others.
20+
21+
The OFL allows the licensed fonts to be used, studied, modified and
22+
redistributed freely as long as they are not sold by themselves. The
23+
fonts, including any derivative works, can be bundled, embedded,
24+
redistributed and/or sold with any software provided that any reserved
25+
names are not used by derivative works. The fonts and derivatives,
26+
however, cannot be released under any other type of license. The
27+
requirement for fonts to remain under this license does not apply to
28+
any document created using the fonts or their derivatives.
29+
30+
DEFINITIONS
31+
"Font Software" refers to the set of files released by the Copyright
32+
Holder(s) under this license and clearly marked as such. This may
33+
include source files, build scripts and documentation.
34+
35+
"Reserved Font Name" refers to any names specified as such after the
36+
copyright statement(s).
37+
38+
"Original Version" refers to the collection of Font Software
39+
components as distributed by the Copyright Holder(s).
40+
41+
"Modified Version" refers to any derivative made by adding to,
42+
deleting, or substituting -- in part or in whole -- any of the
43+
components of the Original Version, by changing formats or by porting
44+
the Font Software to a new environment.
45+
46+
"Author" refers to any designer, engineer, programmer, technical
47+
writer or other person who contributed to the Font Software.
48+
49+
PERMISSION & CONDITIONS
50+
Permission is hereby granted, free of charge, to any person obtaining
51+
a copy of the Font Software, to use, study, copy, merge, embed,
52+
modify, redistribute, and sell modified and unmodified copies of the
53+
Font Software, subject to the following conditions:
54+
55+
1) Neither the Font Software nor any of its individual components, in
56+
Original or Modified Versions, may be sold by itself.
57+
58+
2) Original or Modified Versions of the Font Software may be bundled,
59+
redistributed and/or sold with any software, provided that each copy
60+
contains the above copyright notice and this license. These can be
61+
included either as stand-alone text files, human-readable headers or
62+
in the appropriate machine-readable metadata fields within text or
63+
binary files as long as those fields can be easily viewed by the user.
64+
65+
3) No Modified Version of the Font Software may use the Reserved Font
66+
Name(s) unless explicit written permission is granted by the
67+
corresponding Copyright Holder. This restriction only applies to the
68+
primary font name as presented to the users.
69+
70+
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
71+
Software shall not be used to promote, endorse or advertise any
72+
Modified Version, except to acknowledge the contribution(s) of the
73+
Copyright Holder(s) and the Author(s) or with their explicit written
74+
permission.
75+
76+
5) The Font Software, modified or unmodified, in part or in whole,
77+
must be distributed entirely under this license, and must not be
78+
distributed under any other license. The requirement for fonts to
79+
remain under this license does not apply to any document created using
80+
the Font Software.
81+
82+
TERMINATION
83+
This license becomes null and void if any of the above conditions are
84+
not met.
85+
86+
DISCLAIMER
87+
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
88+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
89+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
90+
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
91+
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
92+
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
93+
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
94+
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
95+
OTHER DEALINGS IN THE FONT SOFTWARE.
96+
97+
SPDX-License-Identifier: OFL-1.1

doc/api/axes_api.rst

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ Attributes
3838

3939
Axes.viewLim
4040
Axes.dataLim
41+
Axes.spines
4142

4243
Plotting
4344
========

doc/conf.py

+8
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,14 @@ def _parse_skip_subdirs_file():
102102
# usage in the gallery.
103103
warnings.filterwarnings('error', append=True)
104104

105+
# Warnings for missing glyphs occur during `savefig`, and would cause any such plot to
106+
# not be created. Because the exception occurs in savefig, there is no way for the plot
107+
# itself to ignore these warnings locally, so we must do so globally.
108+
warnings.filterwarnings('default', category=UserWarning,
109+
message=r'Glyph \d+ \(.+\) missing from font\(s\)')
110+
warnings.filterwarnings('default', category=UserWarning,
111+
message=r'Matplotlib currently does not support .+ natively\.')
112+
105113
# Add any Sphinx extension module names here, as strings. They can be
106114
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
107115
extensions = [

doc/project/license.rst

+6
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,12 @@ Fonts
125125
.. literalinclude:: ../../LICENSE/LICENSE_COURIERTEN
126126
:language: none
127127

128+
.. dropdown:: Last Resort
129+
:class-container: sdd
130+
131+
.. literalinclude:: ../../LICENSE/LICENSE_LAST_RESORT_FONT
132+
:language: none
133+
128134
.. dropdown:: STIX
129135
:class-container: sdd
130136

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
Missing glyphs use Last Resort font
2+
-----------------------------------
3+
4+
Most fonts do not have 100% character coverage, and will fall back to a "not found"
5+
glyph for characters that are not provided. Often, this glyph will be minimal (e.g., the
6+
default DejaVu Sans "not found" glyph is just a rectangle.) Such minimal glyphs provide
7+
no context as to the characters that are missing.
8+
9+
Now, missing glyphs will fall back to the `Last Resort font
10+
<https://github.com/unicode-org/last-resort-font>`__ produced by the Unicode Consortium.
11+
This special-purpose font provides glyphs that represent types of Unicode characters.
12+
These glyphs show a representative character from the missing Unicode block, and at
13+
larger sizes, more context to help determine which character and font are needed.
14+
15+
To disable this fallback behaviour, set :rc:`font.enable_last_resort` to ``False``.
16+
17+
.. plot::
18+
:alt: An example of missing glyph behaviour, the first glyph from Bengali script,
19+
second glyph from Hiragana, and the last glyph from the Unicode Private Use
20+
Area. Multiple lines repeat the text with increasing font size from top to
21+
bottom.
22+
23+
text_raw = r"'\N{Bengali Digit Zero}\N{Hiragana Letter A}\ufdd0'"
24+
text = eval(text_raw)
25+
sizes = [
26+
(0.85, 8),
27+
(0.80, 10),
28+
(0.75, 12),
29+
(0.70, 16),
30+
(0.63, 20),
31+
(0.55, 24),
32+
(0.45, 32),
33+
(0.30, 48),
34+
(0.10, 64),
35+
]
36+
37+
fig = plt.figure()
38+
fig.text(0.01, 0.90, f'Input: {text_raw}')
39+
for y, size in sizes:
40+
fig.text(0.01, y, f'{size}pt:{text}', fontsize=size)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Resetting the subplot parameters for figure.clear()
2+
---------------------------------------------------
3+
4+
When calling `.Figure.clear()` the settings for `.gridspec.SubplotParams` are restored to the default values.
5+
6+
`~.SubplotParams.to_dict` is a new method to get the subplot parameters as a dict,
7+
and `~.SubplotParams.reset` resets the parameters to the defaults.

galleries/examples/lines_bars_and_markers/multicolored_line.py

+27-36
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from matplotlib.collections import LineCollection
2222

2323

24-
def colored_line(x, y, c, ax, **lc_kwargs):
24+
def colored_line(x, y, c, ax=None, **lc_kwargs):
2525
"""
2626
Plot a line with a color specified along the line by a third value.
2727
@@ -36,8 +36,8 @@ def colored_line(x, y, c, ax, **lc_kwargs):
3636
The horizontal and vertical coordinates of the data points.
3737
c : array-like
3838
The color values, which should be the same size as x and y.
39-
ax : Axes
40-
Axis object on which to plot the colored line.
39+
ax : matplotlib.axes.Axes, optional
40+
The axes to plot on. If not provided, the current axes will be used.
4141
**lc_kwargs
4242
Any additional arguments to pass to matplotlib.collections.LineCollection
4343
constructor. This should not include the array keyword argument because
@@ -49,36 +49,32 @@ def colored_line(x, y, c, ax, **lc_kwargs):
4949
The generated line collection representing the colored line.
5050
"""
5151
if "array" in lc_kwargs:
52-
warnings.warn('The provided "array" keyword argument will be overridden')
52+
warnings.warn(
53+
'The provided "array" keyword argument will be overridden',
54+
UserWarning,
55+
stacklevel=2,
56+
)
57+
58+
xy = np.stack((x, y), axis=-1)
59+
xy_mid = np.concat(
60+
(xy[0, :][None, :], (xy[:-1, :] + xy[1:, :]) / 2, xy[-1, :][None, :]), axis=0
61+
)
62+
segments = np.stack((xy_mid[:-1, :], xy, xy_mid[1:, :]), axis=-2)
63+
# Note that
64+
# segments[0, :, :] is [xy[0, :], xy[0, :], (xy[0, :] + xy[1, :]) / 2]
65+
# segments[i, :, :] is [(xy[i - 1, :] + xy[i, :]) / 2, xy[i, :],
66+
# (xy[i, :] + xy[i + 1, :]) / 2] if i not in {0, len(x) - 1}
67+
# segments[-1, :, :] is [(xy[-2, :] + xy[-1, :]) / 2, xy[-1, :], xy[-1, :]]
68+
69+
lc_kwargs["array"] = c
70+
lc = LineCollection(segments, **lc_kwargs)
5371

54-
# Default the capstyle to butt so that the line segments smoothly line up
55-
default_kwargs = {"capstyle": "butt"}
56-
default_kwargs.update(lc_kwargs)
57-
58-
# Compute the midpoints of the line segments. Include the first and last points
59-
# twice so we don't need any special syntax later to handle them.
60-
x = np.asarray(x)
61-
y = np.asarray(y)
62-
x_midpts = np.hstack((x[0], 0.5 * (x[1:] + x[:-1]), x[-1]))
63-
y_midpts = np.hstack((y[0], 0.5 * (y[1:] + y[:-1]), y[-1]))
64-
65-
# Determine the start, middle, and end coordinate pair of each line segment.
66-
# Use the reshape to add an extra dimension so each pair of points is in its
67-
# own list. Then concatenate them to create:
68-
# [
69-
# [(x1_start, y1_start), (x1_mid, y1_mid), (x1_end, y1_end)],
70-
# [(x2_start, y2_start), (x2_mid, y2_mid), (x2_end, y2_end)],
71-
# ...
72-
# ]
73-
coord_start = np.column_stack((x_midpts[:-1], y_midpts[:-1]))[:, np.newaxis, :]
74-
coord_mid = np.column_stack((x, y))[:, np.newaxis, :]
75-
coord_end = np.column_stack((x_midpts[1:], y_midpts[1:]))[:, np.newaxis, :]
76-
segments = np.concatenate((coord_start, coord_mid, coord_end), axis=1)
77-
78-
lc = LineCollection(segments, **default_kwargs)
79-
lc.set_array(c) # set the colors of each segment
72+
# Plot the line collection to the axes
73+
ax = ax or plt.gca()
74+
ax.add_collection(lc)
75+
ax.autoscale_view()
8076

81-
return ax.add_collection(lc)
77+
return lc
8278

8379

8480
# -------------- Create and show plot --------------
@@ -93,11 +89,6 @@ def colored_line(x, y, c, ax, **lc_kwargs):
9389
lines = colored_line(x, y, color, ax1, linewidth=10, cmap="plasma")
9490
fig1.colorbar(lines) # add a color legend
9591

96-
# Set the axis limits and tick positions
97-
ax1.set_xlim(-1, 1)
98-
ax1.set_ylim(-1, 1)
99-
ax1.set_xticks((-1, 0, 1))
100-
ax1.set_yticks((-1, 0, 1))
10192
ax1.set_title("Color at each point")
10293

10394
plt.show()

lib/matplotlib/_api/__init__.py

+9
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@
2525
MatplotlibDeprecationWarning)
2626

2727

28+
# A sentinel value for optional arguments, when None cannot be used as
29+
# default because we need to distinguish between None passed explicitly
30+
# and parameter not given. Usage: def foo(arg=_api.UNSET):
31+
class _Unset:
32+
def __repr__(self):
33+
return "<UNSET>"
34+
UNSET = _Unset()
35+
36+
2837
class classproperty:
2938
"""
3039
Like `property`, but also triggers on access via the class, and it is the

lib/matplotlib/_api/__init__.pyi

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ from .deprecation import ( # noqa: F401, re-exported API
1818

1919
_T = TypeVar("_T")
2020

21+
class _Unset: ...
22+
2123
class classproperty(Any):
2224
def __init__(
2325
self,

lib/matplotlib/artist.py

+1-7
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,6 @@ def _stale_axes_callback(self, val):
107107
_XYPair = namedtuple("_XYPair", "x y")
108108

109109

110-
class _Unset:
111-
def __repr__(self):
112-
return "<UNSET>"
113-
_UNSET = _Unset()
114-
115-
116110
class Artist:
117111
"""
118112
Abstract base class for objects that render into a FigureCanvas.
@@ -166,7 +160,7 @@ def _update_set_signature_and_docstring(cls):
166160
"""
167161
cls.set.__signature__ = Signature(
168162
[Parameter("self", Parameter.POSITIONAL_OR_KEYWORD),
169-
*[Parameter(prop, Parameter.KEYWORD_ONLY, default=_UNSET)
163+
*[Parameter(prop, Parameter.KEYWORD_ONLY, default=_api.UNSET)
170164
for prop in ArtistInspector(cls).get_setters()
171165
if prop not in Artist._PROPERTIES_EXCLUDED_FROM_SET]])
172166
cls.set._autogenerated_signature = True

lib/matplotlib/artist.pyi

-2
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@ class _XYPair(NamedTuple):
2626
x: ArrayLike
2727
y: ArrayLike
2828

29-
class _Unset: ...
30-
3129
class Artist:
3230
zorder: float
3331
stale_callback: Callable[[Artist, bool], None] | None

lib/matplotlib/axes/_axes.py

+3
Original file line numberDiff line numberDiff line change
@@ -3303,6 +3303,9 @@ def pie(self, x, explode=None, labels=None, colors=None,
33033303
if np.any(x < 0):
33043304
raise ValueError("Wedge sizes 'x' must be non negative values")
33053305

3306+
if not np.all(np.isfinite(x)):
3307+
raise ValueError('Wedge sizes must be finite numbers')
3308+
33063309
sx = x.sum()
33073310

33083311
if normalize:

lib/matplotlib/axes/_base.py

+6
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,12 @@ class _AxesBase(martist.Artist):
558558
dataLim: mtransforms.Bbox
559559
"""The bounding `.Bbox` enclosing all data displayed in the Axes."""
560560

561+
spines: mspines.Spines
562+
"""
563+
The `.Spines` container for the Axes' spines, i.e. the lines denoting the
564+
data area boundaries.
565+
"""
566+
561567
xaxis: maxis.XAxis
562568
"""
563569
The `.XAxis` instance.

0 commit comments

Comments
 (0)